[
  {
    "path": ".dockerignore",
    "content": "**/.DS_Store\n\n*/node_modules\n\nserver/swagger.json\nserver/.env\n\nserver/dist\nserver/logs\nserver/test\nserver/.tmp\nserver/.venv\n\nserver/views/index.ejs\n\nserver/data/*\n!server/data/.gitkeep\n\nserver/terms/*\n!server/terms/_template\n!server/terms/cloud\n\nclient/dist\n"
  },
  {
    "path": ".gitattributes",
    "content": "client/src/lib/custom-ui/styles.css linguist-vendored\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-bug-report.yml",
    "content": "name: \"🐛 Bug Report\"\ndescription: Report a bug found while using PLANKA\ntitle: \"[Bug]: \"\nlabels: [\"Type: Bug\", \"Status: Triage\"]\nbody:\n  - type: dropdown\n    id: issue-type\n    attributes:\n      label: Where is the problem occurring?\n      description: Select the part of the application where you encountered the issue.\n      options:\n        - \"I encountered the problem while using the application (Frontend)\"\n        - \"I encountered the problem while interacting with the server (Backend)\"\n        - \"I'm not sure\"\n  - type: dropdown\n    id: browsers\n    attributes:\n      label: What browsers are you seeing the problem on?\n      multiple: true\n      options:\n        - Brave\n        - Chrome\n        - Firefox\n        - Microsoft Edge\n        - Safari\n        - Other\n  - type: textarea\n    id: current-behavior\n    attributes:\n      label: Current behavior\n      description: A description of what is currently happening, including screenshots and other useful information (**DO NOT INCLUDE PRIVATE INFORMATION**).\n      placeholder: Currently...\n    validations:\n      required: true\n  - type: textarea\n    id: desired-behavior\n    attributes:\n      label: Desired behavior\n      description: A clear description of what you think should happen.\n      placeholder: In this situation, I expected ...\n  - type: textarea\n    id: reproduction\n    attributes:\n      label: Steps to reproduce\n      description: Clearly describe which steps or actions you have taken to arrive at the problem. If you have some experience with the code, please link to the specific pieces of code.\n      placeholder: I did X, then Y, before arriving at Z, when ERROR...\n    validations:\n      required: true\n  - type: textarea\n    id: other\n    attributes:\n      label: Other information\n      description: Any other details?\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2-feature-request.yml",
    "content": "name: \"✨ Feature Request\"\ndescription: Suggest a feature or enhancement to improve PLANKA.\nlabels: [\"Type: Idea\"]\nbody:\n  - type: dropdown\n    id: idea-type\n    attributes:\n      label: Which part of the project does this feature apply to?\n      multiple: true\n      options:\n        - Backend\n        - Frontend\n        - Chart\n    validations:\n      required: true\n  - type: textarea\n    id: feature\n    attributes:\n      label: What would you like?\n      description: A clear description of the feature or enhancement wanted.\n      placeholder: I'd like to be able to...\n    validations:\n      required: true\n  - type: textarea\n    id: reason\n    attributes:\n      label: Why is this needed?\n      description: A clear description of why this would be useful to have.\n      placeholder: I want this because...\n  - type: textarea\n    id: other\n    attributes:\n      label: Other information\n      placeholder: Any other details?\n"
  },
  {
    "path": ".github/workflows/build-and-publish-release-package.yml",
    "content": "name: Build and Publish Release Package\n\non:\n  release:\n    types: [created]\n\njobs:\n  build-and-publish-release-package:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '22'\n          cache: 'npm'\n\n      - name: Update npm\n        run: npm install npm --global\n\n      - name: Install server dependencies\n        run: npm install --omit=prod --ignore-scripts\n        working-directory: ./server\n\n      - name: Build server\n        run: npm run build\n        working-directory: ./server\n\n      - name: Install client dependencies\n        run: npm install --omit=dev\n        working-directory: ./client\n\n      - name: Build client\n        run: INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build\n        working-directory: ./client\n\n      - name: Include licenses into dist\n        run: |\n          mv LICENSE.md server/dist\n          mv \"LICENSES/PLANKA Community License DE.md\" server/dist/LICENSE_DE.md\n\n      - name: Include built client into dist\n        run: |\n          mv ../../client/dist/* public\n          mv public/index.ejs views\n        working-directory: ./server/dist\n\n      - name: Create release package\n        run: |\n          mv dist planka\n          zip -r planka-prebuild.zip planka\n        working-directory: ./server\n\n      - name: Publish release package\n        env:\n          GH_TOKEN: ${{ github.token }}\n        run: gh release upload ${{ github.event.release.tag_name }} planka-prebuild.zip\n        working-directory: ./server\n"
  },
  {
    "path": ".github/workflows/build-and-push-docker-image.yml",
    "content": "name: Build and Push Docker Image\n\non:\n  release:\n    types: [created]\n\njobs:\n  build-and-push-docker-image:\n    runs-on: self-hosted\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '22'\n          cache: 'npm'\n\n      - name: Install client dependencies\n        run: npm install --omit=dev\n        working-directory: ./client\n\n      - name: Build client\n        run: |\n          INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build\n          mv dist build\n        working-directory: ./client\n\n      - name: Update Dockerfile to use prebuilt client\n        run: |\n          sed -i '/^FROM node:22 AS client/,/^  && INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build$/c\\\n          FROM node:22 AS client\\n\\\n          WORKDIR /app\\n\\\n          COPY client/build /app/dist' Dockerfile\n          cat Dockerfile\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v2\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v2\n\n      - name: Log in to GitHub Container Registry\n        uses: docker/login-action@v2\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Set version from release tag\n        uses: actions/github-script@v6\n        id: set-version\n        with:\n          result-encoding: string\n          script: return context.payload.release.tag_name.replace('v', '')\n\n      - name: Generate Docker image tags\n        id: metadata\n        uses: docker/metadata-action@v5\n        with:\n          images: |\n            name=ghcr.io/${{ github.repository }}\n          tags: |\n            type=raw,value=${{ steps.set-version.outputs.result }}\n            type=raw,value=latest\n          labels: |\n            org.opencontainers.image.licenses=Fair Use License\n            org.opencontainers.image.url=https://planka.app\n\n      - name: Build and push Docker image\n        uses: docker/build-push-action@v4\n        with:\n          context: .\n          platforms: linux/amd64,linux/arm64,linux/arm/v7\n          push: true\n          tags: ${{ steps.metadata.outputs.tags }}\n          labels: ${{ steps.metadata.outputs.labels }}\n          cache-from: type=gha\n          cache-to: type=gha,mode=max\n"
  },
  {
    "path": ".github/workflows/build-and-push-docker-nightly-image.yml",
    "content": "name: Build and Push Docker Nightly Image\n\non:\n  push:\n    paths-ignore:\n      - '.github/**'\n      - 'charts/**'\n      - 'docker-*.yml'\n      - '*.md'\n    branches: [master]\n  workflow_dispatch:\n\njobs:\n  build-and-push-docker-nightly-image:\n    runs-on: self-hosted\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '22'\n          cache: 'npm'\n\n      - name: Update version with build number\n        run: |\n          npm version \"$(node -p \"require('./package.json').version\")-nightly.$(git rev-list --count HEAD)\" --no-git-tag-version\n          npx --yes genversion --source . --template server/version-template.ejs server/version.js\n          npx --yes genversion --source . --template client/version-template.ejs client/src/version.js\n\n      - name: Install client dependencies\n        run: npm install --omit=dev\n        working-directory: ./client\n\n      - name: Build client\n        run: |\n          INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build\n          mv dist build\n        working-directory: ./client\n\n      - name: Update Dockerfile to use prebuilt client\n        run: |\n          sed -i '/^FROM node:22 AS client/,/^  && INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build$/c\\\n          FROM node:22 AS client\\n\\\n          WORKDIR /app\\n\\\n          COPY client/build /app/dist' Dockerfile\n          cat Dockerfile\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v2\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v2\n\n      - name: Log in to GitHub Container Registry\n        uses: docker/login-action@v2\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Generate Docker image tags\n        id: metadata\n        uses: docker/metadata-action@v5\n        with:\n          images: |\n            name=ghcr.io/${{ github.repository }}\n          tags: |\n            type=raw,value=nightly\n          labels: |\n            org.opencontainers.image.licenses=Fair Use License\n            org.opencontainers.image.url=https://planka.app\n\n      - name: Build and push Docker image\n        uses: docker/build-push-action@v4\n        with:\n          context: .\n          platforms: linux/amd64,linux/arm64,linux/arm/v7\n          push: true\n          tags: ${{ steps.metadata.outputs.tags }}\n          labels: ${{ steps.metadata.outputs.labels }}\n          cache-from: type=gha\n          cache-to: type=gha,mode=max\n"
  },
  {
    "path": ".github/workflows/build-and-test.yml",
    "content": "name: Build and Test\n\non:\n  pull_request:\n    branches:\n      - master\n  push:\n    branches:\n      - master\n\njobs:\n  build-and-test:\n    runs-on: ubuntu-latest\n\n    env:\n      POSTGRES_USERNAME: planka\n      POSTGRES_PASSWORD: planka\n      POSTGRES_DATABASE: planka\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '22'\n          cache: 'npm'\n\n      - name: Set up PostgreSQL\n        uses: ikalnytskyi/action-setup-postgres@v5\n        with:\n          username: ${{ env.POSTGRES_USERNAME }}\n          password: ${{ env.POSTGRES_PASSWORD }}\n          database: ${{ env.POSTGRES_DATABASE }}\n\n      - name: Cache Node.js modules\n        uses: actions/cache@v3\n        with:\n          path: client/node_modules\n          key: ${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}\n          restore-keys: |\n            ${{ runner.os }}-node-\n\n      - name: Install dependencies and build client\n        run: |\n          npm install\n          cd client\n          INDEX_FORMAT=ejs npm run build\n\n      - name: Set up and start server for testing\n        env:\n          DEFAULT_ADMIN_EMAIL: demo@demo.demo\n          DEFAULT_ADMIN_PASSWORD: demo\n          DEFAULT_ADMIN_NAME: Demo Demo\n          DEFAULT_ADMIN_USERNAME: demo\n        run: |\n          client/tests/setup-symlinks.sh\n          cd server\n          cp .env.sample .env\n          sed -i \"s|^DATABASE_URL=.*|DATABASE_URL=postgresql://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@localhost/${POSTGRES_DATABASE}|\" .env\n          npm run db:init\n          npm start --prod &\n\n      - name: Wait for server to start\n        run: |\n          sudo apt-get install wait-for-it -y\n          wait-for-it -h localhost -p 1337 -t 10\n\n      - name: Seed database with terms signature\n        run: |\n          TERMS_SIGNATURE=$(sha256sum terms/_template/en-US.md | awk '{print $1}')\n          PGPASSWORD=$POSTGRES_PASSWORD psql -h localhost -U $POSTGRES_USERNAME -d $POSTGRES_DATABASE -c \"UPDATE user_account SET terms_signature = '$TERMS_SIGNATURE';\"\n        working-directory: ./server\n\n      - name: Run UI tests\n        run: |\n          npx playwright install chromium\n          npm run test:acceptance\n        working-directory: ./client\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  pull_request:\n    branches:\n      - master\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '22'\n          cache: 'npm'\n\n      - name: Cache Node.js modules\n        uses: actions/cache@v3\n        with:\n          path: client/node_modules\n          key: ${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}\n          restore-keys: |\n            ${{ runner.os }}-node-\n\n      - name: Install dependencies\n        run: npm install\n\n      - name: Run linter\n        run: npm run lint\n"
  },
  {
    "path": ".github/workflows/release-helm-chart.yml",
    "content": "name: Release Charts\n\non:\n  push:\n    paths:\n      - \"charts/**\"\n    branches:\n      - master\n\njobs:\n  release-helm-chart:\n    # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions\n    # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token\n    permissions:\n      contents: write\n\n    runs-on: self-hosted\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Configure Git\n        run: |\n          git config user.name \"$GITHUB_ACTOR\"\n          git config user.email \"$GITHUB_ACTOR@users.noreply.github.com\"\n\n      - name: Install Helm\n        uses: azure/setup-helm@v3\n\n      - name: Add Helm chart repositories\n        run: |\n          for dir in $(ls -d charts/*/); do\n            helm dependency list $dir 2> /dev/null | tail +2 | head -n -1 | awk '{ print \"helm repo add \" $1 \" \" $3 }' | while read cmd; do $cmd; done\n          done\n\n      - name: Run chart-releaser\n        uses: helm/chart-releaser-action@v1.6.0\n        with:\n          charts_dir: charts\n          mark_as_latest: false\n        env:\n          CR_TOKEN: \"${{ secrets.GITHUB_TOKEN }}\"\n"
  },
  {
    "path": ".gitignore",
    "content": "docker-compose.override.yml\n.idea\n.DS_Store\n\nnode_modules\n\n# Prevent another lockfile than package-lock.json (npm) from being created\n# If some case you are using pnpm or yarn, don't forget to generate npm lockfile\n# before commiting your code by running:\n# `npm i --package-lock-only`\npnpm-lock.yaml\nyarn.lock\n\n# Chart dependencies\n**/charts/*.tgz\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\nnpx lint-staged\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"dbaeumer.vscode-eslint\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.tabSize\": 2,\n  \"editor.formatOnSave\": true,\n  \"files.insertFinalNewline\": true,\n  \"files.trimFinalNewlines\": true,\n  \"files.trimTrailingWhitespace\": true,\n  \"css.format.spaceAroundSelectorSeparator\": true,\n  \"scss.format.spaceAroundSelectorSeparator\": true,\n  \"eslint.format.enable\": true,\n  \"eslint.workingDirectories\": [\n    \"./client\",\n    \"./server\"\n  ],\n  \"[javascript]\": {\n    \"editor.defaultFormatter\": \"dbaeumer.vscode-eslint\"\n  },\n  \"[javascriptreact]\": {\n    \"editor.defaultFormatter\": \"dbaeumer.vscode-eslint\"\n  }\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at [github@planka.group](mailto:github@planka.group).\nAll complaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to PLANKA\n\nFirst off, thanks for taking the time to contribute!\n\n## Code of Conduct\n\nThis project and everyone participating in it is governed by the [PLANKA Code of Conduct](https://github.com/plankanban/planka/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.\n\n## How Can I Contribute?\n\n### Reporting Bugs\n\nFeel free to create a bug report as a new issue on GitHub. Before creating, please check if there is already existing one. When creating a bug report, please include as many details as possible.\n\n### Suggesting Enhancements\n\nFeel free to create an enhancement suggestion as a new issue on GitHub. Before creating, please check if there is already existing one. When creating an enhancement suggestion, please include as many details as possible.\n\n### Pull Requests\n\nBefore submitting a pull request please discuss with the core team by creating or commenting in an issue on GitHub – we'd also love to hear from you in the discussions. This way we can ensure that an approach is agreed on before code is written. This will result in a much higher liklihood of your code being accepted.\n\nIf you’re looking for ways to get started, here's a list of ways to help us improve PLANKA:\n\n- [Translation](https://github.com/plankanban/planka/issues/66) into other languages\n- Issues with [`good first issue`](https://github.com/plankanban/planka/labels/good%20first%20issue) label\n- Performance improvements, both on client and server\n- Developer happiness and documentation\n- Bugs and other issues listed on GitHub\n\n## Styleguides\n\n### Git Commit Messages\n\nCommit messages should follow the [commit message convention](https://conventionalcommits.org), so changelogs could be generated automatically by that.\n\nAdditional rules:\n\n- Separate subject from body with a blank line\n- Limit the subject line to 70 characters\n- Capitalize the subject line\n- Do not end the subject line with a period\n- Use the imperative mood in the subject line\n- Use the body to explain what and why vs. how\n- Each commit should be a single, stable change\n\n### JavaScript\n\nAll JavaScript code should follow this [JavaScript style guide](https://github.com/airbnb/javascript). The pre-commit hook will help you find linting errors before committing.\n"
  },
  {
    "path": "CONTRIBUTOR_LICENSE_AGREEMENT.md",
    "content": "# PLANKA Contributor License Agreement\n\nI give PLANKA Software GmbH permission to license my contributions on any terms they like. I am giving them this license in order to make it possible for them to accept my contributions into their project.\n\nAS FAR AS THE LAW ALLOWS, MY CONTRIBUTIONS COME AS IS, WITHOUT ANY WARRANTY OR CONDITION, AND I WILL NOT BE LIABLE TO ANYONE FOR ANY DAMAGES RELATED TO THIS SOFTWARE OR THIS LICENSE, UNDER ANY KIND OF LEGAL CLAIM.\n"
  },
  {
    "path": "Dockerfile",
    "content": "# Stage 1: Server build\nFROM node:22-alpine AS server\n\nRUN apk -U upgrade \\\n  && apk add build-base python3 --no-cache\n\nWORKDIR /app\n\nCOPY server .\n\nRUN npm install npm --global \\\n  && npm install \\\n  && npm run build \\\n  && npm prune --production\n\n# Stage 2: Client build\nFROM node:22 AS client\n\nWORKDIR /app\n\nCOPY client .\n\nRUN npm install npm --global \\\n  && npm install --omit=dev \\\n  && INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build\n\n# Stage 3: Final image\nFROM node:22-alpine\n\nRUN apk -U upgrade \\\n  && apk add bash python3 squid --no-cache \\\n  && npm install npm --global\n\nUSER node\nWORKDIR /app\n\nCOPY --chown=node:node LICENSE.md .\nCOPY --chown=node:node [\"LICENSES/PLANKA Community License DE.md\", \"LICENSE_DE.md\"]\n\nCOPY --from=server --chown=node:node /app/node_modules node_modules\nCOPY --from=server --chown=node:node /app/dist .\n\nCOPY --from=client --chown=node:node /app/dist public\n\nRUN python3 -m venv .venv \\\n  && .venv/bin/pip3 install --upgrade pip \\\n  && .venv/bin/pip3 install -r requirements.txt --no-cache-dir \\\n  && mv .env.sample .env \\\n  && mv public/index.ejs views \\\n  && npm config set update-notifier false\n\nVOLUME /app/data\nEXPOSE 1337\n\nHEALTHCHECK --interval=10s --timeout=2s --start-period=15s \\\n  CMD node ./healthcheck.js\n\nCMD [\"./start.sh\"]\n"
  },
  {
    "path": "Dockerfile.dev",
    "content": "FROM node:22-alpine\n\nRUN apk -U upgrade \\\n  && apk add bash build-base python3 xdg-utils --no-cache \\\n  && npm install npm --global\n\nWORKDIR /app\n"
  },
  {
    "path": "LICENSE.md",
    "content": "**PLANKA Community License**\n\nVersion 1.1 - Last updated: May 20, 2025\n\nRelated files in English:\n\n- PLANKA Community License EN.md (this file)\n- [PLANKA Commercial License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md)\n- [PLANKA License Guide EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20EN.md)\n\nRelated files in German:\n\n- [PLANKA Community License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20DE.md)\n- [PLANKA Commercial License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20DE.md)\n- [PLANKA License Guide DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20DE.md)\n\n---\n\n# PLANKA Community License\n\nFiles accessible to and marked for community use are licensed as follows:\n\n- Content of branches other than the main branch (usually \"master\" or \"main\") is not licensed.\n- Source code files or other files that contain \".pe.\" (for \"PLANKA Pro/Enterprise\") in their file names or folder names or are otherwise marked as \"PLANKA Pro/Enterprise\" in their file headers or folders are NOT licensed under the \"Fair Use License\". These files are \"PLANKA Pro/Enterprise\" files and are licensed under the \"PLANKA Pro/Enterprise License\".\n- To use any \"PLANKA Pro/Enterprise\" files or sources, you must own a valid \"PLANKA Pro/Enterprise License\". You can read more about our commercial license in the [PLANKA Commercial License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md).\n- All third-party components incorporated into our software are licensed under the original license provided by the owner of the applicable component.\n- Content outside of the above-mentioned files or restrictions is available under the \"Fair Use License\" as defined below.\n\n## Fair Use License\n\nVersion 1.1\n\n### Acceptance\n\nBy using the software, you agree to all of the terms and conditions above and below.\n\n### Copyright License\n\nThe licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below.\n\n### Trademark\n\n\"You may use the PLANKA name or logo only to describe that your service incorporates the software. Any other trademark use (e.g., in product names, domains, or marketing material) requires our prior written consent.\"\n\n### Permitted Use\n\nYou may use or modify PLANKA (a) for personal, hobby, or educational purposes, (b) internally within your own organization, (c) for private hosting for a typical number of friends, family, or personal projects, (d) to provide free access to non-profit organizations (as recognized by applicable tax authorities), or if you are a recognized non-profit organization yourself involving external users into your mission, and (e) public educational institutions for academic/research purposes only.\n\n### Restricted Use\n\nSharing accounts/credentials with third parties for business purposes or operating PLANKA as a hosted service for third parties for any commercial gain whatsoever is prohibited. Commercial gain includes any form of payment, advertising revenue, data monetization, or indirect commercial benefit or business advantage.\n\nFor all other PLANKA-based hosting services or shared use of PLANKA accounts across organizations, you need to buy a commercial PLANKA license.\n\n### Limitations\n\nYou may not alter, remove, or obscure any licensing, copyright, or other notices from the software provided by the licensor. Any use of the licensor's trademarks is subject to applicable law.\n\n### Patents\n\nThe licensor grants you a license, under any patent claims the licensor can license or becomes able to license, to make, have made, use, sell, offer for sale, import, and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company and everyone connected to your company.\n\n### Notices\n\nYou must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. If you modify the software, you must include in any modified copies of the software a prominent notice stating that you have modified the software.\n\n### No Other Rights\n\nThese terms do not imply any licenses other than those expressly granted in these terms.\n\n### Termination\n\nIf you use the software in violation of these terms, such use is not licensed, and your license will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your license will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your license to terminate automatically and permanently.\n\n### Violation\n\nViolation of our restricted use clauses will constitute a material breach of terms. PLANKA Software GmbH reserves the right to immediately terminate your access to its services and to pursue all available legal and equitable remedies.\n\n### No Liability\n\nAs far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim. Additionally, we are not responsible for bugs and mistakes in any third-party submodule or their referring license definition. If you find something problematic, please report it to us.\n\n### Definitions\n\nThe \"licensor\" is the entity offering these terms.\n\nThe \"software\" is the software the licensor makes available under these terms, including any portion of it.\n\n\"You\" refers to the individual or entity agreeing to these terms.\n\n\"Your company\" is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Control means ownership of substantially all the assets of an entity.\n\n\"Your license\" is the license granted to you for the software under these terms.\n\n\"Use\" means anything you do with the software requiring your license.\n\n\"Trademark\" means trademarks, service marks, and similar rights.\n"
  },
  {
    "path": "LICENSES/PLANKA Commercial License DE.md",
    "content": "**PLANKA Commercial License**\n\nVersion 1.2 - Zuletzt aktualisiert: 28. Nov 2025\n\nZugehörige Dateien in Englisch:\n\n- [PLANKA Community License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20EN.md)\n- [PLANKA Commercial License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md)\n- [PLANKA License Guide EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20EN.md)\n\nZugehörige Dateien in Deutsch:\n\n- [PLANKA Community License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20DE.md)\n- PLANKA Commercial License DE.md (diese Datei)\n- [PLANKA License Guide DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20DE.md)\n\n---\n\n# PLANKA Pro/Enterprise-Lizenz\n\nCopyright (c) 2025 bis heute von PLANKA Software GmbH.\n\nUnsere Software und zugehörige Dokumentationsdateien (die \"Software\") dürfen nur dann produktiv genutzt werden, wenn Sie (und jede juristische Person, die Sie vertreten) eine gültige \"PLANKA Pro/Enterprise-Lizenz\" besitzen, die Ihrer Nutzung entspricht. Sie stimmen zu, dass die PLANKA Software GmbH und/oder ihre Lizenzgeber (falls zutreffend) alle Rechte, Titel und Ansprüche an und auf alle solche Modifikationen und/oder Patches behalten, und alle solche Modifikationen und/oder Patches dürfen nur mit einer gültigen \"PLANKA Pro/Enterprise-Lizenz\" für die entsprechende Nutzung verwendet, kopiert, modifiziert, angezeigt, verteilt oder anderweitig genutzt werden. Ungeachtet des Vorstehenden dürfen Sie die Software für Entwicklungs- und Testzwecke ohne Abonnement kopieren und modifizieren. Sie stimmen zu, dass PLANKA Software GmbH und/oder ihre Lizenzgeber (falls zutreffend) alle Rechte, Titel und Ansprüche an und auf alle solche Modifikationen behalten. Es werden Ihnen keine anderen Rechte gewährt als die, die hier ausdrücklich genannt sind. Vorbehaltlich des Vorstehenden ist es verboten, die Software zu kopieren, zusammenzuführen, zu veröffentlichen, zu verteilen, zu unterlizenzieren und/oder zu verkaufen.\n\n#### Komponenten von Drittanbietern\n\nFür alle Komponenten von Drittanbietern, die in unsere Software integriert sind, werden diese Komponenten unter der ursprünglichen Lizenz lizenziert, die vom Eigentümer der jeweiligen Komponente bereitgestellt wird.\n\n## PLANKA Pro/Enterprise Repositories\n\nNach dem Erwerb einer \"PLANKA Pro/Enterprise-Lizenz\" erhalten Sie Zugang zu unseren \"PLANKA Pro/Enterprise\"-Repositories. Dort finden Sie unsere neuesten stabilen Builds, die umfangreiche Tests durchlaufen haben und als produktionsreif gelten.\n\nWichtiger Hinweis zum Zugriffsumfang: Der Standardzugang umfasst ausschließlich die vorkompilierten Versionen unserer Software. Zugang zum Quellcode wird nur in Ausnahmefällen und nach gesonderter Vereinbarung mit PLANKA Software GmbH gewährt.\n\nUnabhängig vom Zugriffsumfang gilt: Die Weitergabe von Dateien, Quellcode oder Teilen davon aus unseren \"PLANKA Pro/Enterprise\"-Repositories an Dritte, die nicht zugriffsberechtigt sind, ist ohne vorherige schriftliche Genehmigung von PLANKA Software GmbH untersagt.\n\n## Eingeschränkte Garantie\n\nUNSERE SOFTWARE WIRD \"WIE SIE IST\" BEREITGESTELLT, OHNE JEGLICHE GARANTIE, AUSDRÜCKLICH ODER IMPLIZIERT, EINSCHLIEßLICH, ABER NICHT BESCHRÄNKT AUF DIE GARANTIEN DER MARKTGÄNGIGKEIT, EIGNUNG FÜR EINEN BESTIMMTEN ZWECK UND NICHTVERLETZUNG VON RECHTEN DRITTER. IN KEINEM FALL HAFTEN DIE AUTOREN ODER URHEBERRECHTSINHABER FÜR ANSPRÜCHE, SCHÄDEN ODER ANDERE HAFTUNG, OB AUS VERTRAG, UNERLAUBTER HANDLUNG ODER ANDERWEITIG, DIE SICH AUS, AUS ODER IN VERBINDUNG MIT DER SOFTWARE ODER DER NUTZUNG ODER ANDEREN GESCHÄFTEN MIT DER SOFTWARE ERGEBEN.\n"
  },
  {
    "path": "LICENSES/PLANKA Commercial License EN.md",
    "content": "**PLANKA Commercial License**\n\nVersion 1.2 - Last updated: Nov 28, 2025\n\nRelated files in English:\n\n- [PLANKA Community License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20EN.md)\n- PLANKA Commercial License EN.md (this file)\n- [PLANKA License Guide EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20EN.md)\n\nRelated files in German:\n\n- [PLANKA Community License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20DE.md)\n- [PLANKA Commercial License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20DE.md)\n- [PLANKA License Guide DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20DE.md)\n\n---\n\n# PLANKA Pro/Enterprise License\n\nCopyright (c) 2025 to present by PLANKA Software GmbH.\n\nOur software and associated documentation files (the \"Software\") may only be used in production if you (and any entity that you represent) hold a valid \"PLANKA Pro/Enterprise License\" corresponding to your usage. You agree that PLANKA Software GmbH and/or its licensors (as applicable) retain all right, title, and interest in and to all such modifications and/or patches, and all such modifications and/or patches may only be used, copied, modified, displayed, distributed, or otherwise exploited with a valid \"PLANKA Pro/Enterprise License\" for the corresponding usage. Notwithstanding the foregoing, you may copy and modify the Software for development and testing purposes without requiring a subscription. You agree that PLANKA Software GmbH and/or its licensors (as applicable) retain all right, title, and interest in and to all such modifications. You are not granted any other rights beyond what is expressly stated herein. Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense, and/or sell the Software.\n\n#### Third-Party Components\n\nFor all third-party components incorporated into our Software, those components are licensed under the original license provided by the owner of the applicable component.\n\n## PLANKA Pro/Enterprise Repositories\n\nAfter purchasing a \"PLANKA Pro/Enterprise License\", you will receive access to our \"PLANKA Pro/Enterprise\" repositories. There you will find our latest stable builds, which have undergone extensive testing and are considered production-ready.\n\nImportant note on access scope: Standard access includes only the precompiled versions of our software. Access to the source code is granted only in exceptional cases and requires a separate agreement with PLANKA Software GmbH.\n\nRegardless of access scope: The distribution of files, source code, or any parts thereof from our \"PLANKA Pro/Enterprise\" repositories to third parties who are not authorized for access is prohibited without prior written permission from PLANKA Software GmbH.\n\n## Limited Warranty\n\nOUR 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.\n"
  },
  {
    "path": "LICENSES/PLANKA Community License DE.md",
    "content": "**PLANKA Community License**\n\nVersion 1.1 - Zuletzt aktualisiert: 20. Mai 2025\n\nZugehörige Dateien in Englisch:\n\n- [PLANKA Community License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20EN.md)\n- [PLANKA Commercial License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md)\n- [PLANKA License Guide EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20EN.md)\n\nZugehörige Dateien in Deutsch:\n\n- PLANKA Community License DE.md (diese Datei)\n- [PLANKA Commercial License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20DE.md)\n- [PLANKA License Guide DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20DE.md)\n\n---\n\n# PLANKA Community-Lizenz\n\nFür die Gemeinschaft zugängliche und gekennzeichnete Dateien sind wie folgt lizenziert:\n\n- Inhalte von Sourcecode-Branches außer dem Hauptbranch (üblicherweise \"master\" oder \"main\") sind nicht lizenziert.\n- Quellcode-Dateien oder andere Dateien, die \".pe.\" (für \"PLANKA Pro/Enterprise\") in ihren Datei- oder Ordnernamen enthalten oder anderweitig durch \"PLANKA Pro/Enterprise\" in ihren Dateiköpfen oder Ordnern gekennzeichnet sind, sind NICHT unter der \"Fair Use Lizenz\" lizenziert. Diese Dateien sind \"PLANKA Pro/Enterprise\"-Dateien und sind unter der \"PLANKA Pro/Enterprise-Lizenz\" lizenziert.\n- Um \"PLANKA Pro/Enterprise\"-Dateien oder Quellen zu nutzen, müssen Sie eine gültige \"PLANKA Pro/Enterprise-Lizenz\" besitzen. Sie können mehr über unsere kommerzielle Lizenz in der [PLANKA Commercial License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20DE.md) lesen.\n- Alle Komponenten von Drittanbietern, die in unsere Software integriert sind, sind unter der ursprünglichen Lizenz lizenziert, die vom Eigentümer der jeweiligen Komponente bereitgestellt wird.\n- Inhalte außerhalb der oben genannten Dateien oder Einschränkungen sind unter der \"Fair Use Lizenz\" verfügbar, wie unten definiert.\n\n## Fair Use Lizenz\n\nVersion 1.1\n\n### Annahme\n\nDurch die Nutzung der Software stimmen Sie allen oben und unten aufgeführten Bedingungen zu.\n\n### Urheberrechtslizenz\n\nDer Lizenzgeber gewährt Ihnen eine nicht-exklusive, gebührenfreie, weltweite, nicht unterlizenzierbare, nicht übertragbare Lizenz zur Nutzung, Kopie, Verteilung, Verfügbarmachung und Erstellung abgeleiteter Werke der Software, in jedem Fall vorbehaltlich der unten genannten Einschränkungen.\n\n### Marke\n\n\"Sie dürfen den Namen oder das Logo von PLANKA nur verwenden, um zu beschreiben, dass Ihr Dienst die Software enthält. Jede andere Markennutzung (z.B. in Produktnamen, Domains oder Marketingmaterial) bedarf unserer vorherigen schriftlichen Zustimmung.\"\n\n### Erlaubte Nutzung\n\nSie dürfen PLANKA nutzen oder modifizieren (a) für persönliche, Hobby- oder Bildungszwecke, (b) intern innerhalb Ihrer eigenen Organisation, (c) für privates Hosting für eine typische Anzahl von Freunden, Familie oder persönlichen Projekten, (d) um gemeinnützigen Organisationen (wie von den jeweiligen Steuerbehörden anerkannt) kostenlosen Zugang zu gewähren, oder wenn Sie selbst eine anerkannte gemeinnützige Organisation sind, die externe Nutzer in Ihre Mission einbezieht, und (e) öffentliche Bildungseinrichtungen ausschließlich für akademische/Forschungszwecke.\n\n### Eingeschränkte Nutzung\n\nDas Teilen von Konten/Zugangsdaten mit Dritten für geschäftliche Zwecke oder der Betrieb von PLANKA als gehosteter Dienst für Dritte zu jeglichen kommerziellen Gewinn ist untersagt. Kommerzieller Gewinn umfasst jede Form von Zahlung, Werbeeinnahmen, Datenmonetarisierung oder indirekten kommerziellen Nutzen oder Geschäftsvorteil.\n\nFür alle anderen PLANKA-basierten Hosting-Dienste oder die gemeinsame Nutzung von PLANKA-Konten zwischen Organisationen müssen Sie eine kommerzielle PLANKA-Lizenz erwerben.\n\n### Einschränkungen\n\nSie dürfen keine vom Lizenzgeber bereitgestellten Lizenz-, Urheber- oder anderen Hinweise in der Software verändern, entfernen oder verschleiern. Jede Nutzung der Marken des Lizenzgebers unterliegt dem geltenden Recht.\n\n### Patente\n\nDer Lizenzgeber gewährt Ihnen eine Lizenz unter allen Patentansprüchen, die der Lizenzgeber lizenzieren kann oder lizenzieren können wird, um die Software herzustellen, herstellen zu lassen, zu nutzen, zu verkaufen, zum Verkauf anzubieten, zu importieren und importieren zu lassen, jeweils vorbehaltlich der Einschränkungen und Bedingungen in dieser Lizenz. Diese Lizenz erstreckt sich nicht auf Patentansprüche, die Sie durch Modifikationen oder Ergänzungen der Software verletzen lassen. Wenn Sie oder Ihr Unternehmen einen schriftlichen Anspruch geltend machen, dass die Software ein Patent verletzt oder zur Verletzung beiträgt, endet Ihre unter diesen Bedingungen gewährte Patentlizenz für die Software sofort. Wenn Ihr Unternehmen einen solchen Anspruch geltend macht, endet Ihre Patentlizenz sofort für Arbeiten im Auftrag Ihres Unternehmens und für alle mit Ihrem Unternehmen verbundenen Personen.\n\n### Hinweise\n\nSie müssen sicherstellen, dass jeder, der eine Kopie eines Teils der Software von Ihnen erhält, auch eine Kopie dieser Bedingungen erhält. Wenn Sie die Software modifizieren, müssen Sie in allen modifizierten Kopien der Software einen auffälligen Hinweis aufnehmen, der besagt, dass Sie die Software modifiziert haben.\n\n### Keine weiteren Rechte\n\nDiese Bedingungen implizieren keine anderen Lizenzen als die, die in diesen Bedingungen ausdrücklich gewährt werden.\n\n### Kündigung\n\nWenn Sie die Software unter Verletzung dieser Bedingungen nutzen, ist eine solche Nutzung nicht lizenziert, und Ihre Lizenz wird automatisch gekündigt. Wenn der Lizenzgeber Ihnen eine Mitteilung über Ihre Verletzung zukommen lässt und Sie alle Verletzungen dieser Lizenz spätestens 30 Tage nach Erhalt dieser Mitteilung einstellen, wird Ihre Lizenz rückwirkend wiederhergestellt. Wenn Sie jedoch nach einer solchen Wiederherstellung gegen diese Bedingungen verstoßen, führt jeder weitere Verstoß gegen diese Bedingungen dazu, dass Ihre Lizenz automatisch und dauerhaft gekündigt wird.\n\n### Verletzung\n\nDie Verletzung unserer Nutzungsbeschränkungsklauseln stellt eine wesentliche Vertragsverletzung dar. Die PLANKA Software GmbH behält sich das Recht vor, Ihren Zugang zu seinen Diensten sofort zu beenden und alle verfügbaren rechtlichen und durchsetzbaren Rechtsmittel zu verfolgen.\n\n### Keine Haftung\n\nSoweit es das Gesetz erlaubt, wird die Software wie sie ist, ohne jegliche Garantie oder Bedingung geliefert, und der Lizenzgeber haftet Ihnen gegenüber nicht für Schäden, die sich aus diesen Bedingungen oder der Nutzung oder Art der Software ergeben, unter keiner Art von Rechtsanspruch. Darüber hinaus sind wir nicht verantwortlich für Fehler und Irrtümer in Submodulen von Drittanbietern oder deren jeweiligen Lizenzdefinitionen. Wenn Sie etwas Problematisches finden, melden Sie es uns bitte.\n\n### Definitionen\n\nDer \"Lizenzgeber\" ist die juristische Person, die diese Bedingungen anbietet.\n\nDie \"Software\" ist die Software, die der Lizenzgeber unter diesen Bedingungen verfügbar macht, einschließlich einzelner Teile davon.\n\n\"Sie\" bezieht sich auf die natürliche oder juristische Person, die diesen Bedingungen zustimmt.\n\n\"Ihr Unternehmen\" ist jede juristische Person, Einzelunternehmen oder eine andere Art von Organisation, für die Sie arbeiten, sowie alle Organisationen, die die Kontrolle über diese Organisation haben, unter der Kontrolle dieser Organisation stehen oder unter gemeinsamer Kontrolle mit dieser Organisation stehen. Kontrolle bedeutet Eigentum an im Wesentlichen allen Vermögenswerten einer Einheit.\n\n\"Ihre Lizenz\" ist die Lizenz, die Ihnen unter diesen Bedingungen für die Software gewährt wird.\n\n\"Nutzung\" bedeutet alles, was Sie mit der Software tun, wofür Ihre Lizenz erforderlich ist.\n\n\"Marke\" bedeutet Marken, Dienstleistungsmarken und ähnliche Rechte.\n"
  },
  {
    "path": "LICENSES/PLANKA Community License EN.md",
    "content": "**PLANKA Community License**\n\nVersion 1.1 - Last updated: May 20, 2025\n\nRelated files in English:\n\n- PLANKA Community License EN.md (this file)\n- [PLANKA Commercial License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md)\n- [PLANKA License Guide EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20EN.md)\n\nRelated files in German:\n\n- [PLANKA Community License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20DE.md)\n- [PLANKA Commercial License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20DE.md)\n- [PLANKA License Guide DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20DE.md)\n\n---\n\n# PLANKA Community License\n\nFiles accessible to and marked for community use are licensed as follows:\n\n- Content of branches other than the main branch (usually \"master\" or \"main\") is not licensed.\n- Source code files or other files that contain \".pe.\" (for \"PLANKA Pro/Enterprise\") in their file names or folder names or are otherwise marked as \"PLANKA Pro/Enterprise\" in their file headers or folders are NOT licensed under the \"Fair Use License\". These files are \"PLANKA Pro/Enterprise\" files and are licensed under the \"PLANKA Pro/Enterprise License\".\n- To use any \"PLANKA Pro/Enterprise\" files or sources, you must own a valid \"PLANKA Pro/Enterprise License\". You can read more about our commercial license in the [PLANKA Commercial License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md).\n- All third-party components incorporated into our software are licensed under the original license provided by the owner of the applicable component.\n- Content outside of the above-mentioned files or restrictions is available under the \"Fair Use License\" as defined below.\n\n## Fair Use License\n\nVersion 1.1\n\n### Acceptance\n\nBy using the software, you agree to all of the terms and conditions above and below.\n\n### Copyright License\n\nThe licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below.\n\n### Trademark\n\n\"You may use the PLANKA name or logo only to describe that your service incorporates the software. Any other trademark use (e.g., in product names, domains, or marketing material) requires our prior written consent.\"\n\n### Permitted Use\n\nYou may use or modify PLANKA (a) for personal, hobby, or educational purposes, (b) internally within your own organization, (c) for private hosting for a typical number of friends, family, or personal projects, (d) to provide free access to non-profit organizations (as recognized by applicable tax authorities), or if you are a recognized non-profit organization yourself involving external users into your mission, and (e) public educational institutions for academic/research purposes only.\n\n### Restricted Use\n\nSharing accounts/credentials with third parties for business purposes or operating PLANKA as a hosted service for third parties for any commercial gain whatsoever is prohibited. Commercial gain includes any form of payment, advertising revenue, data monetization, or indirect commercial benefit or business advantage.\n\nFor all other PLANKA-based hosting services or shared use of PLANKA accounts across organizations, you need to buy a commercial PLANKA license.\n\n### Limitations\n\nYou may not alter, remove, or obscure any licensing, copyright, or other notices from the software provided by the licensor. Any use of the licensor's trademarks is subject to applicable law.\n\n### Patents\n\nThe licensor grants you a license, under any patent claims the licensor can license or becomes able to license, to make, have made, use, sell, offer for sale, import, and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company and everyone connected to your company.\n\n### Notices\n\nYou must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. If you modify the software, you must include in any modified copies of the software a prominent notice stating that you have modified the software.\n\n### No Other Rights\n\nThese terms do not imply any licenses other than those expressly granted in these terms.\n\n### Termination\n\nIf you use the software in violation of these terms, such use is not licensed, and your license will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your license will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your license to terminate automatically and permanently.\n\n### Violation\n\nViolation of our restricted use clauses will constitute a material breach of terms. PLANKA Software GmbH reserves the right to immediately terminate your access to its services and to pursue all available legal and equitable remedies.\n\n### No Liability\n\nAs far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim. Additionally, we are not responsible for bugs and mistakes in any third-party submodule or their referring license definition. If you find something problematic, please report it to us.\n\n### Definitions\n\nThe \"licensor\" is the entity offering these terms.\n\nThe \"software\" is the software the licensor makes available under these terms, including any portion of it.\n\n\"You\" refers to the individual or entity agreeing to these terms.\n\n\"Your company\" is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Control means ownership of substantially all the assets of an entity.\n\n\"Your license\" is the license granted to you for the software under these terms.\n\n\"Use\" means anything you do with the software requiring your license.\n\n\"Trademark\" means trademarks, service marks, and similar rights.\n"
  },
  {
    "path": "LICENSES/PLANKA License Guide DE.md",
    "content": "**PLANKA License Guide**\n\nVersion 1.1 - Zuletzt aktualisiert: 20. Mai 2025\n\nZugehörige Dateien in Englisch:\n\n- [PLANKA Community License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20EN.md)\n- [PLANKA Commercial License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md)\n- [PLANKA License Guide EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20EN.md)\n\nZugehörige Dateien in Deutsch:\n\n- [PLANKA Community License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20DE.md)\n- [PLANKA Commercial License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20DE.md)\n- PLANKA License Guide DE.md (diese Datei)\n\n---\n\n## PLANKAs \"Fair Use Lizenz\" und die \"PLANKA Pro/Enterprise-Lizenz\"\n\nUnsere [Fair Use Lizenz](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20DE.md) und unsere [PLANKA Pro/Enterprise-Lizenz](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20DE.md) basieren auf dem [fair-code](http://faircode.io)-Modell.\n\n### Spezielle Lizenzierung für Bildungseinrichtungen und gemeinnützige Organisationen\n\nBildungseinrichtungen und gemeinnützige Organisationen, die kommerzielle Funktionen oder eine Nutzung über unsere \"Fair Use Lizenz\" hinaus benötigen, sind eingeladen, uns für maßgeschneiderte Lizenzlösungen und Bildungspreise zu kontaktieren. Wir freuen uns darauf, Ihrer Organisation dabei zu helfen, ihre Mission zu erfüllen!\n\n# Lizenz-FAQs\n\n### Unter welcher Lizenz wird PLANKA angeboten?\n\nPLANKA verwendet die [Fair Use Lizenz](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20DE.md) und die [PLANKA Pro/Enterprise-Lizenz](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20DE.md). Diese Lizenzen basieren auf dem [fair-code](http://faircode.io)-Modell.\n\n### Welcher Quellcode ist durch PLANKAs \"Fair Use Lizenz\" abgedeckt?\n\nDie [Fair Use Lizenz](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20DE.md) gilt für unseren Quellcode, der in unserem [GitHub-Hauptrepository](https://github.com/plankanban/planka) gehostet wird, mit folgenden Ausnahmen:\n\n- Inhalte von Branches außer dem Hauptbranch (üblicherweise \"master\" oder \"main\").\n\n- Quellcode-Dateien oder andere Dateien, die \".pe.\" (für \"PLANKA Pro/Enterprise\") in ihren Datei- oder Ordnernamen enthalten.\n\n- Quellcode-Dateien, die in den Dateien oder Ordnern als \"PLANKA Pro/Enterprise\" gekennzeichnet sind.\n\n- Quellcode in Ordnern, die separate Lizenzdateien enthalten, die sie eindeutig als \"PLANKA Pro/Enterprise\" kennzeichnen.\n\nDiese Ausnahmen sind unter der [PLANKA Pro/Enterprise-Lizenz](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20DE.md) lizenziert.\n\n### Was genau ist die \"Fair Use Lizenz\"?\n\nDie \"Fair Use Lizenz\" fällt in die Kategorie der sogenannten [fair-code](http://faircode.io)-Lizenzen. PLANKAs Lizenz basiert auf und erweitert die \"Sustainable Use License\", die von [n8n](https://n8n.io) eingeführt wurde und für deren Beratung wir sehr dankbar sind. Mit ähnlichen Zielen vor Augen haben wir beschlossen, deren Beispiel zu folgen und dieses Lizenz-Modell für unsere eigenen Bedürfnisse anzupassen. [Weiter unten](#warum-haben-sie-diese-lizenz-gewählt) können Sie nachlesen, warum wir diese Lizenzart gewählt haben.\n\nDie Lizenz gewährt Ihnen das kostenlose Recht, die Software zu nutzen, zu modifizieren, abgeleitete Werke zu erstellen und weiterzugeben unter folgenden Bedingungen:\n\n- Sie dürfen PLANKA nutzen oder modifizieren (a) für persönliche, Hobby- oder Bildungszwecke, (b) intern innerhalb Ihrer eigenen Organisation, (c) für privates Hosting für eine typische Anzahl von Freunden, Familie oder persönlichen Projekten, (d) um gemeinnützigen Organisationen (wie von den jeweiligen Steuerbehörden anerkannt) kostenlosen Zugang zu gewähren, oder wenn Sie selbst eine anerkannte gemeinnützige Organisation sind, die externe Nutzer in Ihre Mission einbezieht, und (e) öffentliche Bildungseinrichtungen ausschließlich für akademische/Forschungszwecke.\n\n- Das Teilen von Konten/Zugangsdaten mit Dritten für geschäftliche Zwecke oder der Betrieb von PLANKA als gehosteter Dienst für Dritte zu jeglichen kommerziellen Gewinn ist untersagt. Kommerzieller Gewinn umfasst jede Form von Zahlung, Werbeeinnahmen, Datenmonetarisierung oder indirekten kommerziellen Nutzen oder Geschäftsvorteil.\n\n- Sie dürfen keine vom Lizenzgeber bereitgestellten Lizenz-, Urheber- oder anderen Hinweise in der Software verändern, entfernen oder verschleiern. Jede Nutzung der Marken des Lizenzgebers unterliegt dem geltenden Recht.\n\n### Was ist im Rahmen der Lizenz im Kontext von PLANKAs Produkten erlaubt und was NICHT?\n\nUnsere Lizenz erlaubt die Nutzung für \"interne Geschäftszwecke\" sowie für persönliche, Hobby-, Bildungs- und begrenzte private Hosting-Szenarien. Sie verbietet jedoch die Nutzung von PLANKA für jede Form des kommerziellen Gewinns, wie z.B. den Verkauf eines Produkts oder einer Dienstleistung, bei dem der Wert in irgendeiner Form von PLANKAs Funktionalität abgeleitet wird, den Betrieb als kostenpflichtiger gehosteter Dienst, die Monetarisierung von Zugang oder Benutzerdaten oder die Erzielung anderer indirekter kommerzieller Vorteile.\n\n##### Hier sind einige Beispiele, die nicht erlaubt wären:\n\n- PLANKA unter einem White-Label anzubieten und es Ihren Kunden oder Partnern gegen Geld anzubieten.\n\n- PLANKA zu hosten und Dritten für den Zugriff Geld berechnen.\n\n- PLANKAs API für Dienste zu nutzen, für die Geld verlangt wird.\n\n- Die Verwendung von PLANKA, um Ihre eigene juristische Person mit juristischen Personen oder Personen außerhalb Ihrer Organisation im Kontext einer kommerziellen Beziehung zu verbinden.\n\n- PLANKA zur Durchführung oder Unterstützung illegaler oder rechtswidriger Aktivitäten zu verwenden.\n\n##### Die folgenden Beispiele sind unter unserer Lizenz erlaubt:\n\n- Verwendung von PLANKA zur Kontrolle Ihrer internen Prozesse und Verwaltung Ihrer internen Projekte.\n\n- Integration von PLANKA in andere intern genutzte Produkte, um deren Fähigkeiten zu erweitern.\n\n- Bereitstellung von Beratungs- oder öffentlichen Bildungsdienstleistungen im Zusammenhang mit PLANKA, zum Beispiel zum Aufbau oder zur Integration von Arbeitsabläufen für oder in Verbindung mit PLANKA oder zur Entwicklung benutzerdefinierter Module zur Erweiterung seiner Funktionalitäten.\n\n- Unterstützung von PLANKA, zum Beispiel durch Einrichtung oder Wartung auf einem internen Firmenserver.\n\n### Ist es erlaubt, PLANKA als Backend-Integration zu nutzen?\n\nWenn Sie PLANKA und seine Backend-Dienste über interne Betriebsabläufe innerhalb Ihrer eigenen Organisation hinaus nutzen, indem Sie Dritten Zugang für kommerziellen Gewinn ermöglichen, wie z.B. das Teilen von Konten mit Kunden oder den Verkauf eines Produkts oder einer Dienstleistung, bei dem der Wert in irgendeiner Form von PLANKAs Funktionalität abgeleitet wird, den Betrieb als kostenpflichtiger gehosteter Dienst, die Monetarisierung von Zugang oder Benutzerdaten oder die Erzielung anderer indirekter kommerzieller Vorteile, sind Sie verpflichtet, eine \"PLANKA Pro/Enterprise-Lizenz\" zu erwerben.\n\nDies umfasst Szenarien, in denen PLANKA als Kerninfrastruktur für Drittanbieterlösungen dient, von Benutzern außerhalb Ihrer juristischen Person als Teil eines kostenpflichtigen oder kommerziellen Angebots genutzt wird oder bei denen Sie PLANKA unterlizenzieren, neu verpacken oder anderweitig externen Parteien zur Verfügung stellen. Jede Vereinbarung, die die Integration von PLANKA in ein anderes Produkt beinhaltet, um als primärer Betriebsmotor für dieses Produkt zu dienen, erfordert ebenfalls eine gültige \"PLANKA Pro/Enterprise-Lizenz\" oder proprietäre Lizenz, die für unsere Unternehmenskunden verfügbar ist.\n\n### Können Sie mir einige schnelle Beispiele geben, um kostenlose Nutzung vs. Unternehmensnutzung zu verdeutlichen?\n\n---\n\n##### Beispiel 1: Verwendung der PLANKA-API zur Steuerung oder Reaktion auf Fertigungsprozess-Ereignissen\n\nNutzen Sie unsere API, um das Feedback aus Fertigungsschritten in Ihrem Unternehmen anzuzeigen und zu steuern oder um Produktionslinien-Ereignisse durch das Verschieben von Karten zu steuern.\n\n**ERLAUBT** unter der \"Fair Use Lizenz\". Sie können PLANKA in Ihre anderen Systeme integrieren, um seine API zur Steuerung Ihrer internen Prozesse zu nutzen.\n\n##### Beispiel 2: Kommerzielle Beratungs- oder Supportdienste anbieten\n\nSie bieten Ihrem Kunden einen Dienst an, der ihm hilft, neue Arbeitsabläufe und Board-Konzepte in einer PLANKA-Instanz zu implementieren.\n\n**ERLAUBT** unter der \"Fair Use Lizenz\". Sie können frei kommerzielle Beratungs- oder Integrations- und Supportdienste für PLANKA anbieten, ohne dass eine separate Lizenzvereinbarung mit uns erforderlich ist.\n\n##### Beispiel 3: PLANKA in einem kostenlosen und öffentlichen Docker-Image bündeln\n\nEine Schule oder gemeinnützige Organisation bündelt PLANKA in einem kostenlosen, öffentlichen Docker-Image für Studenten, die PLANKA zur Organisation ihrer Studienbereiche nutzen möchten.\n\n**ERLAUBT** unter der \"Fair Use Lizenz\". Da PLANKA kostenlos und ohne kommerzielle Einnahmen im Sinn weitergegeben wird, sind Sie mehr als willkommen, gemeinnützigen Einrichtungen und Schulen einen besseren Zugang zu PLANKA zu ermöglichen.\n\n**JEDOCH** wenn Sie auch planen, Schülern und Studenten Zugang zu einer ansonsten schulinternen PLANKA-Instanz zu gewähren, würde dies unsere \"PLANKA-Bildungslizenz\" erfordern, die wir sehr gerne auf Anfrage anbieten.\n\n##### Beispiel 4: PLANKA-Logins für Kunden und Partner bereitstellen\n\nUm ein besseres Projekt-Feedback zu ermöglichen, bieten Sie Ihrem Kunden und jemandem aus einem Joint Venture, mit denen Sie beide eine kommerzielle Beziehung haben, Zugang zu Projektboards innerhalb Ihres Unternehmens an. Sie können nun z.B. Karten kommentieren und auch von PLANKAs Echtzeit-Update-Funktionen profitieren.\n\n**NICHT ERLAUBT** unter der \"Fair Use Lizenz\". Das Anbieten von PLANKA als Teil eines kostenpflichtigen Dienstes für Dritte oder die Bereitstellung von PLANKA-Zugang für Drittbenutzer außerhalb Ihrer eigenen juristischen Person im Kontext einer kommerziellen Beziehung erfordert, dass Sie eine \"PLANKA Pro/Enterprise-Lizenz\" registrieren.\n\n##### Beispiel 5: PLANKA als gehostetes Produkt für andere Unternehmen anbieten\n\nSie möchten Geld damit verdienen, indem Sie PLANKA Unternehmen, Freiberuflern und anderen Personen zur Verfügung stellen. Auf diese Weise haben sie einfachen Zugang zu Projektmanagement und -kontrolle von überall.\n\n**NICHT ERLAUBT** unter der \"Fair Use Lizenz\". Der Verkauf PLANKA-basierter Dienste erfordert, dass Sie einer \"PLANKA-Wiederverkäuferlizenz\" zustimmen. Wir haben eine spezielle Hosting-Vereinbarung für diejenigen, die kostenpflichtige PLANKA-Dienste für Kunden anbieten oder einfach als Wiederverkäufer für unsere eigenen \"PLANKA Corporate Hosting Services\" fungieren möchten.\n\n---\n\n### Kurzübersicht der \"Fair Use Lizenz\"\n\n- Persönliche, Bildungs-, Hobby- oder interne Geschäftsnutzung:\n\n    Kostenlos nutzbar. Sie können PLANKA für sich selbst, Ihr Team oder Ihre Organisation für persönliche, Bildungs- oder interne operative Zwecke nutzen, modifizieren und hosten.\n\n- Nutzung durch gemeinnützige Organisationen und öffentliche Bildungseinrichtungen:\n\n    Kostenlos für akademische oder Forschungszwecke nutzbar, vorausgesetzt die Organisation qualifiziert sich als gemeinnützig unter den geltenden Steuergesetzen. Keine Anmeldung oder spezielle Lizenz erforderlich.\n\n- Bereitstellung von Zugang für Dritte für kommerziellen Gewinn:\n\n    Nicht erlaubt. Das Teilen von Konten oder Zugangsdaten mit Dritten für kommerziellen Gewinn ist unter der Lizenz verboten, auch wenn Sie nicht direkt für den Zugang berechnen.\n\n- Hosting von PLANKA als kommerzieller Dienst:\n\n    Nicht erlaubt. Sie dürfen PLANKA nicht als gehosteten Dienst anbieten oder in ein kostenpflichtiges Produkt oder eine Dienstleistung integrieren, bei dem der Wert wesentlich von PLANKA stammt. Dies umfasst Werbeeinnahmen, Datenmonetarisierung oder indirekten kommerziellen Nutzen oder Geschäftsvorteil.\n\n### Was, wenn ich PLANKA für etwas nutzen möchte, das nicht durch die Lizenz erlaubt ist?\n\nUm die Grenzen der selbst gehosteten Community-Version zu überschreiten, benötigen Sie eine \"PLANKA Pro/Enterprise-Lizenz\". Sobald Sie sich bei uns registrieren, werden die Beschränkungen der \"Fair Use Lizenz\" nicht nur rechtlich aufgehoben, sondern Sie erhalten auch Zugang zum vollständigen Enterprise-Funktionsumfang.\n\nAlternativ können Sie diese Grenzen durch ein Abonnement einer gehosteten Version - entweder Community oder Pro/Enterprise - überschreiten. Dies ermöglicht beispielsweise die Bereitstellung von Konten für Ihre Kunden.\n\nWenn Sie PLANKA als Dienstleistung für andere Organisationen anbieten oder PLANKA in Ihre eigene Software integrieren möchten, müssen Sie eine separate Umsatzvereinbarung mit uns abschließen. Wir ermutigen aktiv Softwareintegratoren und technisches Personal, PLANKA in ihre anderen Produkte zu integrieren und zu verbinden und unsere umfangreiche API zu nutzen, um auf Prozesse innerhalb ihres Unternehmens zu reagieren, sie zu steuern und zu beherrschen; wir bitten sie lediglich, eine Vereinbarung zu unterzeichnen, die die Nutzungsbedingungen und die von PLANKA für die Nutzung des Produkts erforderlichen Lizenzgebühren festlegt. Über PLANKAs API kann es externe Systeme steuern und auf sie reagieren. Sie können [hier](https://docs.planka.cloud/docs/category/api-reference/) mehr darüber erfahren oder uns kontaktieren.\n\nWenn Sie sich nicht sicher sind, ob der von Ihnen ins Auge gefasste Anwendungsfall einen internen Geschäftszweck darstellt oder nicht, werfen Sie einen Blick auf die [Beispiele oben](#können-sie-mir-einige-schnelle-beispiele-geben-um-kostenlose-nutzung-vs-unternehmensnutzung-zu-verdeutlichen), und falls Sie immer noch unsicher sind, kontaktieren Sie uns bitte unter [license@planka.group](mailto:license@planka.group).\n\n### Warum gibt es keine kommerzielle Lizenz für eine selbst-gehostete Community-Version um die \"Fair Use License\"-Beschränkungen aufzuheben?\n\nWir unterstützen die selbst-gehostete Community-Version über unsere GitHub- und Discord-Community-Kanäle, aber wir können nicht effizient direkten Telefon- oder E-Mail-Support für mehrere Versionen bereitstellen, während wir unseren Verpflichtungen gegenüber zahlenden Kunden nachkommen. Deshalb ist direkter Support ausschließlich Pro/Enterprise-Kunden vorbehalten.\n\nWir bieten jedoch professionelle Installationsdienste und monatliche Serviceverträge für die Einrichtung, Aktualisierung und Überwachung aller Versionen an, um Organisationen dabei zu helfen, das Beste aus ihrer PLANKA-Bereitstellung herauszuholen - bitte kontaktieren Sie uns unter [license@planka.group](mailto:license@planka.group).\n\n### Warum nutzt PLANKA keine Standard-Open-Source-Lizenz?\n\nWir verbringen viel Zeit damit, ein einfaches, aber leistungsstarkes Tool zu erstellen, welches die Kontrolle und Beherrschung von Projekten zu einer angenehmen Erfahrung macht. Außerdem wollten wir, dass PLANKA so weit als möglich frei verfügbar ist, während wir gleichzeitig sicherstellen müssen, dass wir ein nachhaltiges und tragfähiges Geschäft aufbauen können. Indem wir unser Produkt kostenlos nutzbar, einfach verteilbar und quelloffen machten, helfen wir allen, auf das Produkt zuzugreifen. Indem wir als Unternehmen tätig sind, können wir langfristig neue Funktionen entwickeln und veröffentlichen, Fehler beheben und zuverlässige Software in großem Maßstab bereitstellen.\n\n### Warum haben Sie diese Lizenz gewählt?\n\nWir glauben, dass die \"Fair Use Lizenz\" sowohl für die Gemeinschaft als auch für die Entwickler von Vorteil ist. Entwicklung ist ein kostspieliges Unterfangen, und eine Community-Version kostenlos weiterzugeben, ist ein Risiko, welches viele Unternehmen nicht überleben, ohne ihre Software oder ihr Unternehmen zu veräußern. Daher leben viele Open-Source-Unternehmen von Spenden oder Finanzinvestoren. Anstatt unsere Seele zu verkaufen, verkaufen wir Dienstleistungen und Softwarelizenzen. Auf diese Weise können wir weiter wachsen, programmieren und unsere Community unterstützen. Die kurze Antwort lautet also \"Leben und leben lassen\" - so denken wir über PLANKA.\n\nDaher helfen wir dabei, [fair-code](https://faircode.io)-Software zu fördern, mit dem Ziel, sie zu einem bekannten Sammelbegriff zu machen, um Softwaremodelle wie unseres zu beschreiben. Um jegliche Reibung um unsere proprietäre Lizenz auf ein absolutes Minimum zu beschränken, konzentrieren wir uns auf zwei Dinge:\n\n1. Klare Sprache und minimale Länge - die Lizenz ist in klarem, präzisem Deutsch (eine englische Version existiert ebenfalls) geschrieben, mit nur den unbedingt notwendigen Klauseln.\n\n2. Förderung von fair-code - wir fördern aktiv das fair-code-Modell, damit die Menschen es als unkomplizierte, nachhaltige Möglichkeit erkennen, Software wie PLANKA zu teilen und zu verbessern.\n\n### Mein Unternehmen hat eine Richtlinie gegen die Verwendung von Code, der die kommerzielle Nutzung einschränkt - kann ich PLANKA trotzdem nutzen?\n\nVorausgesetzt, Sie nutzen PLANKA für interne Geschäftszwecke und stellen PLANKA nicht Ihren Kunden oder Partnern zur Verfügung, so sollten Sie natürlich auch PLANKA nutzen können. Wenn Sie sich nicht sicher sind, ob der von Ihnen ins Auge gefasste Anwendungsfall einen internen Geschäftszweck darstellt oder nicht, werfen Sie einen Blick auf die [Beispiele oben](#können-sie-mir-einige-schnelle-beispiele-geben-um-kostenlose-nutzung-vs-unternehmensnutzung-zu-verdeutlichen), und wenn Sie immer noch unsicher sind, schreiben Sie uns eine E-Mail an [license@planka.group](mailto:license@planka.group).\n\n### Was passiert mit Code, den ich zu PLANKA beitrage, in Bezug auf seine \"Fair Use Lizenz\"?\n\nJeder Code, den Sie auf GitHub beitragen, unterliegt GitHubs [Nutzungsbedingungen](https://docs.github.com/en/site-policy/github-terms/github-terms-of-service#d_user_generated_content). Einfach ausgedrückt bedeutet dies, dass Sie alles, was Sie beitragen, besitzen und dafür verantwortlich sind, dass Sie jedoch anderen GitHub-Benutzern bestimmte Rechte zur Nutzung dieses Codes einräumen. Wenn Sie Code zu einem Repository beitragen, das einen Hinweis auf eine Lizenz enthält, lizenzieren Sie den Code unter denselben Bedingungen.\n\nPLANKA bittet jeden Mitwirkenden, unsere [Contributor License Agreement](https://github.com/plankanban/planka/blob/master/CONTRIBUTOR_LICENSE_AGREEMENT.md) zu unterzeichnen. Zusätzlich zu den oben genannten Punkten gibt dies PLANKA die Möglichkeit, seine Lizenz zu ändern, ohne zusätzliche Genehmigung einzuholen. Es bedeutet auch, dass Sie nicht für Ihre Beiträge haftbar sind (z.B. falls sie den Geschäftsbetrieb einer anderen Person schädigen sollten).\n\nEs ist einfach, mit den Code-Beiträgen zu PLANKA auf [GitHub](https://github.com/plankanban) zu beginnen, und wir haben weitere Möglichkeiten zur Teilnahme an unserer Community [hier](https://github.com/plankanban/planka/blob/master/CONTRIBUTING.md) aufgelistet.\n\n### Ist PLANKA Open Source?\n\nPLANKAs Quellcode ist unter der \"Fair Use Lizenz\" frei verfügbar. Während dies nicht mit der strengen Definition der Open Source Initiative übereinstimmt (die keine Nutzungsbeschränkungen erlaubt), bietet PLANKA den meisten Benutzern, einschließlich Unternehmen, dennoch fast die gleichen Vorteile wie traditionelle Open-Source-Software.\n\nWir befürworten, was oft als 'fair-code'-Modell bezeichnet wird - unser Code ist quelloffen und folgt einer einfachen \"Leben und leben lassen\"-Philosophie. Dieser Ansatz ermöglicht es uns, ein nachhaltiges Unternehmen zu führen und gleichzeitig Transparenz und Flexibilität für unsere Community zu bieten. Viele Unternehmen übernehmen diesen ausgewogenen Lizenzierungsansatz, der den Geist der Offenheit bewahrt und gleichzeitig die langfristige Lebensfähigkeit des Projekts sicherstellt. Wir sind stolz darauf, Teil dieser Bewegung zu sein!\n\n### Was ist fair-code, und wie verhält sich die \"Fair Use Lizenz\" dazu?\n\nFair-code ist keine Softwarelizenz. Es beschreibt ein Softwaremodell, bei dem Software:\n\n- Allgemein frei verfügbar ist und von jedermann verbreitet werden kann.\n\n- Ihren Quellcode öffentlich verfügbar hat.\n\n- Von jedermann in öffentlichen und privaten Gemeinschaften erweitert werden kann.\n\n- Von ihren Autoren kommerziell eingeschränkt wird.\n\nDie \"Fair Use Lizenz\" ist eine fair-code-Lizenz. Sie können mehr darüber lesen und andere Beispiele für fair-code-Lizenzen [hier](https://faircode.io) sehen. Um mit uns bzgl. Lizenzfragen in Verbindung zu treten, senden Sie bitte eine E-Mail an [license@planka.group](mailto:license@planka.group).\n\n### Kann ich die \"Fair Use Lizenz\" für mein eigenes Projekt verwenden?\n\nJa! Wir selbst haben die \"Fair Use Lizenz\" genutzt, indem wir den Fußstapfen anderer folgten, die auf ihrer Website und in ihrer Lizenz offen andere dazu einladen, dem fair-code-Pfad zu folgen. Wie sie sind auch wir gespannt darauf, mehr Software zu sehen, die die \"Fair Use Lizenz\" verwendet.\n"
  },
  {
    "path": "LICENSES/PLANKA License Guide EN.md",
    "content": "**PLANKA License Guide**\n\nVersion 1.1 - Last updated: May 20, 2025\n\nRelated files in English:\n\n- [PLANKA Community License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20EN.md)\n- [PLANKA Commercial License EN.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md)\n- PLANKA License Guide EN.md (this file)\n\nRelated files in German:\n\n- [PLANKA Community License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20DE.md)\n- [PLANKA Commercial License DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20DE.md)\n- [PLANKA License Guide DE.md](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20DE.md)\n\n---\n\n## PLANKA's \"Fair Use License\" and the \"PLANKA Pro/Enterprise License\"\n\nOur [Fair Use License](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20EN.md) and our [PLANKA Pro/Enterprise License](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md) are based on the [fair-code](http://faircode.io) model.\n\n### Special licensing for educational & non-profit organizations\n\nEducational institutions and non-profit organizations requiring commercial-level features or usage beyond our \"Fair Use License\" are encouraged to contact us for tailored licensing solutions and educational pricing. We look forward to helping your organization accomplish its mission!\n\n# License FAQs\n\n### What license do you use for PLANKA?\n\nPLANKA uses the [Fair Use License](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20EN.md) and the [PLANKA Pro/Enterprise License](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md). These licenses are based on the [fair-code](http://faircode.io) model.\n\n### What source code is covered by the PLANKA's \"Fair Use License\"?\n\nThe [Fair Use License](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20EN.md) applies to our source code hosted in our [main GitHub repository](https://github.com/plankanban/planka) except:\n\n- Content of branches other than the main branch (usually \"master\" or \"main\").\n\n- Source code files or other files that contain \".pe.\" (for \"PLANKA Pro/Enterprise\") in their file names or folder names.\n\n- Source code files that are marked as \"PLANKA Pro/Enterprise\" in their file headers or folders.\n\n- Source code in folders that contain separate license files that clearly mark them as \"PLANKA Pro/Enterprise\".\n\nThese exceptions are licensed under the [PLANKA Pro/Enterprise License](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md).\n\n### What is the \"Fair Use License\"?\n\nThe \"Fair Use License\" falls under the so-called [fair-code](http://faircode.io) licenses category. PLANKA's license is based on and extends the \"Sustainable Use License\" introduced by [n8n](https://n8n.io) who's advice was greatly appreciated. With similar goals in mind, we decided to follow their lead and adopt their model for our own needs. [Further below](#why-did-you-choose-this-license) you can read why we chose this license.\n\nThe license allows you the free right to use, modify, create derivative works, and redistribute under the following conditions:\n\n- You may use or modify PLANKA (a) for personal, hobby, or educational purposes, (b) internally within your own organization, (c) for private hosting for a typical number of friends, family, or personal projects, (d) to provide free access to non-profit organizations (as recognized by applicable tax authorities), or if you are a recognized non-profit organization yourself involving external users into your mission, and (e) public educational institutions for academic/research purposes only.\n\n- Sharing accounts/credentials with third parties for business purposes or operating PLANKA as a hosted service for third parties for any commercial gain whatsoever is prohibited. Commercial gain includes any form of payment, advertising revenue, data monetization, or indirect commercial benefit or business advantage.\n\n- You may not alter, remove, or obscure any licensing, copyright, or other notices from the software provided by the licensor. Any use of the licensor's trademarks is subject to applicable law.\n\n### What is and is NOT allowed under the license in the context of PLANKA's products?\n\nOur license allows use for \"internal business purposes\", as well as for personal, hobby, educational, and limited private hosting scenarios. However, it prohibits using PLANKA for any form of commercial gain, such as selling a product or service where the value derives in any form from PLANKA's functionality, operating it as a paid hosted service, monetizing access or user data, or deriving other indirect commercial benefits.\n\n##### Here are some examples that would not be allowed:\n\n- White-labeling PLANKA and offering it to your customers or affiliates for money.\n\n- Hosting PLANKA and charging people money to access it.\n\n- Use PLANKA's API to power services for which money is charged.\n\n- Use of PLANKA to connect your own legal entity with legal entities or persons outside your organization in the context of a commercial relationship.\n\n- Use PLANKA to conduct or support any kind of illegal or unlawful activity.\n\n##### All of the following examples are allowed under our license:\n\n- Using PLANKA to control your internal processes and manage your internal projects.\n\n- Integrate PLANKA into other internally used products to enhance their capabilities.\n\n- Providing consulting or public educational services related to PLANKA, for example, to build or integrate workflows for or in connection with PLANKA or develop custom modules to extend its functionalities.\n\n- Supporting PLANKA, for example, by setting it up or maintaining it on an internal company server.\n\n### Is it allowed to use PLANKA as a backend integration?\n\nIf you use PLANKA and its backend services beyond internal operations within your own organization by enabling third-party access for commercial gain, such as sharing accounts with clients or selling a product or service where the value derives in any form from PLANKA's functionality, operating it as a paid hosted service, monetizing access or user data, or deriving other indirect commercial benefits, you are required to purchase a \"PLANKA Pro/Enterprise License\".\n\nThis includes scenarios where PLANKA serves as core infrastructure for third-party solutions, is accessed by users outside your legal entity as part of a paid or commercial offering, or where you sublicense, repackage, or otherwise make PLANKA available to external parties. Any arrangement that involves integrating PLANKA into another product to serve as the primary operational engine for that product also requires a valid \"PLANKA Pro/Enterprise License\" or proprietary license available for our enterprise customers.\n\n### Can you give me some quick examples to clarify free use vs. enterprise use?\n\n---\n\n##### Example 1: Use PLANKA's API to control or respond to fabrication machinery processes\n\nUse our API to show and control the feedback coming from fabrication steps inside your company or to control production line events by moving cards.\n\n**ALLOWED** under the \"Fair Use License\". You can integrate PLANKA into your other systems to use its API to control your internal processes.\n\n##### Example 2: Offer commercial consulting or support services\n\nYou provide a service to your client to help them implement new workflows and board concepts into the PLANKA setup.\n\n**ALLOWED** under the \"Fair Use License\". You are free to offer commercial consulting or integration and support services for PLANKA without the need for a separate license agreement with us.\n\n##### Example 3: Bundle PLANKA in a free and public Docker image\n\nA school or charity bundles PLANKA in a free, public Docker image for students who want to use PLANKA to organize their fields of study.\n\n**ALLOWED** under the \"Fair Use License\". Since PLANKA is given away for free and without commercial revenues in mind, you are more than welcome to allow charitable entities and schools better access to PLANKA.\n\n**HOWEVER** if you also plan to provide students access to an otherwise school's internal PLANKA instance, this would require our \"PLANKA Educational License\", which we will gladly offer on request.\n\n##### Example 4: Provide PLANKA logins to clients and affiliates\n\nTo facilitate better project feedback, you offer your client and someone from a joint venture, both of whom you have a commercial relationship with, access to project boards inside your company. They can now comment on cards and also benefit from PLANKA's real-time update capabilities.\n\n**NOT ALLOWED** under the \"Fair Use License\". Offering PLANKA as part of a paid service to third parties or providing PLANKA access to third-party users outside your own legal entity in the context of a commercial relationship requires you to register a \"PLANKA Pro/Enterprise License\".\n\n##### Example 5: Offer PLANKA as a hosted product to other companies\n\nYou want to earn money by providing PLANKA to companies, freelancers, and other people. This way they have easy access to project management and control from everywhere.\n\n**NOT ALLOWED** under the \"Fair Use License\". Selling PLANKA-based services requires you to agree to a \"PLANKA Reseller License\". We have a special hosting agreement for those who want to provide paid PLANKA service to customers or simply act as resellers for our own \"PLANKA Corporate Hosting Services\".\n\n---\n\n### Quick \"Fair Use License\" summary\n\n- Personal, educational, hobby, or internal business use:\n\n    Free to use. You can use, modify, and host PLANKA for yourself, your team, or your organization for personal, educational, or internal operational purposes.\n\n- Use by non-profits and public educational institutions:\n\n    Free to use for academic or research purposes, provided the organization qualifies as a non-profit under applicable tax laws. No application or special license is required.\n\n- Providing access to third parties for any commercial gain:\n\n    Not allowed. Sharing accounts or credentials with third parties for any commercial gain is prohibited under the license, even if you are not directly charging for access.\n\n- Hosting PLANKA as a commercial service:\n\n    Not allowed. You may not offer PLANKA as a hosted service or integrate it into a paid product or service where the value substantially comes from PLANKA. This includes advertising revenue, data monetization, or indirect commercial benefit or business advantage.\n\n### What if I want to use PLANKA for something that's not permitted by the license?\n\nTo exceed the limits of the self-hosted Community version, you'll need a \"PLANKA Pro/Enterprise License\". Once you register with us, the \"Fair Use License\" restrictions are not only legally lifted, but you also gain access to the full enterprise feature set.\n\nAlternatively, you can exceed these limits by subscribing to a hosted version - either Community or Pro/Enterprise. This allows, for example, providing accounts for your customers.\n\nIf you want to provide PLANKA as a service for other organizations or integrate PLANKA into your own software, you must sign a separate revenue agreement with us. We actively encourage software integrators and technical staff to integrate and connect PLANKA within their other products and use our extensive API to respond to, control, and master processes within their company; we just ask them to sign an agreement laying out the terms of use and the license fees required by PLANKA for using the product. Through PLANKA's API, it is capable of controlling and responding to external systems. You can learn more [here](https://docs.planka.cloud/docs/category/api-reference/) or contact us about it.\n\nIf you are unsure whether the use case you have in mind constitutes an internal business purpose or not, take a look at the [examples above](#can-you-give-me-some-quick-examples-to-clarify-free-use-vs-enterprise-use), and if you're still not sure, please contact us at [license@planka.group](mailto:license@planka.group).\n\n### Why isn't there a commercial license for a self-hosted Community version which lifts the \"Fair Use License\" limits?\n\nWe support the self-hosted Community version through our GitHub and Discord community channels, but we can't efficiently provide direct phone or email support for multiple versions while meeting our obligations to paying customers. That's why direct support is reserved exclusively for Pro/Enterprise customers.\n\nHowever, we do offer professional installation services and monthly-based service contracts for setting up, updating, and monitoring all versions to help organizations get the most out of their PLANKA deployment - please contact us at [license@planka.group](mailto:license@planka.group).\n\n### Why doesn't PLANKA use a default open-source license?\n\nWe spend a lot of time creating an easy yet powerful tool that makes controlling and mastering projects a fun experience. Also, we wanted PLANKA to be as widely and freely available as possible while also ensuring that we can build a sustainable and viable business. By making our product free to use, easy to distribute, and source-available, we help everyone access the product. By operating as a business, we can develop and release new features, fix bugs, and provide reliable software at scale long-term.\n\n### Why did you choose this license?\n\nWe believe that the \"Fair Use License\" is beneficial for the community as well as for the developers. Development is a costly enterprise, and giving away a Community version for free is a risk that many companies don't survive without selling software or the company. Therefore, many open-source companies live from donations or financial investors. Instead of selling our soul, we sell services and software licenses. This way we continue to grow, code, and support our community. So the short answer is \"Live and let live\" is how we feel about PLANKA.\n\nTherefore, we are helping to promote [fair-code](https://faircode.io) software with the goal of making it a well-known umbrella term to describe software models like ours. To keep any friction around our proprietary license to an absolute minimum, we focus on two things:\n\n1. Plain language, minimal length - the license is written in clear, concise English (a German version exists as well), with only the clauses absolutely needed.\n\n2. Advocating fair-code - we actively promote the fair-code model so people recognize it as a straightforward, sustainable way to share and improve software like PLANKA.\n\n### My company has a policy against using code that restricts commercial use - can I still use PLANKA?\n\nProvided you are using PLANKA for internal business purposes and not making PLANKA available to your customers or affiliates, then of course you should be able to use PLANKA. If you are unsure whether the use case you have in mind constitutes an internal business purpose or not, take a look at the [examples above](#can-you-give-me-some-quick-examples-to-clarify-free-use-vs-enterprise-use), and if you're still unclear, email us at [license@planka.group](mailto:license@planka.group).\n\n### What happens to code I contribute to PLANKA in regard to its \"Fair Use License\"?\n\nAny code you contribute on GitHub is subject to GitHub's [terms of use](https://docs.github.com/en/site-policy/github-terms/github-terms-of-service#d_user_generated_content). In simple terms, this means you own and are responsible for anything you contribute, but that you grant other GitHub users certain rights to use this code. When you contribute code to a repository containing notice of a license, you license the code under the same terms.\n\nPLANKA asks every contributor to sign our [Contributor License Agreement](https://github.com/plankanban/planka/blob/master/CONTRIBUTOR_LICENSE_AGREEMENT.md). In addition to the above, this gives PLANKA the ability to change its license without seeking additional permission. It also means you aren't liable for your contributions (e.g., in case they cause damage to someone else's business).\n\nIt's easy to get started contributing code to PLANKA on [GitHub](https://github.com/plankanban), and we've listed broader ways of participating in our community [here](https://github.com/plankanban/planka/blob/master/CONTRIBUTING.md).\n\n### Is PLANKA open source?\n\nPLANKA's source code is freely available under the \"Fair Use License\". While this doesn't align with the Open Source Initiative's strict definition (which doesn't allow any use limitations), PLANKA still offers nearly all the same benefits as traditionally open-source software to most users, including corporations.\n\nWe embrace what's often called the 'fair-code' model - our code is source-available and follows a simple \"Live and let live\" philosophy. This approach allows us to maintain a sustainable company while still providing transparency and flexibility to our community. Many companies are adopting this balanced licensing approach that preserves the spirit of openness while ensuring the project's long-term viability. We're proud to be part of this movement!\n\n### What is fair-code, and how does the \"Fair Use License\" relate to it?\n\nFair-code is not a software license. It describes a software model where software:\n\n- Is generally free to use and can be distributed by anyone.\n\n- Has its source code openly available.\n\n- Can be extended by anyone in public and private communities.\n\n- Is commercially restricted by its authors.\n\nThe \"Fair Use License\" is a fair-code license. You can read more about it and see other examples of fair-code licenses [here](https://faircode.io). To get in touch with us about license questions, please email [license@planka.group](mailto:license@planka.group).\n\n### Can I use the \"Fair Use License\" for my own project?\n\nYes! We ourselves made use of the \"Fair Use License\" by following others' footsteps who openly invite others on their website and in their license to follow the fair code path. Like them, we're excited to see more software use the \"Fair Use License\".\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n  ![Logo](https://raw.githubusercontent.com/plankanban/planka/master/assets/logo.png)\n\n  # PLANKA\n\n  _Project mastering driven by fun_\n\n  ![Version](https://img.shields.io/github/package-json/v/plankanban/planka?style=flat-square) [![Docker Pulls](https://img.shields.io/badge/docker_pulls-8M%2B-%23066da5?style=flat-square&color=red)](https://github.com/plankanban/planka/pkgs/container/planka) [![Contributors](https://img.shields.io/github/contributors/plankanban/planka?style=flat-square&color=blue)](https://github.com/plankanban/planka/graphs/contributors) [![Chat](https://img.shields.io/discord/1041440072953765979?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/WqqYNd7Jvt)\n\n  [Install](https://docs.planka.cloud/docs/installation/docker/production-version/) ·  [Demo](https://planka.app) · [Docs](https://docs.planka.cloud/docs/welcome/) · [API](https://plankanban.github.io/planka/swagger-ui/) · [Cloud](https://planka.app/pricing) · [Pro version](https://planka.app/pro)\n\n  ![Demo](https://raw.githubusercontent.com/plankanban/planka/master/assets/demo.gif)\n\n</div>\n\n## Key Features\n\n- **Collaborative Kanban Boards:** Create projects, boards, lists, cards, and manage tasks with an intuitive drag-and-drop interface\n- **Real-Time Updates:** Instant syncing across all users, no refresh needed\n- **Rich Markdown Support:** Write beautifully formatted card descriptions with a powerful markdown editor\n- **Flexible Notifications:** Get alerts through 100+ providers, fully customizable to your workflow\n- **Seamless Authentication:** Single sign-on with OpenID Connect integration\n- **Multilingual & Easy to Translate:** Full internationalization support for a global audience\n\n## How to Deploy\n\nPLANKA is easy to install using multiple methods - learn more in the [installation guide](https://docs.planka.cloud/docs/welcome/).\n\nFor configuration and environment settings, see the [configuration section](https://docs.planka.cloud/docs/category/configuration/).\n\nInterested in a hosted or [Pro version](https://planka.app/pro) of PLANKA? Check out the pricing on our [website](https://planka.app/pricing).\n\n## Notes App\n\nA testing version of the Notes app is now available on multiple platforms:\n\n- **iOS:** Join the [TestFlight](https://testflight.apple.com/join/5eJqTaJW) to try the app\n- **Windows & Android:** Download the app [here](https://planka-notes.hillerdaniel.de)\n\n## Contact\n\nFor any security issues, please do not create a public issue on GitHub - instead, report it privately by emailing [security@planka.group](mailto:security@planka.group).\n\n**Note:** We do NOT offer any public support via email, please use GitHub.\n\n**Join our community:** Get help, share ideas, or contribute on our [Discord server](https://discord.gg/WqqYNd7Jvt).\n\n## License\n\nPLANKA is [fair-code](https://faircode.io) distributed under the [Fair Use License](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20EN.md) and [PLANKA Pro/Enterprise License](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md).\n\n- **Source Available:** The source code is always visible\n- **Self-Hostable:** Deploy and host it anywhere\n- **Extensible:** Customize with your own functionality\n- **Enterprise Licenses:** Available for additional features and support\n\nFor more details, check the [License Guide](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20EN.md).\n\n## Contributing\n\nFound a bug or have a feature request? Check out our [Contributing Guide](https://github.com/plankanban/planka/blob/master/CONTRIBUTING.md) to get started.\n\nFor setting up the project locally, see the [development section](https://docs.planka.cloud/docs/category/development/).\n\n**Thanks to all our contributors!**\n\n[![Contributors](https://contrib.rocks/image?repo=plankanban/planka)](https://github.com/plankanban/planka/graphs/contributors)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nMost recent release.\n\n## Reporting a Vulnerability\n\nPlease report any security issues you discovered to [security@planka.group](mailto:security@planka.group). If the issue is confirmed, we will release a patch as soon as possible depending on complexity.\n\n**Do NOT create public issues on GitHub for security vulnerabilities.**\n\nThank you for your contribution!\n"
  },
  {
    "path": "charts/planka/.helmignore",
    "content": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation (prefixed with !). Only one pattern per line.\n.DS_Store\n# Common VCS dirs\n.git/\n.gitignore\n.bzr/\n.bzrignore\n.hg/\n.hgignore\n.svn/\n# Common backup files\n*.swp\n*.bak\n*.tmp\n*.orig\n*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n.vscode/\n"
  },
  {
    "path": "charts/planka/Chart.yaml",
    "content": "apiVersion: v2\nname: planka\ndescription: A Helm chart to deploy PLANKA and it's dependencies.\n\n# A chart can be either an 'application' or a 'library' chart.\n#\n# Application charts are a collection of templates that can be packaged into versioned archives\n# to be deployed.\n#\n# Library charts provide useful utilities or functions for the chart developer. They're included as\n# a dependency of application charts to inject those utilities and functions into the rendering\n# pipeline. Library charts do not define any templates and therefore cannot be deployed.\ntype: application\n\n# This is the chart version. This version number should be incremented each time you make changes\n# to the chart and its templates, including the app version.\n# Versions are expected to follow Semantic Versioning (https://semver.org/)\nversion: 2.1.0\n\n# This is the version number of the application being deployed. This version number should be\n# incremented each time you make changes to the application. Versions are not expected to\n# follow Semantic Versioning. They should reflect the version the application is using.\n# It is recommended to use it with quotes.\nappVersion: \"2.1.0\"\n\ndependencies:\n  - alias: postgresql\n    condition: postgresql.enabled\n    name: postgresql\n    repository: &bitnami-repo https://charts.bitnami.com/bitnami\n    version: 16.6.6\n"
  },
  {
    "path": "charts/planka/README.md",
    "content": "# PLANKA Helm Chart\n\nThis Helm Chart simplifies the deployment of [PLANKA](https://github.com/plankanban/planka) on Kubernetes.\n\nShoutout to [this issue](https://github.com/plankanban/planka/issues/192) for requesting a Helm Chart!\n\n## Issues\n\nBy using the Bitnami chart for PostgreSQL, there is an issue where once deployed, if trying to use a different password then it will be ignored as the Persistant Volume (PV) will already exist with the previous password. See warning from Bitnami below:\n\n> **Warning!** Setting a password will be ignored on new installation in the case when previous Posgresql release was deleted through the helm command. In that case, old PVC will have an old password, and setting it through helm won't take effect. Deleting persistent volumes (PVs) will solve the issue. Refer to [issue 2061](https://github.com/bitnami/charts/issues/2061) for more details\n\nIf you want to fully uninstall this chart including the data, follow [these steps](https://github.com/bitnami/charts/blob/main/bitnami/postgresql/README.md#uninstalling-the-chart) from the Bitnami Chart's docs.\n\n## Usage\n\nIf you just want to spin up an instance using help, please see [these docs](https://docs.planka.cloud/docs/installation/kubernetes/helm-chart/). If you want to make changes to the chart locally, and deploy them, see the below section.\n\n## Local Building and Using the Chart\n\nThe basic usage of the chart can be found below:\n\n```bash\ngit clone https://github.com/plankanban/planka.git\ncd planka/charts/planka\nhelm dependency build\nexport SECRETKEY=$(openssl rand -hex 64)\nhelm install planka . --set secretkey=$SECRETKEY  \\\n--set admin_email=\"demo@demo.demo\"  \\\n--set admin_password=\"demo\"  \\\n--set admin_name=\"Demo Demo\" \\\n--set admin_username=\"demo\"\n```\n\n> **Note:** The command `openssl rand -hex 64` is needed to create a random hexadecimal key for planka. On Windows you can use Git Bash to run that command.\n\nTo access PLANKA you can port forward using the following command:\n\n```bash\nkubectl port-forward $POD_NAME 3000:1337\n```\n\n### Accessing Externally\n\nTo access PLANKA externally you can use the following configuration\n\n```bash\n# HTTP only\nhelm install planka . --set secretkey=$SECRETKEY \\\n--set admin_email=\"demo@demo.demo\"  \\\n--set admin_password=\"demo\"  \\\n--set admin_name=\"Demo Demo\" \\\n--set admin_username=\"demo\" \\\n--set ingress.enabled=true \\\n--set ingress.hosts[0].host=planka.example.dev \\\n\n# HTTPS\nhelm install planka . --set secretkey=$SECRETKEY \\\n--set admin_email=\"demo@demo.demo\"  \\\n--set admin_password=\"demo\"  \\\n--set admin_name=\"Demo Demo\" \\\n--set admin_username=\"demo\" \\\n--set ingress.enabled=true \\\n--set ingress.hosts[0].host=planka.example.dev \\\n--set ingress.tls[0].secretName=planka-tls \\\n--set ingress.tls[0].hosts[0]=planka.example.dev \\\n```\n\nor create a values.yaml file like:\n\n````yaml\nsecretkey: \"<InsertSecretKey>\"\n# The admin section needs to be present for new instances of PLANKA, after the first start you can remove the lines starting with admin_. If you want the admin user to be unchangeable admin_email: has to stay\n# After changing the config you have to run ```helm upgrade  planka . -f values.yaml```\n\n# Admin user\nadmin_email: \"demo@demo.demo\" # Do not remove if you want to prevent this user from being edited/deleted\nadmin_password: \"demo\"\nadmin_name: \"Demo Demo\"\nadmin_username: \"demo\"\n# Admin user\n\n# Ingress\ningress:\n  enabled: true\n  hosts:\n    - host: planka.example.dev\n      paths:\n        - path: /\n          pathType: ImplementationSpecific\n\n  # Needed for HTTPS\n  tls:\n    - secretName: planka-tls # existing TLS secret in k8s\n      hosts:\n        - planka.example.dev\n```\n\n```bash\nhelm install planka . -f values.yaml\n```\n\n### Things to consider if production hosting\n\nIf you want to host PLANKA for more than just playing around with, you might want to do the following things:\n\n- Create a `values.yaml` with your config, as this will make applying upgrades much easier in the future.\n- Create your `secretkey` once and store it either in a secure vault, or in your `values.yaml` file so it will be the same for upgrading in the future.\n- Specify a password for `postgresql.auth.password` as there have been issues with the postgresql chart generating new passwords locking you out of the data you've already stored. (see [this issue](https://github.com/bitnami/charts/issues/2061))\n\nAny questions or concerns, [raise an issue](https://github.com/Chris-Greaves/planka-helm-chart/issues/new).\n\n## Advanced Configuration\n\n### Extra Volume Mounts\n\nThe Helm chart supports mounting arbitrary ConfigMaps, Secrets, and Volumes to the PLANKA deployment using the `extraMounts` configuration. This is especially useful for scenarios like:\n\n- Mounting custom CA certificates for OIDC with self-hosted identity providers\n- Adding custom configuration files\n- Mounting TLS certificates from existing secrets\n- Adding temporary or persistent storage volumes\n\n**Note**: ConfigMaps and Secrets must be created separately before referencing them in `extraMounts`.\n\n#### Basic Usage\n\nUse the `extraMounts` section to mount any type of volume:\n\n```yaml\nextraMounts:\n  # Mount CA certificate from existing ConfigMap\n  - name: ca-certs\n    mountPath: /etc/ssl/certs/custom-ca.crt\n    subPath: ca.crt\n    readOnly: true\n    configMap:\n      name: ca-certificates # Must exist\n\n  # Mount TLS certificates from existing Secret\n  - name: tls-certs\n    mountPath: /etc/ssl/private\n    readOnly: true\n    secret:\n      secretName: planka-tls-secret # Must exist\n      items:\n        - key: tls.crt\n          path: server.crt\n        - key: tls.key\n          path: server.key\n\n  # Temporary storage\n  - name: temp-storage\n    mountPath: /tmp/planka-temp\n    readOnly: false\n    emptyDir:\n      sizeLimit: 1Gi\n\n  # Host path mount\n  - name: backup-storage\n    mountPath: /var/lib/planka-backups\n    readOnly: false\n    hostPath:\n      path: /var/lib/planka-backups\n      type: DirectoryOrCreate\n\n  # NFS mount\n  - name: nfs-storage\n    mountPath: /shared/data\n    readOnly: false\n    nfs:\n      server: nfs.example.com\n      path: /exports/planka\n```\n\n### OIDC with Self-Hosted Keycloak\n\nA common use case is configuring OIDC with a self-hosted Keycloak instance that uses custom CA certificates.\n\nFirst, create the CA certificate ConfigMap:\n\n```bash\nkubectl create configmap ca-certificates --from-file=ca.crt=/path/to/your/ca.crt\n```\n\nThen configure the chart:\n\n```yaml\n# Mount custom CA certificate from existing ConfigMap\nextraMounts:\n  - name: keycloak-ca\n    mountPath: /etc/ssl/certs/keycloak-ca.crt\n    subPath: ca.crt\n    readOnly: true\n    configMap:\n      name: ca-certificates\n\n# Configure Node.js to trust the custom CA\nextraEnv:\n  - name: NODE_EXTRA_CA_CERTS\n    value: \"/etc/ssl/certs/keycloak-ca.crt\"\n\n# Enable OIDC\noidc:\n  enabled: true\n  clientId: \"planka-client\"\n  clientSecret: \"your-client-secret\"\n  issuerUrl: \"https://keycloak.example.com/realms/master\"\n  admin:\n    roles:\n      - \"planka-admin\"\n```\n\n### Environment Variables from Secrets\n\nYou can reference values from existing secrets in environment variables:\n\n```yaml\nextraEnv:\n  - name: SMTP_PASSWORD\n    valueFrom:\n      secretName: smtp-credentials\n      key: password\n  - name: CUSTOM_API_KEY\n    valueFrom:\n      secretName: api-credentials\n      key: api-key\n```\n\n### Custom Terms of Service\n\nYou can provide your own End User Terms of Service by passing the markdown files directly via `values.yaml` in the `terms` configuration block. This automates the creation of a corresponding ConfigMap and volume mount.\n\n```yaml\nterms:\n  enabled: true\n  customFiles:\n    en-US.md: |\n      # End User Terms of Service\n      ...\n      [confirmations]::\n      ---\n      ✔️ **I have read and accept these End User Terms of Service**\n    de-DE.md: |\n      # Nutzungsbedingungen\n      ...\n      [confirmations]::\n      ---\n      ✔️ **Ich habe diese Nutzungsbedingungen gelesen und akzeptiere sie**\n```\n\n### Image Digest Pinning\n\nFor enhanced security and reproducibility, you can pin the container image using its SHA256 digest instead of relying solely on tags. This ensures you always deploy the exact same image, preventing tag mutations or accidental updates.\n\n#### Finding the Image Digest\n\nYou can find the digest of a specific image tag using:\n\n```bash\ndocker inspect ghcr.io/plankanban/planka:latest --format='{{index .RepoDigests 0}}'\n# Output: ghcr.io/plankanban/planka@sha256:abc123def456...\n\n# Or with skopeo\nskopeo inspect docker://ghcr.io/plankanban/planka:latest\n```\n\n#### Usage\n\nYou can use digest pinning in several ways:\n\n**Option 1: Digest with tag (recommended)**\n\nIncludes the tag for reference while using the digest for verification:\n\n```bash\nhelm install planka . --set secretkey=$SECRETKEY \\\n  --set image.tag=latest \\\n  --set image.digest=abc123def456... \\\n  --set admin_email=\"demo@demo.demo\" \\\n  --set admin_password=\"demo\" \\\n  --set admin_name=\"Demo Demo\" \\\n  --set admin_username=\"demo\"\n```\n\nOr in values.yaml:\n\n```yaml\nimage:\n  repository: ghcr.io/plankanban/planka\n  tag: latest\n  digest: \"abc123def456ab89cd12ef34ab56cd78ef90ab12cd34ef56ab78cd90ef12ab34\"\n```\n\n**Option 2: Digest only**\n\nIf you prefer to pin only by digest without specifying a tag:\n\n```yaml\nimage:\n  repository: ghcr.io/plankanban/planka\n  tag: \"\" # Empty - digest alone identifies the image\n  digest: \"abc123def456ab89cd12ef34ab56cd78ef90ab12cd34ef56ab78cd90ef12ab34\"\n```\n\n#### Security Benefits\n\n- **Immutability**: Ensures you always deploy the exact same image\n- **Supply Chain Security**: Protects against tag mutations or registry compromise\n- **Reproducibility**: Makes deployments fully reproducible across environments\n- **Audit Trail**: Provides clear image identity in deployment manifests\n\n### Complete Example\n\nSee `values-example.yaml` for a comprehensive example that demonstrates all the advanced features including OIDC configuration with custom CA certificates.\n````\n"
  },
  {
    "path": "charts/planka/templates/NOTES.txt",
    "content": "1. Get the application URL by running these commands:\n{{- if .Values.ingress.enabled }}\n{{- range $host := .Values.ingress.hosts }}\n  {{- range .paths }}\n  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}\n  {{- end }}\n{{- end }}\n{{- else if contains \"NodePort\" .Values.service.type }}\n  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath=\"{.spec.ports[0].nodePort}\" services {{ include \"planka.fullname\" . }})\n  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath=\"{.items[0].status.addresses[0].address}\")\n  echo http://$NODE_IP:$NODE_PORT\n{{- else if contains \"LoadBalancer\" .Values.service.type }}\n     NOTE: It may take a few minutes for the LoadBalancer IP to be available.\n           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include \"planka.fullname\" . }}'\n  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include \"planka.fullname\" . }} --template \"{{\"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}\"}}\")\n  echo http://$SERVICE_IP:{{ .Values.service.port }}\n{{- else if contains \"ClusterIP\" .Values.service.type }}\n  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l \"app.kubernetes.io/name={{ include \"planka.name\" . }},app.kubernetes.io/instance={{ .Release.Name }}\" -o jsonpath=\"{.items[0].metadata.name}\")\n  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath=\"{.spec.containers[0].ports[0].containerPort}\")\n  echo \"Visit http://localhost:3000 to use your application\"\n  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 3000:$CONTAINER_PORT\n{{- end }}\n"
  },
  {
    "path": "charts/planka/templates/_helpers.tpl",
    "content": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"planka.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"planka.fullname\" -}}\n{{- if .Values.fullnameOverride }}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- $name := default .Chart.Name .Values.nameOverride }}\n{{- if contains $name .Release.Name }}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n{{- end }}\n\n{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"planka.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCommon labels\n*/}}\n{{- define \"planka.labels\" -}}\nhelm.sh/chart: {{ include \"planka.chart\" . }}\n{{ include \"planka.selectorLabels\" . }}\n{{- if .Chart.AppVersion }}\napp.kubernetes.io/version: {{ .Chart.AppVersion | quote }}\n{{- end }}\napp.kubernetes.io/managed-by: {{ .Release.Service }}\n{{- end }}\n\n{{/*\nSelector labels\n*/}}\n{{- define \"planka.selectorLabels\" -}}\napp.kubernetes.io/name: {{ include \"planka.name\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\n{{- end }}\n\n{{/*\nCreate the name of the service account to use\n*/}}\n{{- define \"planka.serviceAccountName\" -}}\n{{- if .Values.serviceAccount.create }}\n{{- default (include \"planka.fullname\" .) .Values.serviceAccount.name }}\n{{- else }}\n{{- default \"default\" .Values.serviceAccount.name }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/planka/templates/configmap-terms.yaml",
    "content": "{{- if .Values.terms.enabled }}\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: {{ include \"planka.fullname\" . }}-terms\n  labels:\n    {{- include \"planka.labels\" . | nindent 4 }}\ndata:\n  {{- range $key, $value := .Values.terms.customFiles }}\n  {{ $key }}: |\n    {{- $value | nindent 4 }}\n  {{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/planka/templates/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ include \"planka.fullname\" . }}\n  labels:\n    {{- include \"planka.labels\" . | nindent 4 }}\n  {{- with .Values.deploymentAnnotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\nspec:\n  {{- if not .Values.autoscaling.enabled }}\n  replicas: {{ .Values.replicaCount }}\n  {{- end }}\n  selector:\n    matchLabels:\n      {{- include \"planka.selectorLabels\" . | nindent 6 }}\n  {{- if .Values.persistence.enabled }}\n  strategy:\n    type: Recreate\n  {{- end }}\n  template:\n    metadata:\n      {{- with .Values.podAnnotations }}\n      annotations:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      labels:\n        {{- include \"planka.selectorLabels\" . | nindent 8 }}\n    spec:\n      {{- with .Values.imagePullSecrets }}\n      imagePullSecrets:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      serviceAccountName: {{ include \"planka.serviceAccountName\" . }}\n      securityContext:\n        {{- toYaml .Values.podSecurityContext | nindent 8 }}\n      containers:\n        - name: {{ .Chart.Name }}\n          securityContext:\n            {{- toYaml .Values.securityContext | nindent 12 }}\n          {{- $imageTag := .Values.image.tag | default .Chart.AppVersion }}\n          {{- if .Values.image.digest }}\n            {{- if $imageTag }}\n          image: \"{{ .Values.image.repository }}:{{ $imageTag }}@sha256:{{ .Values.image.digest }}\"\n            {{- else }}\n          image: \"{{ .Values.image.repository }}@sha256:{{ .Values.image.digest }}\"\n            {{- end }}\n          {{- else }}\n          image: \"{{ .Values.image.repository }}:{{ $imageTag }}\"\n          {{- end }}\n          imagePullPolicy: {{ .Values.image.pullPolicy }}\n          ports:\n            - name: http\n              containerPort: {{ .Values.service.containerPort | default 1337 }}\n              protocol: TCP\n          livenessProbe:\n            httpGet:\n              path: /\n              port: http\n          readinessProbe:\n            httpGet:\n              path: /\n              port: http\n          volumeMounts:\n            - mountPath: /app/data\n              subPath: data\n              name: planka\n          {{- if .Values.securityContext.readOnlyRootFilesystem }}\n            - mountPath: /app/logs\n              subPath: app-logs\n              name: emptydir\n            - mountPath: /app/.tmp\n              subPath: app-tmp\n              name: emptydir\n            - mountPath: /tmp\n              subPath: tmp\n              name: emptydir\n          {{- end }}\n          {{- if .Values.terms.enabled }}\n            - mountPath: /app/terms/custom\n              name: planka-terms\n          {{- end }}\n          {{- /* Extra volume mounts */}}\n          {{- range .Values.extraMounts }}\n            - name: {{ .name }}\n              mountPath: {{ .mountPath }}\n              {{- if .subPath }}\n              subPath: {{ .subPath }}\n              {{- end }}\n              {{- if hasKey . \"readOnly\" }}\n              readOnly: {{ .readOnly }}\n              {{- else }}\n              readOnly: true\n              {{- end }}\n          {{- end }}\n          resources:\n            {{- toYaml .Values.resources | nindent 12 }}\n          env:\n          {{- if .Values.extraEnv }}\n          {{- range .Values.extraEnv }}\n            - name: {{ .name }}\n          {{- if  .value }}\n              value: {{ .value | quote}}\n          {{- end }}\n          {{- if .valueFrom }}\n              valueFrom:\n                secretKeyRef:\n                  name: {{ .valueFrom.secretName }}\n                  key: {{ .valueFrom.key }}\n          {{- end }}\n          {{- end }}\n          {{- end }}\n          {{- if not .Values.postgresql.enabled }}\n          {{- if .Values.existingDburlSecret }}\n            - name: DATABASE_URL\n              valueFrom:\n                secretKeyRef:\n                  name: {{ .Values.existingDburlSecret }}\n                  key: uri\n          {{- else }}\n            - name: DATABASE_URL\n              value: {{ required \"If the included postgresql deployment is disabled you need to provide an existing secret in .Values.existingDburlSecret or define a Database URL in 'dburl'\" .Values.dburl }}\n          {{- end }}\n          {{- else }}\n            - name: DATABASE_URL\n              valueFrom:\n                secretKeyRef:\n                  name: {{ include \"planka.fullname\" . }}-postgresql-svcbind-custom-user\n                  key: uri\n          {{- end }}\n            - name: BASE_URL\n              {{- if .Values.baseUrl }}\n              value: {{ .Values.baseUrl }}\n              {{- else if .Values.ingress.enabled }}\n              value: {{ printf \"https://%s\" (first .Values.ingress.hosts).host }}\n              {{- else }}\n              value: http://localhost:3000\n              {{- end }}\n            - name: SECRET_KEY\n              {{- if .Values.existingSecretkeySecret }}\n              valueFrom:\n                secretKeyRef:\n                  name: {{ .Values.existingSecretkeySecret }}\n                  key: key\n              {{- else }}\n              value: {{ required \"A secret key needs to be generated using 'openssl rand -hex 64' and assigned to secretkey.\" .Values.secretkey }}\n              {{- end }}\n            - name: TRUST_PROXY\n              value: \"true\"\n            - name: DEFAULT_ADMIN_EMAIL\n              value: {{ .Values.admin_email }}\n            - name: DEFAULT_ADMIN_NAME\n              value: {{ .Values.admin_name }}\n            {{- if .Values.existingAdminCredsSecret }}\n            - name: DEFAULT_ADMIN_USERNAME\n              valueFrom:\n                secretKeyRef:\n                  name: {{ .Values.existingAdminCredsSecret }}\n                  key: username\n            - name: DEFAULT_ADMIN_PASSWORD\n              valueFrom:\n                secretKeyRef:\n                  name: {{ .Values.existingAdminCredsSecret }}\n                  key: password\n            {{- else }}\n            - name: DEFAULT_ADMIN_USERNAME\n              value: {{ .Values.admin_username }}\n            - name: DEFAULT_ADMIN_PASSWORD\n              value: {{ .Values.admin_password }}\n            {{- end }}\n          {{ range $k, $v := .Values.env }}\n            - name: {{ $k | quote }}\n              value: {{ $v | quote }}\n          {{- end }}\n          {{- if .Values.oidc.enabled }}\n          {{- $secretName := default (printf \"%s-oidc\" (include \"planka.fullname\" .)) .Values.oidc.existingSecret }}\n            - name: OIDC_CLIENT_ID\n              valueFrom:\n                secretKeyRef:\n                  key:  clientId\n                  name: {{ $secretName }}\n            - name: OIDC_CLIENT_SECRET\n              valueFrom:\n                secretKeyRef:\n                  key:  clientSecret\n                  name: {{ $secretName }}\n            - name: OIDC_ISSUER\n              value: {{ required \"issuerUrl is required when configuring OIDC\" .Values.oidc.issuerUrl | quote }}\n            - name: OIDC_SCOPES\n              value: {{ join \" \" .Values.oidc.scopes | default \"openid profile email\" | quote }}\n          {{- if .Values.oidc.admin.roles }}\n            - name: OIDC_ADMIN_ROLES\n              value: {{ join \",\" .Values.oidc.admin.roles | quote }}\n          {{- end }}\n            - name: OIDC_ROLES_ATTRIBUTE\n              value: {{ .Values.oidc.admin.rolesAttribute | default \"groups\" | quote }}\n          {{- if .Values.oidc.admin.ignoreRoles }}\n            - name: OIDC_IGNORE_ROLES\n              value: {{ .Values.oidc.admin.ignoreRoles | quote }}\n          {{- end }}\n          {{- end }}\n        {{- if .Values.extraContainers -}}\n          {{ toYaml .Values.extraContainers | nindent 8 }}\n        {{- end }}\n      {{- with .Values.nodeSelector }}\n      nodeSelector:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.affinity }}\n      affinity:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.tolerations }}\n      tolerations:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      volumes:\n        - name: planka\n    {{- if .Values.persistence.enabled }}\n          persistentVolumeClaim:\n            claimName: {{ .Values.persistence.existingClaim | default (include \"planka.fullname\" .) }}\n    {{- else }}\n          emptyDir: {}\n    {{- end }}\n    {{- if .Values.securityContext.readOnlyRootFilesystem }}\n        - name: emptydir\n          emptyDir: {}\n    {{- end }}\n    {{- if .Values.terms.enabled }}\n        - name: planka-terms\n          configMap:\n            name: {{ include \"planka.fullname\" . }}-terms\n    {{- end }}\n        {{- /* Extra volumes */}}\n        {{- range .Values.extraMounts }}\n        - name: {{ .name }}\n          {{- if .configMap }}\n          configMap:\n            {{- toYaml .configMap | nindent 12 }}\n          {{- else if .secret }}\n          secret:\n            {{- toYaml .secret | nindent 12 }}\n          {{- else if .emptyDir }}\n          emptyDir:\n            {{- toYaml .emptyDir | nindent 12 }}\n          {{- else if .hostPath }}\n          hostPath:\n            {{- toYaml .hostPath | nindent 12 }}\n          {{- else if .persistentVolumeClaim }}\n          persistentVolumeClaim:\n            {{- toYaml .persistentVolumeClaim | nindent 12 }}\n          {{- else if .nfs }}\n          nfs:\n            {{- toYaml .nfs | nindent 12 }}\n          {{- else }}\n          {{- /* Support any other volume type by removing known mount-specific keys */}}\n          {{- $volume := omit . \"name\" \"mountPath\" \"subPath\" \"readOnly\" }}\n          {{- toYaml $volume | nindent 10 }}\n          {{- end }}\n        {{- end }}\n"
  },
  {
    "path": "charts/planka/templates/hpa.yaml",
    "content": "{{- if .Values.autoscaling.enabled }}\napiVersion: autoscaling/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: {{ include \"planka.fullname\" . }}\n  labels:\n    {{- include \"planka.labels\" . | nindent 4 }}\nspec:\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: {{ include \"planka.fullname\" . }}\n  minReplicas: {{ .Values.autoscaling.minReplicas }}\n  maxReplicas: {{ .Values.autoscaling.maxReplicas }}\n  metrics:\n    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}\n    - type: Resource\n      resource:\n        name: cpu\n        target:\n          type: Utilization\n          averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}\n    {{- end }}\n    {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}\n    - type: Resource\n      resource:\n        name: memory\n        target:\n          type: Utilization\n          averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}\n    {{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/planka/templates/ingress.yaml",
    "content": "{{- if .Values.ingress.enabled -}}\n{{- $fullName := include \"planka.fullname\" . -}}\n{{- $svcPort := .Values.service.port -}}\n{{- if and .Values.ingress.className (not (semverCompare \">=1.18-0\" .Capabilities.KubeVersion.GitVersion)) }}\n  {{- if not (hasKey .Values.ingress.annotations \"kubernetes.io/ingress.class\") }}\n  {{- $_ := set .Values.ingress.annotations \"kubernetes.io/ingress.class\" .Values.ingress.className}}\n  {{- end }}\n{{- end }}\n{{- if semverCompare \">=1.19-0\" .Capabilities.KubeVersion.GitVersion -}}\napiVersion: networking.k8s.io/v1\n{{- else if semverCompare \">=1.14-0\" .Capabilities.KubeVersion.GitVersion -}}\napiVersion: networking.k8s.io/v1beta1\n{{- else -}}\napiVersion: extensions/v1beta1\n{{- end }}\nkind: Ingress\nmetadata:\n  name: {{ $fullName }}\n  labels:\n    {{- include \"planka.labels\" . | nindent 4 }}\n    {{- with .Values.ingress.labels }}\n    {{- toYaml . | nindent 4 }}\n    {{- end }}\n  {{- with .Values.ingress.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\nspec:\n  {{- if and .Values.ingress.className (semverCompare \">=1.18-0\" .Capabilities.KubeVersion.GitVersion) }}\n  ingressClassName: {{ .Values.ingress.className }}\n  {{- end }}\n  {{- if .Values.ingress.tls }}\n  tls:\n    {{- range .Values.ingress.tls }}\n    - hosts:\n        {{- range .hosts }}\n        - {{ . | quote }}\n        {{- end }}\n      secretName: {{ .secretName }}\n    {{- end }}\n  {{- end }}\n  rules:\n    {{- range .Values.ingress.hosts }}\n    - host: {{ .host | quote }}\n      http:\n        paths:\n          {{- range .paths }}\n          - path: {{ .path }}\n            {{- if and .pathType (semverCompare \">=1.18-0\" $.Capabilities.KubeVersion.GitVersion) }}\n            pathType: {{ .pathType }}\n            {{- end }}\n            backend:\n              {{- if semverCompare \">=1.19-0\" $.Capabilities.KubeVersion.GitVersion }}\n              service:\n                name: {{ $fullName }}\n                port:\n                  number: {{ $svcPort }}\n              {{- else }}\n              serviceName: {{ $fullName }}\n              servicePort: {{ $svcPort }}\n              {{- end }}\n          {{- end }}\n    {{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/planka/templates/pvc.yaml",
    "content": "{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}\n---\nkind: PersistentVolumeClaim\napiVersion: v1\nmetadata:\n  name: {{ include \"planka.fullname\" . }}\n  labels:\n    app.kubernetes.io/name: {{ include \"planka.name\" . }}\n    helm.sh/chart: {{ include \"planka.chart\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\nspec:\n  accessModes:\n    - {{ .Values.persistence.accessMode }}\n  resources:\n    requests:\n      storage: {{ .Values.persistence.size | quote }}\n{{- if .Values.persistence.storageClass }}\n{{- if (eq \"-\" .Values.persistence.storageClass) }}\n  storageClassName: \"\"\n{{- else }}\n  storageClassName: \"{{ .Values.persistence.storageClass }}\"\n{{- end }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/planka/templates/secret-oidc.yaml",
    "content": "{{- if .Values.oidc.enabled }}\n{{- if eq (and (not (empty .Values.oidc.clientId)) (not (empty .Values.oidc.clientSecret))) (not (empty .Values.oidc.existingSecret)) -}}\n  {{- fail \"Either specify inline `clientId` and `clientSecret` or refer to them via `existingSecret`\" -}}\n{{- end }}\n{{- if (and (and (not (empty .Values.oidc.clientId)) (not (empty .Values.oidc.clientSecret))) (empty .Values.oidc.existingSecret)) -}}\napiVersion: v1\nkind: Secret\nmetadata:\n  name: {{ include \"planka.fullname\" . }}-oidc\n  labels:\n    {{- include \"planka.labels\" . | nindent 4 }}\ntype: Opaque\ndata:\n  clientId: {{ .Values.oidc.clientId | b64enc | quote }}\n  clientSecret: {{ .Values.oidc.clientSecret | b64enc | quote }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/planka/templates/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"planka.fullname\" . }}\n  labels:\n    {{- include \"planka.labels\" . | nindent 4 }}\n  {{- with .Values.service.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\nspec:\n  type: {{ .Values.service.type }}\n  ports:\n    - port: {{ .Values.service.port }}\n      targetPort: http\n      protocol: TCP\n      name: http\n  selector:\n    {{- include \"planka.selectorLabels\" . | nindent 4 }}\n"
  },
  {
    "path": "charts/planka/templates/serviceaccount.yaml",
    "content": "{{- if .Values.serviceAccount.create -}}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ include \"planka.serviceAccountName\" . }}\n  labels:\n    {{- include \"planka.labels\" . | nindent 4 }}\n  {{- with .Values.serviceAccount.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/planka/templates/tests/test-connection.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: \"{{ include \"planka.fullname\" . }}-test-connection\"\n  labels:\n    {{- include \"planka.labels\" . | nindent 4 }}\n  annotations:\n    \"helm.sh/hook\": test\nspec:\n  containers:\n    - name: wget\n      image: busybox\n      command: ['wget']\n      args: ['{{ include \"planka.fullname\" . }}:{{ .Values.service.port }}']\n  restartPolicy: Never\n"
  },
  {
    "path": "charts/planka/values.yaml",
    "content": "# Default values for planka.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nreplicaCount: 1\n\nimage:\n  repository: ghcr.io/plankanban/planka\n  pullPolicy: IfNotPresent\n  # Overrides the image tag whose default is the chart appVersion.\n  tag: \"\"\n  # Optional: specify the image digest for pinning by SHA256\n  # When set, the image reference will include the digest for enhanced security\n  # Example: \"abc123def456...\" (without sha256: prefix)\n  digest: \"\"\n\nimagePullSecrets: []\nnameOverride: \"\"\nfullnameOverride: \"\"\n\n# Generate a secret using openssl rand -base64 45\nsecretkey: \"\"\n\n## @param existingSecretkeySecret Name of an existing secret containing the session key string\n## NOTE: Must contain key `key`\n## NOTE: When it's set, the secretkey parameter is ignored\nexistingSecretkeySecret: \"\"\n\n## @param existingAdminCredsSecret Name of an existing secret containing the admin username and password\n## NOTE: Must contain keys `username` and `password`\n## NOTE: When it's set, the `admin_username` and `admin_password` parameters are ignored\nexistingAdminCredsSecret: \"\"\n\nadmin_email: \"\"\nadmin_password: \"\"\nadmin_name: \"\"\nadmin_username: \"\"\n\n# Base url for PLANKA. Will override `ingress.hosts[0].host`\n# Defaults to `http://localhost:3000` if ingress is disabled.\nbaseUrl: \"\"\n\nserviceAccount:\n  # Specifies whether a service account should be created\n  create: true\n  # Annotations to add to the service account\n  annotations: {}\n  # The name of the service account to use.\n  # If not set and create is true, a name is generated using the fullname template\n  name: \"\"\n\npodAnnotations: {}\n\npodSecurityContext: {}\n  # fsGroup: 2000\n\n# Annotations to add to the deployment\ndeploymentAnnotations: {}\n\nsecurityContext: {}\n  # capabilities:\n  #   drop:\n  #   - ALL\n  # readOnlyRootFilesystem: true\n  # runAsNonRoot: true\n  # runAsUser: 1000\n\nservice:\n  annotations: {}\n  type: ClusterIP\n  port: 1337\n  ## @param service.containerPort PLANKA HTTP container port\n  ## If empty will default to 1337\n  ##\n  containerPort: 1337\n\ningress:\n  enabled: false\n  className: \"\"\n  labels: {}\n  annotations: {}\n    # kubernetes.io/ingress.class: nginx\n    # kubernetes.io/tls-acme: \"true\"\n  hosts:\n    # Used to set planka BASE_URL if no `baseurl` is provided.\n    - host: planka.local\n      paths:\n        - path: /\n          pathType: ImplementationSpecific\n  tls: []\n  #  - secretName: planka-tls\n  #    hosts:\n  #      - planka.local\n\nresources: {}\n  # We usually recommend not to specify default resources and to leave this as a conscious\n  # choice for the user. This also increases chances charts run on environments with little\n  # resources, such as Minikube. If you do want to specify resources, uncomment the following\n  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n  # limits:\n  #   cpu: 100m\n  #   memory: 128Mi\n  # requests:\n  #   cpu: 100m\n  #   memory: 128Mi\n\nautoscaling:\n  enabled: false\n  minReplicas: 1\n  maxReplicas: 100\n  targetCPUUtilizationPercentage: 80\n  # targetMemoryUtilizationPercentage: 80\n\nnodeSelector: {}\n\ntolerations: []\n\naffinity: {}\n\npostgresql:\n  global:\n    security:\n      allowInsecureImages: true\n  image:\n    repository: bitnamilegacy/postgresql\n  enabled: true\n  auth:\n    database: planka\n    username: planka\n    password: \"\"\n    postgresPassword: \"\"\n    replicationPassword: \"\"\n    # existingSecret: planka-postgresql\n  serviceBindings:\n    enabled: true\n\n## Set this or existingDburlSecret if you disable the built-in postgresql deployment\ndburl:\n\n## @param existingDburlSecret Name of an existing secret containing a DBurl connection string\n## NOTE: Must contain key `uri`\n## NOTE: When it's set, the `dburl` parameter is ignored\n##\nexistingDburlSecret: \"\"\n\n## PVC-based data storage configuration\npersistence:\n  enabled: false\n  # existingClaim: netbox-data\n  # storageClass: \"-\"\n  accessMode: ReadWriteOnce\n  size: 10Gi\n\n## OpenID Identity Management configuration\n##\n## Example:\n## ---------------\n## oidc:\n##   enabled: true\n##   clientId: sxxaAIAxVXlCxTmc1YLHBbQr8NL8MqLI2DUbt42d\n##   clientSecret: om4RTMRVHRszU7bqxB7RZNkHIzA8e4sGYWxeCwIMYQXPwEBWe4SY5a0wwCe9ltB3zrq5f0dnFnp34cEHD7QSMHsKvV9AiV5Z7eqDraMnv0I8IFivmuV5wovAECAYreSI\n##   issuerUrl: https://auth.local/application/o/planka/\n##   admin:\n##     roles:\n##       - planka-admin\n##\n## ---------------\n## NOTE: A minimal configuration requires setting `clientId`, `clientSecret` and `issuerUrl`. (plus `admin.roles` for administrators)\n## ref: https://docs.planka.cloud/docs/configuration/oidc/\n##\noidc:\n  ## @param oidc.enabled Enable single sign-on (SSO) with OpenID Connect (OIDC)\n  ##\n  enabled: false\n\n  ## OIDC credentials\n  ## @param oidc.clientId A string unique to the provider that identifies your app.\n  ## @param oidc.clientSecret A secret string that the provider uses to confirm ownership of a client ID.\n  ##\n  ## NOTE: Either specify inline `clientId` and `clientSecret` or refer to them via `existingSecret`\n  ##\n  clientId: \"\"\n  clientSecret: \"\"\n\n  ## @param oidc.existingSecret Name of an existing secret containing OIDC credentials\n  ## NOTE: Must contain key `clientId` and `clientSecret`\n  ## NOTE: When it's set, the `clientId` and `clientSecret` parameters are ignored\n  ##\n  existingSecret: \"\"\n\n  ## @param oidc.issuerUrl The OpenID connect metadata document endpoint\n  ##\n  issuerUrl: \"\"\n\n  ## @param oidc.scopes A list of scopes required for OIDC client.\n  ## If empty will default to `openid`, `profile` and `email`\n  ## NOTE: PLANKA needs the email and name claims\n  ##\n  scopes: []\n\n  ## Admin permissions configuration\n  admin:\n    ## @param oidc.admin.ignoreRoles If set to true, the admin roles will be ignored.\n    ## It is useful if you want to use OIDC for authentication but not for authorization.\n    ## If empty will default to `false`\n    ##\n    ignoreRoles: false\n\n    ## @param oidc.admin.rolesAttribute The name of a custom group claim that you have configured in your OIDC provider\n    ## If empty will default to `groups`\n    ##\n    rolesAttribute: groups\n\n    ## @param oidc.admin.roles The names of the admin groups\n    ##\n    roles: []\n      # - planka-admin\n\n## Extra environment variables for planka deployment\n## Supports hard coded and getting values from a k8s secret\n## - name: test\n##   value: valuetest\n## - name: another\n##   value: another\n## - name: test-secret\n##   valueFrom:\n##     secretName: k8s-secret-name\n##     key: key-inside-the-secret\n##\nextraEnv: []\n\n## Example extraEnv for configuring SMTP\n## extraEnv:\n##   - name: SMTP_HOST\n##     value: \"smtp.example.com\"\n##   - name: SMTP_PORT\n##     value: \"587\"\n##   - name: SMTP_NAME\n##     value: \"Your Name\"\n##   - name: SMTP_SECURE\n##     value: \"true\"\n##   - name: SMTP_TLS_REJECT_UNAUTHORIZED\n##     value: \"false\"\n##   - name: SMTP_USER\n##     value: \"your_email@example.com\"\n##   - name: SMTP_PASSWORD\n##     value: \"your_password\"\n##   - name: SMTP_FROM\n##     value: \"your_email@example.com\"\n\n## End User Terms of Service configuration\n## Mount custom terms of service markdown files into the Planka deployment\n##\nterms:\n  enabled: false\n  # Provide individual language files as key-value pairs\n  # e.g.,\n  # customFiles:\n  #   en-US.md: |\n  #     # End User Terms of Service\n  #     ...\n  #   de-DE.md: |\n  #     # Nutzungsbedingungen\n  #     ...\n  customFiles: {}\n\n## Extra volume mounts configuration\n## Mount ConfigMaps, Secrets, and arbitrary volumes to the PLANKA container\n## This allows mounting any pre-existing ConfigMaps, Secrets, or other volume types\n##\nextraMounts: []\n## Example extraMounts:\n## extraMounts:\n##   - name: ca-certs\n##     mountPath: /etc/ssl/certs/ca-certificates.crt\n##     subPath: ca-bundle.crt\n##     readOnly: true\n##     configMap:\n##       name: ca-certificates\n##   - name: tls-certs\n##     mountPath: /etc/ssl/private\n##     readOnly: true\n##     secret:\n##       secretName: planka-tls\n##       items:\n##         - key: tls.crt\n##           path: server.crt\n##         - key: tls.key\n##           path: server.key\n##   - name: temp-storage\n##     mountPath: /tmp/planka-temp\n##     readOnly: false\n##     emptyDir:\n##       sizeLimit: 1Gi\n\n## Example configuration for OIDC with self-hosted Keycloak using custom CA\n## (Requires pre-existing ConfigMap \"ca-certificates\")\n## extraMounts:\n##   - name: keycloak-ca\n##     mountPath: /etc/ssl/certs/keycloak-ca.crt\n##     subPath: ca.crt\n##     readOnly: true\n##     configMap:\n##       name: ca-certificates\n##\n## extraEnv:\n##   - name: NODE_EXTRA_CA_CERTS\n##     value: \"/etc/ssl/certs/keycloak-ca.crt\"\n\nextraContainers: []\n## Extra sidecar containers\n## Add additional containers to the PLANKA pod\n##\n## Example extraContainers:\n## extraContainers:\n##   - name: nginx-sidecar\n##     image: nginx:latest\n##     ports:\n##       - containerPort: 8085\n##         name: nginx-http\n##   - name: log-collector\n##     image: busybox:latest\n##     command: ['sh', '-c', 'tail -f /var/log/app.log']\n##     volumeMounts:\n##       - name: planka\n##         mountPath: /var/log\n##         subPath: app-logs\n##\n"
  },
  {
    "path": "client/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "client/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n    <meta name=\"description\" content=\"PLANKA is the kanban-style project mastering tool for everyone\" />\n    <title>PLANKA</title>\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <link rel=\"apple-touch-icon\" href=\"/logo192.png\" />\n    <link rel=\"manifest\" href=\"/manifest.json\" />\n  </head>\n  <body id=\"app\"></body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/index.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "client/package.json",
    "content": "{\n  \"name\": \"planka-client\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"vite build\",\n    \"postinstall\": \"patch-package\",\n    \"lint\": \"eslint --ext js,jsx src --report-unused-disable-directives\",\n    \"start\": \"vite\",\n    \"test\": \"jest\",\n    \"test:acceptance\": \"cucumber-js --import tests/acceptance/cucumber.conf.js --import tests/acceptance/steps/**/*.js --format @cucumber/pretty-formatter tests\"\n  },\n  \"babel\": {\n    \"presets\": [\n      \"@babel/preset-env\"\n    ]\n  },\n  \"eslintConfig\": {\n    \"env\": {\n      \"browser\": true,\n      \"jest\": true\n    },\n    \"parser\": \"@babel/eslint-parser\",\n    \"parserOptions\": {\n      \"babelOptions\": {\n        \"presets\": [\n          \"airbnb\"\n        ]\n      },\n      \"requireConfigFile\": false\n    },\n    \"plugins\": [\n      \"prettier\"\n    ],\n    \"extends\": [\n      \"airbnb\",\n      \"airbnb/hooks\",\n      \"plugin:prettier/recommended\"\n    ],\n    \"rules\": {\n      \"import/no-unresolved\": [\n        \"error\",\n        {\n          \"ignore\": [\n            \"\\\\?url$\",\n            \"\\\\.svg\\\\?react$\"\n          ]\n        }\n      ],\n      \"prettier/prettier\": [\n        \"error\",\n        {\n          \"endOfLine\": \"auto\",\n          \"printWidth\": 100,\n          \"singleQuote\": true,\n          \"trailingComma\": \"all\"\n        }\n      ]\n    },\n    \"overrides\": [\n      {\n        \"files\": [\n          \"tests/acceptance/**/*.js\"\n        ],\n        \"rules\": {\n          \"import/extensions\": \"off\"\n        },\n        \"globals\": {\n          \"browser\": \"readonly\",\n          \"context\": \"readonly\",\n          \"page\": \"readonly\"\n        }\n      }\n    ]\n  },\n  \"jest\": {\n    \"transform\": {\n      \"^.+\\\\.(js|jsx)$\": \"babel-jest\"\n    }\n  },\n  \"overrides\": {\n    \"react-mentions\": {\n      \"@babel/runtime\": \"^7.28.6\"\n    }\n  },\n  \"dependencies\": {\n    \"@ballerina/highlightjs-ballerina\": \"^1.0.1\",\n    \"@diplodoc/cut-extension\": \"^1.1.1\",\n    \"@diplodoc/transform\": \"^4.70.2\",\n    \"@gravity-ui/components\": \"^4.18.0\",\n    \"@gravity-ui/markdown-editor\": \"^15.35.1\",\n    \"@gravity-ui/uikit\": \"^7.34.0\",\n    \"@juggle/resize-observer\": \"^3.4.0\",\n    \"@vitejs/plugin-react\": \"^5.2.0\",\n    \"browserslist-to-esbuild\": \"^2.1.1\",\n    \"classnames\": \"^2.5.1\",\n    \"date-fns\": \"^4.1.0\",\n    \"dequal\": \"^2.0.3\",\n    \"highlight.js\": \"^11.11.1\",\n    \"highlightjs-4d\": \"^1.0.6\",\n    \"highlightjs-alan\": \"^0.0.2\",\n    \"highlightjs-apex\": \"^1.5.0\",\n    \"highlightjs-blade\": \"^0.1.0\",\n    \"highlightjs-cobol\": \"^0.3.3\",\n    \"highlightjs-cshtml-razor\": \"^2.2.0\",\n    \"highlightjs-gf\": \"^1.0.1\",\n    \"highlightjs-jolie\": \"^0.1.8\",\n    \"highlightjs-lean\": \"^1.2.0\",\n    \"highlightjs-lookml\": \"^1.0.2\",\n    \"highlightjs-macaulay2\": \"^0.5.0\",\n    \"highlightjs-mlir\": \"^0.0.1\",\n    \"highlightjs-qsharp\": \"^1.0.2\",\n    \"highlightjs-redbol\": \"^2.1.2\",\n    \"highlightjs-rpm-specfile\": \"^1.0.0\",\n    \"highlightjs-sap-abap\": \"^0.3.0\",\n    \"highlightjs-solidity\": \"^2.0.6\",\n    \"highlightjs-supercollider\": \"^1.0.0\",\n    \"highlightjs-svelte\": \"^1.0.6\",\n    \"highlightjs-xsharp\": \"^1.0.0\",\n    \"highlightjs-zenscript\": \"^2.0.0\",\n    \"hightlightjs-papyrus\": \"^0.0.4\",\n    \"history\": \"^5.3.0\",\n    \"i18next\": \"^25.8.18\",\n    \"i18next-browser-languagedetector\": \"^8.2.1\",\n    \"initials\": \"^3.1.2\",\n    \"javascript-time-ago\": \"^2.6.4\",\n    \"js-cookie\": \"^3.0.5\",\n    \"jwt-decode\": \"^4.0.0\",\n    \"linkify-react\": \"^4.3.2\",\n    \"linkifyjs\": \"^4.3.2\",\n    \"lodash\": \"^4.17.23\",\n    \"lowlight\": \"^3.3.0\",\n    \"markdown-it\": \"^13.0.2\",\n    \"nanoid\": \"^5.1.7\",\n    \"papaparse\": \"^5.5.3\",\n    \"patch-package\": \"^8.0.1\",\n    \"photoswipe\": \"^5.4.4\",\n    \"prop-types\": \"^15.8.1\",\n    \"react\": \"18.2.0\",\n    \"react-beautiful-dnd\": \"^13.1.1\",\n    \"react-datepicker\": \"^9.1.0\",\n    \"react-dom\": \"18.2.0\",\n    \"react-dropzone\": \"^15.0.0\",\n    \"react-frame-component\": \"^5.2.7\",\n    \"react-hot-toast\": \"^2.6.0\",\n    \"react-i18next\": \"^16.5.8\",\n    \"react-input-mask\": \"^2.0.4\",\n    \"react-intersection-observer\": \"^10.0.3\",\n    \"react-mentions\": \"^4.4.10\",\n    \"react-photoswipe-gallery\": \"^4.0.0\",\n    \"react-redux\": \"^9.2.0\",\n    \"react-router\": \"^7.13.1\",\n    \"react-textarea-autosize\": \"^8.5.9\",\n    \"react-time-ago\": \"^7.4.4\",\n    \"redux\": \"^5.0.1\",\n    \"redux-logger\": \"^3.0.6\",\n    \"redux-orm\": \"^0.16.2\",\n    \"redux-saga\": \"^1.4.2\",\n    \"reselect\": \"^5.1.1\",\n    \"sails.io.js\": \"^1.2.1\",\n    \"sass-embedded\": \"^1.98.0\",\n    \"semantic-ui-react\": \"^2.1.5\",\n    \"socket.io-client\": \"^4.8.3\",\n    \"validator\": \"^13.15.26\",\n    \"vite\": \"^7.3.1\",\n    \"vite-plugin-commonjs\": \"^0.10.4\",\n    \"vite-plugin-node-polyfills\": \"^0.25.0\",\n    \"vite-plugin-svgr\": \"^4.5.0\",\n    \"zxcvbn\": \"^4.4.2\"\n  },\n  \"devDependencies\": {\n    \"@babel/eslint-parser\": \"^7.28.6\",\n    \"@babel/preset-env\": \"^7.29.0\",\n    \"@cucumber/cucumber\": \"^12.7.0\",\n    \"@cucumber/pretty-formatter\": \"^3.2.0\",\n    \"@playwright/test\": \"^1.58.2\",\n    \"babel-jest\": \"^30.3.0\",\n    \"babel-preset-airbnb\": \"^5.0.0\",\n    \"eslint\": \"^8.57.1\",\n    \"eslint-config-airbnb\": \"^19.0.4\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-import\": \"^2.32.0\",\n    \"eslint-plugin-jsx-a11y\": \"^6.10.2\",\n    \"eslint-plugin-prettier\": \"^5.5.5\",\n    \"eslint-plugin-react\": \"^7.37.5\",\n    \"eslint-plugin-react-hooks\": \"^4.6.2\",\n    \"jest\": \"^30.3.0\",\n    \"playwright\": \"^1.58.2\",\n    \"prettier\": \"3.8.1\"\n  }\n}\n"
  },
  {
    "path": "client/patches/@diplodoc+transform+4.70.2.patch",
    "content": "diff --git a/node_modules/@diplodoc/transform/lib/md.js b/node_modules/@diplodoc/transform/lib/md.js\nindex c9faa96..e4bef9b 100644\n--- a/node_modules/@diplodoc/transform/lib/md.js\n+++ b/node_modules/@diplodoc/transform/lib/md.js\n@@ -107,8 +107,12 @@ function initPlugins(md, options, pluginOptions) {\n     }\n     md.use(ol_attr_conversion_1.olAttrConversion);\n     plugins.forEach((plugin) => md.use(plugin, pluginOptions));\n-    if (linkify && linkifyTlds) {\n-        md.linkify.tlds(linkifyTlds, true);\n+    if (linkify) {\n+        if (linkifyTlds) {\n+            md.linkify.tlds(linkifyTlds, true);\n+        } else if (linkifyTlds === null) {\n+            md.linkify.set({ fuzzyLink: false });\n+        }\n     }\n }\n function initParser(md, options, env, pluginOptions) {\n"
  },
  {
    "path": "client/patches/@gravity-ui+markdown-editor+15.35.1.patch",
    "content": "diff --git a/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js b/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js\nindex c0d13c3..4c6e4e9 100644\n--- a/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js\n+++ b/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js\n@@ -107,7 +107,6 @@ export const BundlePreset = (builder, opts) => {\n             enableNewImageSizeCalculation: opts.enableNewImageSizeCalculation,\n             ...opts.imgSize,\n         },\n-        checkbox: { checkboxLabelPlaceholder: () => i18nPlaceholder('checkbox'), ...opts.checkbox },\n         deflist: {\n             deflistTermPlaceholder: () => i18nPlaceholder('deflist_term'),\n             deflistDescPlaceholder: () => i18nPlaceholder('deflist_desc'),\n@@ -128,11 +127,6 @@ export const BundlePreset = (builder, opts) => {\n             ...opts.yfmTable,\n             controls: opts.mobile ? false : opts.yfmTable?.controls,\n         },\n-        yfmFile: {\n-            fileUploadHandler: opts.fileUploadHandler,\n-            needToSetDimensionsForUploadedImages: opts.needToSetDimensionsForUploadedImages,\n-            ...opts.yfmFile,\n-        },\n         yfmHeading: {\n             h1Key: f.toPM(A.Heading1),\n             h2Key: f.toPM(A.Heading2),\ndiff --git a/node_modules/@gravity-ui/markdown-editor/build/esm/core/ExtensionsManager.js b/node_modules/@gravity-ui/markdown-editor/build/esm/core/ExtensionsManager.js\nindex 8aefe20..99e59e3 100644\n--- a/node_modules/@gravity-ui/markdown-editor/build/esm/core/ExtensionsManager.js\n+++ b/node_modules/@gravity-ui/markdown-editor/build/esm/core/ExtensionsManager.js\n@@ -42,6 +42,9 @@ export class ExtensionsManager {\n         if (options.linkifyTlds) {\n             this.#mdForMarkup.linkify.tlds(options.linkifyTlds, true);\n             this.#mdForText.linkify.tlds(options.linkifyTlds, true);\n+        } else if (options.linkifyTlds === null) {\n+            this.#mdForMarkup.linkify.set({ fuzzyLink: false });\n+            this.#mdForText.linkify.set({ fuzzyLink: false });\n         }\n         if (options.pmTransformers) {\n             this.#pmTransformers = options.pmTransformers;\ndiff --git a/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/markdown/CodeBlock/CodeBlockHighlight/TooltipPlugin/index.js b/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/markdown/CodeBlock/CodeBlockHighlight/TooltipPlugin/index.js\nindex 5eec9bb..3abd31a 100644\n--- a/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/markdown/CodeBlock/CodeBlockHighlight/TooltipPlugin/index.js\n+++ b/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/markdown/CodeBlock/CodeBlockHighlight/TooltipPlugin/index.js\n@@ -75,12 +75,6 @@ export const codeLangSelectTooltipViewCreator = (view, langItems, mapping = {},\n                                     dispatch: view.dispatch,\n                                 }),\n                             },\n-                        {\n-                            id: 'code-block-copy',\n-                            type: ToolbarDataType.ReactNodeFn,\n-                            width: 28,\n-                            content: () => _jsx(ClipboardButton, { text: node.textContent }),\n-                        },\n                     ].filter(isTruthy),\n                     [\n                         {\ndiff --git a/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/yfm/YfmNote/YfmNoteSpecs/index.js b/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/yfm/YfmNote/YfmNoteSpecs/index.js\nindex 212c583..b709383 100644\n--- a/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/yfm/YfmNote/YfmNoteSpecs/index.js\n+++ b/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/yfm/YfmNote/YfmNoteSpecs/index.js\n@@ -10,7 +10,7 @@ export { noteType, noteTitleType } from \"./utils.js\";\n export const YfmNoteSpecs = (builder, opts) => {\n     const schemaSpecs = getSchemaSpecs(opts, builder.context.get('placeholder'));\n     builder\n-        .configureMd((md) => md.use(yfmPlugin, { log, lang: getConfig().lang || 'en' }))\n+        .configureMd((md) => md.use(yfmPlugin, { log, lang: getConfig().lang || 'en', notesAutotitle: false }))\n         .addNode(NoteNode.Note, () => ({\n         spec: schemaSpecs[NoteNode.Note],\n         toMd: serializerTokens[NoteNode.Note],\ndiff --git a/node_modules/@gravity-ui/markdown-editor/build/esm/presets/yfm.js b/node_modules/@gravity-ui/markdown-editor/build/esm/presets/yfm.js\nindex ed2a9db..77f6d08 100644\n--- a/node_modules/@gravity-ui/markdown-editor/build/esm/presets/yfm.js\n+++ b/node_modules/@gravity-ui/markdown-editor/build/esm/presets/yfm.js\n@@ -1,5 +1,5 @@\n import { Deflist, Subscript, Superscript, Underline, } from \"../extensions/markdown/index.js\";\n-import { Checkbox, ImgSize, Monospace, Video, YfmConfigs, YfmCut, YfmFile, YfmHeading, YfmNote, YfmTable, YfmTabs, } from \"../extensions/yfm/index.js\";\n+import { ImgSize, Monospace, Video, YfmConfigs, YfmCut, YfmHeading, YfmNote, YfmTable, } from \"../extensions/yfm/index.js\";\n import { DefaultPreset } from \"./default.js\";\n export const YfmPreset = (builder, opts) => {\n     builder.use(DefaultPreset, { ...opts, image: false, heading: false });\n@@ -8,16 +8,13 @@ export const YfmPreset = (builder, opts) => {\n         .use(Subscript)\n         .use(Superscript)\n         .use(Underline, opts.underline ?? {})\n-        .use(Checkbox, opts.checkbox ?? {})\n         .use(ImgSize, opts.imgSize ?? {})\n         .use(Monospace)\n         .use(Video, opts.video ?? {})\n         .use(YfmConfigs, opts.yfmConfigs ?? {})\n         .use(YfmCut, opts.yfmCut ?? {})\n         .use(YfmNote, opts.yfmNote ?? {})\n-        .use(YfmFile, opts.yfmFile ?? {})\n         .use(YfmHeading, opts.yfmHeading ?? {})\n-        .use(YfmTable, opts.yfmTable ?? {})\n-        .use(YfmTabs);\n+        .use(YfmTable, opts.yfmTable ?? {});\n };\n //# sourceMappingURL=yfm.js.map\n"
  },
  {
    "path": "client/patches/react-mentions+4.4.10.patch",
    "content": "diff --git a/node_modules/react-mentions/dist/react-mentions.esm.js b/node_modules/react-mentions/dist/react-mentions.esm.js\nindex 2efebba..b244446 100644\n--- a/node_modules/react-mentions/dist/react-mentions.esm.js\n+++ b/node_modules/react-mentions/dist/react-mentions.esm.js\n@@ -1426,7 +1426,7 @@ var MentionsInput = /*#__PURE__*/function (_React$Component) {\n \n       var mentions = getMentions(newValue, config);\n \n-      if (ev.nativeEvent.isComposing && selectionStart === selectionEnd) {\n+      if ((ev.nativeEvent.isComposing || newValue.length < value.length) && selectionStart === selectionEnd) {\n         _this.updateMentionsQueries(_this.inputElement.value, selectionStart);\n       } // Propagate change\n       // let handleChange = this.getOnChange(this.props) || emptyFunction;\n@@ -1454,7 +1454,9 @@ var MentionsInput = /*#__PURE__*/function (_React$Component) {\n       var el = _this.inputElement;\n \n       if (ev.target.selectionStart === ev.target.selectionEnd) {\n-        _this.updateMentionsQueries(el.value, ev.target.selectionStart);\n+        requestAnimationFrame(function () {\n+          _this.updateMentionsQueries(el.value, ev.target.selectionStart);\n+        });\n       } else {\n         _this.clearSuggestions();\n       } // sync highlighters scroll position\n"
  },
  {
    "path": "client/patches/redux-orm+0.16.2.patch",
    "content": "diff --git a/node_modules/redux-orm/dist/redux-orm.js b/node_modules/redux-orm/dist/redux-orm.js\nindex 9298fea..d53d03e 100644\n--- a/node_modules/redux-orm/dist/redux-orm.js\n+++ b/node_modules/redux-orm/dist/redux-orm.js\n@@ -103,7 +103,7 @@ return /******/ (function(modules) { // webpackBootstrap\n /*! no static exports found */\n /***/ (function(module, exports) {\n \n-eval(\"function _arrayLikeToArray(arr, len) {\\n  if (len == null || len > arr.length) len = arr.length;\\n\\n  for (var i = 0, arr2 = new Array(len); i < len; i++) {\\n    arr2[i] = arr[i];\\n  }\\n\\n  return arr2;\\n}\\n\\nmodule.exports = _arrayLikeToArray;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2FycmF5TGlrZVRvQXJyYXkuanM/NWE0MyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQUNBOztBQUVBLHdDQUF3QyxTQUFTO0FBQ2pEO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQSIsImZpbGUiOiIuL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2FycmF5TGlrZVRvQXJyYXkuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBfYXJyYXlMaWtlVG9BcnJheShhcnIsIGxlbikge1xuICBpZiAobGVuID09IG51bGwgfHwgbGVuID4gYXJyLmxlbmd0aCkgbGVuID0gYXJyLmxlbmd0aDtcblxuICBmb3IgKHZhciBpID0gMCwgYXJyMiA9IG5ldyBBcnJheShsZW4pOyBpIDwgbGVuOyBpKyspIHtcbiAgICBhcnIyW2ldID0gYXJyW2ldO1xuICB9XG5cbiAgcmV0dXJuIGFycjI7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gX2FycmF5TGlrZVRvQXJyYXk7Il0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/arrayLikeToArray.js\\n\");\n+eval(\"function _arrayLikeToArray(arr, len) {\\n  if (len == null || len > arr.length) len = arr.length;\\n\\n  for (var i = 0, arr2 = new Array(len); i < len; i++) {\\n    arr2[i] = arr[i];\\n  }\\n\\n  return arr2;\\n}\\n\\nmodule.exports = _arrayLikeToArray;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2FycmF5TGlrZVRvQXJyYXkuanM/NWE0MyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQUNBOztBQUVBLHdDQUF3QyxTQUFTO0FBQ2pEO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBIiwiZmlsZSI6Ii4vbm9kZV9tb2R1bGVzL0BiYWJlbC9ydW50aW1lL2hlbHBlcnMvYXJyYXlMaWtlVG9BcnJheS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIF9hcnJheUxpa2VUb0FycmF5KGFyciwgbGVuKSB7XG4gIGlmIChsZW4gPT0gbnVsbCB8fCBsZW4gPiBhcnIubGVuZ3RoKSBsZW4gPSBhcnIubGVuZ3RoO1xuXG4gIGZvciAodmFyIGkgPSAwLCBhcnIyID0gbmV3IEFycmF5KGxlbik7IGkgPCBsZW47IGkrKykge1xuICAgIGFycjJbaV0gPSBhcnJbaV07XG4gIH1cblxuICByZXR1cm4gYXJyMjtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBfYXJyYXlMaWtlVG9BcnJheTtcbm1vZHVsZS5leHBvcnRzW1wiZGVmYXVsdFwiXSA9IG1vZHVsZS5leHBvcnRzLCBtb2R1bGUuZXhwb3J0cy5fX2VzTW9kdWxlID0gdHJ1ZTsiXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/arrayLikeToArray.js\\n\");\n \n /***/ }),\n \n@@ -114,7 +114,7 @@ eval(\"function _arrayLikeToArray(arr, len) {\\n  if (len == null || len > arr.len\n /*! no static exports found */\n /***/ (function(module, exports, __webpack_require__) {\n \n-eval(\"var arrayLikeToArray = __webpack_require__(/*! ./arrayLikeToArray */ \\\"./node_modules/@babel/runtime/helpers/arrayLikeToArray.js\\\");\\n\\nfunction _arrayWithoutHoles(arr) {\\n  if (Array.isArray(arr)) return arrayLikeToArray(arr);\\n}\\n\\nmodule.exports = _arrayWithoutHoles;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2FycmF5V2l0aG91dEhvbGVzLmpzPzIyMzYiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsdUJBQXVCLG1CQUFPLENBQUMscUZBQW9COztBQUVuRDtBQUNBO0FBQ0E7O0FBRUEiLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9hcnJheVdpdGhvdXRIb2xlcy5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbInZhciBhcnJheUxpa2VUb0FycmF5ID0gcmVxdWlyZShcIi4vYXJyYXlMaWtlVG9BcnJheVwiKTtcblxuZnVuY3Rpb24gX2FycmF5V2l0aG91dEhvbGVzKGFycikge1xuICBpZiAoQXJyYXkuaXNBcnJheShhcnIpKSByZXR1cm4gYXJyYXlMaWtlVG9BcnJheShhcnIpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IF9hcnJheVdpdGhvdXRIb2xlczsiXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/arrayWithoutHoles.js\\n\");\n+eval(\"var arrayLikeToArray = __webpack_require__(/*! ./arrayLikeToArray.js */ \\\"./node_modules/@babel/runtime/helpers/arrayLikeToArray.js\\\");\\n\\nfunction _arrayWithoutHoles(arr) {\\n  if (Array.isArray(arr)) return arrayLikeToArray(arr);\\n}\\n\\nmodule.exports = _arrayWithoutHoles;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2FycmF5V2l0aG91dEhvbGVzLmpzPzIyMzYiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsdUJBQXVCLG1CQUFPLENBQUMsd0ZBQXVCOztBQUV0RDtBQUNBO0FBQ0E7O0FBRUE7QUFDQSIsImZpbGUiOiIuL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2FycmF5V2l0aG91dEhvbGVzLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsidmFyIGFycmF5TGlrZVRvQXJyYXkgPSByZXF1aXJlKFwiLi9hcnJheUxpa2VUb0FycmF5LmpzXCIpO1xuXG5mdW5jdGlvbiBfYXJyYXlXaXRob3V0SG9sZXMoYXJyKSB7XG4gIGlmIChBcnJheS5pc0FycmF5KGFycikpIHJldHVybiBhcnJheUxpa2VUb0FycmF5KGFycik7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gX2FycmF5V2l0aG91dEhvbGVzO1xubW9kdWxlLmV4cG9ydHNbXCJkZWZhdWx0XCJdID0gbW9kdWxlLmV4cG9ydHMsIG1vZHVsZS5leHBvcnRzLl9fZXNNb2R1bGUgPSB0cnVlOyJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/arrayWithoutHoles.js\\n\");\n \n /***/ }),\n \n@@ -125,7 +125,7 @@ eval(\"var arrayLikeToArray = __webpack_require__(/*! ./arrayLikeToArray */ \\\"./n\n /*! no static exports found */\n /***/ (function(module, exports) {\n \n-eval(\"function _defineProperties(target, props) {\\n  for (var i = 0; i < props.length; i++) {\\n    var descriptor = props[i];\\n    descriptor.enumerable = descriptor.enumerable || false;\\n    descriptor.configurable = true;\\n    if (\\\"value\\\" in descriptor) descriptor.writable = true;\\n    Object.defineProperty(target, descriptor.key, descriptor);\\n  }\\n}\\n\\nfunction _createClass(Constructor, protoProps, staticProps) {\\n  if (protoProps) _defineProperties(Constructor.prototype, protoProps);\\n  if (staticProps) _defineProperties(Constructor, staticProps);\\n  return Constructor;\\n}\\n\\nmodule.exports = _createClass;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2NyZWF0ZUNsYXNzLmpzPzViYzMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQSxpQkFBaUIsa0JBQWtCO0FBQ25DO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUEiLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9jcmVhdGVDbGFzcy5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIF9kZWZpbmVQcm9wZXJ0aWVzKHRhcmdldCwgcHJvcHMpIHtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBwcm9wcy5sZW5ndGg7IGkrKykge1xuICAgIHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07XG4gICAgZGVzY3JpcHRvci5lbnVtZXJhYmxlID0gZGVzY3JpcHRvci5lbnVtZXJhYmxlIHx8IGZhbHNlO1xuICAgIGRlc2NyaXB0b3IuY29uZmlndXJhYmxlID0gdHJ1ZTtcbiAgICBpZiAoXCJ2YWx1ZVwiIGluIGRlc2NyaXB0b3IpIGRlc2NyaXB0b3Iud3JpdGFibGUgPSB0cnVlO1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0YXJnZXQsIGRlc2NyaXB0b3Iua2V5LCBkZXNjcmlwdG9yKTtcbiAgfVxufVxuXG5mdW5jdGlvbiBfY3JlYXRlQ2xhc3MoQ29uc3RydWN0b3IsIHByb3RvUHJvcHMsIHN0YXRpY1Byb3BzKSB7XG4gIGlmIChwcm90b1Byb3BzKSBfZGVmaW5lUHJvcGVydGllcyhDb25zdHJ1Y3Rvci5wcm90b3R5cGUsIHByb3RvUHJvcHMpO1xuICBpZiAoc3RhdGljUHJvcHMpIF9kZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLCBzdGF0aWNQcm9wcyk7XG4gIHJldHVybiBDb25zdHJ1Y3Rvcjtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBfY3JlYXRlQ2xhc3M7Il0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/createClass.js\\n\");\n+eval(\"function _defineProperties(target, props) {\\n  for (var i = 0; i < props.length; i++) {\\n    var descriptor = props[i];\\n    descriptor.enumerable = descriptor.enumerable || false;\\n    descriptor.configurable = true;\\n    if (\\\"value\\\" in descriptor) descriptor.writable = true;\\n    Object.defineProperty(target, descriptor.key, descriptor);\\n  }\\n}\\n\\nfunction _createClass(Constructor, protoProps, staticProps) {\\n  if (protoProps) _defineProperties(Constructor.prototype, protoProps);\\n  if (staticProps) _defineProperties(Constructor, staticProps);\\n  return Constructor;\\n}\\n\\nmodule.exports = _createClass;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2NyZWF0ZUNsYXNzLmpzPzViYzMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQSxpQkFBaUIsa0JBQWtCO0FBQ25DO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSIsImZpbGUiOiIuL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2NyZWF0ZUNsYXNzLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiZnVuY3Rpb24gX2RlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykge1xuICBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7XG4gICAgdmFyIGRlc2NyaXB0b3IgPSBwcm9wc1tpXTtcbiAgICBkZXNjcmlwdG9yLmVudW1lcmFibGUgPSBkZXNjcmlwdG9yLmVudW1lcmFibGUgfHwgZmFsc2U7XG4gICAgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlO1xuICAgIGlmIChcInZhbHVlXCIgaW4gZGVzY3JpcHRvcikgZGVzY3JpcHRvci53cml0YWJsZSA9IHRydWU7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRhcmdldCwgZGVzY3JpcHRvci5rZXksIGRlc2NyaXB0b3IpO1xuICB9XG59XG5cbmZ1bmN0aW9uIF9jcmVhdGVDbGFzcyhDb25zdHJ1Y3RvciwgcHJvdG9Qcm9wcywgc3RhdGljUHJvcHMpIHtcbiAgaWYgKHByb3RvUHJvcHMpIF9kZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLnByb3RvdHlwZSwgcHJvdG9Qcm9wcyk7XG4gIGlmIChzdGF0aWNQcm9wcykgX2RlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IsIHN0YXRpY1Byb3BzKTtcbiAgcmV0dXJuIENvbnN0cnVjdG9yO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IF9jcmVhdGVDbGFzcztcbm1vZHVsZS5leHBvcnRzW1wiZGVmYXVsdFwiXSA9IG1vZHVsZS5leHBvcnRzLCBtb2R1bGUuZXhwb3J0cy5fX2VzTW9kdWxlID0gdHJ1ZTsiXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/createClass.js\\n\");\n \n /***/ }),\n \n@@ -134,9 +134,9 @@ eval(\"function _defineProperties(target, props) {\\n  for (var i = 0; i < props.l\n   !*** ./node_modules/@babel/runtime/helpers/inheritsLoose.js ***!\n   \\**************************************************************/\n /*! no static exports found */\n-/***/ (function(module, exports) {\n+/***/ (function(module, exports, __webpack_require__) {\n \n-eval(\"function _inheritsLoose(subClass, superClass) {\\n  subClass.prototype = Object.create(superClass.prototype);\\n  subClass.prototype.constructor = subClass;\\n  subClass.__proto__ = superClass;\\n}\\n\\nmodule.exports = _inheritsLoose;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2luaGVyaXRzTG9vc2UuanM/NTViNSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBIiwiZmlsZSI6Ii4vbm9kZV9tb2R1bGVzL0BiYWJlbC9ydW50aW1lL2hlbHBlcnMvaW5oZXJpdHNMb29zZS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIF9pbmhlcml0c0xvb3NlKHN1YkNsYXNzLCBzdXBlckNsYXNzKSB7XG4gIHN1YkNsYXNzLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoc3VwZXJDbGFzcy5wcm90b3R5cGUpO1xuICBzdWJDbGFzcy5wcm90b3R5cGUuY29uc3RydWN0b3IgPSBzdWJDbGFzcztcbiAgc3ViQ2xhc3MuX19wcm90b19fID0gc3VwZXJDbGFzcztcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBfaW5oZXJpdHNMb29zZTsiXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/inheritsLoose.js\\n\");\n+eval(\"var setPrototypeOf = __webpack_require__(/*! ./setPrototypeOf.js */ \\\"./node_modules/@babel/runtime/helpers/setPrototypeOf.js\\\");\\n\\nfunction _inheritsLoose(subClass, superClass) {\\n  subClass.prototype = Object.create(superClass.prototype);\\n  subClass.prototype.constructor = subClass;\\n  setPrototypeOf(subClass, superClass);\\n}\\n\\nmodule.exports = _inheritsLoose;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2luaGVyaXRzTG9vc2UuanM/NTViNSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxxQkFBcUIsbUJBQU8sQ0FBQyxvRkFBcUI7O0FBRWxEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSIsImZpbGUiOiIuL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2luaGVyaXRzTG9vc2UuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgc2V0UHJvdG90eXBlT2YgPSByZXF1aXJlKFwiLi9zZXRQcm90b3R5cGVPZi5qc1wiKTtcblxuZnVuY3Rpb24gX2luaGVyaXRzTG9vc2Uoc3ViQ2xhc3MsIHN1cGVyQ2xhc3MpIHtcbiAgc3ViQ2xhc3MucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShzdXBlckNsYXNzLnByb3RvdHlwZSk7XG4gIHN1YkNsYXNzLnByb3RvdHlwZS5jb25zdHJ1Y3RvciA9IHN1YkNsYXNzO1xuICBzZXRQcm90b3R5cGVPZihzdWJDbGFzcywgc3VwZXJDbGFzcyk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gX2luaGVyaXRzTG9vc2U7XG5tb2R1bGUuZXhwb3J0c1tcImRlZmF1bHRcIl0gPSBtb2R1bGUuZXhwb3J0cywgbW9kdWxlLmV4cG9ydHMuX19lc01vZHVsZSA9IHRydWU7Il0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/inheritsLoose.js\\n\");\n \n /***/ }),\n \n@@ -147,7 +147,7 @@ eval(\"function _inheritsLoose(subClass, superClass) {\\n  subClass.prototype = Ob\n /*! no static exports found */\n /***/ (function(module, exports) {\n \n-eval(\"function _iterableToArray(iter) {\\n  if (typeof Symbol !== \\\"undefined\\\" && Symbol.iterator in Object(iter)) return Array.from(iter);\\n}\\n\\nmodule.exports = _iterableToArray;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2l0ZXJhYmxlVG9BcnJheS5qcz8xMWIwIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTs7QUFFQSIsImZpbGUiOiIuL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2l0ZXJhYmxlVG9BcnJheS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIF9pdGVyYWJsZVRvQXJyYXkoaXRlcikge1xuICBpZiAodHlwZW9mIFN5bWJvbCAhPT0gXCJ1bmRlZmluZWRcIiAmJiBTeW1ib2wuaXRlcmF0b3IgaW4gT2JqZWN0KGl0ZXIpKSByZXR1cm4gQXJyYXkuZnJvbShpdGVyKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBfaXRlcmFibGVUb0FycmF5OyJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/iterableToArray.js\\n\");\n+eval(\"function _iterableToArray(iter) {\\n  if (typeof Symbol !== \\\"undefined\\\" && iter[Symbol.iterator] != null || iter[\\\"@@iterator\\\"] != null) return Array.from(iter);\\n}\\n\\nmodule.exports = _iterableToArray;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2l0ZXJhYmxlVG9BcnJheS5qcz8xMWIwIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBIiwiZmlsZSI6Ii4vbm9kZV9tb2R1bGVzL0BiYWJlbC9ydW50aW1lL2hlbHBlcnMvaXRlcmFibGVUb0FycmF5LmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiZnVuY3Rpb24gX2l0ZXJhYmxlVG9BcnJheShpdGVyKSB7XG4gIGlmICh0eXBlb2YgU3ltYm9sICE9PSBcInVuZGVmaW5lZFwiICYmIGl0ZXJbU3ltYm9sLml0ZXJhdG9yXSAhPSBudWxsIHx8IGl0ZXJbXCJAQGl0ZXJhdG9yXCJdICE9IG51bGwpIHJldHVybiBBcnJheS5mcm9tKGl0ZXIpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IF9pdGVyYWJsZVRvQXJyYXk7XG5tb2R1bGUuZXhwb3J0c1tcImRlZmF1bHRcIl0gPSBtb2R1bGUuZXhwb3J0cywgbW9kdWxlLmV4cG9ydHMuX19lc01vZHVsZSA9IHRydWU7Il0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/iterableToArray.js\\n\");\n \n /***/ }),\n \n@@ -158,7 +158,18 @@ eval(\"function _iterableToArray(iter) {\\n  if (typeof Symbol !== \\\"undefined\\\" &\n /*! no static exports found */\n /***/ (function(module, exports) {\n \n-eval(\"function _nonIterableSpread() {\\n  throw new TypeError(\\\"Invalid attempt to spread non-iterable instance.\\\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\\\");\\n}\\n\\nmodule.exports = _nonIterableSpread;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL25vbkl0ZXJhYmxlU3ByZWFkLmpzPzA2NzYiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBOztBQUVBIiwiZmlsZSI6Ii4vbm9kZV9tb2R1bGVzL0BiYWJlbC9ydW50aW1lL2hlbHBlcnMvbm9uSXRlcmFibGVTcHJlYWQuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBfbm9uSXRlcmFibGVTcHJlYWQoKSB7XG4gIHRocm93IG5ldyBUeXBlRXJyb3IoXCJJbnZhbGlkIGF0dGVtcHQgdG8gc3ByZWFkIG5vbi1pdGVyYWJsZSBpbnN0YW5jZS5cXG5JbiBvcmRlciB0byBiZSBpdGVyYWJsZSwgbm9uLWFycmF5IG9iamVjdHMgbXVzdCBoYXZlIGEgW1N5bWJvbC5pdGVyYXRvcl0oKSBtZXRob2QuXCIpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IF9ub25JdGVyYWJsZVNwcmVhZDsiXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/nonIterableSpread.js\\n\");\n+eval(\"function _nonIterableSpread() {\\n  throw new TypeError(\\\"Invalid attempt to spread non-iterable instance.\\\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\\\");\\n}\\n\\nmodule.exports = _nonIterableSpread;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL25vbkl0ZXJhYmxlU3ByZWFkLmpzPzA2NzYiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBOztBQUVBO0FBQ0EiLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9ub25JdGVyYWJsZVNwcmVhZC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIF9ub25JdGVyYWJsZVNwcmVhZCgpIHtcbiAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkludmFsaWQgYXR0ZW1wdCB0byBzcHJlYWQgbm9uLWl0ZXJhYmxlIGluc3RhbmNlLlxcbkluIG9yZGVyIHRvIGJlIGl0ZXJhYmxlLCBub24tYXJyYXkgb2JqZWN0cyBtdXN0IGhhdmUgYSBbU3ltYm9sLml0ZXJhdG9yXSgpIG1ldGhvZC5cIik7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gX25vbkl0ZXJhYmxlU3ByZWFkO1xubW9kdWxlLmV4cG9ydHNbXCJkZWZhdWx0XCJdID0gbW9kdWxlLmV4cG9ydHMsIG1vZHVsZS5leHBvcnRzLl9fZXNNb2R1bGUgPSB0cnVlOyJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/nonIterableSpread.js\\n\");\n+\n+/***/ }),\n+\n+/***/ \"./node_modules/@babel/runtime/helpers/setPrototypeOf.js\":\n+/*!***************************************************************!*\\\n+  !*** ./node_modules/@babel/runtime/helpers/setPrototypeOf.js ***!\n+  \\***************************************************************/\n+/*! no static exports found */\n+/***/ (function(module, exports) {\n+\n+eval(\"function _setPrototypeOf(o, p) {\\n  module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {\\n    o.__proto__ = p;\\n    return o;\\n  };\\n\\n  module.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\\n  return _setPrototypeOf(o, p);\\n}\\n\\nmodule.exports = _setPrototypeOf;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL3NldFByb3RvdHlwZU9mLmpzPzRhNGIiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSIsImZpbGUiOiIuL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL3NldFByb3RvdHlwZU9mLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiZnVuY3Rpb24gX3NldFByb3RvdHlwZU9mKG8sIHApIHtcbiAgbW9kdWxlLmV4cG9ydHMgPSBfc2V0UHJvdG90eXBlT2YgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHwgZnVuY3Rpb24gX3NldFByb3RvdHlwZU9mKG8sIHApIHtcbiAgICBvLl9fcHJvdG9fXyA9IHA7XG4gICAgcmV0dXJuIG87XG4gIH07XG5cbiAgbW9kdWxlLmV4cG9ydHNbXCJkZWZhdWx0XCJdID0gbW9kdWxlLmV4cG9ydHMsIG1vZHVsZS5leHBvcnRzLl9fZXNNb2R1bGUgPSB0cnVlO1xuICByZXR1cm4gX3NldFByb3RvdHlwZU9mKG8sIHApO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IF9zZXRQcm90b3R5cGVPZjtcbm1vZHVsZS5leHBvcnRzW1wiZGVmYXVsdFwiXSA9IG1vZHVsZS5leHBvcnRzLCBtb2R1bGUuZXhwb3J0cy5fX2VzTW9kdWxlID0gdHJ1ZTsiXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/setPrototypeOf.js\\n\");\n \n /***/ }),\n \n@@ -169,7 +180,7 @@ eval(\"function _nonIterableSpread() {\\n  throw new TypeError(\\\"Invalid attempt t\n /*! no static exports found */\n /***/ (function(module, exports, __webpack_require__) {\n \n-eval(\"var arrayWithoutHoles = __webpack_require__(/*! ./arrayWithoutHoles */ \\\"./node_modules/@babel/runtime/helpers/arrayWithoutHoles.js\\\");\\n\\nvar iterableToArray = __webpack_require__(/*! ./iterableToArray */ \\\"./node_modules/@babel/runtime/helpers/iterableToArray.js\\\");\\n\\nvar unsupportedIterableToArray = __webpack_require__(/*! ./unsupportedIterableToArray */ \\\"./node_modules/@babel/runtime/helpers/unsupportedIterableToArray.js\\\");\\n\\nvar nonIterableSpread = __webpack_require__(/*! ./nonIterableSpread */ \\\"./node_modules/@babel/runtime/helpers/nonIterableSpread.js\\\");\\n\\nfunction _toConsumableArray(arr) {\\n  return arrayWithoutHoles(arr) || iterableToArray(arr) || unsupportedIterableToArray(arr) || nonIterableSpread();\\n}\\n\\nmodule.exports = _toConsumableArray;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL3RvQ29uc3VtYWJsZUFycmF5LmpzPzQ0OGEiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsd0JBQXdCLG1CQUFPLENBQUMsdUZBQXFCOztBQUVyRCxzQkFBc0IsbUJBQU8sQ0FBQyxtRkFBbUI7O0FBRWpELGlDQUFpQyxtQkFBTyxDQUFDLHlHQUE4Qjs7QUFFdkUsd0JBQXdCLG1CQUFPLENBQUMsdUZBQXFCOztBQUVyRDtBQUNBO0FBQ0E7O0FBRUEiLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy90b0NvbnN1bWFibGVBcnJheS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbInZhciBhcnJheVdpdGhvdXRIb2xlcyA9IHJlcXVpcmUoXCIuL2FycmF5V2l0aG91dEhvbGVzXCIpO1xuXG52YXIgaXRlcmFibGVUb0FycmF5ID0gcmVxdWlyZShcIi4vaXRlcmFibGVUb0FycmF5XCIpO1xuXG52YXIgdW5zdXBwb3J0ZWRJdGVyYWJsZVRvQXJyYXkgPSByZXF1aXJlKFwiLi91bnN1cHBvcnRlZEl0ZXJhYmxlVG9BcnJheVwiKTtcblxudmFyIG5vbkl0ZXJhYmxlU3ByZWFkID0gcmVxdWlyZShcIi4vbm9uSXRlcmFibGVTcHJlYWRcIik7XG5cbmZ1bmN0aW9uIF90b0NvbnN1bWFibGVBcnJheShhcnIpIHtcbiAgcmV0dXJuIGFycmF5V2l0aG91dEhvbGVzKGFycikgfHwgaXRlcmFibGVUb0FycmF5KGFycikgfHwgdW5zdXBwb3J0ZWRJdGVyYWJsZVRvQXJyYXkoYXJyKSB8fCBub25JdGVyYWJsZVNwcmVhZCgpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IF90b0NvbnN1bWFibGVBcnJheTsiXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/toConsumableArray.js\\n\");\n+eval(\"var arrayWithoutHoles = __webpack_require__(/*! ./arrayWithoutHoles.js */ \\\"./node_modules/@babel/runtime/helpers/arrayWithoutHoles.js\\\");\\n\\nvar iterableToArray = __webpack_require__(/*! ./iterableToArray.js */ \\\"./node_modules/@babel/runtime/helpers/iterableToArray.js\\\");\\n\\nvar unsupportedIterableToArray = __webpack_require__(/*! ./unsupportedIterableToArray.js */ \\\"./node_modules/@babel/runtime/helpers/unsupportedIterableToArray.js\\\");\\n\\nvar nonIterableSpread = __webpack_require__(/*! ./nonIterableSpread.js */ \\\"./node_modules/@babel/runtime/helpers/nonIterableSpread.js\\\");\\n\\nfunction _toConsumableArray(arr) {\\n  return arrayWithoutHoles(arr) || iterableToArray(arr) || unsupportedIterableToArray(arr) || nonIterableSpread();\\n}\\n\\nmodule.exports = _toConsumableArray;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL3RvQ29uc3VtYWJsZUFycmF5LmpzPzQ0OGEiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsd0JBQXdCLG1CQUFPLENBQUMsMEZBQXdCOztBQUV4RCxzQkFBc0IsbUJBQU8sQ0FBQyxzRkFBc0I7O0FBRXBELGlDQUFpQyxtQkFBTyxDQUFDLDRHQUFpQzs7QUFFMUUsd0JBQXdCLG1CQUFPLENBQUMsMEZBQXdCOztBQUV4RDtBQUNBO0FBQ0E7O0FBRUE7QUFDQSIsImZpbGUiOiIuL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL3RvQ29uc3VtYWJsZUFycmF5LmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsidmFyIGFycmF5V2l0aG91dEhvbGVzID0gcmVxdWlyZShcIi4vYXJyYXlXaXRob3V0SG9sZXMuanNcIik7XG5cbnZhciBpdGVyYWJsZVRvQXJyYXkgPSByZXF1aXJlKFwiLi9pdGVyYWJsZVRvQXJyYXkuanNcIik7XG5cbnZhciB1bnN1cHBvcnRlZEl0ZXJhYmxlVG9BcnJheSA9IHJlcXVpcmUoXCIuL3Vuc3VwcG9ydGVkSXRlcmFibGVUb0FycmF5LmpzXCIpO1xuXG52YXIgbm9uSXRlcmFibGVTcHJlYWQgPSByZXF1aXJlKFwiLi9ub25JdGVyYWJsZVNwcmVhZC5qc1wiKTtcblxuZnVuY3Rpb24gX3RvQ29uc3VtYWJsZUFycmF5KGFycikge1xuICByZXR1cm4gYXJyYXlXaXRob3V0SG9sZXMoYXJyKSB8fCBpdGVyYWJsZVRvQXJyYXkoYXJyKSB8fCB1bnN1cHBvcnRlZEl0ZXJhYmxlVG9BcnJheShhcnIpIHx8IG5vbkl0ZXJhYmxlU3ByZWFkKCk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gX3RvQ29uc3VtYWJsZUFycmF5O1xubW9kdWxlLmV4cG9ydHNbXCJkZWZhdWx0XCJdID0gbW9kdWxlLmV4cG9ydHMsIG1vZHVsZS5leHBvcnRzLl9fZXNNb2R1bGUgPSB0cnVlOyJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/toConsumableArray.js\\n\");\n \n /***/ }),\n \n@@ -180,7 +191,7 @@ eval(\"var arrayWithoutHoles = __webpack_require__(/*! ./arrayWithoutHoles */ \\\".\n /*! no static exports found */\n /***/ (function(module, exports) {\n \n-eval(\"function _typeof(obj) {\\n  \\\"@babel/helpers - typeof\\\";\\n\\n  if (typeof Symbol === \\\"function\\\" && typeof Symbol.iterator === \\\"symbol\\\") {\\n    module.exports = _typeof = function _typeof(obj) {\\n      return typeof obj;\\n    };\\n  } else {\\n    module.exports = _typeof = function _typeof(obj) {\\n      return obj && typeof Symbol === \\\"function\\\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \\\"symbol\\\" : typeof obj;\\n    };\\n  }\\n\\n  return _typeof(obj);\\n}\\n\\nmodule.exports = _typeof;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL3R5cGVvZi5qcz83MDM3Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQSIsImZpbGUiOiIuL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL3R5cGVvZi5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIF90eXBlb2Yob2JqKSB7XG4gIFwiQGJhYmVsL2hlbHBlcnMgLSB0eXBlb2ZcIjtcblxuICBpZiAodHlwZW9mIFN5bWJvbCA9PT0gXCJmdW5jdGlvblwiICYmIHR5cGVvZiBTeW1ib2wuaXRlcmF0b3IgPT09IFwic3ltYm9sXCIpIHtcbiAgICBtb2R1bGUuZXhwb3J0cyA9IF90eXBlb2YgPSBmdW5jdGlvbiBfdHlwZW9mKG9iaikge1xuICAgICAgcmV0dXJuIHR5cGVvZiBvYmo7XG4gICAgfTtcbiAgfSBlbHNlIHtcbiAgICBtb2R1bGUuZXhwb3J0cyA9IF90eXBlb2YgPSBmdW5jdGlvbiBfdHlwZW9mKG9iaikge1xuICAgICAgcmV0dXJuIG9iaiAmJiB0eXBlb2YgU3ltYm9sID09PSBcImZ1bmN0aW9uXCIgJiYgb2JqLmNvbnN0cnVjdG9yID09PSBTeW1ib2wgJiYgb2JqICE9PSBTeW1ib2wucHJvdG90eXBlID8gXCJzeW1ib2xcIiA6IHR5cGVvZiBvYmo7XG4gICAgfTtcbiAgfVxuXG4gIHJldHVybiBfdHlwZW9mKG9iaik7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gX3R5cGVvZjsiXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/typeof.js\\n\");\n+eval(\"function _typeof(obj) {\\n  \\\"@babel/helpers - typeof\\\";\\n\\n  if (typeof Symbol === \\\"function\\\" && typeof Symbol.iterator === \\\"symbol\\\") {\\n    module.exports = _typeof = function _typeof(obj) {\\n      return typeof obj;\\n    };\\n\\n    module.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\\n  } else {\\n    module.exports = _typeof = function _typeof(obj) {\\n      return obj && typeof Symbol === \\\"function\\\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \\\"symbol\\\" : typeof obj;\\n    };\\n\\n    module.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\\n  }\\n\\n  return _typeof(obj);\\n}\\n\\nmodule.exports = _typeof;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL3R5cGVvZi5qcz83MDM3Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBIiwiZmlsZSI6Ii4vbm9kZV9tb2R1bGVzL0BiYWJlbC9ydW50aW1lL2hlbHBlcnMvdHlwZW9mLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiZnVuY3Rpb24gX3R5cGVvZihvYmopIHtcbiAgXCJAYmFiZWwvaGVscGVycyAtIHR5cGVvZlwiO1xuXG4gIGlmICh0eXBlb2YgU3ltYm9sID09PSBcImZ1bmN0aW9uXCIgJiYgdHlwZW9mIFN5bWJvbC5pdGVyYXRvciA9PT0gXCJzeW1ib2xcIikge1xuICAgIG1vZHVsZS5leHBvcnRzID0gX3R5cGVvZiA9IGZ1bmN0aW9uIF90eXBlb2Yob2JqKSB7XG4gICAgICByZXR1cm4gdHlwZW9mIG9iajtcbiAgICB9O1xuXG4gICAgbW9kdWxlLmV4cG9ydHNbXCJkZWZhdWx0XCJdID0gbW9kdWxlLmV4cG9ydHMsIG1vZHVsZS5leHBvcnRzLl9fZXNNb2R1bGUgPSB0cnVlO1xuICB9IGVsc2Uge1xuICAgIG1vZHVsZS5leHBvcnRzID0gX3R5cGVvZiA9IGZ1bmN0aW9uIF90eXBlb2Yob2JqKSB7XG4gICAgICByZXR1cm4gb2JqICYmIHR5cGVvZiBTeW1ib2wgPT09IFwiZnVuY3Rpb25cIiAmJiBvYmouY29uc3RydWN0b3IgPT09IFN5bWJvbCAmJiBvYmogIT09IFN5bWJvbC5wcm90b3R5cGUgPyBcInN5bWJvbFwiIDogdHlwZW9mIG9iajtcbiAgICB9O1xuXG4gICAgbW9kdWxlLmV4cG9ydHNbXCJkZWZhdWx0XCJdID0gbW9kdWxlLmV4cG9ydHMsIG1vZHVsZS5leHBvcnRzLl9fZXNNb2R1bGUgPSB0cnVlO1xuICB9XG5cbiAgcmV0dXJuIF90eXBlb2Yob2JqKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBfdHlwZW9mO1xubW9kdWxlLmV4cG9ydHNbXCJkZWZhdWx0XCJdID0gbW9kdWxlLmV4cG9ydHMsIG1vZHVsZS5leHBvcnRzLl9fZXNNb2R1bGUgPSB0cnVlOyJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/typeof.js\\n\");\n \n /***/ }),\n \n@@ -191,7 +202,7 @@ eval(\"function _typeof(obj) {\\n  \\\"@babel/helpers - typeof\\\";\\n\\n  if (typeof Sy\n /*! no static exports found */\n /***/ (function(module, exports, __webpack_require__) {\n \n-eval(\"var arrayLikeToArray = __webpack_require__(/*! ./arrayLikeToArray */ \\\"./node_modules/@babel/runtime/helpers/arrayLikeToArray.js\\\");\\n\\nfunction _unsupportedIterableToArray(o, minLen) {\\n  if (!o) return;\\n  if (typeof o === \\\"string\\\") return arrayLikeToArray(o, minLen);\\n  var n = Object.prototype.toString.call(o).slice(8, -1);\\n  if (n === \\\"Object\\\" && o.constructor) n = o.constructor.name;\\n  if (n === \\\"Map\\\" || n === \\\"Set\\\") return Array.from(o);\\n  if (n === \\\"Arguments\\\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return arrayLikeToArray(o, minLen);\\n}\\n\\nmodule.exports = _unsupportedIterableToArray;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL3Vuc3VwcG9ydGVkSXRlcmFibGVUb0FycmF5LmpzPzY2MTMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsdUJBQXVCLG1CQUFPLENBQUMscUZBQW9COztBQUVuRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBIiwiZmlsZSI6Ii4vbm9kZV9tb2R1bGVzL0BiYWJlbC9ydW50aW1lL2hlbHBlcnMvdW5zdXBwb3J0ZWRJdGVyYWJsZVRvQXJyYXkuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgYXJyYXlMaWtlVG9BcnJheSA9IHJlcXVpcmUoXCIuL2FycmF5TGlrZVRvQXJyYXlcIik7XG5cbmZ1bmN0aW9uIF91bnN1cHBvcnRlZEl0ZXJhYmxlVG9BcnJheShvLCBtaW5MZW4pIHtcbiAgaWYgKCFvKSByZXR1cm47XG4gIGlmICh0eXBlb2YgbyA9PT0gXCJzdHJpbmdcIikgcmV0dXJuIGFycmF5TGlrZVRvQXJyYXkobywgbWluTGVuKTtcbiAgdmFyIG4gPSBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nLmNhbGwobykuc2xpY2UoOCwgLTEpO1xuICBpZiAobiA9PT0gXCJPYmplY3RcIiAmJiBvLmNvbnN0cnVjdG9yKSBuID0gby5jb25zdHJ1Y3Rvci5uYW1lO1xuICBpZiAobiA9PT0gXCJNYXBcIiB8fCBuID09PSBcIlNldFwiKSByZXR1cm4gQXJyYXkuZnJvbShvKTtcbiAgaWYgKG4gPT09IFwiQXJndW1lbnRzXCIgfHwgL14oPzpVaXxJKW50KD86OHwxNnwzMikoPzpDbGFtcGVkKT9BcnJheSQvLnRlc3QobikpIHJldHVybiBhcnJheUxpa2VUb0FycmF5KG8sIG1pbkxlbik7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gX3Vuc3VwcG9ydGVkSXRlcmFibGVUb0FycmF5OyJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/unsupportedIterableToArray.js\\n\");\n+eval(\"var arrayLikeToArray = __webpack_require__(/*! ./arrayLikeToArray.js */ \\\"./node_modules/@babel/runtime/helpers/arrayLikeToArray.js\\\");\\n\\nfunction _unsupportedIterableToArray(o, minLen) {\\n  if (!o) return;\\n  if (typeof o === \\\"string\\\") return arrayLikeToArray(o, minLen);\\n  var n = Object.prototype.toString.call(o).slice(8, -1);\\n  if (n === \\\"Object\\\" && o.constructor) n = o.constructor.name;\\n  if (n === \\\"Map\\\" || n === \\\"Set\\\") return Array.from(o);\\n  if (n === \\\"Arguments\\\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return arrayLikeToArray(o, minLen);\\n}\\n\\nmodule.exports = _unsupportedIterableToArray;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL3Vuc3VwcG9ydGVkSXRlcmFibGVUb0FycmF5LmpzPzY2MTMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsdUJBQXVCLG1CQUFPLENBQUMsd0ZBQXVCOztBQUV0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0EiLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy91bnN1cHBvcnRlZEl0ZXJhYmxlVG9BcnJheS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbInZhciBhcnJheUxpa2VUb0FycmF5ID0gcmVxdWlyZShcIi4vYXJyYXlMaWtlVG9BcnJheS5qc1wiKTtcblxuZnVuY3Rpb24gX3Vuc3VwcG9ydGVkSXRlcmFibGVUb0FycmF5KG8sIG1pbkxlbikge1xuICBpZiAoIW8pIHJldHVybjtcbiAgaWYgKHR5cGVvZiBvID09PSBcInN0cmluZ1wiKSByZXR1cm4gYXJyYXlMaWtlVG9BcnJheShvLCBtaW5MZW4pO1xuICB2YXIgbiA9IE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChvKS5zbGljZSg4LCAtMSk7XG4gIGlmIChuID09PSBcIk9iamVjdFwiICYmIG8uY29uc3RydWN0b3IpIG4gPSBvLmNvbnN0cnVjdG9yLm5hbWU7XG4gIGlmIChuID09PSBcIk1hcFwiIHx8IG4gPT09IFwiU2V0XCIpIHJldHVybiBBcnJheS5mcm9tKG8pO1xuICBpZiAobiA9PT0gXCJBcmd1bWVudHNcIiB8fCAvXig/OlVpfEkpbnQoPzo4fDE2fDMyKSg/OkNsYW1wZWQpP0FycmF5JC8udGVzdChuKSkgcmV0dXJuIGFycmF5TGlrZVRvQXJyYXkobywgbWluTGVuKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBfdW5zdXBwb3J0ZWRJdGVyYWJsZVRvQXJyYXk7XG5tb2R1bGUuZXhwb3J0c1tcImRlZmF1bHRcIl0gPSBtb2R1bGUuZXhwb3J0cywgbW9kdWxlLmV4cG9ydHMuX19lc01vZHVsZSA9IHRydWU7Il0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./node_modules/@babel/runtime/helpers/unsupportedIterableToArray.js\\n\");\n \n /***/ }),\n \n@@ -262,6 +273,17 @@ eval(\"/**\\n * Gets the first element of `array`.\\n *\\n * @static\\n * @memberOf _\n \n /***/ }),\n \n+/***/ \"./node_modules/lodash/_baseGet.js\":\n+/*!*****************************************!*\\\n+  !*** ./node_modules/lodash/_baseGet.js ***!\n+  \\*****************************************/\n+/*! no static exports found */\n+/***/ (function(module, exports) {\n+\n+eval(\"/**\\n * Gets the value at `key` of `object`.\\n *\\n * @private\\n * @param {Object} [object] The object to query.\\n * @param {string} key The key of the property to get.\\n * @returns {*} Returns the property value.\\n */\\nfunction getValue(object, key) {\\n  return object == null ? undefined : object[key];\\n}\\n\\nmodule.exports = getValue;\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9sb2Rhc2gvX2Jhc2VHZXQuanM/NjU2YiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsT0FBTztBQUNsQixXQUFXLE9BQU87QUFDbEIsYUFBYSxFQUFFO0FBQ2Y7QUFDQTtBQUNBO0FBQ0E7O0FBRUEiLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvbG9kYXNoL19iYXNlR2V0LmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBHZXRzIHRoZSB2YWx1ZSBhdCBga2V5YCBvZiBgb2JqZWN0YC5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtPYmplY3R9IFtvYmplY3RdIFRoZSBvYmplY3QgdG8gcXVlcnkuXG4gKiBAcGFyYW0ge3N0cmluZ30ga2V5IFRoZSBrZXkgb2YgdGhlIHByb3BlcnR5IHRvIGdldC5cbiAqIEByZXR1cm5zIHsqfSBSZXR1cm5zIHRoZSBwcm9wZXJ0eSB2YWx1ZS5cbiAqL1xuZnVuY3Rpb24gZ2V0VmFsdWUob2JqZWN0LCBrZXkpIHtcbiAgcmV0dXJuIG9iamVjdCA9PSBudWxsID8gdW5kZWZpbmVkIDogb2JqZWN0W2tleV07XG59XG5cbm1vZHVsZS5leHBvcnRzID0gZ2V0VmFsdWU7XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./node_modules/lodash/_baseGet.js\\n\");\n+\n+/***/ }),\n+\n /***/ \"./node_modules/lodash/_baseIteratee.js\":\n /*!**********************************************!*\\\n   !*** ./node_modules/lodash/_baseIteratee.js ***!\n@@ -291,7 +313,7 @@ eval(\"/**\\n * A specialized version of `_.map` for arrays without support for it\n /*! no static exports found */\n /***/ (function(module, exports, __webpack_require__) {\n \n-eval(\"var arrayMap = __webpack_require__(/*! ./_arrayMap */ \\\"./node_modules/lodash/_arrayMap.js\\\"),\\n    baseIteratee = __webpack_require__(/*! ./_baseIteratee */ \\\"./node_modules/lodash/_baseIteratee.js\\\"),\\n    baseMap = __webpack_require__(/*! ./_baseMap */ \\\"./node_modules/lodash/_baseMap.js\\\"),\\n    baseSortBy = __webpack_require__(/*! ./_baseSortBy */ \\\"./node_modules/lodash/_baseSortBy.js\\\"),\\n    baseUnary = __webpack_require__(/*! ./_baseUnary */ \\\"./node_modules/lodash/_baseUnary.js\\\"),\\n    compareMultiple = __webpack_require__(/*! ./_compareMultiple */ \\\"./node_modules/lodash/_compareMultiple.js\\\"),\\n    identity = __webpack_require__(/*! ./identity */ \\\"./node_modules/lodash/identity.js\\\");\\n\\n/**\\n * The base implementation of `_.orderBy` without param guards.\\n *\\n * @private\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.\\n * @param {string[]} orders The sort orders of `iteratees`.\\n * @returns {Array} Returns the new sorted array.\\n */\\nfunction baseOrderBy(collection, iteratees, orders) {\\n  var index = -1;\\n  iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(baseIteratee));\\n\\n  var result = baseMap(collection, function(value, key, collection) {\\n    var criteria = arrayMap(iteratees, function(iteratee) {\\n      return iteratee(value);\\n    });\\n    return { 'criteria': criteria, 'index': ++index, 'value': value };\\n  });\\n\\n  return baseSortBy(result, function(object, other) {\\n    return compareMultiple(object, other, orders);\\n  });\\n}\\n\\nmodule.exports = baseOrderBy;\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9sb2Rhc2gvX2Jhc2VPcmRlckJ5LmpzPzZhNWMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsZUFBZSxtQkFBTyxDQUFDLHVEQUFhO0FBQ3BDLG1CQUFtQixtQkFBTyxDQUFDLCtEQUFpQjtBQUM1QyxjQUFjLG1CQUFPLENBQUMscURBQVk7QUFDbEMsaUJBQWlCLG1CQUFPLENBQUMsMkRBQWU7QUFDeEMsZ0JBQWdCLG1CQUFPLENBQUMseURBQWM7QUFDdEMsc0JBQXNCLG1CQUFPLENBQUMscUVBQW9CO0FBQ2xELGVBQWUsbUJBQU8sQ0FBQyxxREFBWTs7QUFFbkM7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLGFBQWE7QUFDeEIsV0FBVyw2QkFBNkI7QUFDeEMsV0FBVyxTQUFTO0FBQ3BCLGFBQWEsTUFBTTtBQUNuQjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wsWUFBWTtBQUNaLEdBQUc7O0FBRUg7QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQSIsImZpbGUiOiIuL25vZGVfbW9kdWxlcy9sb2Rhc2gvX2Jhc2VPcmRlckJ5LmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsidmFyIGFycmF5TWFwID0gcmVxdWlyZSgnLi9fYXJyYXlNYXAnKSxcbiAgICBiYXNlSXRlcmF0ZWUgPSByZXF1aXJlKCcuL19iYXNlSXRlcmF0ZWUnKSxcbiAgICBiYXNlTWFwID0gcmVxdWlyZSgnLi9fYmFzZU1hcCcpLFxuICAgIGJhc2VTb3J0QnkgPSByZXF1aXJlKCcuL19iYXNlU29ydEJ5JyksXG4gICAgYmFzZVVuYXJ5ID0gcmVxdWlyZSgnLi9fYmFzZVVuYXJ5JyksXG4gICAgY29tcGFyZU11bHRpcGxlID0gcmVxdWlyZSgnLi9fY29tcGFyZU11bHRpcGxlJyksXG4gICAgaWRlbnRpdHkgPSByZXF1aXJlKCcuL2lkZW50aXR5Jyk7XG5cbi8qKlxuICogVGhlIGJhc2UgaW1wbGVtZW50YXRpb24gb2YgYF8ub3JkZXJCeWAgd2l0aG91dCBwYXJhbSBndWFyZHMuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7QXJyYXl8T2JqZWN0fSBjb2xsZWN0aW9uIFRoZSBjb2xsZWN0aW9uIHRvIGl0ZXJhdGUgb3Zlci5cbiAqIEBwYXJhbSB7RnVuY3Rpb25bXXxPYmplY3RbXXxzdHJpbmdbXX0gaXRlcmF0ZWVzIFRoZSBpdGVyYXRlZXMgdG8gc29ydCBieS5cbiAqIEBwYXJhbSB7c3RyaW5nW119IG9yZGVycyBUaGUgc29ydCBvcmRlcnMgb2YgYGl0ZXJhdGVlc2AuXG4gKiBAcmV0dXJucyB7QXJyYXl9IFJldHVybnMgdGhlIG5ldyBzb3J0ZWQgYXJyYXkuXG4gKi9cbmZ1bmN0aW9uIGJhc2VPcmRlckJ5KGNvbGxlY3Rpb24sIGl0ZXJhdGVlcywgb3JkZXJzKSB7XG4gIHZhciBpbmRleCA9IC0xO1xuICBpdGVyYXRlZXMgPSBhcnJheU1hcChpdGVyYXRlZXMubGVuZ3RoID8gaXRlcmF0ZWVzIDogW2lkZW50aXR5XSwgYmFzZVVuYXJ5KGJhc2VJdGVyYXRlZSkpO1xuXG4gIHZhciByZXN1bHQgPSBiYXNlTWFwKGNvbGxlY3Rpb24sIGZ1bmN0aW9uKHZhbHVlLCBrZXksIGNvbGxlY3Rpb24pIHtcbiAgICB2YXIgY3JpdGVyaWEgPSBhcnJheU1hcChpdGVyYXRlZXMsIGZ1bmN0aW9uKGl0ZXJhdGVlKSB7XG4gICAgICByZXR1cm4gaXRlcmF0ZWUodmFsdWUpO1xuICAgIH0pO1xuICAgIHJldHVybiB7ICdjcml0ZXJpYSc6IGNyaXRlcmlhLCAnaW5kZXgnOiArK2luZGV4LCAndmFsdWUnOiB2YWx1ZSB9O1xuICB9KTtcblxuICByZXR1cm4gYmFzZVNvcnRCeShyZXN1bHQsIGZ1bmN0aW9uKG9iamVjdCwgb3RoZXIpIHtcbiAgICByZXR1cm4gY29tcGFyZU11bHRpcGxlKG9iamVjdCwgb3RoZXIsIG9yZGVycyk7XG4gIH0pO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGJhc2VPcmRlckJ5O1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./node_modules/lodash/_baseOrderBy.js\\n\");\n+eval(\"var arrayMap = __webpack_require__(/*! ./_arrayMap */ \\\"./node_modules/lodash/_arrayMap.js\\\"),\\n    baseGet = __webpack_require__(/*! ./_baseGet */ \\\"./node_modules/lodash/_baseGet.js\\\"),\\n    baseIteratee = __webpack_require__(/*! ./_baseIteratee */ \\\"./node_modules/lodash/_baseIteratee.js\\\"),\\n    baseMap = __webpack_require__(/*! ./_baseMap */ \\\"./node_modules/lodash/_baseMap.js\\\"),\\n    baseSortBy = __webpack_require__(/*! ./_baseSortBy */ \\\"./node_modules/lodash/_baseSortBy.js\\\"),\\n    baseUnary = __webpack_require__(/*! ./_baseUnary */ \\\"./node_modules/lodash/_baseUnary.js\\\"),\\n    compareMultiple = __webpack_require__(/*! ./_compareMultiple */ \\\"./node_modules/lodash/_compareMultiple.js\\\"),\\n    identity = __webpack_require__(/*! ./identity */ \\\"./node_modules/lodash/identity.js\\\"),\\n    isArray = __webpack_require__(/*! ./isArray */ \\\"./node_modules/lodash/isArray.js\\\");\\n\\n/**\\n * The base implementation of `_.orderBy` without param guards.\\n *\\n * @private\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.\\n * @param {string[]} orders The sort orders of `iteratees`.\\n * @returns {Array} Returns the new sorted array.\\n */\\nfunction baseOrderBy(collection, iteratees, orders) {\\n  if (iteratees.length) {\\n    iteratees = arrayMap(iteratees, function(iteratee) {\\n      if (isArray(iteratee)) {\\n        return function(value) {\\n          return baseGet(value, iteratee.length === 1 ? iteratee[0] : iteratee);\\n        }\\n      }\\n      return iteratee;\\n    });\\n  } else {\\n    iteratees = [identity];\\n  }\\n\\n  var index = -1;\\n  iteratees = arrayMap(iteratees, baseUnary(baseIteratee));\\n\\n  var result = baseMap(collection, function(value, key, collection) {\\n    var criteria = arrayMap(iteratees, function(iteratee) {\\n      return iteratee(value);\\n    });\\n    return { 'criteria': criteria, 'index': ++index, 'value': value };\\n  });\\n\\n  return baseSortBy(result, function(object, other) {\\n    return compareMultiple(object, other, orders);\\n  });\\n}\\n\\nmodule.exports = baseOrderBy;\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9sb2Rhc2gvX2Jhc2VPcmRlckJ5LmpzPzZhNWMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsZUFBZSxtQkFBTyxDQUFDLHVEQUFhO0FBQ3BDLGNBQWMsbUJBQU8sQ0FBQyxxREFBWTtBQUNsQyxtQkFBbUIsbUJBQU8sQ0FBQywrREFBaUI7QUFDNUMsY0FBYyxtQkFBTyxDQUFDLHFEQUFZO0FBQ2xDLGlCQUFpQixtQkFBTyxDQUFDLDJEQUFlO0FBQ3hDLGdCQUFnQixtQkFBTyxDQUFDLHlEQUFjO0FBQ3RDLHNCQUFzQixtQkFBTyxDQUFDLHFFQUFvQjtBQUNsRCxlQUFlLG1CQUFPLENBQUMscURBQVk7QUFDbkMsY0FBYyxtQkFBTyxDQUFDLG1EQUFXOztBQUVqQztBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsYUFBYTtBQUN4QixXQUFXLDZCQUE2QjtBQUN4QyxXQUFXLFNBQVM7QUFDcEIsYUFBYSxNQUFNO0FBQ25CO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMLEdBQUc7QUFDSDtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMLFlBQVk7QUFDWixHQUFHOztBQUVIO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7O0FBRUEiLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvbG9kYXNoL19iYXNlT3JkZXJCeS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbInZhciBhcnJheU1hcCA9IHJlcXVpcmUoJy4vX2FycmF5TWFwJyksXG4gICAgYmFzZUdldCA9IHJlcXVpcmUoJy4vX2Jhc2VHZXQnKSxcbiAgICBiYXNlSXRlcmF0ZWUgPSByZXF1aXJlKCcuL19iYXNlSXRlcmF0ZWUnKSxcbiAgICBiYXNlTWFwID0gcmVxdWlyZSgnLi9fYmFzZU1hcCcpLFxuICAgIGJhc2VTb3J0QnkgPSByZXF1aXJlKCcuL19iYXNlU29ydEJ5JyksXG4gICAgYmFzZVVuYXJ5ID0gcmVxdWlyZSgnLi9fYmFzZVVuYXJ5JyksXG4gICAgY29tcGFyZU11bHRpcGxlID0gcmVxdWlyZSgnLi9fY29tcGFyZU11bHRpcGxlJyksXG4gICAgaWRlbnRpdHkgPSByZXF1aXJlKCcuL2lkZW50aXR5JyksXG4gICAgaXNBcnJheSA9IHJlcXVpcmUoJy4vaXNBcnJheScpO1xuXG4vKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBfLm9yZGVyQnlgIHdpdGhvdXQgcGFyYW0gZ3VhcmRzLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0FycmF5fE9iamVjdH0gY29sbGVjdGlvbiBUaGUgY29sbGVjdGlvbiB0byBpdGVyYXRlIG92ZXIuXG4gKiBAcGFyYW0ge0Z1bmN0aW9uW118T2JqZWN0W118c3RyaW5nW119IGl0ZXJhdGVlcyBUaGUgaXRlcmF0ZWVzIHRvIHNvcnQgYnkuXG4gKiBAcGFyYW0ge3N0cmluZ1tdfSBvcmRlcnMgVGhlIHNvcnQgb3JkZXJzIG9mIGBpdGVyYXRlZXNgLlxuICogQHJldHVybnMge0FycmF5fSBSZXR1cm5zIHRoZSBuZXcgc29ydGVkIGFycmF5LlxuICovXG5mdW5jdGlvbiBiYXNlT3JkZXJCeShjb2xsZWN0aW9uLCBpdGVyYXRlZXMsIG9yZGVycykge1xuICBpZiAoaXRlcmF0ZWVzLmxlbmd0aCkge1xuICAgIGl0ZXJhdGVlcyA9IGFycmF5TWFwKGl0ZXJhdGVlcywgZnVuY3Rpb24oaXRlcmF0ZWUpIHtcbiAgICAgIGlmIChpc0FycmF5KGl0ZXJhdGVlKSkge1xuICAgICAgICByZXR1cm4gZnVuY3Rpb24odmFsdWUpIHtcbiAgICAgICAgICByZXR1cm4gYmFzZUdldCh2YWx1ZSwgaXRlcmF0ZWUubGVuZ3RoID09PSAxID8gaXRlcmF0ZWVbMF0gOiBpdGVyYXRlZSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiBpdGVyYXRlZTtcbiAgICB9KTtcbiAgfSBlbHNlIHtcbiAgICBpdGVyYXRlZXMgPSBbaWRlbnRpdHldO1xuICB9XG5cbiAgdmFyIGluZGV4ID0gLTE7XG4gIGl0ZXJhdGVlcyA9IGFycmF5TWFwKGl0ZXJhdGVlcywgYmFzZVVuYXJ5KGJhc2VJdGVyYXRlZSkpO1xuXG4gIHZhciByZXN1bHQgPSBiYXNlTWFwKGNvbGxlY3Rpb24sIGZ1bmN0aW9uKHZhbHVlLCBrZXksIGNvbGxlY3Rpb24pIHtcbiAgICB2YXIgY3JpdGVyaWEgPSBhcnJheU1hcChpdGVyYXRlZXMsIGZ1bmN0aW9uKGl0ZXJhdGVlKSB7XG4gICAgICByZXR1cm4gaXRlcmF0ZWUodmFsdWUpO1xuICAgIH0pO1xuICAgIHJldHVybiB7ICdjcml0ZXJpYSc6IGNyaXRlcmlhLCAnaW5kZXgnOiArK2luZGV4LCAndmFsdWUnOiB2YWx1ZSB9O1xuICB9KTtcblxuICByZXR1cm4gYmFzZVNvcnRCeShyZXN1bHQsIGZ1bmN0aW9uKG9iamVjdCwgb3RoZXIpIHtcbiAgICByZXR1cm4gY29tcGFyZU11bHRpcGxlKG9iamVjdCwgb3RoZXIsIG9yZGVycyk7XG4gIH0pO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGJhc2VPcmRlckJ5O1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./node_modules/lodash/_baseOrderBy.js\\n\");\n \n /***/ }),\n \n@@ -467,7 +489,7 @@ eval(\"var arrayFilter = __webpack_require__(/*! ./_arrayFilter */ \\\"./node_modul\n /*! no static exports found */\n /***/ (function(module, exports, __webpack_require__) {\n \n-eval(\"var baseFlatten = __webpack_require__(/*! ./_baseFlatten */ \\\"./node_modules/lodash/_baseFlatten.js\\\"),\\n    baseOrderBy = __webpack_require__(/*! ./_baseOrderBy */ \\\"./node_modules/lodash/_baseOrderBy.js\\\"),\\n    baseRest = __webpack_require__(/*! ./_baseRest */ \\\"./node_modules/lodash/_baseRest.js\\\"),\\n    isIterateeCall = __webpack_require__(/*! ./_isIterateeCall */ \\\"./node_modules/lodash/_isIterateeCall.js\\\");\\n\\n/**\\n * Creates an array of elements, sorted in ascending order by the results of\\n * running each element in a collection thru each iteratee. This method\\n * performs a stable sort, that is, it preserves the original sort order of\\n * equal elements. The iteratees are invoked with one argument: (value).\\n *\\n * @static\\n * @memberOf _\\n * @since 0.1.0\\n * @category Collection\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {...(Function|Function[])} [iteratees=[_.identity]]\\n *  The iteratees to sort by.\\n * @returns {Array} Returns the new sorted array.\\n * @example\\n *\\n * var users = [\\n *   { 'user': 'fred',   'age': 48 },\\n *   { 'user': 'barney', 'age': 36 },\\n *   { 'user': 'fred',   'age': 40 },\\n *   { 'user': 'barney', 'age': 34 }\\n * ];\\n *\\n * _.sortBy(users, [function(o) { return o.user; }]);\\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]\\n *\\n * _.sortBy(users, ['user', 'age']);\\n * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]\\n */\\nvar sortBy = baseRest(function(collection, iteratees) {\\n  if (collection == null) {\\n    return [];\\n  }\\n  var length = iteratees.length;\\n  if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {\\n    iteratees = [];\\n  } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {\\n    iteratees = [iteratees[0]];\\n  }\\n  return baseOrderBy(collection, baseFlatten(iteratees, 1), []);\\n});\\n\\nmodule.exports = sortBy;\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9sb2Rhc2gvc29ydEJ5LmpzP2M3MDciXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsa0JBQWtCLG1CQUFPLENBQUMsNkRBQWdCO0FBQzFDLGtCQUFrQixtQkFBTyxDQUFDLDZEQUFnQjtBQUMxQyxlQUFlLG1CQUFPLENBQUMsdURBQWE7QUFDcEMscUJBQXFCLG1CQUFPLENBQUMsbUVBQW1COztBQUVoRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsYUFBYTtBQUN4QixXQUFXLHlCQUF5QjtBQUNwQztBQUNBLGFBQWEsTUFBTTtBQUNuQjtBQUNBO0FBQ0E7QUFDQSxNQUFNLDhCQUE4QjtBQUNwQyxNQUFNLDhCQUE4QjtBQUNwQyxNQUFNLDhCQUE4QjtBQUNwQyxNQUFNO0FBQ047QUFDQTtBQUNBLGlDQUFpQyxlQUFlLEVBQUU7QUFDbEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBLENBQUM7O0FBRUQiLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvbG9kYXNoL3NvcnRCeS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbInZhciBiYXNlRmxhdHRlbiA9IHJlcXVpcmUoJy4vX2Jhc2VGbGF0dGVuJyksXG4gICAgYmFzZU9yZGVyQnkgPSByZXF1aXJlKCcuL19iYXNlT3JkZXJCeScpLFxuICAgIGJhc2VSZXN0ID0gcmVxdWlyZSgnLi9fYmFzZVJlc3QnKSxcbiAgICBpc0l0ZXJhdGVlQ2FsbCA9IHJlcXVpcmUoJy4vX2lzSXRlcmF0ZWVDYWxsJyk7XG5cbi8qKlxuICogQ3JlYXRlcyBhbiBhcnJheSBvZiBlbGVtZW50cywgc29ydGVkIGluIGFzY2VuZGluZyBvcmRlciBieSB0aGUgcmVzdWx0cyBvZlxuICogcnVubmluZyBlYWNoIGVsZW1lbnQgaW4gYSBjb2xsZWN0aW9uIHRocnUgZWFjaCBpdGVyYXRlZS4gVGhpcyBtZXRob2RcbiAqIHBlcmZvcm1zIGEgc3RhYmxlIHNvcnQsIHRoYXQgaXMsIGl0IHByZXNlcnZlcyB0aGUgb3JpZ2luYWwgc29ydCBvcmRlciBvZlxuICogZXF1YWwgZWxlbWVudHMuIFRoZSBpdGVyYXRlZXMgYXJlIGludm9rZWQgd2l0aCBvbmUgYXJndW1lbnQ6ICh2YWx1ZSkuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSAwLjEuMFxuICogQGNhdGVnb3J5IENvbGxlY3Rpb25cbiAqIEBwYXJhbSB7QXJyYXl8T2JqZWN0fSBjb2xsZWN0aW9uIFRoZSBjb2xsZWN0aW9uIHRvIGl0ZXJhdGUgb3Zlci5cbiAqIEBwYXJhbSB7Li4uKEZ1bmN0aW9ufEZ1bmN0aW9uW10pfSBbaXRlcmF0ZWVzPVtfLmlkZW50aXR5XV1cbiAqICBUaGUgaXRlcmF0ZWVzIHRvIHNvcnQgYnkuXG4gKiBAcmV0dXJucyB7QXJyYXl9IFJldHVybnMgdGhlIG5ldyBzb3J0ZWQgYXJyYXkuXG4gKiBAZXhhbXBsZVxuICpcbiAqIHZhciB1c2VycyA9IFtcbiAqICAgeyAndXNlcic6ICdmcmVkJywgICAnYWdlJzogNDggfSxcbiAqICAgeyAndXNlcic6ICdiYXJuZXknLCAnYWdlJzogMzYgfSxcbiAqICAgeyAndXNlcic6ICdmcmVkJywgICAnYWdlJzogNDAgfSxcbiAqICAgeyAndXNlcic6ICdiYXJuZXknLCAnYWdlJzogMzQgfVxuICogXTtcbiAqXG4gKiBfLnNvcnRCeSh1c2VycywgW2Z1bmN0aW9uKG8pIHsgcmV0dXJuIG8udXNlcjsgfV0pO1xuICogLy8gPT4gb2JqZWN0cyBmb3IgW1snYmFybmV5JywgMzZdLCBbJ2Jhcm5leScsIDM0XSwgWydmcmVkJywgNDhdLCBbJ2ZyZWQnLCA0MF1dXG4gKlxuICogXy5zb3J0QnkodXNlcnMsIFsndXNlcicsICdhZ2UnXSk7XG4gKiAvLyA9PiBvYmplY3RzIGZvciBbWydiYXJuZXknLCAzNF0sIFsnYmFybmV5JywgMzZdLCBbJ2ZyZWQnLCA0MF0sIFsnZnJlZCcsIDQ4XV1cbiAqL1xudmFyIHNvcnRCeSA9IGJhc2VSZXN0KGZ1bmN0aW9uKGNvbGxlY3Rpb24sIGl0ZXJhdGVlcykge1xuICBpZiAoY29sbGVjdGlvbiA9PSBudWxsKSB7XG4gICAgcmV0dXJuIFtdO1xuICB9XG4gIHZhciBsZW5ndGggPSBpdGVyYXRlZXMubGVuZ3RoO1xuICBpZiAobGVuZ3RoID4gMSAmJiBpc0l0ZXJhdGVlQ2FsbChjb2xsZWN0aW9uLCBpdGVyYXRlZXNbMF0sIGl0ZXJhdGVlc1sxXSkpIHtcbiAgICBpdGVyYXRlZXMgPSBbXTtcbiAgfSBlbHNlIGlmIChsZW5ndGggPiAyICYmIGlzSXRlcmF0ZWVDYWxsKGl0ZXJhdGVlc1swXSwgaXRlcmF0ZWVzWzFdLCBpdGVyYXRlZXNbMl0pKSB7XG4gICAgaXRlcmF0ZWVzID0gW2l0ZXJhdGVlc1swXV07XG4gIH1cbiAgcmV0dXJuIGJhc2VPcmRlckJ5KGNvbGxlY3Rpb24sIGJhc2VGbGF0dGVuKGl0ZXJhdGVlcywgMSksIFtdKTtcbn0pO1xuXG5tb2R1bGUuZXhwb3J0cyA9IHNvcnRCeTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./node_modules/lodash/sortBy.js\\n\");\n+eval(\"var baseFlatten = __webpack_require__(/*! ./_baseFlatten */ \\\"./node_modules/lodash/_baseFlatten.js\\\"),\\n    baseOrderBy = __webpack_require__(/*! ./_baseOrderBy */ \\\"./node_modules/lodash/_baseOrderBy.js\\\"),\\n    baseRest = __webpack_require__(/*! ./_baseRest */ \\\"./node_modules/lodash/_baseRest.js\\\"),\\n    isIterateeCall = __webpack_require__(/*! ./_isIterateeCall */ \\\"./node_modules/lodash/_isIterateeCall.js\\\");\\n\\n/**\\n * Creates an array of elements, sorted in ascending order by the results of\\n * running each element in a collection thru each iteratee. This method\\n * performs a stable sort, that is, it preserves the original sort order of\\n * equal elements. The iteratees are invoked with one argument: (value).\\n *\\n * @static\\n * @memberOf _\\n * @since 0.1.0\\n * @category Collection\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {...(Function|Function[])} [iteratees=[_.identity]]\\n *  The iteratees to sort by.\\n * @returns {Array} Returns the new sorted array.\\n * @example\\n *\\n * var users = [\\n *   { 'user': 'fred',   'age': 48 },\\n *   { 'user': 'barney', 'age': 36 },\\n *   { 'user': 'fred',   'age': 30 },\\n *   { 'user': 'barney', 'age': 34 }\\n * ];\\n *\\n * _.sortBy(users, [function(o) { return o.user; }]);\\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 30]]\\n *\\n * _.sortBy(users, ['user', 'age']);\\n * // => objects for [['barney', 34], ['barney', 36], ['fred', 30], ['fred', 48]]\\n */\\nvar sortBy = baseRest(function(collection, iteratees) {\\n  if (collection == null) {\\n    return [];\\n  }\\n  var length = iteratees.length;\\n  if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {\\n    iteratees = [];\\n  } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {\\n    iteratees = [iteratees[0]];\\n  }\\n  return baseOrderBy(collection, baseFlatten(iteratees, 1), []);\\n});\\n\\nmodule.exports = sortBy;\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9sb2Rhc2gvc29ydEJ5LmpzP2M3MDciXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsa0JBQWtCLG1CQUFPLENBQUMsNkRBQWdCO0FBQzFDLGtCQUFrQixtQkFBTyxDQUFDLDZEQUFnQjtBQUMxQyxlQUFlLG1CQUFPLENBQUMsdURBQWE7QUFDcEMscUJBQXFCLG1CQUFPLENBQUMsbUVBQW1COztBQUVoRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsYUFBYTtBQUN4QixXQUFXLHlCQUF5QjtBQUNwQztBQUNBLGFBQWEsTUFBTTtBQUNuQjtBQUNBO0FBQ0E7QUFDQSxNQUFNLDhCQUE4QjtBQUNwQyxNQUFNLDhCQUE4QjtBQUNwQyxNQUFNLDhCQUE4QjtBQUNwQyxNQUFNO0FBQ047QUFDQTtBQUNBLGlDQUFpQyxlQUFlLEVBQUU7QUFDbEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBLENBQUM7O0FBRUQiLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvbG9kYXNoL3NvcnRCeS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbInZhciBiYXNlRmxhdHRlbiA9IHJlcXVpcmUoJy4vX2Jhc2VGbGF0dGVuJyksXG4gICAgYmFzZU9yZGVyQnkgPSByZXF1aXJlKCcuL19iYXNlT3JkZXJCeScpLFxuICAgIGJhc2VSZXN0ID0gcmVxdWlyZSgnLi9fYmFzZVJlc3QnKSxcbiAgICBpc0l0ZXJhdGVlQ2FsbCA9IHJlcXVpcmUoJy4vX2lzSXRlcmF0ZWVDYWxsJyk7XG5cbi8qKlxuICogQ3JlYXRlcyBhbiBhcnJheSBvZiBlbGVtZW50cywgc29ydGVkIGluIGFzY2VuZGluZyBvcmRlciBieSB0aGUgcmVzdWx0cyBvZlxuICogcnVubmluZyBlYWNoIGVsZW1lbnQgaW4gYSBjb2xsZWN0aW9uIHRocnUgZWFjaCBpdGVyYXRlZS4gVGhpcyBtZXRob2RcbiAqIHBlcmZvcm1zIGEgc3RhYmxlIHNvcnQsIHRoYXQgaXMsIGl0IHByZXNlcnZlcyB0aGUgb3JpZ2luYWwgc29ydCBvcmRlciBvZlxuICogZXF1YWwgZWxlbWVudHMuIFRoZSBpdGVyYXRlZXMgYXJlIGludm9rZWQgd2l0aCBvbmUgYXJndW1lbnQ6ICh2YWx1ZSkuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSAwLjEuMFxuICogQGNhdGVnb3J5IENvbGxlY3Rpb25cbiAqIEBwYXJhbSB7QXJyYXl8T2JqZWN0fSBjb2xsZWN0aW9uIFRoZSBjb2xsZWN0aW9uIHRvIGl0ZXJhdGUgb3Zlci5cbiAqIEBwYXJhbSB7Li4uKEZ1bmN0aW9ufEZ1bmN0aW9uW10pfSBbaXRlcmF0ZWVzPVtfLmlkZW50aXR5XV1cbiAqICBUaGUgaXRlcmF0ZWVzIHRvIHNvcnQgYnkuXG4gKiBAcmV0dXJucyB7QXJyYXl9IFJldHVybnMgdGhlIG5ldyBzb3J0ZWQgYXJyYXkuXG4gKiBAZXhhbXBsZVxuICpcbiAqIHZhciB1c2VycyA9IFtcbiAqICAgeyAndXNlcic6ICdmcmVkJywgICAnYWdlJzogNDggfSxcbiAqICAgeyAndXNlcic6ICdiYXJuZXknLCAnYWdlJzogMzYgfSxcbiAqICAgeyAndXNlcic6ICdmcmVkJywgICAnYWdlJzogMzAgfSxcbiAqICAgeyAndXNlcic6ICdiYXJuZXknLCAnYWdlJzogMzQgfVxuICogXTtcbiAqXG4gKiBfLnNvcnRCeSh1c2VycywgW2Z1bmN0aW9uKG8pIHsgcmV0dXJuIG8udXNlcjsgfV0pO1xuICogLy8gPT4gb2JqZWN0cyBmb3IgW1snYmFybmV5JywgMzZdLCBbJ2Jhcm5leScsIDM0XSwgWydmcmVkJywgNDhdLCBbJ2ZyZWQnLCAzMF1dXG4gKlxuICogXy5zb3J0QnkodXNlcnMsIFsndXNlcicsICdhZ2UnXSk7XG4gKiAvLyA9PiBvYmplY3RzIGZvciBbWydiYXJuZXknLCAzNF0sIFsnYmFybmV5JywgMzZdLCBbJ2ZyZWQnLCAzMF0sIFsnZnJlZCcsIDQ4XV1cbiAqL1xudmFyIHNvcnRCeSA9IGJhc2VSZXN0KGZ1bmN0aW9uKGNvbGxlY3Rpb24sIGl0ZXJhdGVlcykge1xuICBpZiAoY29sbGVjdGlvbiA9PSBudWxsKSB7XG4gICAgcmV0dXJuIFtdO1xuICB9XG4gIHZhciBsZW5ndGggPSBpdGVyYXRlZXMubGVuZ3RoO1xuICBpZiAobGVuZ3RoID4gMSAmJiBpc0l0ZXJhdGVlQ2FsbChjb2xsZWN0aW9uLCBpdGVyYXRlZXNbMF0sIGl0ZXJhdGVlc1sxXSkpIHtcbiAgICBpdGVyYXRlZXMgPSBbXTtcbiAgfSBlbHNlIGlmIChsZW5ndGggPiAyICYmIGlzSXRlcmF0ZWVDYWxsKGl0ZXJhdGVlc1swXSwgaXRlcmF0ZWVzWzFdLCBpdGVyYXRlZXNbMl0pKSB7XG4gICAgaXRlcmF0ZWVzID0gW2l0ZXJhdGVlc1swXV07XG4gIH1cbiAgcmV0dXJuIGJhc2VPcmRlckJ5KGNvbGxlY3Rpb24sIGJhc2VGbGF0dGVuKGl0ZXJhdGVlcywgMSksIFtdKTtcbn0pO1xuXG5tb2R1bGUuZXhwb3J0cyA9IHNvcnRCeTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./node_modules/lodash/sortBy.js\\n\");\n \n /***/ }),\n \n@@ -4426,7 +4448,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _int\n /*! no static exports found */\n /***/ (function(module, exports, __webpack_require__) {\n \n-eval(\"(function (global, factory) {\\n   true ? factory(exports, __webpack_require__(/*! reselect */ \\\"./node_modules/reselect/lib/index.js\\\")) :\\n  undefined;\\n}(this, function (exports, reselect) { 'use strict';\\n\\n  function isStringOrNumber(value) {\\n    return typeof value === 'string' || typeof value === 'number';\\n  }\\n\\n  var FlatObjectCache =\\n  /*#__PURE__*/\\n  function () {\\n    function FlatObjectCache() {\\n      this._cache = {};\\n    }\\n\\n    var _proto = FlatObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return FlatObjectCache;\\n  }();\\n\\n  var defaultCacheCreator = FlatObjectCache;\\n\\n  var defaultCacheKeyValidator = function defaultCacheKeyValidator() {\\n    return true;\\n  };\\n\\n  function createCachedSelector() {\\n    for (var _len = arguments.length, funcs = new Array(_len), _key = 0; _key < _len; _key++) {\\n      funcs[_key] = arguments[_key];\\n    }\\n\\n    return function (polymorphicOptions, legacyOptions) {\\n      // @NOTE Versions 0.x/1.x accepted \\\"options\\\" as a function\\n      if (typeof legacyOptions === 'function') {\\n        throw new Error('[re-reselect] Second argument \\\"options\\\" must be an object. Please use \\\"options.selectorCreator\\\" to provide a custom selectorCreator.');\\n      }\\n\\n      var options = {};\\n\\n      if (typeof polymorphicOptions === 'function') {\\n        Object.assign(options, legacyOptions, {\\n          keySelector: polymorphicOptions\\n        }); // @TODO add legacyOptions deprecation notice in next major release\\n      } else {\\n        Object.assign(options, polymorphicOptions);\\n      } // https://github.com/reduxjs/reselect/blob/v4.0.0/src/index.js#L54\\n\\n\\n      var recomputations = 0;\\n      var resultFunc = funcs.pop();\\n      var dependencies = Array.isArray(funcs[0]) ? funcs[0] : [].concat(funcs);\\n\\n      var resultFuncWithRecomputations = function resultFuncWithRecomputations() {\\n        recomputations++;\\n        return resultFunc.apply(void 0, arguments);\\n      };\\n\\n      funcs.push(resultFuncWithRecomputations);\\n      var cache = options.cacheObject || new defaultCacheCreator();\\n      var selectorCreator = options.selectorCreator || reselect.createSelector;\\n      var isValidCacheKey = cache.isValidCacheKey || defaultCacheKeyValidator;\\n\\n      if (options.keySelectorCreator) {\\n        options.keySelector = options.keySelectorCreator({\\n          keySelector: options.keySelector,\\n          inputSelectors: dependencies,\\n          resultFunc: resultFunc\\n        });\\n      } // Application receives this function\\n\\n\\n      var selector = function selector() {\\n        var cacheKey = options.keySelector.apply(options, arguments);\\n\\n        if (isValidCacheKey(cacheKey)) {\\n          var cacheResponse = cache.get(cacheKey);\\n\\n          if (cacheResponse === undefined) {\\n            cacheResponse = selectorCreator.apply(void 0, funcs);\\n            cache.set(cacheKey, cacheResponse);\\n          }\\n\\n          return cacheResponse.apply(void 0, arguments);\\n        }\\n\\n        console.warn(\\\"[re-reselect] Invalid cache key \\\\\\\"\\\" + cacheKey + \\\"\\\\\\\" has been returned by keySelector function.\\\");\\n        return undefined;\\n      }; // Further selector methods\\n\\n\\n      selector.getMatchingSelector = function () {\\n        var cacheKey = options.keySelector.apply(options, arguments); // @NOTE It might update cache hit count in LRU-like caches\\n\\n        return cache.get(cacheKey);\\n      };\\n\\n      selector.removeMatchingSelector = function () {\\n        var cacheKey = options.keySelector.apply(options, arguments);\\n        cache.remove(cacheKey);\\n      };\\n\\n      selector.clearCache = function () {\\n        cache.clear();\\n      };\\n\\n      selector.resultFunc = resultFunc;\\n      selector.dependencies = dependencies;\\n      selector.cache = cache;\\n\\n      selector.recomputations = function () {\\n        return recomputations;\\n      };\\n\\n      selector.resetRecomputations = function () {\\n        return recomputations = 0;\\n      };\\n\\n      selector.keySelector = options.keySelector;\\n      return selector;\\n    };\\n  }\\n\\n  function createStructuredCachedSelector(selectors) {\\n    return reselect.createStructuredSelector(selectors, createCachedSelector);\\n  }\\n\\n  function validateCacheSize(cacheSize) {\\n    if (cacheSize === undefined) {\\n      throw new Error('Missing the required property \\\"cacheSize\\\".');\\n    }\\n\\n    if (!Number.isInteger(cacheSize) || cacheSize <= 0) {\\n      throw new Error('The \\\"cacheSize\\\" property must be a positive integer value.');\\n    }\\n  }\\n\\n  var FifoObjectCache =\\n  /*#__PURE__*/\\n  function () {\\n    function FifoObjectCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = FifoObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n\\n      this._cacheOrdering.push(key);\\n\\n      if (this._cacheOrdering.length > this._cacheSize) {\\n        var earliest = this._cacheOrdering[0];\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      var index = this._cacheOrdering.indexOf(key);\\n\\n      if (index > -1) {\\n        this._cacheOrdering.splice(index, 1);\\n      }\\n\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return FifoObjectCache;\\n  }();\\n\\n  var LruObjectCache =\\n  /*#__PURE__*/\\n  function () {\\n    function LruObjectCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = LruObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n\\n      this._registerCacheHit(key);\\n\\n      if (this._cacheOrdering.length > this._cacheSize) {\\n        var earliest = this._cacheOrdering[0];\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      this._registerCacheHit(key);\\n\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._deleteCacheHit(key);\\n\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n    };\\n\\n    _proto._registerCacheHit = function _registerCacheHit(key) {\\n      this._deleteCacheHit(key);\\n\\n      this._cacheOrdering.push(key);\\n    };\\n\\n    _proto._deleteCacheHit = function _deleteCacheHit(key) {\\n      var index = this._cacheOrdering.indexOf(key);\\n\\n      if (index > -1) {\\n        this._cacheOrdering.splice(index, 1);\\n      }\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return LruObjectCache;\\n  }();\\n\\n  var FlatMapCache =\\n  /*#__PURE__*/\\n  function () {\\n    function FlatMapCache() {\\n      this._cache = new Map();\\n    }\\n\\n    var _proto = FlatMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache.get(key);\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return FlatMapCache;\\n  }();\\n\\n  var FifoMapCache =\\n  /*#__PURE__*/\\n  function () {\\n    function FifoMapCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = new Map();\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = FifoMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n\\n      if (this._cache.size > this._cacheSize) {\\n        var earliest = this._cache.keys().next().value;\\n\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache.get(key);\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return FifoMapCache;\\n  }();\\n\\n  var LruMapCache =\\n  /*#__PURE__*/\\n  function () {\\n    function LruMapCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = new Map();\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = LruMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n\\n      if (this._cache.size > this._cacheSize) {\\n        var earliest = this._cache.keys().next().value;\\n\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      var value = this._cache.get(key); // Register cache hit\\n\\n\\n      if (this._cache.has(key)) {\\n        this.remove(key);\\n\\n        this._cache.set(key, value);\\n      }\\n\\n      return value;\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return LruMapCache;\\n  }();\\n\\n  exports.FifoCacheObject = FifoObjectCache;\\n  exports.FifoMapCache = FifoMapCache;\\n  exports.FifoObjectCache = FifoObjectCache;\\n  exports.FlatCacheObject = FlatObjectCache;\\n  exports.FlatMapCache = FlatMapCache;\\n  exports.FlatObjectCache = FlatObjectCache;\\n  exports.LruCacheObject = LruMapCache;\\n  exports.LruMapCache = LruMapCache;\\n  exports.LruObjectCache = LruObjectCache;\\n  exports.createStructuredCachedSelector = createStructuredCachedSelector;\\n  exports.default = createCachedSelector;\\n\\n  Object.defineProperty(exports, '__esModule', { value: true });\\n\\n}));\\n//# sourceMappingURL=index.js.map\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9yZS1yZXNlbGVjdC9kaXN0L2luZGV4LmpzP2YyM2QiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQSxFQUFFLEtBQTRELG9CQUFvQixtQkFBTyxDQUFDLHNEQUFVO0FBQ3BHLEVBQUUsU0FDK0U7QUFDakYsQ0FBQyxxQ0FBcUM7O0FBRXRDO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLEdBQUc7O0FBRUg7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0Esd0VBQXdFLGFBQWE7QUFDckY7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFNBQVMsRUFBRTtBQUNYLE9BQU87QUFDUDtBQUNBLE9BQU87OztBQUdQO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNULE9BQU87OztBQUdQO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxRQUFROzs7QUFHUjtBQUNBLHFFQUFxRTs7QUFFckU7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDO0FBQ3RDOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLEdBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQ0FBc0M7QUFDdEM7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxHQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxHQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDO0FBQ3RDOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxHQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDO0FBQ3RDOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSx1Q0FBdUM7OztBQUd2QztBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0EsR0FBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLGdEQUFnRCxjQUFjOztBQUU5RCxDQUFDO0FBQ0QiLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvcmUtcmVzZWxlY3QvZGlzdC9pbmRleC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiAoZ2xvYmFsLCBmYWN0b3J5KSB7XG4gIHR5cGVvZiBleHBvcnRzID09PSAnb2JqZWN0JyAmJiB0eXBlb2YgbW9kdWxlICE9PSAndW5kZWZpbmVkJyA/IGZhY3RvcnkoZXhwb3J0cywgcmVxdWlyZSgncmVzZWxlY3QnKSkgOlxuICB0eXBlb2YgZGVmaW5lID09PSAnZnVuY3Rpb24nICYmIGRlZmluZS5hbWQgPyBkZWZpbmUoWydleHBvcnRzJywgJ3Jlc2VsZWN0J10sIGZhY3RvcnkpIDpcbiAgKGdsb2JhbCA9IGdsb2JhbCB8fCBzZWxmLCBmYWN0b3J5KGdsb2JhbFsnUmUtcmVzZWxlY3QnXSA9IHt9LCBnbG9iYWwuUmVzZWxlY3QpKTtcbn0odGhpcywgZnVuY3Rpb24gKGV4cG9ydHMsIHJlc2VsZWN0KSB7ICd1c2Ugc3RyaWN0JztcblxuICBmdW5jdGlvbiBpc1N0cmluZ09yTnVtYmVyKHZhbHVlKSB7XG4gICAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PT0gJ3N0cmluZycgfHwgdHlwZW9mIHZhbHVlID09PSAnbnVtYmVyJztcbiAgfVxuXG4gIHZhciBGbGF0T2JqZWN0Q2FjaGUgPVxuICAvKiNfX1BVUkVfXyovXG4gIGZ1bmN0aW9uICgpIHtcbiAgICBmdW5jdGlvbiBGbGF0T2JqZWN0Q2FjaGUoKSB7XG4gICAgICB0aGlzLl9jYWNoZSA9IHt9O1xuICAgIH1cblxuICAgIHZhciBfcHJvdG8gPSBGbGF0T2JqZWN0Q2FjaGUucHJvdG90eXBlO1xuXG4gICAgX3Byb3RvLnNldCA9IGZ1bmN0aW9uIHNldChrZXksIHNlbGVjdG9yRm4pIHtcbiAgICAgIHRoaXMuX2NhY2hlW2tleV0gPSBzZWxlY3RvckZuO1xuICAgIH07XG5cbiAgICBfcHJvdG8uZ2V0ID0gZnVuY3Rpb24gZ2V0KGtleSkge1xuICAgICAgcmV0dXJuIHRoaXMuX2NhY2hlW2tleV07XG4gICAgfTtcblxuICAgIF9wcm90by5yZW1vdmUgPSBmdW5jdGlvbiByZW1vdmUoa2V5KSB7XG4gICAgICBkZWxldGUgdGhpcy5fY2FjaGVba2V5XTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLmNsZWFyID0gZnVuY3Rpb24gY2xlYXIoKSB7XG4gICAgICB0aGlzLl9jYWNoZSA9IHt9O1xuICAgIH07XG5cbiAgICBfcHJvdG8uaXNWYWxpZENhY2hlS2V5ID0gZnVuY3Rpb24gaXNWYWxpZENhY2hlS2V5KGNhY2hlS2V5KSB7XG4gICAgICByZXR1cm4gaXNTdHJpbmdPck51bWJlcihjYWNoZUtleSk7XG4gICAgfTtcblxuICAgIHJldHVybiBGbGF0T2JqZWN0Q2FjaGU7XG4gIH0oKTtcblxuICB2YXIgZGVmYXVsdENhY2hlQ3JlYXRvciA9IEZsYXRPYmplY3RDYWNoZTtcblxuICB2YXIgZGVmYXVsdENhY2hlS2V5VmFsaWRhdG9yID0gZnVuY3Rpb24gZGVmYXVsdENhY2hlS2V5VmFsaWRhdG9yKCkge1xuICAgIHJldHVybiB0cnVlO1xuICB9O1xuXG4gIGZ1bmN0aW9uIGNyZWF0ZUNhY2hlZFNlbGVjdG9yKCkge1xuICAgIGZvciAodmFyIF9sZW4gPSBhcmd1bWVudHMubGVuZ3RoLCBmdW5jcyA9IG5ldyBBcnJheShfbGVuKSwgX2tleSA9IDA7IF9rZXkgPCBfbGVuOyBfa2V5KyspIHtcbiAgICAgIGZ1bmNzW19rZXldID0gYXJndW1lbnRzW19rZXldO1xuICAgIH1cblxuICAgIHJldHVybiBmdW5jdGlvbiAocG9seW1vcnBoaWNPcHRpb25zLCBsZWdhY3lPcHRpb25zKSB7XG4gICAgICAvLyBATk9URSBWZXJzaW9ucyAwLngvMS54IGFjY2VwdGVkIFwib3B0aW9uc1wiIGFzIGEgZnVuY3Rpb25cbiAgICAgIGlmICh0eXBlb2YgbGVnYWN5T3B0aW9ucyA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1tyZS1yZXNlbGVjdF0gU2Vjb25kIGFyZ3VtZW50IFwib3B0aW9uc1wiIG11c3QgYmUgYW4gb2JqZWN0LiBQbGVhc2UgdXNlIFwib3B0aW9ucy5zZWxlY3RvckNyZWF0b3JcIiB0byBwcm92aWRlIGEgY3VzdG9tIHNlbGVjdG9yQ3JlYXRvci4nKTtcbiAgICAgIH1cblxuICAgICAgdmFyIG9wdGlvbnMgPSB7fTtcblxuICAgICAgaWYgKHR5cGVvZiBwb2x5bW9ycGhpY09wdGlvbnMgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgT2JqZWN0LmFzc2lnbihvcHRpb25zLCBsZWdhY3lPcHRpb25zLCB7XG4gICAgICAgICAga2V5U2VsZWN0b3I6IHBvbHltb3JwaGljT3B0aW9uc1xuICAgICAgICB9KTsgLy8gQFRPRE8gYWRkIGxlZ2FjeU9wdGlvbnMgZGVwcmVjYXRpb24gbm90aWNlIGluIG5leHQgbWFqb3IgcmVsZWFzZVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgT2JqZWN0LmFzc2lnbihvcHRpb25zLCBwb2x5bW9ycGhpY09wdGlvbnMpO1xuICAgICAgfSAvLyBodHRwczovL2dpdGh1Yi5jb20vcmVkdXhqcy9yZXNlbGVjdC9ibG9iL3Y0LjAuMC9zcmMvaW5kZXguanMjTDU0XG5cblxuICAgICAgdmFyIHJlY29tcHV0YXRpb25zID0gMDtcbiAgICAgIHZhciByZXN1bHRGdW5jID0gZnVuY3MucG9wKCk7XG4gICAgICB2YXIgZGVwZW5kZW5jaWVzID0gQXJyYXkuaXNBcnJheShmdW5jc1swXSkgPyBmdW5jc1swXSA6IFtdLmNvbmNhdChmdW5jcyk7XG5cbiAgICAgIHZhciByZXN1bHRGdW5jV2l0aFJlY29tcHV0YXRpb25zID0gZnVuY3Rpb24gcmVzdWx0RnVuY1dpdGhSZWNvbXB1dGF0aW9ucygpIHtcbiAgICAgICAgcmVjb21wdXRhdGlvbnMrKztcbiAgICAgICAgcmV0dXJuIHJlc3VsdEZ1bmMuYXBwbHkodm9pZCAwLCBhcmd1bWVudHMpO1xuICAgICAgfTtcblxuICAgICAgZnVuY3MucHVzaChyZXN1bHRGdW5jV2l0aFJlY29tcHV0YXRpb25zKTtcbiAgICAgIHZhciBjYWNoZSA9IG9wdGlvbnMuY2FjaGVPYmplY3QgfHwgbmV3IGRlZmF1bHRDYWNoZUNyZWF0b3IoKTtcbiAgICAgIHZhciBzZWxlY3RvckNyZWF0b3IgPSBvcHRpb25zLnNlbGVjdG9yQ3JlYXRvciB8fCByZXNlbGVjdC5jcmVhdGVTZWxlY3RvcjtcbiAgICAgIHZhciBpc1ZhbGlkQ2FjaGVLZXkgPSBjYWNoZS5pc1ZhbGlkQ2FjaGVLZXkgfHwgZGVmYXVsdENhY2hlS2V5VmFsaWRhdG9yO1xuXG4gICAgICBpZiAob3B0aW9ucy5rZXlTZWxlY3RvckNyZWF0b3IpIHtcbiAgICAgICAgb3B0aW9ucy5rZXlTZWxlY3RvciA9IG9wdGlvbnMua2V5U2VsZWN0b3JDcmVhdG9yKHtcbiAgICAgICAgICBrZXlTZWxlY3Rvcjogb3B0aW9ucy5rZXlTZWxlY3RvcixcbiAgICAgICAgICBpbnB1dFNlbGVjdG9yczogZGVwZW5kZW5jaWVzLFxuICAgICAgICAgIHJlc3VsdEZ1bmM6IHJlc3VsdEZ1bmNcbiAgICAgICAgfSk7XG4gICAgICB9IC8vIEFwcGxpY2F0aW9uIHJlY2VpdmVzIHRoaXMgZnVuY3Rpb25cblxuXG4gICAgICB2YXIgc2VsZWN0b3IgPSBmdW5jdGlvbiBzZWxlY3RvcigpIHtcbiAgICAgICAgdmFyIGNhY2hlS2V5ID0gb3B0aW9ucy5rZXlTZWxlY3Rvci5hcHBseShvcHRpb25zLCBhcmd1bWVudHMpO1xuXG4gICAgICAgIGlmIChpc1ZhbGlkQ2FjaGVLZXkoY2FjaGVLZXkpKSB7XG4gICAgICAgICAgdmFyIGNhY2hlUmVzcG9uc2UgPSBjYWNoZS5nZXQoY2FjaGVLZXkpO1xuXG4gICAgICAgICAgaWYgKGNhY2hlUmVzcG9uc2UgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgY2FjaGVSZXNwb25zZSA9IHNlbGVjdG9yQ3JlYXRvci5hcHBseSh2b2lkIDAsIGZ1bmNzKTtcbiAgICAgICAgICAgIGNhY2hlLnNldChjYWNoZUtleSwgY2FjaGVSZXNwb25zZSk7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgcmV0dXJuIGNhY2hlUmVzcG9uc2UuYXBwbHkodm9pZCAwLCBhcmd1bWVudHMpO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc29sZS53YXJuKFwiW3JlLXJlc2VsZWN0XSBJbnZhbGlkIGNhY2hlIGtleSBcXFwiXCIgKyBjYWNoZUtleSArIFwiXFxcIiBoYXMgYmVlbiByZXR1cm5lZCBieSBrZXlTZWxlY3RvciBmdW5jdGlvbi5cIik7XG4gICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgICB9OyAvLyBGdXJ0aGVyIHNlbGVjdG9yIG1ldGhvZHNcblxuXG4gICAgICBzZWxlY3Rvci5nZXRNYXRjaGluZ1NlbGVjdG9yID0gZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgY2FjaGVLZXkgPSBvcHRpb25zLmtleVNlbGVjdG9yLmFwcGx5KG9wdGlvbnMsIGFyZ3VtZW50cyk7IC8vIEBOT1RFIEl0IG1pZ2h0IHVwZGF0ZSBjYWNoZSBoaXQgY291bnQgaW4gTFJVLWxpa2UgY2FjaGVzXG5cbiAgICAgICAgcmV0dXJuIGNhY2hlLmdldChjYWNoZUtleSk7XG4gICAgICB9O1xuXG4gICAgICBzZWxlY3Rvci5yZW1vdmVNYXRjaGluZ1NlbGVjdG9yID0gZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgY2FjaGVLZXkgPSBvcHRpb25zLmtleVNlbGVjdG9yLmFwcGx5KG9wdGlvbnMsIGFyZ3VtZW50cyk7XG4gICAgICAgIGNhY2hlLnJlbW92ZShjYWNoZUtleSk7XG4gICAgICB9O1xuXG4gICAgICBzZWxlY3Rvci5jbGVhckNhY2hlID0gZnVuY3Rpb24gKCkge1xuICAgICAgICBjYWNoZS5jbGVhcigpO1xuICAgICAgfTtcblxuICAgICAgc2VsZWN0b3IucmVzdWx0RnVuYyA9IHJlc3VsdEZ1bmM7XG4gICAgICBzZWxlY3Rvci5kZXBlbmRlbmNpZXMgPSBkZXBlbmRlbmNpZXM7XG4gICAgICBzZWxlY3Rvci5jYWNoZSA9IGNhY2hlO1xuXG4gICAgICBzZWxlY3Rvci5yZWNvbXB1dGF0aW9ucyA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIHJlY29tcHV0YXRpb25zO1xuICAgICAgfTtcblxuICAgICAgc2VsZWN0b3IucmVzZXRSZWNvbXB1dGF0aW9ucyA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIHJlY29tcHV0YXRpb25zID0gMDtcbiAgICAgIH07XG5cbiAgICAgIHNlbGVjdG9yLmtleVNlbGVjdG9yID0gb3B0aW9ucy5rZXlTZWxlY3RvcjtcbiAgICAgIHJldHVybiBzZWxlY3RvcjtcbiAgICB9O1xuICB9XG5cbiAgZnVuY3Rpb24gY3JlYXRlU3RydWN0dXJlZENhY2hlZFNlbGVjdG9yKHNlbGVjdG9ycykge1xuICAgIHJldHVybiByZXNlbGVjdC5jcmVhdGVTdHJ1Y3R1cmVkU2VsZWN0b3Ioc2VsZWN0b3JzLCBjcmVhdGVDYWNoZWRTZWxlY3Rvcik7XG4gIH1cblxuICBmdW5jdGlvbiB2YWxpZGF0ZUNhY2hlU2l6ZShjYWNoZVNpemUpIHtcbiAgICBpZiAoY2FjaGVTaXplID09PSB1bmRlZmluZWQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignTWlzc2luZyB0aGUgcmVxdWlyZWQgcHJvcGVydHkgXCJjYWNoZVNpemVcIi4nKTtcbiAgICB9XG5cbiAgICBpZiAoIU51bWJlci5pc0ludGVnZXIoY2FjaGVTaXplKSB8fCBjYWNoZVNpemUgPD0gMCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdUaGUgXCJjYWNoZVNpemVcIiBwcm9wZXJ0eSBtdXN0IGJlIGEgcG9zaXRpdmUgaW50ZWdlciB2YWx1ZS4nKTtcbiAgICB9XG4gIH1cblxuICB2YXIgRmlmb09iamVjdENhY2hlID1cbiAgLyojX19QVVJFX18qL1xuICBmdW5jdGlvbiAoKSB7XG4gICAgZnVuY3Rpb24gRmlmb09iamVjdENhY2hlKF90ZW1wKSB7XG4gICAgICB2YXIgX3JlZiA9IF90ZW1wID09PSB2b2lkIDAgPyB7fSA6IF90ZW1wLFxuICAgICAgICAgIGNhY2hlU2l6ZSA9IF9yZWYuY2FjaGVTaXplO1xuXG4gICAgICB2YWxpZGF0ZUNhY2hlU2l6ZShjYWNoZVNpemUpO1xuICAgICAgdGhpcy5fY2FjaGUgPSB7fTtcbiAgICAgIHRoaXMuX2NhY2hlT3JkZXJpbmcgPSBbXTtcbiAgICAgIHRoaXMuX2NhY2hlU2l6ZSA9IGNhY2hlU2l6ZTtcbiAgICB9XG5cbiAgICB2YXIgX3Byb3RvID0gRmlmb09iamVjdENhY2hlLnByb3RvdHlwZTtcblxuICAgIF9wcm90by5zZXQgPSBmdW5jdGlvbiBzZXQoa2V5LCBzZWxlY3RvckZuKSB7XG4gICAgICB0aGlzLl9jYWNoZVtrZXldID0gc2VsZWN0b3JGbjtcblxuICAgICAgdGhpcy5fY2FjaGVPcmRlcmluZy5wdXNoKGtleSk7XG5cbiAgICAgIGlmICh0aGlzLl9jYWNoZU9yZGVyaW5nLmxlbmd0aCA+IHRoaXMuX2NhY2hlU2l6ZSkge1xuICAgICAgICB2YXIgZWFybGllc3QgPSB0aGlzLl9jYWNoZU9yZGVyaW5nWzBdO1xuICAgICAgICB0aGlzLnJlbW92ZShlYXJsaWVzdCk7XG4gICAgICB9XG4gICAgfTtcblxuICAgIF9wcm90by5nZXQgPSBmdW5jdGlvbiBnZXQoa2V5KSB7XG4gICAgICByZXR1cm4gdGhpcy5fY2FjaGVba2V5XTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLnJlbW92ZSA9IGZ1bmN0aW9uIHJlbW92ZShrZXkpIHtcbiAgICAgIHZhciBpbmRleCA9IHRoaXMuX2NhY2hlT3JkZXJpbmcuaW5kZXhPZihrZXkpO1xuXG4gICAgICBpZiAoaW5kZXggPiAtMSkge1xuICAgICAgICB0aGlzLl9jYWNoZU9yZGVyaW5nLnNwbGljZShpbmRleCwgMSk7XG4gICAgICB9XG5cbiAgICAgIGRlbGV0ZSB0aGlzLl9jYWNoZVtrZXldO1xuICAgIH07XG5cbiAgICBfcHJvdG8uY2xlYXIgPSBmdW5jdGlvbiBjbGVhcigpIHtcbiAgICAgIHRoaXMuX2NhY2hlID0ge307XG4gICAgICB0aGlzLl9jYWNoZU9yZGVyaW5nID0gW107XG4gICAgfTtcblxuICAgIF9wcm90by5pc1ZhbGlkQ2FjaGVLZXkgPSBmdW5jdGlvbiBpc1ZhbGlkQ2FjaGVLZXkoY2FjaGVLZXkpIHtcbiAgICAgIHJldHVybiBpc1N0cmluZ09yTnVtYmVyKGNhY2hlS2V5KTtcbiAgICB9O1xuXG4gICAgcmV0dXJuIEZpZm9PYmplY3RDYWNoZTtcbiAgfSgpO1xuXG4gIHZhciBMcnVPYmplY3RDYWNoZSA9XG4gIC8qI19fUFVSRV9fKi9cbiAgZnVuY3Rpb24gKCkge1xuICAgIGZ1bmN0aW9uIExydU9iamVjdENhY2hlKF90ZW1wKSB7XG4gICAgICB2YXIgX3JlZiA9IF90ZW1wID09PSB2b2lkIDAgPyB7fSA6IF90ZW1wLFxuICAgICAgICAgIGNhY2hlU2l6ZSA9IF9yZWYuY2FjaGVTaXplO1xuXG4gICAgICB2YWxpZGF0ZUNhY2hlU2l6ZShjYWNoZVNpemUpO1xuICAgICAgdGhpcy5fY2FjaGUgPSB7fTtcbiAgICAgIHRoaXMuX2NhY2hlT3JkZXJpbmcgPSBbXTtcbiAgICAgIHRoaXMuX2NhY2hlU2l6ZSA9IGNhY2hlU2l6ZTtcbiAgICB9XG5cbiAgICB2YXIgX3Byb3RvID0gTHJ1T2JqZWN0Q2FjaGUucHJvdG90eXBlO1xuXG4gICAgX3Byb3RvLnNldCA9IGZ1bmN0aW9uIHNldChrZXksIHNlbGVjdG9yRm4pIHtcbiAgICAgIHRoaXMuX2NhY2hlW2tleV0gPSBzZWxlY3RvckZuO1xuXG4gICAgICB0aGlzLl9yZWdpc3RlckNhY2hlSGl0KGtleSk7XG5cbiAgICAgIGlmICh0aGlzLl9jYWNoZU9yZGVyaW5nLmxlbmd0aCA+IHRoaXMuX2NhY2hlU2l6ZSkge1xuICAgICAgICB2YXIgZWFybGllc3QgPSB0aGlzLl9jYWNoZU9yZGVyaW5nWzBdO1xuICAgICAgICB0aGlzLnJlbW92ZShlYXJsaWVzdCk7XG4gICAgICB9XG4gICAgfTtcblxuICAgIF9wcm90by5nZXQgPSBmdW5jdGlvbiBnZXQoa2V5KSB7XG4gICAgICB0aGlzLl9yZWdpc3RlckNhY2hlSGl0KGtleSk7XG5cbiAgICAgIHJldHVybiB0aGlzLl9jYWNoZVtrZXldO1xuICAgIH07XG5cbiAgICBfcHJvdG8ucmVtb3ZlID0gZnVuY3Rpb24gcmVtb3ZlKGtleSkge1xuICAgICAgdGhpcy5fZGVsZXRlQ2FjaGVIaXQoa2V5KTtcblxuICAgICAgZGVsZXRlIHRoaXMuX2NhY2hlW2tleV07XG4gICAgfTtcblxuICAgIF9wcm90by5jbGVhciA9IGZ1bmN0aW9uIGNsZWFyKCkge1xuICAgICAgdGhpcy5fY2FjaGUgPSB7fTtcbiAgICAgIHRoaXMuX2NhY2hlT3JkZXJpbmcgPSBbXTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLl9yZWdpc3RlckNhY2hlSGl0ID0gZnVuY3Rpb24gX3JlZ2lzdGVyQ2FjaGVIaXQoa2V5KSB7XG4gICAgICB0aGlzLl9kZWxldGVDYWNoZUhpdChrZXkpO1xuXG4gICAgICB0aGlzLl9jYWNoZU9yZGVyaW5nLnB1c2goa2V5KTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLl9kZWxldGVDYWNoZUhpdCA9IGZ1bmN0aW9uIF9kZWxldGVDYWNoZUhpdChrZXkpIHtcbiAgICAgIHZhciBpbmRleCA9IHRoaXMuX2NhY2hlT3JkZXJpbmcuaW5kZXhPZihrZXkpO1xuXG4gICAgICBpZiAoaW5kZXggPiAtMSkge1xuICAgICAgICB0aGlzLl9jYWNoZU9yZGVyaW5nLnNwbGljZShpbmRleCwgMSk7XG4gICAgICB9XG4gICAgfTtcblxuICAgIF9wcm90by5pc1ZhbGlkQ2FjaGVLZXkgPSBmdW5jdGlvbiBpc1ZhbGlkQ2FjaGVLZXkoY2FjaGVLZXkpIHtcbiAgICAgIHJldHVybiBpc1N0cmluZ09yTnVtYmVyKGNhY2hlS2V5KTtcbiAgICB9O1xuXG4gICAgcmV0dXJuIExydU9iamVjdENhY2hlO1xuICB9KCk7XG5cbiAgdmFyIEZsYXRNYXBDYWNoZSA9XG4gIC8qI19fUFVSRV9fKi9cbiAgZnVuY3Rpb24gKCkge1xuICAgIGZ1bmN0aW9uIEZsYXRNYXBDYWNoZSgpIHtcbiAgICAgIHRoaXMuX2NhY2hlID0gbmV3IE1hcCgpO1xuICAgIH1cblxuICAgIHZhciBfcHJvdG8gPSBGbGF0TWFwQ2FjaGUucHJvdG90eXBlO1xuXG4gICAgX3Byb3RvLnNldCA9IGZ1bmN0aW9uIHNldChrZXksIHNlbGVjdG9yRm4pIHtcbiAgICAgIHRoaXMuX2NhY2hlLnNldChrZXksIHNlbGVjdG9yRm4pO1xuICAgIH07XG5cbiAgICBfcHJvdG8uZ2V0ID0gZnVuY3Rpb24gZ2V0KGtleSkge1xuICAgICAgcmV0dXJuIHRoaXMuX2NhY2hlLmdldChrZXkpO1xuICAgIH07XG5cbiAgICBfcHJvdG8ucmVtb3ZlID0gZnVuY3Rpb24gcmVtb3ZlKGtleSkge1xuICAgICAgdGhpcy5fY2FjaGVbXCJkZWxldGVcIl0oa2V5KTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLmNsZWFyID0gZnVuY3Rpb24gY2xlYXIoKSB7XG4gICAgICB0aGlzLl9jYWNoZS5jbGVhcigpO1xuICAgIH07XG5cbiAgICByZXR1cm4gRmxhdE1hcENhY2hlO1xuICB9KCk7XG5cbiAgdmFyIEZpZm9NYXBDYWNoZSA9XG4gIC8qI19fUFVSRV9fKi9cbiAgZnVuY3Rpb24gKCkge1xuICAgIGZ1bmN0aW9uIEZpZm9NYXBDYWNoZShfdGVtcCkge1xuICAgICAgdmFyIF9yZWYgPSBfdGVtcCA9PT0gdm9pZCAwID8ge30gOiBfdGVtcCxcbiAgICAgICAgICBjYWNoZVNpemUgPSBfcmVmLmNhY2hlU2l6ZTtcblxuICAgICAgdmFsaWRhdGVDYWNoZVNpemUoY2FjaGVTaXplKTtcbiAgICAgIHRoaXMuX2NhY2hlID0gbmV3IE1hcCgpO1xuICAgICAgdGhpcy5fY2FjaGVTaXplID0gY2FjaGVTaXplO1xuICAgIH1cblxuICAgIHZhciBfcHJvdG8gPSBGaWZvTWFwQ2FjaGUucHJvdG90eXBlO1xuXG4gICAgX3Byb3RvLnNldCA9IGZ1bmN0aW9uIHNldChrZXksIHNlbGVjdG9yRm4pIHtcbiAgICAgIHRoaXMuX2NhY2hlLnNldChrZXksIHNlbGVjdG9yRm4pO1xuXG4gICAgICBpZiAodGhpcy5fY2FjaGUuc2l6ZSA+IHRoaXMuX2NhY2hlU2l6ZSkge1xuICAgICAgICB2YXIgZWFybGllc3QgPSB0aGlzLl9jYWNoZS5rZXlzKCkubmV4dCgpLnZhbHVlO1xuXG4gICAgICAgIHRoaXMucmVtb3ZlKGVhcmxpZXN0KTtcbiAgICAgIH1cbiAgICB9O1xuXG4gICAgX3Byb3RvLmdldCA9IGZ1bmN0aW9uIGdldChrZXkpIHtcbiAgICAgIHJldHVybiB0aGlzLl9jYWNoZS5nZXQoa2V5KTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLnJlbW92ZSA9IGZ1bmN0aW9uIHJlbW92ZShrZXkpIHtcbiAgICAgIHRoaXMuX2NhY2hlW1wiZGVsZXRlXCJdKGtleSk7XG4gICAgfTtcblxuICAgIF9wcm90by5jbGVhciA9IGZ1bmN0aW9uIGNsZWFyKCkge1xuICAgICAgdGhpcy5fY2FjaGUuY2xlYXIoKTtcbiAgICB9O1xuXG4gICAgcmV0dXJuIEZpZm9NYXBDYWNoZTtcbiAgfSgpO1xuXG4gIHZhciBMcnVNYXBDYWNoZSA9XG4gIC8qI19fUFVSRV9fKi9cbiAgZnVuY3Rpb24gKCkge1xuICAgIGZ1bmN0aW9uIExydU1hcENhY2hlKF90ZW1wKSB7XG4gICAgICB2YXIgX3JlZiA9IF90ZW1wID09PSB2b2lkIDAgPyB7fSA6IF90ZW1wLFxuICAgICAgICAgIGNhY2hlU2l6ZSA9IF9yZWYuY2FjaGVTaXplO1xuXG4gICAgICB2YWxpZGF0ZUNhY2hlU2l6ZShjYWNoZVNpemUpO1xuICAgICAgdGhpcy5fY2FjaGUgPSBuZXcgTWFwKCk7XG4gICAgICB0aGlzLl9jYWNoZVNpemUgPSBjYWNoZVNpemU7XG4gICAgfVxuXG4gICAgdmFyIF9wcm90byA9IExydU1hcENhY2hlLnByb3RvdHlwZTtcblxuICAgIF9wcm90by5zZXQgPSBmdW5jdGlvbiBzZXQoa2V5LCBzZWxlY3RvckZuKSB7XG4gICAgICB0aGlzLl9jYWNoZS5zZXQoa2V5LCBzZWxlY3RvckZuKTtcblxuICAgICAgaWYgKHRoaXMuX2NhY2hlLnNpemUgPiB0aGlzLl9jYWNoZVNpemUpIHtcbiAgICAgICAgdmFyIGVhcmxpZXN0ID0gdGhpcy5fY2FjaGUua2V5cygpLm5leHQoKS52YWx1ZTtcblxuICAgICAgICB0aGlzLnJlbW92ZShlYXJsaWVzdCk7XG4gICAgICB9XG4gICAgfTtcblxuICAgIF9wcm90by5nZXQgPSBmdW5jdGlvbiBnZXQoa2V5KSB7XG4gICAgICB2YXIgdmFsdWUgPSB0aGlzLl9jYWNoZS5nZXQoa2V5KTsgLy8gUmVnaXN0ZXIgY2FjaGUgaGl0XG5cblxuICAgICAgaWYgKHRoaXMuX2NhY2hlLmhhcyhrZXkpKSB7XG4gICAgICAgIHRoaXMucmVtb3ZlKGtleSk7XG5cbiAgICAgICAgdGhpcy5fY2FjaGUuc2V0KGtleSwgdmFsdWUpO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gdmFsdWU7XG4gICAgfTtcblxuICAgIF9wcm90by5yZW1vdmUgPSBmdW5jdGlvbiByZW1vdmUoa2V5KSB7XG4gICAgICB0aGlzLl9jYWNoZVtcImRlbGV0ZVwiXShrZXkpO1xuICAgIH07XG5cbiAgICBfcHJvdG8uY2xlYXIgPSBmdW5jdGlvbiBjbGVhcigpIHtcbiAgICAgIHRoaXMuX2NhY2hlLmNsZWFyKCk7XG4gICAgfTtcblxuICAgIHJldHVybiBMcnVNYXBDYWNoZTtcbiAgfSgpO1xuXG4gIGV4cG9ydHMuRmlmb0NhY2hlT2JqZWN0ID0gRmlmb09iamVjdENhY2hlO1xuICBleHBvcnRzLkZpZm9NYXBDYWNoZSA9IEZpZm9NYXBDYWNoZTtcbiAgZXhwb3J0cy5GaWZvT2JqZWN0Q2FjaGUgPSBGaWZvT2JqZWN0Q2FjaGU7XG4gIGV4cG9ydHMuRmxhdENhY2hlT2JqZWN0ID0gRmxhdE9iamVjdENhY2hlO1xuICBleHBvcnRzLkZsYXRNYXBDYWNoZSA9IEZsYXRNYXBDYWNoZTtcbiAgZXhwb3J0cy5GbGF0T2JqZWN0Q2FjaGUgPSBGbGF0T2JqZWN0Q2FjaGU7XG4gIGV4cG9ydHMuTHJ1Q2FjaGVPYmplY3QgPSBMcnVNYXBDYWNoZTtcbiAgZXhwb3J0cy5McnVNYXBDYWNoZSA9IExydU1hcENhY2hlO1xuICBleHBvcnRzLkxydU9iamVjdENhY2hlID0gTHJ1T2JqZWN0Q2FjaGU7XG4gIGV4cG9ydHMuY3JlYXRlU3RydWN0dXJlZENhY2hlZFNlbGVjdG9yID0gY3JlYXRlU3RydWN0dXJlZENhY2hlZFNlbGVjdG9yO1xuICBleHBvcnRzLmRlZmF1bHQgPSBjcmVhdGVDYWNoZWRTZWxlY3RvcjtcblxuICBPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuXG59KSk7XG4vLyMgc291cmNlTWFwcGluZ1VSTD1pbmRleC5qcy5tYXBcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./node_modules/re-reselect/dist/index.js\\n\");\n+eval(\"(function (global, factory) {\\n   true ? factory(exports, __webpack_require__(/*! reselect */ \\\"./node_modules/reselect/lib/index.js\\\")) :\\n  undefined;\\n}(this, (function (exports, reselect) { 'use strict';\\n\\n  function isStringOrNumber(value) {\\n    return typeof value === 'string' || typeof value === 'number';\\n  }\\n\\n  var FlatObjectCache = /*#__PURE__*/function () {\\n    function FlatObjectCache() {\\n      this._cache = {};\\n    }\\n\\n    var _proto = FlatObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return FlatObjectCache;\\n  }();\\n\\n  var defaultCacheCreator = FlatObjectCache;\\n\\n  var defaultCacheKeyValidator = function defaultCacheKeyValidator() {\\n    return true;\\n  };\\n\\n  function createCachedSelector() {\\n    for (var _len = arguments.length, funcs = new Array(_len), _key = 0; _key < _len; _key++) {\\n      funcs[_key] = arguments[_key];\\n    }\\n\\n    return function (polymorphicOptions, legacyOptions) {\\n      if (legacyOptions) {\\n        throw new Error('[re-reselect] \\\"options\\\" as second argument is not supported anymore. Please provide an option object as single argument.');\\n      }\\n\\n      var options = typeof polymorphicOptions === 'function' ? {\\n        keySelector: polymorphicOptions\\n      } : Object.assign({}, polymorphicOptions); // https://github.com/reduxjs/reselect/blob/v4.0.0/src/index.js#L54\\n\\n      var recomputations = 0;\\n      var resultFunc = funcs.pop();\\n      var dependencies = Array.isArray(funcs[0]) ? funcs[0] : [].concat(funcs);\\n\\n      var resultFuncWithRecomputations = function resultFuncWithRecomputations() {\\n        recomputations++;\\n        return resultFunc.apply(void 0, arguments);\\n      };\\n\\n      funcs.push(resultFuncWithRecomputations);\\n      var cache = options.cacheObject || new defaultCacheCreator();\\n      var selectorCreator = options.selectorCreator || reselect.createSelector;\\n      var isValidCacheKey = cache.isValidCacheKey || defaultCacheKeyValidator;\\n\\n      if (options.keySelectorCreator) {\\n        options.keySelector = options.keySelectorCreator({\\n          keySelector: options.keySelector,\\n          inputSelectors: dependencies,\\n          resultFunc: resultFunc\\n        });\\n      } // Application receives this function\\n\\n\\n      var selector = function selector() {\\n        var cacheKey = options.keySelector.apply(options, arguments);\\n\\n        if (isValidCacheKey(cacheKey)) {\\n          var cacheResponse = cache.get(cacheKey);\\n\\n          if (cacheResponse === undefined) {\\n            cacheResponse = selectorCreator.apply(void 0, funcs);\\n            cache.set(cacheKey, cacheResponse);\\n          }\\n\\n          return cacheResponse.apply(void 0, arguments);\\n        }\\n\\n        console.warn(\\\"[re-reselect] Invalid cache key \\\\\\\"\\\" + cacheKey + \\\"\\\\\\\" has been returned by keySelector function.\\\");\\n        return undefined;\\n      }; // Further selector methods\\n\\n\\n      selector.getMatchingSelector = function () {\\n        var cacheKey = options.keySelector.apply(options, arguments); // @NOTE It might update cache hit count in LRU-like caches\\n\\n        return cache.get(cacheKey);\\n      };\\n\\n      selector.removeMatchingSelector = function () {\\n        var cacheKey = options.keySelector.apply(options, arguments);\\n        cache.remove(cacheKey);\\n      };\\n\\n      selector.clearCache = function () {\\n        cache.clear();\\n      };\\n\\n      selector.resultFunc = resultFunc;\\n      selector.dependencies = dependencies;\\n      selector.cache = cache;\\n\\n      selector.recomputations = function () {\\n        return recomputations;\\n      };\\n\\n      selector.resetRecomputations = function () {\\n        return recomputations = 0;\\n      };\\n\\n      selector.keySelector = options.keySelector;\\n      return selector;\\n    };\\n  }\\n\\n  function createStructuredCachedSelector(selectors) {\\n    return reselect.createStructuredSelector(selectors, createCachedSelector);\\n  }\\n\\n  function validateCacheSize(cacheSize) {\\n    if (cacheSize === undefined) {\\n      throw new Error('Missing the required property \\\"cacheSize\\\".');\\n    }\\n\\n    if (!Number.isInteger(cacheSize) || cacheSize <= 0) {\\n      throw new Error('The \\\"cacheSize\\\" property must be a positive integer value.');\\n    }\\n  }\\n\\n  var FifoObjectCache = /*#__PURE__*/function () {\\n    function FifoObjectCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = FifoObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n\\n      this._cacheOrdering.push(key);\\n\\n      if (this._cacheOrdering.length > this._cacheSize) {\\n        var earliest = this._cacheOrdering[0];\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      var index = this._cacheOrdering.indexOf(key);\\n\\n      if (index > -1) {\\n        this._cacheOrdering.splice(index, 1);\\n      }\\n\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return FifoObjectCache;\\n  }();\\n\\n  var LruObjectCache = /*#__PURE__*/function () {\\n    function LruObjectCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = LruObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n\\n      this._registerCacheHit(key);\\n\\n      if (this._cacheOrdering.length > this._cacheSize) {\\n        var earliest = this._cacheOrdering[0];\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      this._registerCacheHit(key);\\n\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._deleteCacheHit(key);\\n\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n    };\\n\\n    _proto._registerCacheHit = function _registerCacheHit(key) {\\n      this._deleteCacheHit(key);\\n\\n      this._cacheOrdering.push(key);\\n    };\\n\\n    _proto._deleteCacheHit = function _deleteCacheHit(key) {\\n      var index = this._cacheOrdering.indexOf(key);\\n\\n      if (index > -1) {\\n        this._cacheOrdering.splice(index, 1);\\n      }\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return LruObjectCache;\\n  }();\\n\\n  var FlatMapCache = /*#__PURE__*/function () {\\n    function FlatMapCache() {\\n      this._cache = new Map();\\n    }\\n\\n    var _proto = FlatMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache.get(key);\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return FlatMapCache;\\n  }();\\n\\n  var FifoMapCache = /*#__PURE__*/function () {\\n    function FifoMapCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = new Map();\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = FifoMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n\\n      if (this._cache.size > this._cacheSize) {\\n        var earliest = this._cache.keys().next().value;\\n\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache.get(key);\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return FifoMapCache;\\n  }();\\n\\n  var LruMapCache = /*#__PURE__*/function () {\\n    function LruMapCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = new Map();\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = LruMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n\\n      if (this._cache.size > this._cacheSize) {\\n        var earliest = this._cache.keys().next().value;\\n\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      var value = this._cache.get(key); // Register cache hit\\n\\n\\n      if (this._cache.has(key)) {\\n        this.remove(key);\\n\\n        this._cache.set(key, value);\\n      }\\n\\n      return value;\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return LruMapCache;\\n  }();\\n\\n  exports.FifoMapCache = FifoMapCache;\\n  exports.FifoObjectCache = FifoObjectCache;\\n  exports.FlatMapCache = FlatMapCache;\\n  exports.FlatObjectCache = FlatObjectCache;\\n  exports.LruMapCache = LruMapCache;\\n  exports.LruObjectCache = LruObjectCache;\\n  exports.createCachedSelector = createCachedSelector;\\n  exports.createStructuredCachedSelector = createStructuredCachedSelector;\\n  exports.default = createCachedSelector;\\n\\n  Object.defineProperty(exports, '__esModule', { value: true });\\n\\n})));\\n//# sourceMappingURL=index.js.map\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL25vZGVfbW9kdWxlcy9yZS1yZXNlbGVjdC9kaXN0L2luZGV4LmpzP2YyM2QiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQSxFQUFFLEtBQTRELG9CQUFvQixtQkFBTyxDQUFDLHNEQUFVO0FBQ3BHLEVBQUUsU0FDK0U7QUFDakYsQ0FBQyxzQ0FBc0M7O0FBRXZDO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0EsR0FBRzs7QUFFSDs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSx3RUFBd0UsYUFBYTtBQUNyRjtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxPQUFPLG1CQUFtQixzQkFBc0I7O0FBRWhEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNULE9BQU87OztBQUdQO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxRQUFROzs7QUFHUjtBQUNBLHFFQUFxRTs7QUFFckU7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxzQ0FBc0M7QUFDdEM7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0EsR0FBRzs7QUFFSDtBQUNBO0FBQ0Esc0NBQXNDO0FBQ3RDOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0EsR0FBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxHQUFHOztBQUVIO0FBQ0E7QUFDQSxzQ0FBc0M7QUFDdEM7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLEdBQUc7O0FBRUg7QUFDQTtBQUNBLHNDQUFzQztBQUN0Qzs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0EsdUNBQXVDOzs7QUFHdkM7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLEdBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLGdEQUFnRCxjQUFjOztBQUU5RCxDQUFDO0FBQ0QiLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvcmUtcmVzZWxlY3QvZGlzdC9pbmRleC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiAoZ2xvYmFsLCBmYWN0b3J5KSB7XG4gIHR5cGVvZiBleHBvcnRzID09PSAnb2JqZWN0JyAmJiB0eXBlb2YgbW9kdWxlICE9PSAndW5kZWZpbmVkJyA/IGZhY3RvcnkoZXhwb3J0cywgcmVxdWlyZSgncmVzZWxlY3QnKSkgOlxuICB0eXBlb2YgZGVmaW5lID09PSAnZnVuY3Rpb24nICYmIGRlZmluZS5hbWQgPyBkZWZpbmUoWydleHBvcnRzJywgJ3Jlc2VsZWN0J10sIGZhY3RvcnkpIDpcbiAgKGdsb2JhbCA9IGdsb2JhbCB8fCBzZWxmLCBmYWN0b3J5KGdsb2JhbFsnUmUtcmVzZWxlY3QnXSA9IHt9LCBnbG9iYWwuUmVzZWxlY3QpKTtcbn0odGhpcywgKGZ1bmN0aW9uIChleHBvcnRzLCByZXNlbGVjdCkgeyAndXNlIHN0cmljdCc7XG5cbiAgZnVuY3Rpb24gaXNTdHJpbmdPck51bWJlcih2YWx1ZSkge1xuICAgIHJldHVybiB0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnIHx8IHR5cGVvZiB2YWx1ZSA9PT0gJ251bWJlcic7XG4gIH1cblxuICB2YXIgRmxhdE9iamVjdENhY2hlID0gLyojX19QVVJFX18qL2Z1bmN0aW9uICgpIHtcbiAgICBmdW5jdGlvbiBGbGF0T2JqZWN0Q2FjaGUoKSB7XG4gICAgICB0aGlzLl9jYWNoZSA9IHt9O1xuICAgIH1cblxuICAgIHZhciBfcHJvdG8gPSBGbGF0T2JqZWN0Q2FjaGUucHJvdG90eXBlO1xuXG4gICAgX3Byb3RvLnNldCA9IGZ1bmN0aW9uIHNldChrZXksIHNlbGVjdG9yRm4pIHtcbiAgICAgIHRoaXMuX2NhY2hlW2tleV0gPSBzZWxlY3RvckZuO1xuICAgIH07XG5cbiAgICBfcHJvdG8uZ2V0ID0gZnVuY3Rpb24gZ2V0KGtleSkge1xuICAgICAgcmV0dXJuIHRoaXMuX2NhY2hlW2tleV07XG4gICAgfTtcblxuICAgIF9wcm90by5yZW1vdmUgPSBmdW5jdGlvbiByZW1vdmUoa2V5KSB7XG4gICAgICBkZWxldGUgdGhpcy5fY2FjaGVba2V5XTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLmNsZWFyID0gZnVuY3Rpb24gY2xlYXIoKSB7XG4gICAgICB0aGlzLl9jYWNoZSA9IHt9O1xuICAgIH07XG5cbiAgICBfcHJvdG8uaXNWYWxpZENhY2hlS2V5ID0gZnVuY3Rpb24gaXNWYWxpZENhY2hlS2V5KGNhY2hlS2V5KSB7XG4gICAgICByZXR1cm4gaXNTdHJpbmdPck51bWJlcihjYWNoZUtleSk7XG4gICAgfTtcblxuICAgIHJldHVybiBGbGF0T2JqZWN0Q2FjaGU7XG4gIH0oKTtcblxuICB2YXIgZGVmYXVsdENhY2hlQ3JlYXRvciA9IEZsYXRPYmplY3RDYWNoZTtcblxuICB2YXIgZGVmYXVsdENhY2hlS2V5VmFsaWRhdG9yID0gZnVuY3Rpb24gZGVmYXVsdENhY2hlS2V5VmFsaWRhdG9yKCkge1xuICAgIHJldHVybiB0cnVlO1xuICB9O1xuXG4gIGZ1bmN0aW9uIGNyZWF0ZUNhY2hlZFNlbGVjdG9yKCkge1xuICAgIGZvciAodmFyIF9sZW4gPSBhcmd1bWVudHMubGVuZ3RoLCBmdW5jcyA9IG5ldyBBcnJheShfbGVuKSwgX2tleSA9IDA7IF9rZXkgPCBfbGVuOyBfa2V5KyspIHtcbiAgICAgIGZ1bmNzW19rZXldID0gYXJndW1lbnRzW19rZXldO1xuICAgIH1cblxuICAgIHJldHVybiBmdW5jdGlvbiAocG9seW1vcnBoaWNPcHRpb25zLCBsZWdhY3lPcHRpb25zKSB7XG4gICAgICBpZiAobGVnYWN5T3B0aW9ucykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1tyZS1yZXNlbGVjdF0gXCJvcHRpb25zXCIgYXMgc2Vjb25kIGFyZ3VtZW50IGlzIG5vdCBzdXBwb3J0ZWQgYW55bW9yZS4gUGxlYXNlIHByb3ZpZGUgYW4gb3B0aW9uIG9iamVjdCBhcyBzaW5nbGUgYXJndW1lbnQuJyk7XG4gICAgICB9XG5cbiAgICAgIHZhciBvcHRpb25zID0gdHlwZW9mIHBvbHltb3JwaGljT3B0aW9ucyA9PT0gJ2Z1bmN0aW9uJyA/IHtcbiAgICAgICAga2V5U2VsZWN0b3I6IHBvbHltb3JwaGljT3B0aW9uc1xuICAgICAgfSA6IE9iamVjdC5hc3NpZ24oe30sIHBvbHltb3JwaGljT3B0aW9ucyk7IC8vIGh0dHBzOi8vZ2l0aHViLmNvbS9yZWR1eGpzL3Jlc2VsZWN0L2Jsb2IvdjQuMC4wL3NyYy9pbmRleC5qcyNMNTRcblxuICAgICAgdmFyIHJlY29tcHV0YXRpb25zID0gMDtcbiAgICAgIHZhciByZXN1bHRGdW5jID0gZnVuY3MucG9wKCk7XG4gICAgICB2YXIgZGVwZW5kZW5jaWVzID0gQXJyYXkuaXNBcnJheShmdW5jc1swXSkgPyBmdW5jc1swXSA6IFtdLmNvbmNhdChmdW5jcyk7XG5cbiAgICAgIHZhciByZXN1bHRGdW5jV2l0aFJlY29tcHV0YXRpb25zID0gZnVuY3Rpb24gcmVzdWx0RnVuY1dpdGhSZWNvbXB1dGF0aW9ucygpIHtcbiAgICAgICAgcmVjb21wdXRhdGlvbnMrKztcbiAgICAgICAgcmV0dXJuIHJlc3VsdEZ1bmMuYXBwbHkodm9pZCAwLCBhcmd1bWVudHMpO1xuICAgICAgfTtcblxuICAgICAgZnVuY3MucHVzaChyZXN1bHRGdW5jV2l0aFJlY29tcHV0YXRpb25zKTtcbiAgICAgIHZhciBjYWNoZSA9IG9wdGlvbnMuY2FjaGVPYmplY3QgfHwgbmV3IGRlZmF1bHRDYWNoZUNyZWF0b3IoKTtcbiAgICAgIHZhciBzZWxlY3RvckNyZWF0b3IgPSBvcHRpb25zLnNlbGVjdG9yQ3JlYXRvciB8fCByZXNlbGVjdC5jcmVhdGVTZWxlY3RvcjtcbiAgICAgIHZhciBpc1ZhbGlkQ2FjaGVLZXkgPSBjYWNoZS5pc1ZhbGlkQ2FjaGVLZXkgfHwgZGVmYXVsdENhY2hlS2V5VmFsaWRhdG9yO1xuXG4gICAgICBpZiAob3B0aW9ucy5rZXlTZWxlY3RvckNyZWF0b3IpIHtcbiAgICAgICAgb3B0aW9ucy5rZXlTZWxlY3RvciA9IG9wdGlvbnMua2V5U2VsZWN0b3JDcmVhdG9yKHtcbiAgICAgICAgICBrZXlTZWxlY3Rvcjogb3B0aW9ucy5rZXlTZWxlY3RvcixcbiAgICAgICAgICBpbnB1dFNlbGVjdG9yczogZGVwZW5kZW5jaWVzLFxuICAgICAgICAgIHJlc3VsdEZ1bmM6IHJlc3VsdEZ1bmNcbiAgICAgICAgfSk7XG4gICAgICB9IC8vIEFwcGxpY2F0aW9uIHJlY2VpdmVzIHRoaXMgZnVuY3Rpb25cblxuXG4gICAgICB2YXIgc2VsZWN0b3IgPSBmdW5jdGlvbiBzZWxlY3RvcigpIHtcbiAgICAgICAgdmFyIGNhY2hlS2V5ID0gb3B0aW9ucy5rZXlTZWxlY3Rvci5hcHBseShvcHRpb25zLCBhcmd1bWVudHMpO1xuXG4gICAgICAgIGlmIChpc1ZhbGlkQ2FjaGVLZXkoY2FjaGVLZXkpKSB7XG4gICAgICAgICAgdmFyIGNhY2hlUmVzcG9uc2UgPSBjYWNoZS5nZXQoY2FjaGVLZXkpO1xuXG4gICAgICAgICAgaWYgKGNhY2hlUmVzcG9uc2UgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgY2FjaGVSZXNwb25zZSA9IHNlbGVjdG9yQ3JlYXRvci5hcHBseSh2b2lkIDAsIGZ1bmNzKTtcbiAgICAgICAgICAgIGNhY2hlLnNldChjYWNoZUtleSwgY2FjaGVSZXNwb25zZSk7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgcmV0dXJuIGNhY2hlUmVzcG9uc2UuYXBwbHkodm9pZCAwLCBhcmd1bWVudHMpO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc29sZS53YXJuKFwiW3JlLXJlc2VsZWN0XSBJbnZhbGlkIGNhY2hlIGtleSBcXFwiXCIgKyBjYWNoZUtleSArIFwiXFxcIiBoYXMgYmVlbiByZXR1cm5lZCBieSBrZXlTZWxlY3RvciBmdW5jdGlvbi5cIik7XG4gICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgICB9OyAvLyBGdXJ0aGVyIHNlbGVjdG9yIG1ldGhvZHNcblxuXG4gICAgICBzZWxlY3Rvci5nZXRNYXRjaGluZ1NlbGVjdG9yID0gZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgY2FjaGVLZXkgPSBvcHRpb25zLmtleVNlbGVjdG9yLmFwcGx5KG9wdGlvbnMsIGFyZ3VtZW50cyk7IC8vIEBOT1RFIEl0IG1pZ2h0IHVwZGF0ZSBjYWNoZSBoaXQgY291bnQgaW4gTFJVLWxpa2UgY2FjaGVzXG5cbiAgICAgICAgcmV0dXJuIGNhY2hlLmdldChjYWNoZUtleSk7XG4gICAgICB9O1xuXG4gICAgICBzZWxlY3Rvci5yZW1vdmVNYXRjaGluZ1NlbGVjdG9yID0gZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgY2FjaGVLZXkgPSBvcHRpb25zLmtleVNlbGVjdG9yLmFwcGx5KG9wdGlvbnMsIGFyZ3VtZW50cyk7XG4gICAgICAgIGNhY2hlLnJlbW92ZShjYWNoZUtleSk7XG4gICAgICB9O1xuXG4gICAgICBzZWxlY3Rvci5jbGVhckNhY2hlID0gZnVuY3Rpb24gKCkge1xuICAgICAgICBjYWNoZS5jbGVhcigpO1xuICAgICAgfTtcblxuICAgICAgc2VsZWN0b3IucmVzdWx0RnVuYyA9IHJlc3VsdEZ1bmM7XG4gICAgICBzZWxlY3Rvci5kZXBlbmRlbmNpZXMgPSBkZXBlbmRlbmNpZXM7XG4gICAgICBzZWxlY3Rvci5jYWNoZSA9IGNhY2hlO1xuXG4gICAgICBzZWxlY3Rvci5yZWNvbXB1dGF0aW9ucyA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIHJlY29tcHV0YXRpb25zO1xuICAgICAgfTtcblxuICAgICAgc2VsZWN0b3IucmVzZXRSZWNvbXB1dGF0aW9ucyA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIHJlY29tcHV0YXRpb25zID0gMDtcbiAgICAgIH07XG5cbiAgICAgIHNlbGVjdG9yLmtleVNlbGVjdG9yID0gb3B0aW9ucy5rZXlTZWxlY3RvcjtcbiAgICAgIHJldHVybiBzZWxlY3RvcjtcbiAgICB9O1xuICB9XG5cbiAgZnVuY3Rpb24gY3JlYXRlU3RydWN0dXJlZENhY2hlZFNlbGVjdG9yKHNlbGVjdG9ycykge1xuICAgIHJldHVybiByZXNlbGVjdC5jcmVhdGVTdHJ1Y3R1cmVkU2VsZWN0b3Ioc2VsZWN0b3JzLCBjcmVhdGVDYWNoZWRTZWxlY3Rvcik7XG4gIH1cblxuICBmdW5jdGlvbiB2YWxpZGF0ZUNhY2hlU2l6ZShjYWNoZVNpemUpIHtcbiAgICBpZiAoY2FjaGVTaXplID09PSB1bmRlZmluZWQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignTWlzc2luZyB0aGUgcmVxdWlyZWQgcHJvcGVydHkgXCJjYWNoZVNpemVcIi4nKTtcbiAgICB9XG5cbiAgICBpZiAoIU51bWJlci5pc0ludGVnZXIoY2FjaGVTaXplKSB8fCBjYWNoZVNpemUgPD0gMCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdUaGUgXCJjYWNoZVNpemVcIiBwcm9wZXJ0eSBtdXN0IGJlIGEgcG9zaXRpdmUgaW50ZWdlciB2YWx1ZS4nKTtcbiAgICB9XG4gIH1cblxuICB2YXIgRmlmb09iamVjdENhY2hlID0gLyojX19QVVJFX18qL2Z1bmN0aW9uICgpIHtcbiAgICBmdW5jdGlvbiBGaWZvT2JqZWN0Q2FjaGUoX3RlbXApIHtcbiAgICAgIHZhciBfcmVmID0gX3RlbXAgPT09IHZvaWQgMCA/IHt9IDogX3RlbXAsXG4gICAgICAgICAgY2FjaGVTaXplID0gX3JlZi5jYWNoZVNpemU7XG5cbiAgICAgIHZhbGlkYXRlQ2FjaGVTaXplKGNhY2hlU2l6ZSk7XG4gICAgICB0aGlzLl9jYWNoZSA9IHt9O1xuICAgICAgdGhpcy5fY2FjaGVPcmRlcmluZyA9IFtdO1xuICAgICAgdGhpcy5fY2FjaGVTaXplID0gY2FjaGVTaXplO1xuICAgIH1cblxuICAgIHZhciBfcHJvdG8gPSBGaWZvT2JqZWN0Q2FjaGUucHJvdG90eXBlO1xuXG4gICAgX3Byb3RvLnNldCA9IGZ1bmN0aW9uIHNldChrZXksIHNlbGVjdG9yRm4pIHtcbiAgICAgIHRoaXMuX2NhY2hlW2tleV0gPSBzZWxlY3RvckZuO1xuXG4gICAgICB0aGlzLl9jYWNoZU9yZGVyaW5nLnB1c2goa2V5KTtcblxuICAgICAgaWYgKHRoaXMuX2NhY2hlT3JkZXJpbmcubGVuZ3RoID4gdGhpcy5fY2FjaGVTaXplKSB7XG4gICAgICAgIHZhciBlYXJsaWVzdCA9IHRoaXMuX2NhY2hlT3JkZXJpbmdbMF07XG4gICAgICAgIHRoaXMucmVtb3ZlKGVhcmxpZXN0KTtcbiAgICAgIH1cbiAgICB9O1xuXG4gICAgX3Byb3RvLmdldCA9IGZ1bmN0aW9uIGdldChrZXkpIHtcbiAgICAgIHJldHVybiB0aGlzLl9jYWNoZVtrZXldO1xuICAgIH07XG5cbiAgICBfcHJvdG8ucmVtb3ZlID0gZnVuY3Rpb24gcmVtb3ZlKGtleSkge1xuICAgICAgdmFyIGluZGV4ID0gdGhpcy5fY2FjaGVPcmRlcmluZy5pbmRleE9mKGtleSk7XG5cbiAgICAgIGlmIChpbmRleCA+IC0xKSB7XG4gICAgICAgIHRoaXMuX2NhY2hlT3JkZXJpbmcuc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgIH1cblxuICAgICAgZGVsZXRlIHRoaXMuX2NhY2hlW2tleV07XG4gICAgfTtcblxuICAgIF9wcm90by5jbGVhciA9IGZ1bmN0aW9uIGNsZWFyKCkge1xuICAgICAgdGhpcy5fY2FjaGUgPSB7fTtcbiAgICAgIHRoaXMuX2NhY2hlT3JkZXJpbmcgPSBbXTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLmlzVmFsaWRDYWNoZUtleSA9IGZ1bmN0aW9uIGlzVmFsaWRDYWNoZUtleShjYWNoZUtleSkge1xuICAgICAgcmV0dXJuIGlzU3RyaW5nT3JOdW1iZXIoY2FjaGVLZXkpO1xuICAgIH07XG5cbiAgICByZXR1cm4gRmlmb09iamVjdENhY2hlO1xuICB9KCk7XG5cbiAgdmFyIExydU9iamVjdENhY2hlID0gLyojX19QVVJFX18qL2Z1bmN0aW9uICgpIHtcbiAgICBmdW5jdGlvbiBMcnVPYmplY3RDYWNoZShfdGVtcCkge1xuICAgICAgdmFyIF9yZWYgPSBfdGVtcCA9PT0gdm9pZCAwID8ge30gOiBfdGVtcCxcbiAgICAgICAgICBjYWNoZVNpemUgPSBfcmVmLmNhY2hlU2l6ZTtcblxuICAgICAgdmFsaWRhdGVDYWNoZVNpemUoY2FjaGVTaXplKTtcbiAgICAgIHRoaXMuX2NhY2hlID0ge307XG4gICAgICB0aGlzLl9jYWNoZU9yZGVyaW5nID0gW107XG4gICAgICB0aGlzLl9jYWNoZVNpemUgPSBjYWNoZVNpemU7XG4gICAgfVxuXG4gICAgdmFyIF9wcm90byA9IExydU9iamVjdENhY2hlLnByb3RvdHlwZTtcblxuICAgIF9wcm90by5zZXQgPSBmdW5jdGlvbiBzZXQoa2V5LCBzZWxlY3RvckZuKSB7XG4gICAgICB0aGlzLl9jYWNoZVtrZXldID0gc2VsZWN0b3JGbjtcblxuICAgICAgdGhpcy5fcmVnaXN0ZXJDYWNoZUhpdChrZXkpO1xuXG4gICAgICBpZiAodGhpcy5fY2FjaGVPcmRlcmluZy5sZW5ndGggPiB0aGlzLl9jYWNoZVNpemUpIHtcbiAgICAgICAgdmFyIGVhcmxpZXN0ID0gdGhpcy5fY2FjaGVPcmRlcmluZ1swXTtcbiAgICAgICAgdGhpcy5yZW1vdmUoZWFybGllc3QpO1xuICAgICAgfVxuICAgIH07XG5cbiAgICBfcHJvdG8uZ2V0ID0gZnVuY3Rpb24gZ2V0KGtleSkge1xuICAgICAgdGhpcy5fcmVnaXN0ZXJDYWNoZUhpdChrZXkpO1xuXG4gICAgICByZXR1cm4gdGhpcy5fY2FjaGVba2V5XTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLnJlbW92ZSA9IGZ1bmN0aW9uIHJlbW92ZShrZXkpIHtcbiAgICAgIHRoaXMuX2RlbGV0ZUNhY2hlSGl0KGtleSk7XG5cbiAgICAgIGRlbGV0ZSB0aGlzLl9jYWNoZVtrZXldO1xuICAgIH07XG5cbiAgICBfcHJvdG8uY2xlYXIgPSBmdW5jdGlvbiBjbGVhcigpIHtcbiAgICAgIHRoaXMuX2NhY2hlID0ge307XG4gICAgICB0aGlzLl9jYWNoZU9yZGVyaW5nID0gW107XG4gICAgfTtcblxuICAgIF9wcm90by5fcmVnaXN0ZXJDYWNoZUhpdCA9IGZ1bmN0aW9uIF9yZWdpc3RlckNhY2hlSGl0KGtleSkge1xuICAgICAgdGhpcy5fZGVsZXRlQ2FjaGVIaXQoa2V5KTtcblxuICAgICAgdGhpcy5fY2FjaGVPcmRlcmluZy5wdXNoKGtleSk7XG4gICAgfTtcblxuICAgIF9wcm90by5fZGVsZXRlQ2FjaGVIaXQgPSBmdW5jdGlvbiBfZGVsZXRlQ2FjaGVIaXQoa2V5KSB7XG4gICAgICB2YXIgaW5kZXggPSB0aGlzLl9jYWNoZU9yZGVyaW5nLmluZGV4T2Yoa2V5KTtcblxuICAgICAgaWYgKGluZGV4ID4gLTEpIHtcbiAgICAgICAgdGhpcy5fY2FjaGVPcmRlcmluZy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgfVxuICAgIH07XG5cbiAgICBfcHJvdG8uaXNWYWxpZENhY2hlS2V5ID0gZnVuY3Rpb24gaXNWYWxpZENhY2hlS2V5KGNhY2hlS2V5KSB7XG4gICAgICByZXR1cm4gaXNTdHJpbmdPck51bWJlcihjYWNoZUtleSk7XG4gICAgfTtcblxuICAgIHJldHVybiBMcnVPYmplY3RDYWNoZTtcbiAgfSgpO1xuXG4gIHZhciBGbGF0TWFwQ2FjaGUgPSAvKiNfX1BVUkVfXyovZnVuY3Rpb24gKCkge1xuICAgIGZ1bmN0aW9uIEZsYXRNYXBDYWNoZSgpIHtcbiAgICAgIHRoaXMuX2NhY2hlID0gbmV3IE1hcCgpO1xuICAgIH1cblxuICAgIHZhciBfcHJvdG8gPSBGbGF0TWFwQ2FjaGUucHJvdG90eXBlO1xuXG4gICAgX3Byb3RvLnNldCA9IGZ1bmN0aW9uIHNldChrZXksIHNlbGVjdG9yRm4pIHtcbiAgICAgIHRoaXMuX2NhY2hlLnNldChrZXksIHNlbGVjdG9yRm4pO1xuICAgIH07XG5cbiAgICBfcHJvdG8uZ2V0ID0gZnVuY3Rpb24gZ2V0KGtleSkge1xuICAgICAgcmV0dXJuIHRoaXMuX2NhY2hlLmdldChrZXkpO1xuICAgIH07XG5cbiAgICBfcHJvdG8ucmVtb3ZlID0gZnVuY3Rpb24gcmVtb3ZlKGtleSkge1xuICAgICAgdGhpcy5fY2FjaGVbXCJkZWxldGVcIl0oa2V5KTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLmNsZWFyID0gZnVuY3Rpb24gY2xlYXIoKSB7XG4gICAgICB0aGlzLl9jYWNoZS5jbGVhcigpO1xuICAgIH07XG5cbiAgICByZXR1cm4gRmxhdE1hcENhY2hlO1xuICB9KCk7XG5cbiAgdmFyIEZpZm9NYXBDYWNoZSA9IC8qI19fUFVSRV9fKi9mdW5jdGlvbiAoKSB7XG4gICAgZnVuY3Rpb24gRmlmb01hcENhY2hlKF90ZW1wKSB7XG4gICAgICB2YXIgX3JlZiA9IF90ZW1wID09PSB2b2lkIDAgPyB7fSA6IF90ZW1wLFxuICAgICAgICAgIGNhY2hlU2l6ZSA9IF9yZWYuY2FjaGVTaXplO1xuXG4gICAgICB2YWxpZGF0ZUNhY2hlU2l6ZShjYWNoZVNpemUpO1xuICAgICAgdGhpcy5fY2FjaGUgPSBuZXcgTWFwKCk7XG4gICAgICB0aGlzLl9jYWNoZVNpemUgPSBjYWNoZVNpemU7XG4gICAgfVxuXG4gICAgdmFyIF9wcm90byA9IEZpZm9NYXBDYWNoZS5wcm90b3R5cGU7XG5cbiAgICBfcHJvdG8uc2V0ID0gZnVuY3Rpb24gc2V0KGtleSwgc2VsZWN0b3JGbikge1xuICAgICAgdGhpcy5fY2FjaGUuc2V0KGtleSwgc2VsZWN0b3JGbik7XG5cbiAgICAgIGlmICh0aGlzLl9jYWNoZS5zaXplID4gdGhpcy5fY2FjaGVTaXplKSB7XG4gICAgICAgIHZhciBlYXJsaWVzdCA9IHRoaXMuX2NhY2hlLmtleXMoKS5uZXh0KCkudmFsdWU7XG5cbiAgICAgICAgdGhpcy5yZW1vdmUoZWFybGllc3QpO1xuICAgICAgfVxuICAgIH07XG5cbiAgICBfcHJvdG8uZ2V0ID0gZnVuY3Rpb24gZ2V0KGtleSkge1xuICAgICAgcmV0dXJuIHRoaXMuX2NhY2hlLmdldChrZXkpO1xuICAgIH07XG5cbiAgICBfcHJvdG8ucmVtb3ZlID0gZnVuY3Rpb24gcmVtb3ZlKGtleSkge1xuICAgICAgdGhpcy5fY2FjaGVbXCJkZWxldGVcIl0oa2V5KTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLmNsZWFyID0gZnVuY3Rpb24gY2xlYXIoKSB7XG4gICAgICB0aGlzLl9jYWNoZS5jbGVhcigpO1xuICAgIH07XG5cbiAgICByZXR1cm4gRmlmb01hcENhY2hlO1xuICB9KCk7XG5cbiAgdmFyIExydU1hcENhY2hlID0gLyojX19QVVJFX18qL2Z1bmN0aW9uICgpIHtcbiAgICBmdW5jdGlvbiBMcnVNYXBDYWNoZShfdGVtcCkge1xuICAgICAgdmFyIF9yZWYgPSBfdGVtcCA9PT0gdm9pZCAwID8ge30gOiBfdGVtcCxcbiAgICAgICAgICBjYWNoZVNpemUgPSBfcmVmLmNhY2hlU2l6ZTtcblxuICAgICAgdmFsaWRhdGVDYWNoZVNpemUoY2FjaGVTaXplKTtcbiAgICAgIHRoaXMuX2NhY2hlID0gbmV3IE1hcCgpO1xuICAgICAgdGhpcy5fY2FjaGVTaXplID0gY2FjaGVTaXplO1xuICAgIH1cblxuICAgIHZhciBfcHJvdG8gPSBMcnVNYXBDYWNoZS5wcm90b3R5cGU7XG5cbiAgICBfcHJvdG8uc2V0ID0gZnVuY3Rpb24gc2V0KGtleSwgc2VsZWN0b3JGbikge1xuICAgICAgdGhpcy5fY2FjaGUuc2V0KGtleSwgc2VsZWN0b3JGbik7XG5cbiAgICAgIGlmICh0aGlzLl9jYWNoZS5zaXplID4gdGhpcy5fY2FjaGVTaXplKSB7XG4gICAgICAgIHZhciBlYXJsaWVzdCA9IHRoaXMuX2NhY2hlLmtleXMoKS5uZXh0KCkudmFsdWU7XG5cbiAgICAgICAgdGhpcy5yZW1vdmUoZWFybGllc3QpO1xuICAgICAgfVxuICAgIH07XG5cbiAgICBfcHJvdG8uZ2V0ID0gZnVuY3Rpb24gZ2V0KGtleSkge1xuICAgICAgdmFyIHZhbHVlID0gdGhpcy5fY2FjaGUuZ2V0KGtleSk7IC8vIFJlZ2lzdGVyIGNhY2hlIGhpdFxuXG5cbiAgICAgIGlmICh0aGlzLl9jYWNoZS5oYXMoa2V5KSkge1xuICAgICAgICB0aGlzLnJlbW92ZShrZXkpO1xuXG4gICAgICAgIHRoaXMuX2NhY2hlLnNldChrZXksIHZhbHVlKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHZhbHVlO1xuICAgIH07XG5cbiAgICBfcHJvdG8ucmVtb3ZlID0gZnVuY3Rpb24gcmVtb3ZlKGtleSkge1xuICAgICAgdGhpcy5fY2FjaGVbXCJkZWxldGVcIl0oa2V5KTtcbiAgICB9O1xuXG4gICAgX3Byb3RvLmNsZWFyID0gZnVuY3Rpb24gY2xlYXIoKSB7XG4gICAgICB0aGlzLl9jYWNoZS5jbGVhcigpO1xuICAgIH07XG5cbiAgICByZXR1cm4gTHJ1TWFwQ2FjaGU7XG4gIH0oKTtcblxuICBleHBvcnRzLkZpZm9NYXBDYWNoZSA9IEZpZm9NYXBDYWNoZTtcbiAgZXhwb3J0cy5GaWZvT2JqZWN0Q2FjaGUgPSBGaWZvT2JqZWN0Q2FjaGU7XG4gIGV4cG9ydHMuRmxhdE1hcENhY2hlID0gRmxhdE1hcENhY2hlO1xuICBleHBvcnRzLkZsYXRPYmplY3RDYWNoZSA9IEZsYXRPYmplY3RDYWNoZTtcbiAgZXhwb3J0cy5McnVNYXBDYWNoZSA9IExydU1hcENhY2hlO1xuICBleHBvcnRzLkxydU9iamVjdENhY2hlID0gTHJ1T2JqZWN0Q2FjaGU7XG4gIGV4cG9ydHMuY3JlYXRlQ2FjaGVkU2VsZWN0b3IgPSBjcmVhdGVDYWNoZWRTZWxlY3RvcjtcbiAgZXhwb3J0cy5jcmVhdGVTdHJ1Y3R1cmVkQ2FjaGVkU2VsZWN0b3IgPSBjcmVhdGVTdHJ1Y3R1cmVkQ2FjaGVkU2VsZWN0b3I7XG4gIGV4cG9ydHMuZGVmYXVsdCA9IGNyZWF0ZUNhY2hlZFNlbGVjdG9yO1xuXG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHsgdmFsdWU6IHRydWUgfSk7XG5cbn0pKSk7XG4vLyMgc291cmNlTWFwcGluZ1VSTD1pbmRleC5qcy5tYXBcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./node_modules/re-reselect/dist/index.js\\n\");\n \n /***/ }),\n \n@@ -4450,7 +4472,7 @@ eval(\"\\n\\nexports.__esModule = true;\\nexports.defaultMemoize = defaultMemoize;\\n\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _Session__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Session */ \\\"./src/Session.js\\\");\\n/* harmony import */ var _QuerySet__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./QuerySet */ \\\"./src/QuerySet.js\\\");\\n/* harmony import */ var _fields__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./fields */ \\\"./src/fields/index.js\\\");\\n/* harmony import */ var _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./fields/ForeignKey */ \\\"./src/fields/ForeignKey.js\\\");\\n/* harmony import */ var _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./fields/ManyToMany */ \\\"./src/fields/ManyToMany.js\\\");\\n/* harmony import */ var _fields_OneToOne__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./fields/OneToOne */ \\\"./src/fields/OneToOne.js\\\");\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./constants */ \\\"./src/constants.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n/**\\n * Generates a query specification to get the instance's\\n * corresponding table row using its primary key.\\n *\\n * @private\\n * @returns {Object}\\n */\\n\\nfunction getByIdQuery(modelInstance) {\\n  const modelClass = modelInstance.getClass();\\n  const {\\n    idAttribute,\\n    modelName\\n  } = modelClass;\\n  return {\\n    table: modelName,\\n    clauses: [{\\n      type: _constants__WEBPACK_IMPORTED_MODULE_7__[\\\"FILTER\\\"],\\n      payload: {\\n        [idAttribute]: modelInstance.getId()\\n      }\\n    }]\\n  };\\n}\\n/**\\n * The heart of an ORM, the data model.\\n *\\n * The fields you specify to the Model will be used to generate\\n * a schema to the database, related property accessors, and\\n * possibly through models.\\n *\\n * In each {@link Session} you instantiate from an {@link ORM} instance,\\n * you will receive a session-specific subclass of this Model. The methods\\n * you define here will be available to you in sessions.\\n *\\n * An instance of {@link Model} represents a record in the database, though\\n * it is possible to generate multiple instances from the same record in the database.\\n *\\n * To create data models in your schema, subclass {@link Model}. To define\\n * information about the data model, override static class methods. Define instance\\n * logic by defining prototype methods (without `static` keyword).\\n */\\n\\n\\nconst Model = /*#__PURE__*/function () {\\n  /**\\n   * Creates a Model instance from it's properties.\\n   * Don't use this to create a new record; Use the static method {@link Model#create}.\\n   * @param  {Object} props - the properties to instantiate with\\n   */\\n  function Model(props) {\\n    this._initFields(props);\\n  }\\n\\n  var _proto = Model.prototype;\\n\\n  _proto._initFields = function _initFields(props) {\\n    const propsObj = Object(props);\\n    this._fields = { ...propsObj\\n    };\\n    Object.keys(propsObj).forEach(fieldName => {\\n      // In this case, we got a prop that wasn't defined as a field.\\n      // Assuming it's an arbitrary data field, making an instance-specific\\n      // descriptor for it.\\n      // Using the in operator as the property could be defined anywhere\\n      // on the prototype chain.\\n      if (!(fieldName in this)) {\\n        Object.defineProperty(this, fieldName, {\\n          get: () => this._fields[fieldName],\\n          set: value => this.set(fieldName, value),\\n          configurable: true,\\n          enumerable: true\\n        });\\n      }\\n    });\\n  };\\n\\n  Model.toString = function toString() {\\n    return `ModelClass: ${this.modelName}`;\\n  }\\n  /**\\n   * Returns the options object passed to the database for the table that represents\\n   * this Model class.\\n   *\\n   * Returns an empty object by default, which means the database\\n   * will use default options. You can either override this function to return the options\\n   * you want to use, or assign the options object as a static property of the same name to the\\n   * Model class.\\n   *\\n   * @return {Object} the options object passed to the database for the table\\n   *                  representing this Model class.\\n   */\\n  ;\\n\\n  Model.options = function options() {\\n    return {};\\n  }\\n  /**\\n   * Manually mark individual instances as accessed.\\n   * This allows invalidating selector memoization within mutable sessions.\\n   *\\n   * @param {Array.<*>} ids - Array of primary key values\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  Model.markAccessed = function markAccessed(ids) {\\n    if (typeof this._session === \\\"undefined\\\") {\\n      throw new Error([`Tried to mark rows of the ${this.modelName} model as accessed without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].markAccessed\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    this.session.markAccessed(this.modelName, ids);\\n  }\\n  /**\\n   * Manually mark this model's table as scanned.\\n   * This allows invalidating selector memoization within mutable sessions.\\n   *\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  Model.markFullTableScanned = function markFullTableScanned() {\\n    if (typeof this._session === \\\"undefined\\\") {\\n      throw new Error([`Tried to mark the ${this.modelName} model as full table scanned without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].markFullTableScanned\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    this.session.markFullTableScanned(this.modelName);\\n  }\\n  /**\\n   * Manually mark indexes as accessed.\\n   * This allows invalidating selector memoization within mutable sessions.\\n   *\\n   * @param {Array.<Array.<*,*>>} indexes - Array of column-value pairs\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  Model.markAccessedIndexes = function markAccessedIndexes(indexes) {\\n    if (typeof this._session === \\\"undefined\\\") {\\n      throw new Error([`Tried to mark indexes for the ${this.modelName} model as accessed without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].markAccessedIndexes\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    this.session.markAccessedIndexes(indexes.map(([attribute, value]) => [this.modelName, attribute, value]));\\n  }\\n  /**\\n   * Returns the id attribute of this {@link Model}.\\n   *\\n   * @return {string} The id attribute of this {@link Model}.\\n   */\\n  ;\\n\\n  /**\\n   * Connect the model class to a {@link Session}.\\n   *\\n   * @private\\n   * @param  {Session} session - The session to connect to.\\n   */\\n  Model.connect = function connect(session) {\\n    if (!(session instanceof _Session__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"])) {\\n      throw new Error(\\\"A model can only be connected to instances of Session.\\\");\\n    }\\n\\n    this._session = session;\\n  }\\n  /**\\n   * Get the current {@link Session} instance.\\n   *\\n   * @private\\n   * @return {Session} The current {@link Session} instance.\\n   */\\n  ;\\n\\n  /**\\n   * Returns an instance of the model's `querySetClass` field.\\n   * By default, this will be an empty {@link QuerySet}.\\n   *\\n   * @return {Object} An instance of the model's `querySetClass`.\\n   */\\n  Model.getQuerySet = function getQuerySet() {\\n    const {\\n      querySetClass: QuerySetClass\\n    } = this;\\n    return new QuerySetClass(this);\\n  }\\n  /**\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  Model.invalidateClassCache = function invalidateClassCache() {\\n    this.isSetUp = undefined;\\n    this.virtualFields = {};\\n  }\\n  /**\\n   * @see {@link Model.getQuerySet}\\n   */\\n  ;\\n\\n  /**\\n   * Returns parameters to be passed to {@link Table} instance.\\n   *\\n   * @private\\n   */\\n  Model.tableOptions = function tableOptions() {\\n    if (typeof this.backend === \\\"function\\\") {\\n      Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"warnDeprecated\\\"])(\\\"`Model.backend` has been deprecated. Please rename to `.options`.\\\");\\n      return this.backend();\\n    }\\n\\n    if (this.backend) {\\n      Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"warnDeprecated\\\"])(\\\"`Model.backend` has been deprecated. Please rename to `.options`.\\\");\\n      return this.backend;\\n    }\\n\\n    if (typeof this.options === \\\"function\\\") {\\n      return this.options();\\n    }\\n\\n    return this.options;\\n  }\\n  /**\\n   * Creates a new record in the database, instantiates a {@link Model} and returns it.\\n   *\\n   * If you pass values for many-to-many fields, instances are created on the through\\n   * model as well.\\n   *\\n   * @param  {Object} userProps - the new {@link Model}'s properties.\\n   * @return {Model} a new {@link Model} instance.\\n   */\\n  ;\\n\\n  Model.create = function create(userProps) {\\n    if (typeof this._session === \\\"undefined\\\") {\\n      throw new Error([`Tried to create a ${this.modelName} model instance without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].create\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    const props = { ...userProps\\n    };\\n    const m2mRelations = {};\\n    const declaredFieldNames = Object.keys(this.fields);\\n    const declaredVirtualFieldNames = Object.keys(this.virtualFields);\\n    declaredFieldNames.forEach(key => {\\n      const field = this.fields[key];\\n      const valuePassed = userProps.hasOwnProperty(key);\\n\\n      if (!(field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"])) {\\n        if (valuePassed) {\\n          const value = userProps[key];\\n          props[key] = Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"normalizeEntity\\\"])(value);\\n        } else if (field.getDefault) {\\n          props[key] = field.getDefault();\\n        }\\n      } else if (valuePassed) {\\n        // Save for later processing\\n        m2mRelations[key] = userProps[key];\\n\\n        if (!field.as) {\\n          /**\\n           * The relationship does not have an accessor\\n           * Discard the value from props as the field will be populated later with instances\\n           * from the target models when refreshing the M2M relations.\\n           * If the relationship does have an accessor (`as`) field then we do want to keep this\\n           * original value in the props to expose the raw list of IDs from the instance.\\n           */\\n          delete props[key];\\n        }\\n      }\\n    }); // add backward many-many if required\\n\\n    declaredVirtualFieldNames.forEach(key => {\\n      if (!m2mRelations.hasOwnProperty(key)) {\\n        const field = this.virtualFields[key];\\n\\n        if (userProps.hasOwnProperty(key) && field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n          // If a value is supplied for a ManyToMany field,\\n          // discard them from props and save for later processing.\\n          m2mRelations[key] = userProps[key];\\n          delete props[key];\\n        }\\n      }\\n    });\\n    const newEntry = this.session.applyUpdate({\\n      action: _constants__WEBPACK_IMPORTED_MODULE_7__[\\\"CREATE\\\"],\\n      table: this.modelName,\\n      payload: props\\n    });\\n    const ThisModel = this;\\n    const instance = new ThisModel(newEntry);\\n\\n    instance._refreshMany2Many(m2mRelations); // eslint-disable-line no-underscore-dangle\\n\\n\\n    return instance;\\n  }\\n  /**\\n   * Creates a new or update existing record in the database, instantiates a {@link Model} and returns it.\\n   *\\n   * If you pass values for many-to-many fields, instances are created on the through\\n   * model as well.\\n   *\\n   * @param  {Object} userProps - the required {@link Model}'s properties.\\n   * @return {Model} a {@link Model} instance.\\n   */\\n  ;\\n\\n  Model.upsert = function upsert(userProps) {\\n    if (typeof this.session === \\\"undefined\\\") {\\n      throw new Error([`Tried to upsert a ${this.modelName} model instance without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].upsert\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    const {\\n      idAttribute\\n    } = this;\\n\\n    if (userProps.hasOwnProperty(idAttribute)) {\\n      const id = userProps[idAttribute];\\n\\n      if (this.idExists(id)) {\\n        const model = this.withId(id);\\n        model.update(userProps);\\n        return model;\\n      }\\n    }\\n\\n    return this.create(userProps);\\n  }\\n  /**\\n   * Returns a {@link Model} instance for the object with id `id`.\\n   * Returns `null` if the model has no instance with id `id`.\\n   *\\n   * You can use {@link Model#idExists} to check for existence instead.\\n   *\\n   * @param  {*} id - the `id` of the object to get\\n   * @throws If object with id `id` doesn't exist\\n   * @return {Model|null} {@link Model} instance with id `id`\\n   */\\n  ;\\n\\n  Model.withId = function withId(id) {\\n    return this.get({\\n      [this.idAttribute]: id\\n    });\\n  }\\n  /**\\n   * Returns a boolean indicating if an entity\\n   * with the id `id` exists in the state.\\n   *\\n   * @param  {*}  id - a value corresponding to the id attribute of the {@link Model} class.\\n   * @return {Boolean} a boolean indicating if entity with `id` exists in the state\\n   *\\n   * @since 0.11.0\\n   */\\n  ;\\n\\n  Model.idExists = function idExists(id) {\\n    return this.exists({\\n      [this.idAttribute]: id\\n    });\\n  }\\n  /**\\n   * Returns a boolean indicating if an entity\\n   * with the given props exists in the state.\\n   *\\n   * @param  {*}  props - a key-value that {@link Model} instances should have to be considered as existing.\\n   * @return {Boolean} a boolean indicating if entity with `props` exists in the state\\n   */\\n  ;\\n\\n  Model.exists = function exists(lookupObj) {\\n    if (typeof this.session === \\\"undefined\\\") {\\n      throw new Error([`Tried to check if a ${this.modelName} model instance exists without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].exists\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    return Boolean(this._findDatabaseRows(lookupObj).length);\\n  }\\n  /**\\n   * Gets the {@link Model} instance that matches properties in `lookupObj`.\\n   * Throws an error if {@link Model} if multiple records match\\n   * the properties.\\n   *\\n   * @param  {Object} lookupObj - the properties used to match a single entity.\\n   * @throws {Error} If more than one entity matches the properties in `lookupObj`.\\n   * @return {Model} a {@link Model} instance that matches the properties in `lookupObj`.\\n   */\\n  ;\\n\\n  Model.get = function get(lookupObj) {\\n    const ThisModel = this;\\n\\n    const rows = this._findDatabaseRows(lookupObj);\\n\\n    if (rows.length === 0) {\\n      return null;\\n    }\\n\\n    if (rows.length > 1) {\\n      throw new Error(`Expected to find a single row in \\\\`${this.modelName}.get\\\\`. Found ${rows.length}.`);\\n    }\\n\\n    return new ThisModel(rows[0]);\\n  }\\n  /**\\n   * Gets the {@link Model} class or subclass constructor (the class that\\n   * instantiated this instance).\\n   *\\n   * @return {Model} The {@link Model} class or subclass constructor used to instantiate\\n   *                 this instance.\\n   */\\n  ;\\n\\n  _proto.getClass = function getClass() {\\n    return this.constructor;\\n  }\\n  /**\\n   * Gets the id value of the current instance by looking up the id attribute.\\n   * @return {*} The id value of the current instance.\\n   */\\n  ;\\n\\n  _proto.getId = function getId() {\\n    return this._fields[this.getClass().idAttribute];\\n  }\\n  /**\\n   * Returns a reference to the plain JS object in the store.\\n   * It contains all the properties that you pass when creating the model,\\n   * except for primary keys of many-to-many relationships with a custom accessor.\\n   *\\n   * Make sure never to mutate this.\\n   *\\n   * @return {Object} a reference to the plain JS object in the store\\n   */\\n  ;\\n\\n  /**\\n   * Finds all rows in this model's table that match the given `lookupObj`.\\n   * If no `lookupObj` is passed, all rows in the model's table will be returned.\\n   *\\n   * @param  {*}  props - a key-value that {@link Model} instances should have to be considered as existing.\\n   * @return {Boolean} a boolean indicating if entity with `props` exists in the state\\n   * @private\\n   */\\n  Model._findDatabaseRows = function _findDatabaseRows(lookupObj) {\\n    const querySpec = {\\n      table: this.modelName\\n    };\\n\\n    if (lookupObj) {\\n      querySpec.clauses = [{\\n        type: _constants__WEBPACK_IMPORTED_MODULE_7__[\\\"FILTER\\\"],\\n        payload: lookupObj\\n      }];\\n    }\\n\\n    return this.session.query(querySpec).rows;\\n  }\\n  /**\\n   * Returns a string representation of the {@link Model} instance.\\n   *\\n   * @return {string} A string representation of this {@link Model} instance.\\n   */\\n  ;\\n\\n  _proto.toString = function toString() {\\n    const ThisModel = this.getClass();\\n    const className = ThisModel.modelName;\\n    const fieldNames = Object.keys(ThisModel.fields);\\n    const fields = fieldNames.map(fieldName => {\\n      const field = ThisModel.fields[fieldName];\\n\\n      if (field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n        const ids = this[fieldName].toModelArray().map(model => model.getId());\\n        return `${fieldName}: [${ids.join(\\\", \\\")}]`;\\n      }\\n\\n      const val = this._fields[fieldName];\\n      return `${fieldName}: ${val}`;\\n    }).join(\\\", \\\");\\n    return `${className}: {${fields}}`;\\n  }\\n  /**\\n   * Returns a boolean indicating if `otherModel` equals this {@link Model} instance.\\n   * Equality is determined by shallow comparing their attributes.\\n   *\\n   * This equality is used when you call {@link Model#update}.\\n   * You can prevent model updates by returning `true` here.\\n   * However, a model will always be updated if its relationships are changed.\\n   *\\n   * @param  {Model} otherModel - a {@link Model} instance to compare\\n   * @return {Boolean} a boolean indicating if the {@link Model} instance's are equal.\\n   */\\n  ;\\n\\n  _proto.equals = function equals(otherModel) {\\n    // eslint-disable-next-line no-underscore-dangle\\n    return Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"objectShallowEquals\\\"])(this._fields, otherModel._fields);\\n  }\\n  /**\\n   * Updates a property name to given value for this {@link Model} instance.\\n   * The values are immediately committed to the database.\\n   *\\n   * @param {string} propertyName - name of the property to set\\n   * @param {*} value - value assigned to the property\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.set = function set(propertyName, value) {\\n    this.update({\\n      [propertyName]: value\\n    });\\n  }\\n  /**\\n   * Assigns multiple fields and corresponding values to this {@link Model} instance.\\n   * The updates are immediately committed to the database.\\n   *\\n   * @param  {Object} userMergeObj - an object that will be merged with this instance.\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.update = function update(userMergeObj) {\\n    const ThisModel = this.getClass();\\n\\n    if (typeof ThisModel.session === \\\"undefined\\\") {\\n      throw new Error([`Tried to update a ${ThisModel.modelName} model instance without a session. `, \\\"You cannot call `.update` on an instance that you did not receive from the database.\\\"].join(\\\"\\\"));\\n    }\\n\\n    const mergeObj = { ...userMergeObj\\n    };\\n    const {\\n      fields,\\n      virtualFields\\n    } = ThisModel;\\n    const m2mRelations = {}; // If an array of entities or id's is supplied for a\\n    // many-to-many related field, clear the old relations\\n    // and add the new ones.\\n    // eslint-disable-next-line guard-for-in, no-restricted-syntax\\n\\n    for (const mergeKey in mergeObj) {\\n      const isRealField = fields.hasOwnProperty(mergeKey);\\n\\n      if (isRealField) {\\n        const field = fields[mergeKey];\\n\\n        if (field instanceof _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"] || field instanceof _fields_OneToOne__WEBPACK_IMPORTED_MODULE_6__[\\\"default\\\"]) {\\n          // update one-one/fk relations\\n          mergeObj[mergeKey] = Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"normalizeEntity\\\"])(mergeObj[mergeKey]);\\n        } else if (field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n          // field is forward relation\\n          m2mRelations[mergeKey] = mergeObj[mergeKey];\\n\\n          if (!field.as) {\\n            /**\\n             * The relationship does not have an accessor\\n             * Discard the value from props as the field will be populated later with instances\\n             * from the target models when refreshing the M2M relations.\\n             * If the relationship does have an accessor (`as`) field then we do want to keep this\\n             * original value in the props to expose the raw list of IDs from the instance.\\n             */\\n            delete mergeObj[mergeKey];\\n          }\\n        }\\n      } else if (virtualFields.hasOwnProperty(mergeKey)) {\\n        const field = virtualFields[mergeKey];\\n\\n        if (field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n          // field is backward relation\\n          m2mRelations[mergeKey] = mergeObj[mergeKey];\\n          delete mergeObj[mergeKey];\\n        }\\n      }\\n    }\\n\\n    const mergedFields = { ...this._fields,\\n      ...mergeObj\\n    };\\n    const updatedModel = new ThisModel(mergedFields); // only update fields if they have changed (referentially)\\n\\n    if (!this.equals(updatedModel)) {\\n      this._initFields(mergedFields);\\n\\n      ThisModel.session.applyUpdate({\\n        action: _constants__WEBPACK_IMPORTED_MODULE_7__[\\\"UPDATE\\\"],\\n        query: getByIdQuery(this),\\n        payload: mergeObj\\n      });\\n    } // update virtual fields\\n\\n\\n    this._refreshMany2Many(m2mRelations);\\n  }\\n  /**\\n   * Updates {@link Model} instance attributes to reflect the\\n   * database state in the current session.\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.refreshFromState = function refreshFromState() {\\n    this._initFields(this.ref);\\n  }\\n  /**\\n   * Deletes the record for this {@link Model} instance.\\n   * You'll still be able to access fields and values on the instance.\\n   *\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.delete = function _delete() {\\n    const ThisModel = this.getClass();\\n\\n    if (typeof ThisModel.session === \\\"undefined\\\") {\\n      throw new Error([`Tried to delete a ${ThisModel.modelName} model instance without a session. `, \\\"You cannot call `.delete` on an instance that you did not receive from the database.\\\"].join(\\\"\\\"));\\n    }\\n\\n    this._onDelete();\\n\\n    ThisModel.session.applyUpdate({\\n      action: _constants__WEBPACK_IMPORTED_MODULE_7__[\\\"DELETE\\\"],\\n      query: getByIdQuery(this)\\n    });\\n  }\\n  /**\\n   * Update many-many relations for model.\\n   * @param relations\\n   * @return undefined\\n   * @private\\n   */\\n  ;\\n\\n  _proto._refreshMany2Many = function _refreshMany2Many(relations) {\\n    const ThisModel = this.getClass();\\n    const {\\n      fields,\\n      virtualFields,\\n      modelName\\n    } = ThisModel;\\n    Object.keys(relations).forEach(name => {\\n      const reverse = !fields.hasOwnProperty(name);\\n      const field = virtualFields[name];\\n      const values = relations[name];\\n\\n      if (!Array.isArray(values)) {\\n        throw new TypeError(`Failed to resolve many-to-many relationship: ${modelName}[${name}] must be an array (passed: ${values})`);\\n      }\\n\\n      const normalizedNewIds = values.map(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"normalizeEntity\\\"]);\\n      const uniqueIds = [...new Set(normalizedNewIds)];\\n\\n      if (normalizedNewIds.length !== uniqueIds.length) {\\n        throw new Error(`Found duplicate id(s) when passing \\\"${normalizedNewIds}\\\" to ${ThisModel.modelName}.${name} value`);\\n      }\\n\\n      const throughModelName = field.through || Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"m2mName\\\"])(ThisModel.modelName, name);\\n      const ThroughModel = ThisModel.session[throughModelName];\\n      let fromField;\\n      let toField;\\n\\n      if (!reverse) {\\n        ({\\n          from: fromField,\\n          to: toField\\n        } = field.throughFields);\\n      } else {\\n        ({\\n          from: toField,\\n          to: fromField\\n        } = field.throughFields);\\n      }\\n\\n      const currentIds = ThroughModel.filter(through => through[fromField] === this[ThisModel.idAttribute]).toRefArray().map(ref => ref[toField]);\\n      const diffActions = Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"arrayDiffActions\\\"])(currentIds, normalizedNewIds);\\n\\n      if (diffActions) {\\n        const {\\n          delete: idsToDelete,\\n          add: idsToAdd\\n        } = diffActions;\\n\\n        if (idsToDelete.length > 0) {\\n          this[field.as || name].remove(...idsToDelete);\\n        }\\n\\n        if (idsToAdd.length > 0) {\\n          this[field.as || name].add(...idsToAdd);\\n        }\\n      }\\n    });\\n  }\\n  /**\\n   * @return {undefined}\\n   * @private\\n   */\\n  ;\\n\\n  _proto._onDelete = function _onDelete() {\\n    const {\\n      virtualFields\\n    } = this.getClass(); // eslint-disable-next-line guard-for-in, no-restricted-syntax\\n\\n    for (const key in virtualFields) {\\n      const field = virtualFields[key];\\n\\n      if (field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n        // Delete any many-to-many rows the entity is included in.\\n        const descriptorKey = field.as || key;\\n        this[descriptorKey].clear();\\n      } else if (field instanceof _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"]) {\\n        const relatedQs = this[key];\\n\\n        if (relatedQs.exists()) {\\n          relatedQs.update({\\n            [field.relatedName]: null\\n          });\\n        }\\n      } else if (field instanceof _fields_OneToOne__WEBPACK_IMPORTED_MODULE_6__[\\\"default\\\"]) {\\n        // Set null to any foreign keys or one to ones pointed to\\n        // this instance.\\n        if (this[key] !== null) {\\n          this[key][field.relatedName] = null;\\n        }\\n      }\\n    }\\n  } // DEPRECATED AND REMOVED METHODS\\n\\n  /**\\n   * Returns a boolean indicating if an entity\\n   * with the id `id` exists in the state.\\n   *\\n   * @param  {*}  id - a value corresponding to the id attribute of the {@link Model} class.\\n   * @return {Boolean} a boolean indicating if entity with `id` exists in the state\\n   * @deprecated Please use {@link Model.idExists} instead.\\n   */\\n  ;\\n\\n  Model.hasId = function hasId(id) {\\n    console.warn(\\\"`Model.hasId` has been deprecated. Please use `Model.idExists` instead.\\\");\\n    return this.idExists(id);\\n  }\\n  /**\\n   * @deprecated See the 0.9 migration guide on the GitHub repo.\\n   * @throws {Error} Due to deprecation.\\n   */\\n  ;\\n\\n  _proto.getNextState = function getNextState() {\\n    throw new Error(\\\"`Model.prototype.getNextState` has been removed. See the 0.9 \\\" + \\\"migration guide on the GitHub repo.\\\");\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(Model, [{\\n    key: \\\"ref\\\",\\n    get: function () {\\n      const ThisModel = this.getClass(); // eslint-disable-next-line no-underscore-dangle\\n\\n      return ThisModel._findDatabaseRows({\\n        [ThisModel.idAttribute]: this.getId()\\n      })[0];\\n    }\\n  }], [{\\n    key: \\\"idAttribute\\\",\\n    get: function () {\\n      if (typeof this._session === \\\"undefined\\\") {\\n        throw new Error([`Tried to get the ${this.modelName} model's id attribute without a session. `, \\\"Create a session using `session = orm.session()` and access \\\", `\\\\`session[\\\"${this.modelName}\\\"].idAttribute\\\\` instead.`].join(\\\"\\\"));\\n      }\\n\\n      return this.session.db.describe(this.modelName).idAttribute;\\n    }\\n  }, {\\n    key: \\\"session\\\",\\n    get: function () {\\n      return this._session;\\n    }\\n  }, {\\n    key: \\\"query\\\",\\n    get: function () {\\n      return this.getQuerySet();\\n    }\\n  }]);\\n\\n  return Model;\\n}();\\n\\nModel.fields = {\\n  id: Object(_fields__WEBPACK_IMPORTED_MODULE_3__[\\\"attr\\\"])()\\n};\\nModel.virtualFields = {};\\nModel.querySetClass = _QuerySet__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"];\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (Model);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9Nb2RlbC5qcz9mYzVkIl0sIm5hbWVzIjpbImdldEJ5SWRRdWVyeSIsIm1vZGVsSW5zdGFuY2UiLCJtb2RlbENsYXNzIiwiZ2V0Q2xhc3MiLCJpZEF0dHJpYnV0ZSIsIm1vZGVsTmFtZSIsInRhYmxlIiwiY2xhdXNlcyIsInR5cGUiLCJGSUxURVIiLCJwYXlsb2FkIiwiZ2V0SWQiLCJNb2RlbCIsInByb3BzIiwiX2luaXRGaWVsZHMiLCJwcm9wc09iaiIsIk9iamVjdCIsIl9maWVsZHMiLCJrZXlzIiwiZm9yRWFjaCIsImZpZWxkTmFtZSIsImRlZmluZVByb3BlcnR5IiwiZ2V0Iiwic2V0IiwidmFsdWUiLCJjb25maWd1cmFibGUiLCJlbnVtZXJhYmxlIiwidG9TdHJpbmciLCJvcHRpb25zIiwibWFya0FjY2Vzc2VkIiwiaWRzIiwiX3Nlc3Npb24iLCJFcnJvciIsImpvaW4iLCJzZXNzaW9uIiwibWFya0Z1bGxUYWJsZVNjYW5uZWQiLCJtYXJrQWNjZXNzZWRJbmRleGVzIiwiaW5kZXhlcyIsIm1hcCIsImF0dHJpYnV0ZSIsImNvbm5lY3QiLCJTZXNzaW9uIiwiZ2V0UXVlcnlTZXQiLCJxdWVyeVNldENsYXNzIiwiUXVlcnlTZXRDbGFzcyIsImludmFsaWRhdGVDbGFzc0NhY2hlIiwiaXNTZXRVcCIsInVuZGVmaW5lZCIsInZpcnR1YWxGaWVsZHMiLCJ0YWJsZU9wdGlvbnMiLCJiYWNrZW5kIiwid2FybkRlcHJlY2F0ZWQiLCJjcmVhdGUiLCJ1c2VyUHJvcHMiLCJtMm1SZWxhdGlvbnMiLCJkZWNsYXJlZEZpZWxkTmFtZXMiLCJmaWVsZHMiLCJkZWNsYXJlZFZpcnR1YWxGaWVsZE5hbWVzIiwia2V5IiwiZmllbGQiLCJ2YWx1ZVBhc3NlZCIsImhhc093blByb3BlcnR5IiwiTWFueVRvTWFueSIsIm5vcm1hbGl6ZUVudGl0eSIsImdldERlZmF1bHQiLCJhcyIsIm5ld0VudHJ5IiwiYXBwbHlVcGRhdGUiLCJhY3Rpb24iLCJDUkVBVEUiLCJUaGlzTW9kZWwiLCJpbnN0YW5jZSIsIl9yZWZyZXNoTWFueTJNYW55IiwidXBzZXJ0IiwiaWQiLCJpZEV4aXN0cyIsIm1vZGVsIiwid2l0aElkIiwidXBkYXRlIiwiZXhpc3RzIiwibG9va3VwT2JqIiwiQm9vbGVhbiIsIl9maW5kRGF0YWJhc2VSb3dzIiwibGVuZ3RoIiwicm93cyIsImNvbnN0cnVjdG9yIiwicXVlcnlTcGVjIiwicXVlcnkiLCJjbGFzc05hbWUiLCJmaWVsZE5hbWVzIiwidG9Nb2RlbEFycmF5IiwidmFsIiwiZXF1YWxzIiwib3RoZXJNb2RlbCIsIm9iamVjdFNoYWxsb3dFcXVhbHMiLCJwcm9wZXJ0eU5hbWUiLCJ1c2VyTWVyZ2VPYmoiLCJtZXJnZU9iaiIsIm1lcmdlS2V5IiwiaXNSZWFsRmllbGQiLCJGb3JlaWduS2V5IiwiT25lVG9PbmUiLCJtZXJnZWRGaWVsZHMiLCJ1cGRhdGVkTW9kZWwiLCJVUERBVEUiLCJyZWZyZXNoRnJvbVN0YXRlIiwicmVmIiwiZGVsZXRlIiwiX29uRGVsZXRlIiwiREVMRVRFIiwicmVsYXRpb25zIiwibmFtZSIsInJldmVyc2UiLCJ2YWx1ZXMiLCJBcnJheSIsImlzQXJyYXkiLCJUeXBlRXJyb3IiLCJub3JtYWxpemVkTmV3SWRzIiwidW5pcXVlSWRzIiwiU2V0IiwidGhyb3VnaE1vZGVsTmFtZSIsInRocm91Z2giLCJtMm1OYW1lIiwiVGhyb3VnaE1vZGVsIiwiZnJvbUZpZWxkIiwidG9GaWVsZCIsImZyb20iLCJ0byIsInRocm91Z2hGaWVsZHMiLCJjdXJyZW50SWRzIiwiZmlsdGVyIiwidG9SZWZBcnJheSIsImRpZmZBY3Rpb25zIiwiYXJyYXlEaWZmQWN0aW9ucyIsImlkc1RvRGVsZXRlIiwiYWRkIiwiaWRzVG9BZGQiLCJyZW1vdmUiLCJkZXNjcmlwdG9yS2V5IiwiY2xlYXIiLCJyZWxhdGVkUXMiLCJyZWxhdGVkTmFtZSIsImhhc0lkIiwiY29uc29sZSIsIndhcm4iLCJnZXROZXh0U3RhdGUiLCJkYiIsImRlc2Ny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Q0Ysa0VBQ0kscUNBRkYsQ0FBTjtBQUlILEdBNXVCTTs7QUFBQTtBQUFBO0FBQUEscUJBNGFHO0FBQ04sWUFBTXNDLFNBQVMsR0FBRyxLQUFLbkUsUUFBTCxFQUFsQixDQURNLENBR047O0FBQ0EsYUFBT21FLFNBQVMsQ0FBQ1ksaUJBQVYsQ0FBNEI7QUFDL0IsU0FBQ1osU0FBUyxDQUFDbEUsV0FBWCxHQUF5QixLQUFLTyxLQUFMO0FBRE0sT0FBNUIsRUFFSixDQUZJLENBQVA7QUFHSDtBQW5iTTtBQUFBO0FBQUEscUJBeUhrQjtBQUNyQixVQUFJLE9BQU8sS0FBS29CLFFBQVosS0FBeUIsV0FBN0IsRUFBMEM7QUFDdEMsY0FBTSxJQUFJQyxLQUFKLENBQ0YsQ0FDSyxvQkFBbUIsS0FBSzNCLFNBQVUsMkNBRHZDLEVBRUksOERBRkosRUFHSyxjQUFhLEtBQUtBLFNBQVUsMkJBSGpDLEVBSUU0QixJQUpGLENBSU8sRUFKUCxDQURFLENBQU47QUFPSDs7QUFDRCxhQUFPLEtBQUtDLE9BQUwsQ0FBYWdILEVBQWIsQ0FBZ0JDLFFBQWhCLENBQXlCLEtBQUs5SSxTQUE5QixFQUF5Q0QsV0FBaEQ7QUFDSDtBQXBJTTtBQUFBO0FBQUEscUJBMkpjO0FBQ2pCLGFBQU8sS0FBSzJCLFFBQVo7QUFDSDtBQTdKTTtBQUFBO0FBQUEscUJBcUxZO0FBQ2YsYUFBTyxLQUFLVyxXQUFMLEVBQVA7QUFDSDtBQXZMTTs7QUFBQTtBQUFBLEdBQVg7O0FBK3VCQTlCLEtBQUssQ0FBQzRDLE1BQU4sR0FBZTtBQUNYa0IsSUFBRSxFQUFFMEUsb0RBQUk7QUFERyxDQUFmO0FBR0F4SSxLQUFLLENBQUNvQyxhQUFOLEdBQXNCLEVBQXRCO0FBQ0FwQyxLQUFLLENBQUMrQixhQUFOLEdBQXNCMEcsaURBQXRCO0FBRWV6SSxvRUFBZiIsImZpbGUiOiIuL3NyYy9Nb2RlbC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBTZXNzaW9uIGZyb20gXCIuL1Nlc3Npb25cIjtcbmltcG9ydCBRdWVyeVNldCBmcm9tIFwiLi9RdWVyeVNldFwiO1xuXG5pbXBvcnQgeyBhdHRyIH0gZnJvbSBcIi4vZmllbGRzXCI7XG5pbXBvcnQgRm9yZWlnbktleSBmcm9tIFwiLi9maWVsZHMvRm9yZWlnbktleVwiO1xuaW1wb3J0IE1hbnlUb01hbnkgZnJvbSBcIi4vZmllbGRzL01hbnlUb01hbnlcIjtcbmltcG9ydCBPbmVUb09uZSBmcm9tIFwiLi9maWVsZHMvT25lVG9PbmVcIjtcblxuaW1wb3J0IHsgQ1JFQVRFLCBVUERBVEUsIERFTEVURSwgRklMVEVSIH0gZnJvbSBcIi4vY29uc3RhbnRzXCI7XG5pbXBvcnQge1xuICAgIG5vcm1hbGl6ZUVudGl0eSxcbiAgICBhcnJheURpZmZBY3Rpb25zLFxuICAgIG9iamVjdFNoYWxsb3dFcXVhbHMsXG4gICAgd2FybkRlcHJlY2F0ZWQsXG4gICAgbTJtTmFtZSxcbn0gZnJvbSBcIi4vdXRpbHNcIjtcblxuLyoqXG4gKiBHZW5lcmF0ZXMgYSBxdWVyeSBzcGVjaWZpY2F0aW9uIHRvIGdldCB0aGUgaW5zdGFuY2Unc1xuICogY29ycmVzcG9uZGluZyB0YWJsZSByb3cgdXNpbmcgaXRzIHByaW1hcnkga2V5LlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcmV0dXJucyB7T2JqZWN0fVxuICovXG5mdW5jdGlvbiBnZXRCeUlkUXVlcnkobW9kZWxJbnN0YW5jZSkge1xuICAgIGNvbnN0IG1vZGVsQ2xhc3MgPSBtb2RlbEluc3RhbmNlLmdldENsYXNzKCk7XG4gICAgY29uc3QgeyBpZEF0dHJpYnV0ZSwgbW9kZWxOYW1lIH0gPSBtb2RlbENsYXNzO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgICAgdGFibGU6IG1vZGVsTmFtZSxcbiAgICAgICAgY2xhdXNlczogW1xuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHR5cGU6IEZJTFRFUixcbiAgICAgICAgICAgICAgICBwYXlsb2FkOiB7XG4gICAgICAgICAgICAgICAgICAgIFtpZEF0dHJpYnV0ZV06IG1vZGVsSW5zdGFuY2UuZ2V0SWQoKSxcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfSxcbiAgICAgICAgXSxcbiAgICB9O1xufVxuXG4vKipcbiAqIFRoZSBoZWFydCBvZiBhbiBPUk0sIHRoZSBkYXRhIG1vZGVsLlxuICpcbiAqIFRoZSBmaWVsZHMgeW91IHNwZWNpZnkgdG8gdGhlIE1vZGVsIHdpbGwgYmUgdXNlZCB0byBnZW5lcmF0ZVxuICogYSBzY2hlbWEgdG8gdGhlIGRhdGFiYXNlLCByZWxhdGVkIHByb3BlcnR5IGFjY2Vzc29ycywgYW5kXG4gKiBwb3NzaWJseSB0aHJvdWdoIG1vZGVscy5cbiAqXG4gKiBJbiBlYWNoIHtAbGluayBTZXNzaW9ufSB5b3UgaW5zdGFudGlhdGUgZnJvbSBhbiB7QGxpbmsgT1JNfSBpbnN0YW5jZSxcbiAqIHlvdSB3aWxsIHJlY2VpdmUgYSBzZXNzaW9uLXNwZWNpZmljIHN1YmNsYXNzIG9mIHRoaXMgTW9kZWwuIFRoZSBtZXRob2RzXG4gKiB5b3UgZGVmaW5lIGhlcmUgd2lsbCBiZSBhdmFpbGFibGUgdG8geW91IGluIHNlc3Npb25zLlxuICpcbiAqIEFuIGluc3RhbmNlIG9mIHtAbGluayBNb2RlbH0gcmVwcmVzZW50cyBhIHJlY29yZCBpbiB0aGUgZGF0YWJhc2UsIHRob3VnaFxuICogaXQgaXMgcG9zc2libGUgdG8gZ2VuZXJhdGUgbXVsdGlwbGUgaW5zdGFuY2VzIGZyb20gdGhlIHNhbWUgcmVjb3JkIGluIHRoZSBkYXRhYmFzZS5cbiAqXG4gKiBUbyBjcmVhdGUgZGF0YSBtb2RlbHMgaW4geW91ciBzY2hlbWEsIHN1YmNsYXNzIHtAbGluayBNb2RlbH0uIFRvIGRlZmluZVxuICogaW5mb3JtYXRpb24gYWJvdXQgdGhlIGRhdGEgbW9kZWwsIG92ZXJyaWRlIHN0YXRpYyBjbGFzcyBtZXRob2RzLiBEZWZpbmUgaW5zdGFuY2VcbiAqIGxvZ2ljIGJ5IGRlZmluaW5nIHByb3RvdHlwZSBtZXRob2RzICh3aXRob3V0IGBzdGF0aWNgIGtleXdvcmQpLlxuICovXG5jb25zdCBNb2RlbCA9IGNsYXNzIE1vZGVsIHtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgTW9kZWwgaW5zdGFuY2UgZnJvbSBpdCdzIHByb3BlcnRpZXMuXG4gICAgICogRG9uJ3QgdXNlIHRoaXMgdG8gY3JlYXRlIGEgbmV3IHJlY29yZDsgVXNlIHRoZSBzdGF0aWMgbWV0aG9kIHtAbGluayBNb2RlbCNjcmVhdGV9LlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gcHJvcHMgLSB0aGUgcHJvcGVydGllcyB0byBpbnN0YW50aWF0ZSB3aXRoXG4gICAgICovXG4gICAgY29uc3RydWN0b3IocHJvcHMpIHtcbiAgICAgICAgdGhpcy5faW5pdEZpZWxkcyhwcm9wcyk7XG4gICAgfVxuXG4gICAgX2luaXRGaWVsZHMocHJvcHMpIHtcbiAgICAgICAgY29uc3QgcHJvcHNPYmogPSBPYmplY3QocHJvcHMpO1xuICAgICAgICB0aGlzLl9maWVsZHMgPSB7IC4uLnByb3BzT2JqIH07XG5cbiAgICAgICAgT2JqZWN0LmtleXMocHJvcHNPYmopLmZvckVhY2goZmllbGROYW1lID0+IHtcbiAgICAgICAgICAgIC8vIEluIHRoaXMgY2FzZSwgd2UgZ290IGEgcHJvcCB0aGF0IHdhc24ndCBkZWZpbmVkIGFzIGEgZmllbGQuXG4gICAgICAgICAgICAvLyBBc3N1bWluZyBpdCdzIGFuIGFyYml0cmFyeSBkYXRhIGZpZWxkLCBtYWtpbmcgYW4gaW5zdGFuY2Utc3BlY2lmaWNcbiAgICAgICAgICAgIC8vIGRlc2NyaXB0b3IgZm9yIGl0LlxuICAgICAgICAgICAgLy8gVXNpbmcgdGhlIGluIG9wZXJhdG9yIGFzIHRoZSBwcm9wZXJ0eSBjb3VsZCBiZSBkZWZpbmVkIGFueXdoZXJlXG4gICAgICAgICAgICAvLyBvbiB0aGUgcHJvdG90eXBlIGNoYWluLlxuICAgICAgICAgICAgaWYgKCEoZmllbGROYW1lIGluIHRoaXMpKSB7XG4gICAgICAgICAgICAgICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsIGZpZWxkTmFtZSwge1xuICAgICAgICAgICAgICAgICAgICBnZXQ6ICgpID0+IHRoaXMuX2ZpZWxkc1tmaWVsZE5hbWVdLFxuICAgICAgICAgICAgICAgICAgICBzZXQ6IHZhbHVlID0+IHRoaXMuc2V0KGZpZWxkTmFtZSwgdmFsdWUpLFxuICAgICAgICAgICAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgICAgICAgICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIHN0YXRpYyB0b1N0cmluZygpIHtcbiAgICAgICAgcmV0dXJuIGBNb2RlbENsYXNzOiAke3RoaXMubW9kZWxOYW1lfWA7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgb3B0aW9ucyBvYmplY3QgcGFzc2VkIHRvIHRoZSBkYXRhYmFzZSBmb3IgdGhlIHRhYmxlIHRoYXQgcmVwcmVzZW50c1xuICAgICAqIHRoaXMgTW9kZWwgY2xhc3MuXG4gICAgICpcbiAgICAgKiBSZXR1cm5zIGFuIGVtcHR5IG9iamVjdCBieSBkZWZhdWx0LCB3aGljaCBtZWFucyB0aGUgZGF0YWJhc2VcbiAgICAgKiB3aWxsIHVzZSBkZWZhdWx0IG9wdGlvbnMuIFlvdSBjYW4gZWl0aGVyIG92ZXJyaWRlIHRoaXMgZnVuY3Rpb24gdG8gcmV0dXJuIHRoZSBvcHRpb25zXG4gICAgICogeW91IHdhbnQgdG8gdXNlLCBvciBhc3NpZ24gdGhlIG9wdGlvbnMgb2JqZWN0IGFzIGEgc3RhdGljIHByb3BlcnR5IG9mIHRoZSBzYW1lIG5hbWUgdG8gdGhlXG4gICAgICogTW9kZWwgY2xhc3MuXG4gICAgICpcbiAgICAgKiBAcmV0dXJuIHtPYmplY3R9IHRoZSBvcHRpb25zIG9iamVjdCBwYXNzZWQgdG8gdGhlIGRhdGFiYXNlIGZvciB0aGUgdGFibGVcbiAgICAgKiAgICAgICAgICAgICAgICAgIHJlcHJlc2VudGluZyB0aGlzIE1vZGVsIGNsYXNzLlxuICAgICAqL1xuICAgIHN0YXRpYyBvcHRpb25zKCkge1xuICAgICAgICByZXR1cm4ge307XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogTWFudWFsbHkgbWFyayBpbmRpdmlkdWFsIGluc3RhbmNlcyBhcyBhY2Nlc3NlZC5cbiAgICAgKiBUaGlzIGFsbG93cyBpbnZhbGlkYXRpbmcgc2VsZWN0b3IgbWVtb2l6YXRpb24gd2l0aGluIG11dGFibGUgc2Vzc2lvbnMuXG4gICAgICpcbiAgICAgKiBAcGFyYW0ge0FycmF5LjwqPn0gaWRzIC0gQXJyYXkgb2YgcHJpbWFyeSBrZXkgdmFsdWVzXG4gICAgICogQHJldHVybiB7dW5kZWZpbmVkfVxuICAgICAqL1xuICAgIHN0YXRpYyBtYXJrQWNjZXNzZWQoaWRzKSB7XG4gICAgICAgIGlmICh0eXBlb2YgdGhpcy5fc2Vzc2lvbiA9PT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIFtcbiAgICAgICAgICAgICAgICAgICAgYFRyaWVkIHRvIG1hcmsgcm93cyBvZiB0aGUgJHt0aGlzLm1vZGVsTmFtZX0gbW9kZWwgYXMgYWNjZXNzZWQgd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBjYWxsIFwiLFxuICAgICAgICAgICAgICAgICAgICBgXFxgc2Vzc2lvbltcIiR7dGhpcy5tb2RlbE5hbWV9XCJdLm1hcmtBY2Nlc3NlZFxcYCBpbnN0ZWFkLmAsXG4gICAgICAgICAgICAgICAgXS5qb2luKFwiXCIpXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuc2Vzc2lvbi5tYXJrQWNjZXNzZWQodGhpcy5tb2RlbE5hbWUsIGlkcyk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogTWFudWFsbHkgbWFyayB0aGlzIG1vZGVsJ3MgdGFibGUgYXMgc2Nhbm5lZC5cbiAgICAgKiBUaGlzIGFsbG93cyBpbnZhbGlkYXRpbmcgc2VsZWN0b3IgbWVtb2l6YXRpb24gd2l0aGluIG11dGFibGUgc2Vzc2lvbnMuXG4gICAgICpcbiAgICAgKiBAcmV0dXJuIHt1bmRlZmluZWR9XG4gICAgICovXG4gICAgc3RhdGljIG1hcmtGdWxsVGFibGVTY2FubmVkKCkge1xuICAgICAgICBpZiAodHlwZW9mIHRoaXMuX3Nlc3Npb24gPT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBbXG4gICAgICAgICAgICAgICAgICAgIGBUcmllZCB0byBtYXJrIHRoZSAke3RoaXMubW9kZWxOYW1lfSBtb2RlbCBhcyBmdWxsIHRhYmxlIHNjYW5uZWQgd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBjYWxsIFwiLFxuICAgICAgICAgICAgICAgICAgICBgXFxgc2Vzc2lvbltcIiR7dGhpcy5tb2RlbE5hbWV9XCJdLm1hcmtGdWxsVGFibGVTY2FubmVkXFxgIGluc3RlYWQuYCxcbiAgICAgICAgICAgICAgICBdLmpvaW4oXCJcIilcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5zZXNzaW9uLm1hcmtGdWxsVGFibGVTY2FubmVkKHRoaXMubW9kZWxOYW1lKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBNYW51YWxseSBtYXJrIGluZGV4ZXMgYXMgYWNjZXNzZWQuXG4gICAgICogVGhpcyBhbGxvd3MgaW52YWxpZGF0aW5nIHNlbGVjdG9yIG1lbW9pemF0aW9uIHdpdGhpbiBtdXRhYmxlIHNlc3Npb25zLlxuICAgICAqXG4gICAgICogQHBhcmFtIHtBcnJheS48QXJyYXkuPCosKj4+fSBpbmRleGVzIC0gQXJyYXkgb2YgY29sdW1uLXZhbHVlIHBhaXJzXG4gICAgICogQHJldHVybiB7dW5kZWZpbmVkfVxuICAgICAqL1xuICAgIHN0YXRpYyBtYXJrQWNjZXNzZWRJbmRleGVzKGluZGV4ZXMpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLl9zZXNzaW9uID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgW1xuICAgICAgICAgICAgICAgICAgICBgVHJpZWQgdG8gbWFyayBpbmRleGVzIGZvciB0aGUgJHt0aGlzLm1vZGVsTmFtZX0gbW9kZWwgYXMgYWNjZXNzZWQgd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBjYWxsIFwiLFxuICAgICAgICAgICAgICAgICAgICBgXFxgc2Vzc2lvbltcIiR7dGhpcy5tb2RlbE5hbWV9XCJdLm1hcmtBY2Nlc3NlZEluZGV4ZXNcXGAgaW5zdGVhZC5gLFxuICAgICAgICAgICAgICAgIF0uam9pbihcIlwiKVxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLnNlc3Npb24ubWFya0FjY2Vzc2VkSW5kZXhlcyhcbiAgICAgICAgICAgIGluZGV4ZXMubWFwKChbYXR0cmlidXRlLCB2YWx1ZV0pID0+IFtcbiAgICAgICAgICAgICAgICB0aGlzLm1vZGVsTmFtZSxcbiAgICAgICAgICAgICAgICBhdHRyaWJ1dGUsXG4gICAgICAgICAgICAgICAgdmFsdWUsXG4gICAgICAgICAgICBdKVxuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGlkIGF0dHJpYnV0ZSBvZiB0aGlzIHtAbGluayBNb2RlbH0uXG4gICAgICpcbiAgICAgKiBAcmV0dXJuIHtzdHJpbmd9IFRoZSBpZCBhdHRyaWJ1dGUgb2YgdGhpcyB7QGxpbmsgTW9kZWx9LlxuICAgICAqL1xuICAgIHN0YXRpYyBnZXQgaWRBdHRyaWJ1dGUoKSB7XG4gICAgICAgIGlmICh0eXBlb2YgdGhpcy5fc2Vzc2lvbiA9PT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIFtcbiAgICAgICAgICAgICAgICAgICAgYFRyaWVkIHRvIGdldCB0aGUgJHt0aGlzLm1vZGVsTmFtZX0gbW9kZWwncyBpZCBhdHRyaWJ1dGUgd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBhY2Nlc3MgXCIsXG4gICAgICAgICAgICAgICAgICAgIGBcXGBzZXNzaW9uW1wiJHt0aGlzLm1vZGVsTmFtZX1cIl0uaWRBdHRyaWJ1dGVcXGAgaW5zdGVhZC5gLFxuICAgICAgICAgICAgICAgIF0uam9pbihcIlwiKVxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5zZXNzaW9uLmRiLmRlc2NyaWJlKHRoaXMubW9kZWxOYW1lKS5pZEF0dHJpYnV0ZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDb25uZWN0IHRoZSBtb2RlbCBjbGFzcyB0byBhIHtAbGluayBTZXNzaW9ufS5cbiAgICAgKlxuICAgICAqIEBwcml2YXRlXG4gICAgICogQHBhcmFtICB7U2Vzc2lvbn0gc2Vzc2lvbiAtIFRoZSBzZXNzaW9uIHRvIGNvbm5lY3QgdG8uXG4gICAgICovXG4gICAgc3RhdGljIGNvbm5lY3Qoc2Vzc2lvbikge1xuICAgICAgICBpZiAoIShzZXNzaW9uIGluc3RhbmNlb2YgU2Vzc2lvbikpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBcIkEgbW9kZWwgY2FuIG9ubHkgYmUgY29ubmVjdGVkIHRvIGluc3RhbmNlcyBvZiBTZXNzaW9uLlwiXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuX3Nlc3Npb24gPSBzZXNzaW9uO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEdldCB0aGUgY3VycmVudCB7QGxpbmsgU2Vzc2lvbn0gaW5zdGFuY2UuXG4gICAgICpcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqIEByZXR1cm4ge1Nlc3Npb259IFRoZSBjdXJyZW50IHtAbGluayBTZXNzaW9ufSBpbnN0YW5jZS5cbiAgICAgKi9cbiAgICBzdGF0aWMgZ2V0IHNlc3Npb24oKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9zZXNzaW9uO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYW4gaW5zdGFuY2Ugb2YgdGhlIG1vZGVsJ3MgYHF1ZXJ5U2V0Q2xhc3NgIGZpZWxkLlxuICAgICAqIEJ5IGRlZmF1bHQsIHRoaXMgd2lsbCBiZSBhbiBlbXB0eSB7QGxpbmsgUXVlcnlTZXR9LlxuICAgICAqXG4gICAgICogQHJldHVybiB7T2JqZWN0fSBBbiBpbnN0YW5jZSBvZiB0aGUgbW9kZWwncyBgcXVlcnlTZXRDbGFzc2AuXG4gICAgICovXG4gICAgc3RhdGljIGdldFF1ZXJ5U2V0KCkge1xuICAgICAgICBjb25zdCB7IHF1ZXJ5U2V0Q2xhc3M6IFF1ZXJ5U2V0Q2xhc3MgfSA9IHRoaXM7XG4gICAgICAgIHJldHVybiBuZXcgUXVlcnlTZXRDbGFzcyh0aGlzKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAcmV0dXJuIHt1bmRlZmluZWR9XG4gICAgICovXG4gICAgc3RhdGljIGludmFsaWRhdGVDbGFzc0NhY2hlKCkge1xuICAgICAgICB0aGlzLmlzU2V0VXAgPSB1bmRlZmluZWQ7XG4gICAgICAgIHRoaXMudmlydHVhbEZpZWxkcyA9IHt9O1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEBzZWUge0BsaW5rIE1vZGVsLmdldFF1ZXJ5U2V0fVxuICAgICAqL1xuICAgIHN0YXRpYyBnZXQgcXVlcnkoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldFF1ZXJ5U2V0KCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBwYXJhbWV0ZXJzIHRvIGJlIHBhc3NlZCB0byB7QGxpbmsgVGFibGV9IGluc3RhbmNlLlxuICAgICAqXG4gICAgICogQHByaXZhdGVcbiAgICAgKi9cbiAgICBzdGF0aWMgdGFibGVPcHRpb25zKCkge1xuICAgICAgICBpZiAodHlwZW9mIHRoaXMuYmFja2VuZCA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgICB3YXJuRGVwcmVjYXRlZChcbiAgICAgICAgICAgICAgICBcImBNb2RlbC5iYWNrZW5kYCBoYXMgYmVlbiBkZXByZWNhdGVkLiBQbGVhc2UgcmVuYW1lIHRvIGAub3B0aW9uc2AuXCJcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5iYWNrZW5kKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuYmFja2VuZCkge1xuICAgICAgICAgICAgd2FybkRlcHJlY2F0ZWQoXG4gICAgICAgICAgICAgICAgXCJgTW9kZWwuYmFja2VuZGAgaGFzIGJlZW4gZGVwcmVjYXRlZC4gUGxlYXNlIHJlbmFtZSB0byBgLm9wdGlvbnNgLlwiXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuYmFja2VuZDtcbiAgICAgICAgfVxuICAgICAgICBpZiAodHlwZW9mIHRoaXMub3B0aW9ucyA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zKCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucztcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IHJlY29yZCBpbiB0aGUgZGF0YWJhc2UsIGluc3RhbnRpYXRlcyBhIHtAbGluayBNb2RlbH0gYW5kIHJldHVybnMgaXQuXG4gICAgICpcbiAgICAgKiBJZiB5b3UgcGFzcyB2YWx1ZXMgZm9yIG1hbnktdG8tbWFueSBmaWVsZHMsIGluc3RhbmNlcyBhcmUgY3JlYXRlZCBvbiB0aGUgdGhyb3VnaFxuICAgICAqIG1vZGVsIGFzIHdlbGwuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IHVzZXJQcm9wcyAtIHRoZSBuZXcge0BsaW5rIE1vZGVsfSdzIHByb3BlcnRpZXMuXG4gICAgICogQHJldHVybiB7TW9kZWx9IGEgbmV3IHtAbGluayBNb2RlbH0gaW5zdGFuY2UuXG4gICAgICovXG4gICAgc3RhdGljIGNyZWF0ZSh1c2VyUHJvcHMpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLl9zZXNzaW9uID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgW1xuICAgICAgICAgICAgICAgICAgICBgVHJpZWQgdG8gY3JlYXRlIGEgJHt0aGlzLm1vZGVsTmFtZX0gbW9kZWwgaW5zdGFuY2Ugd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBjYWxsIFwiLFxuICAgICAgICAgICAgICAgICAgICBgXFxgc2Vzc2lvbltcIiR7dGhpcy5tb2RlbE5hbWV9XCJdLmNyZWF0ZVxcYCBpbnN0ZWFkLmAsXG4gICAgICAgICAgICAgICAgXS5qb2luKFwiXCIpXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHByb3BzID0geyAuLi51c2VyUHJvcHMgfTtcblxuICAgICAgICBjb25zdCBtMm1SZWxhdGlvbnMgPSB7fTtcblxuICAgICAgICBjb25zdCBkZWNsYXJlZEZpZWxkTmFtZXMgPSBPYmplY3Qua2V5cyh0aGlzLmZpZWxkcyk7XG4gICAgICAgIGNvbnN0IGRlY2xhcmVkVmlydHVhbEZpZWxkTmFtZXMgPSBPYmplY3Qua2V5cyh0aGlzLnZpcnR1YWxGaWVsZHMpO1xuXG4gICAgICAgIGRlY2xhcmVkRmllbGROYW1lcy5mb3JFYWNoKGtleSA9PiB7XG4gICAgICAgICAgICBjb25zdCBmaWVsZCA9IHRoaXMuZmllbGRzW2tleV07XG4gICAgICAgICAgICBjb25zdCB2YWx1ZVBhc3NlZCA9IHVzZXJQcm9wcy5oYXNPd25Qcm9wZXJ0eShrZXkpO1xuICAgICAgICAgICAgaWYgKCEoZmllbGQgaW5zdGFuY2VvZiBNYW55VG9NYW55KSkge1xuICAgICAgICAgICAgICAgIGlmICh2YWx1ZVBhc3NlZCkge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCB2YWx1ZSA9IHVzZXJQcm9wc1trZXldO1xuICAgICAgICAgICAgICAgICAgICBwcm9wc1trZXldID0gbm9ybWFsaXplRW50aXR5KHZhbHVlKTtcbiAgICAgICAgICAgICAgICB9IGVsc2UgaWYgKGZpZWxkLmdldERlZmF1bHQpIHtcbiAgICAgICAgICAgICAgICAgICAgcHJvcHNba2V5XSA9IGZpZWxkLmdldERlZmF1bHQoKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9IGVsc2UgaWYgKHZhbHVlUGFzc2VkKSB7XG4gICAgICAgICAgICAgICAgLy8gU2F2ZSBmb3IgbGF0ZXIgcHJvY2Vzc2luZ1xuICAgICAgICAgICAgICAgIG0ybVJlbGF0aW9uc1trZXldID0gdXNlclByb3BzW2tleV07XG5cbiAgICAgICAgICAgICAgICBpZiAoIWZpZWxkLmFzKSB7XG4gICAgICAgICAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICAgICAgICAgKiBUaGUgcmVsYXRpb25zaGlwIGRvZXMgbm90IGhhdmUgYW4gYWNjZXNzb3JcbiAgICAgICAgICAgICAgICAgICAgICogRGlzY2FyZCB0aGUgdmFsdWUgZnJvbSBwcm9wcyBhcyB0aGUgZmllbGQgd2lsbCBiZSBwb3B1bGF0ZWQgbGF0ZXIgd2l0aCBpbnN0YW5jZXNcbiAgICAgICAgICAgICAgICAgICAgICogZnJvbSB0aGUgdGFyZ2V0IG1vZGVscyB3aGVuIHJlZnJlc2hpbmcgdGhlIE0yTSByZWxhdGlvbnMuXG4gICAgICAgICAgICAgICAgICAgICAqIElmIHRoZSByZWxhdGlvbnNoaXAgZG9lcyBoYXZlIGFuIGFjY2Vzc29yIChgYXNgKSBmaWVsZCB0aGVuIHdlIGRvIHdhbnQgdG8ga2VlcCB0aGlzXG4gICAgICAgICAgICAgICAgICAgICAqIG9yaWdpbmFsIHZhbHVlIGluIHRoZSBwcm9wcyB0byBleHBvc2UgdGhlIHJhdyBsaXN0IG9mIElEcyBmcm9tIHRoZSBpbnN0YW5jZS5cbiAgICAgICAgICAgICAgICAgICAgICovXG4gICAgICAgICAgICAgICAgICAgIGRlbGV0ZSBwcm9wc1trZXldO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gYWRkIGJhY2t3YXJkIG1hbnktbWFueSBpZiByZXF1aXJlZFxuICAgICAgICBkZWNsYXJlZFZpcnR1YWxGaWVsZE5hbWVzLmZvckVhY2goa2V5ID0+IHtcbiAgICAgICAgICAgIGlmICghbTJtUmVsYXRpb25zLmhhc093blByb3BlcnR5KGtleSkpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBmaWVsZCA9IHRoaXMudmlydHVhbEZpZWxkc1trZXldO1xuICAgICAgICAgICAgICAgIGlmIChcbiAgICAgICAgICAgICAgICAgICAgdXNlclByb3BzLmhhc093blByb3BlcnR5KGtleSkgJiZcbiAgICAgICAgICAgICAgICAgICAgZmllbGQgaW5zdGFuY2VvZiBNYW55VG9NYW55XG4gICAgICAgICAgICAgICAgKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIElmIGEgdmFsdWUgaXMgc3VwcGxpZWQgZm9yIGEgTWFueVRvTWFueSBmaWVsZCxcbiAgICAgICAgICAgICAgICAgICAgLy8gZGlzY2FyZCB0aGVtIGZyb20gcHJvcHMgYW5kIHNhdmUgZm9yIGxhdGVyIHByb2Nlc3NpbmcuXG4gICAgICAgICAgICAgICAgICAgIG0ybVJlbGF0aW9uc1trZXldID0gdXNlclByb3BzW2tleV07XG4gICAgICAgICAgICAgICAgICAgIGRlbGV0ZSBwcm9wc1trZXldO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG5cbiAgICAgICAgY29uc3QgbmV3RW50cnkgPSB0aGlzLnNlc3Npb24uYXBwbHlVcGRhdGUoe1xuICAgICAgICAgICAgYWN0aW9uOiBDUkVBVEUsXG4gICAgICAgICAgICB0YWJsZTogdGhpcy5tb2RlbE5hbWUsXG4gICAgICAgICAgICBwYXlsb2FkOiBwcm9wcyxcbiAgICAgICAgfSk7XG5cbiAgICAgICAgY29uc3QgVGhpc01vZGVsID0gdGhpcztcbiAgICAgICAgY29uc3QgaW5zdGFuY2UgPSBuZXcgVGhpc01vZGVsKG5ld0VudHJ5KTtcbiAgICAgICAgaW5zdGFuY2UuX3JlZnJlc2hNYW55Mk1hbnkobTJtUmVsYXRpb25zKTsgLy8gZXNsaW50LWRpc2FibGUtbGluZSBuby11bmRlcnNjb3JlLWRhbmdsZVxuICAgICAgICByZXR1cm4gaW5zdGFuY2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyBvciB1cGRhdGUgZXhpc3RpbmcgcmVjb3JkIGluIHRoZSBkYXRhYmFzZSwgaW5zdGFudGlhdGVzIGEge0BsaW5rIE1vZGVsfSBhbmQgcmV0dXJucyBpdC5cbiAgICAgKlxuICAgICAqIElmIHlvdSBwYXNzIHZhbHVlcyBmb3IgbWFueS10by1tYW55IGZpZWxkcywgaW5zdGFuY2VzIGFyZSBjcmVhdGVkIG9uIHRoZSB0aHJvdWdoXG4gICAgICogbW9kZWwgYXMgd2VsbC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gdXNlclByb3BzIC0gdGhlIHJlcXVpcmVkIHtAbGluayBNb2RlbH0ncyBwcm9wZXJ0aWVzLlxuICAgICAqIEByZXR1cm4ge01vZGVsfSBhIHtAbGluayBNb2RlbH0gaW5zdGFuY2UuXG4gICAgICovXG4gICAgc3RhdGljIHVwc2VydCh1c2VyUHJvcHMpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLnNlc3Npb24gPT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBbXG4gICAgICAgICAgICAgICAgICAgIGBUcmllZCB0byB1cHNlcnQgYSAke3RoaXMubW9kZWxOYW1lfSBtb2RlbCBpbnN0YW5jZSB3aXRob3V0IGEgc2Vzc2lvbi4gYCxcbiAgICAgICAgICAgICAgICAgICAgXCJDcmVhdGUgYSBzZXNzaW9uIHVzaW5nIGBzZXNzaW9uID0gb3JtLnNlc3Npb24oKWAgYW5kIGNhbGwgXCIsXG4gICAgICAgICAgICAgICAgICAgIGBcXGBzZXNzaW9uW1wiJHt0aGlzLm1vZGVsTmFtZX1cIl0udXBzZXJ0XFxgIGluc3RlYWQuYCxcbiAgICAgICAgICAgICAgICBdLmpvaW4oXCJcIilcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCB7IGlkQXR0cmlidXRlIH0gPSB0aGlzO1xuICAgICAgICBpZiAodXNlclByb3BzLmhhc093blByb3BlcnR5KGlkQXR0cmlidXRlKSkge1xuICAgICAgICAgICAgY29uc3QgaWQgPSB1c2VyUHJvcHNbaWRBdHRyaWJ1dGVdO1xuICAgICAgICAgICAgaWYgKHRoaXMuaWRFeGlzdHMoaWQpKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgbW9kZWwgPSB0aGlzLndpdGhJZChpZCk7XG4gICAgICAgICAgICAgICAgbW9kZWwudXBkYXRlKHVzZXJQcm9wcyk7XG4gICAgICAgICAgICAgICAgcmV0dXJuIG1vZGVsO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXMuY3JlYXRlKHVzZXJQcm9wcyk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhIHtAbGluayBNb2RlbH0gaW5zdGFuY2UgZm9yIHRoZSBvYmplY3Qgd2l0aCBpZCBgaWRgLlxuICAgICAqIFJldHVybnMgYG51bGxgIGlmIHRoZSBtb2RlbCBoYXMgbm8gaW5zdGFuY2Ugd2l0aCBpZCBgaWRgLlxuICAgICAqXG4gICAgICogWW91IGNhbiB1c2Uge0BsaW5rIE1vZGVsI2lkRXhpc3RzfSB0byBjaGVjayBmb3IgZXhpc3RlbmNlIGluc3RlYWQuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHsqfSBpZCAtIHRoZSBgaWRgIG9mIHRoZSBvYmplY3QgdG8gZ2V0XG4gICAgICogQHRocm93cyBJZiBvYmplY3Qgd2l0aCBpZCBgaWRgIGRvZXNuJ3QgZXhpc3RcbiAgICAgKiBAcmV0dXJuIHtNb2RlbHxudWxsfSB7QGxpbmsgTW9kZWx9IGluc3RhbmNlIHdpdGggaWQgYGlkYFxuICAgICAqL1xuICAgIHN0YXRpYyB3aXRoSWQoaWQpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0KHtcbiAgICAgICAgICAgIFt0aGlzLmlkQXR0cmlidXRlXTogaWQsXG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBib29sZWFuIGluZGljYXRpbmcgaWYgYW4gZW50aXR5XG4gICAgICogd2l0aCB0aGUgaWQgYGlkYCBleGlzdHMgaW4gdGhlIHN0YXRlLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7Kn0gIGlkIC0gYSB2YWx1ZSBjb3JyZXNwb25kaW5nIHRvIHRoZSBpZCBhdHRyaWJ1dGUgb2YgdGhlIHtAbGluayBNb2RlbH0gY2xhc3MuXG4gICAgICogQHJldHVybiB7Qm9vbGVhbn0gYSBib29sZWFuIGluZGljYXRpbmcgaWYgZW50aXR5IHdpdGggYGlkYCBleGlzdHMgaW4gdGhlIHN0YXRlXG4gICAgICpcbiAgICAgKiBAc2luY2UgMC4xMS4wXG4gICAgICovXG4gICAgc3RhdGljIGlkRXhpc3RzKGlkKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmV4aXN0cyh7XG4gICAgICAgICAgICBbdGhpcy5pZEF0dHJpYnV0ZV06IGlkLFxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIGEgYm9vbGVhbiBpbmRpY2F0aW5nIGlmIGFuIGVudGl0eVxuICAgICAqIHdpdGggdGhlIGdpdmVuIHByb3BzIGV4aXN0cyBpbiB0aGUgc3RhdGUuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHsqfSAgcHJvcHMgLSBhIGtleS12YWx1ZSB0aGF0IHtAbGluayBNb2RlbH0gaW5zdGFuY2VzIHNob3VsZCBoYXZlIHRvIGJlIGNvbnNpZGVyZWQgYXMgZXhpc3RpbmcuXG4gICAgICogQHJldHVybiB7Qm9vbGVhbn0gYSBib29sZWFuIGluZGljYXRpbmcgaWYgZW50aXR5IHdpdGggYHByb3BzYCBleGlzdHMgaW4gdGhlIHN0YXRlXG4gICAgICovXG4gICAgc3RhdGljIGV4aXN0cyhsb29rdXBPYmopIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLnNlc3Npb24gPT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBbXG4gICAgICAgICAgICAgICAgICAgIGBUcmllZCB0byBjaGVjayBpZiBhICR7dGhpcy5tb2RlbE5hbWV9IG1vZGVsIGluc3RhbmNlIGV4aXN0cyB3aXRob3V0IGEgc2Vzc2lvbi4gYCxcbiAgICAgICAgICAgICAgICAgICAgXCJDcmVhdGUgYSBzZXNzaW9uIHVzaW5nIGBzZXNzaW9uID0gb3JtLnNlc3Npb24oKWAgYW5kIGNhbGwgXCIsXG4gICAgICAgICAgICAgICAgICAgIGBcXGBzZXNzaW9uW1wiJHt0aGlzLm1vZGVsTmFtZX1cIl0uZXhpc3RzXFxgIGluc3RlYWQuYCxcbiAgICAgICAgICAgICAgICBdLmpvaW4oXCJcIilcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gQm9vbGVhbih0aGlzLl9maW5kRGF0YWJhc2VSb3dzKGxvb2t1cE9iaikubGVuZ3RoKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXRzIHRoZSB7QGxpbmsgTW9kZWx9IGluc3RhbmNlIHRoYXQgbWF0Y2hlcyBwcm9wZXJ0aWVzIGluIGBsb29rdXBPYmpgLlxuICAgICAqIFRocm93cyBhbiBlcnJvciBpZiB7QGxpbmsgTW9kZWx9IGlmIG11bHRpcGxlIHJlY29yZHMgbWF0Y2hcbiAgICAgKiB0aGUgcHJvcGVydGllcy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gbG9va3VwT2JqIC0gdGhlIHByb3BlcnRpZXMgdXNlZCB0byBtYXRjaCBhIHNpbmdsZSBlbnRpdHkuXG4gICAgICogQHRocm93cyB7RXJyb3J9IElmIG1vcmUgdGhhbiBvbmUgZW50aXR5IG1hdGNoZXMgdGhlIHByb3BlcnRpZXMgaW4gYGxvb2t1cE9iamAuXG4gICAgICogQHJldHVybiB7TW9kZWx9IGEge0BsaW5rIE1vZGVsfSBpbnN0YW5jZSB0aGF0IG1hdGNoZXMgdGhlIHByb3BlcnRpZXMgaW4gYGxvb2t1cE9iamAuXG4gICAgICovXG4gICAgc3RhdGljIGdldChsb29rdXBPYmopIHtcbiAgICAgICAgY29uc3QgVGhpc01vZGVsID0gdGhpcztcblxuICAgICAgICBjb25zdCByb3dzID0gdGhpcy5fZmluZERhdGFiYXNlUm93cyhsb29rdXBPYmopO1xuICAgICAgICBpZiAocm93cy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICB9XG4gICAgICAgIGlmIChyb3dzLmxlbmd0aCA+IDEpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBgRXhwZWN0ZWQgdG8gZmluZCBhIHNpbmdsZSByb3cgaW4gXFxgJHt0aGlzLm1vZGVsTmFtZX0uZ2V0XFxgLiBGb3VuZCAke3Jvd3MubGVuZ3RofS5gXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG5ldyBUaGlzTW9kZWwocm93c1swXSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogR2V0cyB0aGUge0BsaW5rIE1vZGVsfSBjbGFzcyBvciBzdWJjbGFzcyBjb25zdHJ1Y3RvciAodGhlIGNsYXNzIHRoYXRcbiAgICAgKiBpbnN0YW50aWF0ZWQgdGhpcyBpbnN0YW5jZSkuXG4gICAgICpcbiAgICAgKiBAcmV0dXJuIHtNb2RlbH0gVGhlIHtAbGluayBNb2RlbH0gY2xhc3Mgb3Igc3ViY2xhc3MgY29uc3RydWN0b3IgdXNlZCB0byBpbnN0YW50aWF0ZVxuICAgICAqICAgICAgICAgICAgICAgICB0aGlzIGluc3RhbmNlLlxuICAgICAqL1xuICAgIGdldENsYXNzKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5jb25zdHJ1Y3RvcjtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXRzIHRoZSBpZCB2YWx1ZSBvZiB0aGUgY3VycmVudCBpbnN0YW5jZSBieSBsb29raW5nIHVwIHRoZSBpZCBhdHRyaWJ1dGUuXG4gICAgICogQHJldHVybiB7Kn0gVGhlIGlkIHZhbHVlIG9mIHRoZSBjdXJyZW50IGluc3RhbmNlLlxuICAgICAqL1xuICAgIGdldElkKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fZmllbGRzW3RoaXMuZ2V0Q2xhc3MoKS5pZEF0dHJpYnV0ZV07XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhIHJlZmVyZW5jZSB0byB0aGUgcGxhaW4gSlMgb2JqZWN0IGluIHRoZSBzdG9yZS5cbiAgICAgKiBJdCBjb250YWlucyBhbGwgdGhlIHByb3BlcnRpZXMgdGhhdCB5b3UgcGFzcyB3aGVuIGNyZWF0aW5nIHRoZSBtb2RlbCxcbiAgICAgKiBleGNlcHQgZm9yIHByaW1hcnkga2V5cyBvZiBtYW55LXRvLW1hbnkgcmVsYXRpb25zaGlwcyB3aXRoIGEgY3VzdG9tIGFjY2Vzc29yLlxuICAgICAqXG4gICAgICogTWFrZSBzdXJlIG5ldmVyIHRvIG11dGF0ZSB0aGlzLlxuICAgICAqXG4gICAgICogQHJldHVybiB7T2JqZWN0fSBhIHJlZmVyZW5jZSB0byB0aGUgcGxhaW4gSlMgb2JqZWN0IGluIHRoZSBzdG9yZVxuICAgICAqL1xuICAgIGdldCByZWYoKSB7XG4gICAgICAgIGNvbnN0IFRoaXNNb2RlbCA9IHRoaXMuZ2V0Q2xhc3MoKTtcblxuICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tdW5kZXJzY29yZS1kYW5nbGVcbiAgICAgICAgcmV0dXJuIFRoaXNNb2RlbC5fZmluZERhdGFiYXNlUm93cyh7XG4gICAgICAgICAgICBbVGhpc01vZGVsLmlkQXR0cmlidXRlXTogdGhpcy5nZXRJZCgpLFxuICAgICAgICB9KVswXTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGaW5kcyBhbGwgcm93cyBpbiB0aGlzIG1vZGVsJ3MgdGFibGUgdGhhdCBtYXRjaCB0aGUgZ2l2ZW4gYGxvb2t1cE9iamAuXG4gICAgICogSWYgbm8gYGxvb2t1cE9iamAgaXMgcGFzc2VkLCBhbGwgcm93cyBpbiB0aGUgbW9kZWwncyB0YWJsZSB3aWxsIGJlIHJldHVybmVkLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7Kn0gIHByb3BzIC0gYSBrZXktdmFsdWUgdGhhdCB7QGxpbmsgTW9kZWx9IGluc3RhbmNlcyBzaG91bGQgaGF2ZSB0byBiZSBjb25zaWRlcmVkIGFzIGV4aXN0aW5nLlxuICAgICAqIEByZXR1cm4ge0Jvb2xlYW59IGEgYm9vbGVhbiBpbmRpY2F0aW5nIGlmIGVudGl0eSB3aXRoIGBwcm9wc2AgZXhpc3RzIGluIHRoZSBzdGF0ZVxuICAgICAqIEBwcml2YXRlXG4gICAgICovXG4gICAgc3RhdGljIF9maW5kRGF0YWJhc2VSb3dzKGxvb2t1cE9iaikge1xuICAgICAgICBjb25zdCBxdWVyeVNwZWMgPSB7XG4gICAgICAgICAgICB0YWJsZTogdGhpcy5tb2RlbE5hbWUsXG4gICAgICAgIH07XG4gICAgICAgIGlmIChsb29rdXBPYmopIHtcbiAgICAgICAgICAgIHF1ZXJ5U3BlYy5jbGF1c2VzID0gW1xuICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgdHlwZTogRklMVEVSLFxuICAgICAgICAgICAgICAgICAgICBwYXlsb2FkOiBsb29rdXBPYmosXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIF07XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuc2Vzc2lvbi5xdWVyeShxdWVyeVNwZWMpLnJvd3M7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhIHN0cmluZyByZXByZXNlbnRhdGlvbiBvZiB0aGUge0BsaW5rIE1vZGVsfSBpbnN0YW5jZS5cbiAgICAgKlxuICAgICAqIEByZXR1cm4ge3N0cmluZ30gQSBzdHJpbmcgcmVwcmVzZW50YXRpb24gb2YgdGhpcyB7QGxpbmsgTW9kZWx9IGluc3RhbmNlLlxuICAgICAqL1xuICAgIHRvU3RyaW5nKCkge1xuICAgICAgICBjb25zdCBUaGlzTW9kZWwgPSB0aGlzLmdldENsYXNzKCk7XG4gICAgICAgIGNvbnN0IGNsYXNzTmFtZSA9IFRoaXNNb2RlbC5tb2RlbE5hbWU7XG4gICAgICAgIGNvbnN0IGZpZWxkTmFtZXMgPSBPYmplY3Qua2V5cyhUaGlzTW9kZWwuZmllbGRzKTtcbiAgICAgICAgY29uc3QgZmllbGRzID0gZmllbGROYW1lc1xuICAgICAgICAgICAgLm1hcChmaWVsZE5hbWUgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGZpZWxkID0gVGhpc01vZGVsLmZpZWxkc1tmaWVsZE5hbWVdO1xuICAgICAgICAgICAgICAgIGlmIChmaWVsZCBpbnN0YW5jZW9mIE1hbnlUb01hbnkpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgaWRzID0gdGhpc1tmaWVsZE5hbWVdXG4gICAgICAgICAgICAgICAgICAgICAgICAudG9Nb2RlbEFycmF5KClcbiAgICAgICAgICAgICAgICAgICAgICAgIC5tYXAobW9kZWwgPT4gbW9kZWwuZ2V0SWQoKSk7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBgJHtmaWVsZE5hbWV9OiBbJHtpZHMuam9pbihcIiwgXCIpfV1gO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBjb25zdCB2YWwgPSB0aGlzLl9maWVsZHNbZmllbGROYW1lXTtcbiAgICAgICAgICAgICAgICByZXR1cm4gYCR7ZmllbGROYW1lfTogJHt2YWx9YDtcbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAuam9pbihcIiwgXCIpO1xuICAgICAgICByZXR1cm4gYCR7Y2xhc3NOYW1lfTogeyR7ZmllbGRzfX1gO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBib29sZWFuIGluZGljYXRpbmcgaWYgYG90aGVyTW9kZWxgIGVxdWFscyB0aGlzIHtAbGluayBNb2RlbH0gaW5zdGFuY2UuXG4gICAgICogRXF1YWxpdHkgaXMgZGV0ZXJtaW5lZCBieSBzaGFsbG93IGNvbXBhcmluZyB0aGVpciBhdHRyaWJ1dGVzLlxuICAgICAqXG4gICAgICogVGhpcyBlcXVhbGl0eSBpcyB1c2VkIHdoZW4geW91IGNhbGwge0BsaW5rIE1vZGVsI3VwZGF0ZX0uXG4gICAgICogWW91IGNhbiBwcmV2ZW50IG1vZGVsIHVwZGF0ZXMgYnkgcmV0dXJuaW5nIGB0cnVlYCBoZXJlLlxuICAgICAqIEhvd2V2ZXIsIGEgbW9kZWwgd2lsbCBhbHdheXMgYmUgdXBkYXRlZCBpZiBpdHMgcmVsYXRpb25zaGlwcyBhcmUgY2hhbmdlZC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge01vZGVsfSBvdGhlck1vZGVsIC0gYSB7QGxpbmsgTW9kZWx9IGluc3RhbmNlIHRvIGNvbXBhcmVcbiAgICAgKiBAcmV0dXJuIHtCb29sZWFufSBhIGJvb2xlYW4gaW5kaWNhdGluZyBpZiB0aGUge0BsaW5rIE1vZGVsfSBpbnN0YW5jZSdzIGFyZSBlcXVhbC5cbiAgICAgKi9cbiAgICBlcXVhbHMob3RoZXJNb2RlbCkge1xuICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tdW5kZXJzY29yZS1kYW5nbGVcbiAgICAgICAgcmV0dXJuIG9iamVjdFNoYWxsb3dFcXVhbHModGhpcy5fZmllbGRzLCBvdGhlck1vZGVsLl9maWVsZHMpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFVwZGF0ZXMgYSBwcm9wZXJ0eSBuYW1lIHRvIGdpdmVuIHZhbHVlIGZvciB0aGlzIHtAbGluayBNb2RlbH0gaW5zdGFuY2UuXG4gICAgICogVGhlIHZhbHVlcyBhcmUgaW1tZWRpYXRlbHkgY29tbWl0dGVkIHRvIHRoZSBkYXRhYmFzZS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSBwcm9wZXJ0eU5hbWUgLSBuYW1lIG9mIHRoZSBwcm9wZXJ0eSB0byBzZXRcbiAgICAgKiBAcGFyYW0geyp9IHZhbHVlIC0gdmFsdWUgYXNzaWduZWQgdG8gdGhlIHByb3BlcnR5XG4gICAgICogQHJldHVybiB7dW5kZWZpbmVkfVxuICAgICAqL1xuICAgIHNldChwcm9wZXJ0eU5hbWUsIHZhbHVlKSB7XG4gICAgICAgIHRoaXMudXBkYXRlKHtcbiAgICAgICAgICAgIFtwcm9wZXJ0eU5hbWVdOiB2YWx1ZSxcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQXNzaWducyBtdWx0aXBsZSBmaWVsZHMgYW5kIGNvcnJlc3BvbmRpbmcgdmFsdWVzIHRvIHRoaXMge0BsaW5rIE1vZGVsfSBpbnN0YW5jZS5cbiAgICAgKiBUaGUgdXBkYXRlcyBhcmUgaW1tZWRpYXRlbHkgY29tbWl0dGVkIHRvIHRoZSBkYXRhYmFzZS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gdXNlck1lcmdlT2JqIC0gYW4gb2JqZWN0IHRoYXQgd2lsbCBiZSBtZXJnZWQgd2l0aCB0aGlzIGluc3RhbmNlLlxuICAgICAqIEByZXR1cm4ge3VuZGVmaW5lZH1cbiAgICAgKi9cbiAgICB1cGRhdGUodXNlck1lcmdlT2JqKSB7XG4gICAgICAgIGNvbnN0IFRoaXNNb2RlbCA9IHRoaXMuZ2V0Q2xhc3MoKTtcbiAgICAgICAgaWYgKHR5cGVvZiBUaGlzTW9kZWwuc2Vzc2lvbiA9PT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIFtcbiAgICAgICAgICAgICAgICAgICAgYFRyaWVkIHRvIHVwZGF0ZSBhICR7VGhpc01vZGVsLm1vZGVsTmFtZX0gbW9kZWwgaW5zdGFuY2Ugd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiWW91IGNhbm5vdCBjYWxsIGAudXBkYXRlYCBvbiBhbiBpbnN0YW5jZSB0aGF0IHlvdSBkaWQgbm90IHJlY2VpdmUgZnJvbSB0aGUgZGF0YWJhc2UuXCIsXG4gICAgICAgICAgICAgICAgXS5qb2luKFwiXCIpXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgbWVyZ2VPYmogPSB7IC4uLnVzZXJNZXJnZU9iaiB9O1xuXG4gICAgICAgIGNvbnN0IHsgZmllbGRzLCB2aXJ0dWFsRmllbGRzIH0gPSBUaGlzTW9kZWw7XG5cbiAgICAgICAgY29uc3QgbTJtUmVsYXRpb25zID0ge307XG5cbiAgICAgICAgLy8gSWYgYW4gYXJyYXkgb2YgZW50aXRpZXMgb3IgaWQncyBpcyBzdXBwbGllZCBmb3IgYVxuICAgICAgICAvLyBtYW55LXRvLW1hbnkgcmVsYXRlZCBmaWVsZCwgY2xlYXIgdGhlIG9sZCByZWxhdGlvbnNcbiAgICAgICAgLy8gYW5kIGFkZCB0aGUgbmV3IG9uZXMuXG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBndWFyZC1mb3ItaW4sIG5vLXJlc3RyaWN0ZWQtc3ludGF4XG4gICAgICAgIGZvciAoY29uc3QgbWVyZ2VLZXkgaW4gbWVyZ2VPYmopIHtcbiAgICAgICAgICAgIGNvbnN0IGlzUmVhbEZpZWxkID0gZmllbGRzLmhhc093blByb3BlcnR5KG1lcmdlS2V5KTtcblxuICAgICAgICAgICAgaWYgKGlzUmVhbEZpZWxkKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgZmllbGQgPSBmaWVsZHNbbWVyZ2VLZXldO1xuXG4gICAgICAgICAgICAgICAgaWYgKGZpZWxkIGluc3RhbmNlb2YgRm9yZWlnbktleSB8fCBmaWVsZCBpbnN0YW5jZW9mIE9uZVRvT25lKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIHVwZGF0ZSBvbmUtb25lL2ZrIHJlbGF0aW9uc1xuICAgICAgICAgICAgICAgICAgICBtZXJnZU9ialttZXJnZUtleV0gPSBub3JtYWxpemVFbnRpdHkobWVyZ2VPYmpbbWVyZ2VLZXldKTtcbiAgICAgICAgICAgICAgICB9IGVsc2UgaWYgKGZpZWxkIGluc3RhbmNlb2YgTWFueVRvTWFueSkge1xuICAgICAgICAgICAgICAgICAgICAvLyBmaWVsZCBpcyBmb3J3YXJkIHJlbGF0aW9uXG4gICAgICAgICAgICAgICAgICAgIG0ybVJlbGF0aW9uc1ttZXJnZUtleV0gPSBtZXJnZU9ialttZXJnZUtleV07XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKCFmaWVsZC5hcykge1xuICAgICAgICAgICAgICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgICAgICAgICAgICAgKiBUaGUgcmVsYXRpb25zaGlwIGRvZXMgbm90IGhhdmUgYW4gYWNjZXNzb3JcbiAgICAgICAgICAgICAgICAgICAgICAgICAqIERpc2NhcmQgdGhlIHZhbHVlIGZyb20gcHJvcHMgYXMgdGhlIGZpZWxkIHdpbGwgYmUgcG9wdWxhdGVkIGxhdGVyIHdpdGggaW5zdGFuY2VzXG4gICAgICAgICAgICAgICAgICAgICAgICAgKiBmcm9tIHRoZSB0YXJnZXQgbW9kZWxzIHdoZW4gcmVmcmVzaGluZyB0aGUgTTJNIHJlbGF0aW9ucy5cbiAgICAgICAgICAgICAgICAgICAgICAgICAqIElmIHRoZSByZWxhdGlvbnNoaXAgZG9lcyBoYXZlIGFuIGFjY2Vzc29yIChgYXNgKSBmaWVsZCB0aGVuIHdlIGRvIHdhbnQgdG8ga2VlcCB0aGlzXG4gICAgICAgICAgICAgICAgICAgICAgICAgKiBvcmlnaW5hbCB2YWx1ZSBpbiB0aGUgcHJvcHMgdG8gZXhwb3NlIHRoZSByYXcgbGlzdCBvZiBJRHMgZnJvbSB0aGUgaW5zdGFuY2UuXG4gICAgICAgICAgICAgICAgICAgICAgICAgKi9cbiAgICAgICAgICAgICAgICAgICAgICAgIGRlbGV0ZSBtZXJnZU9ialttZXJnZUtleV07XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9IGVsc2UgaWYgKHZpcnR1YWxGaWVsZHMuaGFzT3duUHJvcGVydHkobWVyZ2VLZXkpKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgZmllbGQgPSB2aXJ0dWFsRmllbGRzW21lcmdlS2V5XTtcbiAgICAgICAgICAgICAgICBpZiAoZmllbGQgaW5zdGFuY2VvZiBNYW55VG9NYW55KSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIGZpZWxkIGlzIGJhY2t3YXJkIHJlbGF0aW9uXG4gICAgICAgICAgICAgICAgICAgIG0ybVJlbGF0aW9uc1ttZXJnZUtleV0gPSBtZXJnZU9ialttZXJnZUtleV07XG4gICAgICAgICAgICAgICAgICAgIGRlbGV0ZSBtZXJnZU9ialttZXJnZUtleV07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgbWVyZ2VkRmllbGRzID0ge1xuICAgICAgICAgICAgLi4udGhpcy5fZmllbGRzLFxuICAgICAgICAgICAgLi4ubWVyZ2VPYmosXG4gICAgICAgIH07XG5cbiAgICAgICAgY29uc3QgdXBkYXRlZE1vZGVsID0gbmV3IFRoaXNNb2RlbChtZXJnZWRGaWVsZHMpO1xuICAgICAgICAvLyBvbmx5IHVwZGF0ZSBmaWVsZHMgaWYgdGhleSBoYXZlIGNoYW5nZWQgKHJlZmVyZW50aWFsbHkpXG4gICAgICAgIGlmICghdGhpcy5lcXVhbHModXBkYXRlZE1vZGVsKSkge1xuICAgICAgICAgICAgdGhpcy5faW5pdEZpZWxkcyhtZXJnZWRGaWVsZHMpO1xuICAgICAgICAgICAgVGhpc01vZGVsLnNlc3Npb24uYXBwbHlVcGRhdGUoe1xuICAgICAgICAgICAgICAgIGFjdGlvbjogVVBEQVRFLFxuICAgICAgICAgICAgICAgIHF1ZXJ5OiBnZXRCeUlkUXVlcnkodGhpcyksXG4gICAgICAgICAgICAgICAgcGF5bG9hZDogbWVyZ2VPYmosXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHVwZGF0ZSB2aXJ0dWFsIGZpZWxkc1xuICAgICAgICB0aGlzLl9yZWZyZXNoTWFueTJNYW55KG0ybVJlbGF0aW9ucyk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVXBkYXRlcyB7QGxpbmsgTW9kZWx9IGluc3RhbmNlIGF0dHJpYnV0ZXMgdG8gcmVmbGVjdCB0aGVcbiAgICAgKiBkYXRhYmFzZSBzdGF0ZSBpbiB0aGUgY3VycmVudCBzZXNzaW9uLlxuICAgICAqIEByZXR1cm4ge3VuZGVmaW5lZH1cbiAgICAgKi9cbiAgICByZWZyZXNoRnJvbVN0YXRlKCkge1xuICAgICAgICB0aGlzLl9pbml0RmllbGRzKHRoaXMucmVmKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBEZWxldGVzIHRoZSByZWNvcmQgZm9yIHRoaXMge0BsaW5rIE1vZGVsfSBpbnN0YW5jZS5cbiAgICAgKiBZb3UnbGwgc3RpbGwgYmUgYWJsZSB0byBhY2Nlc3MgZmllbGRzIGFuZCB2YWx1ZXMgb24gdGhlIGluc3RhbmNlLlxuICAgICAqXG4gICAgICogQHJldHVybiB7dW5kZWZpbmVkfVxuICAgICAqL1xuICAgIGRlbGV0ZSgpIHtcbiAgICAgICAgY29uc3QgVGhpc01vZGVsID0gdGhpcy5nZXRDbGFzcygpO1xuICAgICAgICBpZiAodHlwZW9mIFRoaXNNb2RlbC5zZXNzaW9uID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgW1xuICAgICAgICAgICAgICAgICAgICBgVHJpZWQgdG8gZGVsZXRlIGEgJHtUaGlzTW9kZWwubW9kZWxOYW1lfSBtb2RlbCBpbnN0YW5jZSB3aXRob3V0IGEgc2Vzc2lvbi4gYCxcbiAgICAgICAgICAgICAgICAgICAgXCJZb3UgY2Fubm90IGNhbGwgYC5kZWxldGVgIG9uIGFuIGluc3RhbmNlIHRoYXQgeW91IGRpZCBub3QgcmVjZWl2ZSBmcm9tIHRoZSBkYXRhYmFzZS5cIixcbiAgICAgICAgICAgICAgICBdLmpvaW4oXCJcIilcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLl9vbkRlbGV0ZSgpO1xuICAgICAgICBUaGlzTW9kZWwuc2Vzc2lvbi5hcHBseVVwZGF0ZSh7XG4gICAgICAgICAgICBhY3Rpb246IERFTEVURSxcbiAgICAgICAgICAgIHF1ZXJ5OiBnZXRCeUlkUXVlcnkodGhpcyksXG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFVwZGF0ZSBtYW55LW1hbnkgcmVsYXRpb25zIGZvciBtb2RlbC5cbiAgICAgKiBAcGFyYW0gcmVsYXRpb25zXG4gICAgICogQHJldHVybiB1bmRlZmluZWRcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqL1xuICAgIF9yZWZyZXNoTWFueTJNYW55KHJlbGF0aW9ucykge1xuICAgICAgICBjb25zdCBUaGlzTW9kZWwgPSB0aGlzLmdldENsYXNzKCk7XG4gICAgICAgIGNvbnN0IHsgZmllbGRzLCB2aXJ0dWFsRmllbGRzLCBtb2RlbE5hbWUgfSA9IFRoaXNNb2RlbDtcblxuICAgICAgICBPYmplY3Qua2V5cyhyZWxhdGlvbnMpLmZvckVhY2gobmFtZSA9PiB7XG4gICAgICAgICAgICBjb25zdCByZXZlcnNlID0gIWZpZWxkcy5oYXNPd25Qcm9wZXJ0eShuYW1lKTtcbiAgICAgICAgICAgIGNvbnN0IGZpZWxkID0gdmlydHVhbEZpZWxkc1tuYW1lXTtcbiAgICAgICAgICAgIGNvbnN0IHZhbHVlcyA9IHJlbGF0aW9uc1tuYW1lXTtcblxuICAgICAgICAgICAgaWYgKCFBcnJheS5pc0FycmF5KHZhbHVlcykpIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFxuICAgICAgICAgICAgICAgICAgICBgRmFpbGVkIHRvIHJlc29sdmUgbWFueS10by1tYW55IHJlbGF0aW9uc2hpcDogJHttb2RlbE5hbWV9WyR7bmFtZX1dIG11c3QgYmUgYW4gYXJyYXkgKHBhc3NlZDogJHt2YWx1ZXN9KWBcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjb25zdCBub3JtYWxpemVkTmV3SWRzID0gdmFsdWVzLm1hcChub3JtYWxpemVFbnRpdHkpO1xuICAgICAgICAgICAgY29uc3QgdW5pcXVlSWRzID0gWy4uLm5ldyBTZXQobm9ybWFsaXplZE5ld0lkcyldO1xuXG4gICAgICAgICAgICBpZiAobm9ybWFsaXplZE5ld0lkcy5sZW5ndGggIT09IHVuaXF1ZUlkcy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgICAgIGBGb3VuZCBkdXBsaWNhdGUgaWQocykgd2hlbiBwYXNzaW5nIFwiJHtub3JtYWxpemVkTmV3SWRzfVwiIHRvICR7VGhpc01vZGVsLm1vZGVsTmFtZX0uJHtuYW1lfSB2YWx1ZWBcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjb25zdCB0aHJvdWdoTW9kZWxOYW1lID1cbiAgICAgICAgICAgICAgICBmaWVsZC50aHJvdWdoIHx8IG0ybU5hbWUoVGhpc01vZGVsLm1vZGVsTmFtZSwgbmFtZSk7XG4gICAgICAgICAgICBjb25zdCBUaHJvdWdoTW9kZWwgPSBUaGlzTW9kZWwuc2Vzc2lvblt0aHJvdWdoTW9kZWxOYW1lXTtcblxuICAgICAgICAgICAgbGV0IGZyb21GaWVsZDtcbiAgICAgICAgICAgIGxldCB0b0ZpZWxkO1xuXG4gICAgICAgICAgICBpZiAoIXJldmVyc2UpIHtcbiAgICAgICAgICAgICAgICAoeyBmcm9tOiBmcm9tRmllbGQsIHRvOiB0b0ZpZWxkIH0gPSBmaWVsZC50aHJvdWdoRmllbGRzKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgKHsgZnJvbTogdG9GaWVsZCwgdG86IGZyb21GaWVsZCB9ID0gZmllbGQudGhyb3VnaEZpZWxkcyk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGNvbnN0IGN1cnJlbnRJZHMgPSBUaHJvdWdoTW9kZWwuZmlsdGVyKFxuICAgICAgICAgICAgICAgIHRocm91Z2ggPT4gdGhyb3VnaFtmcm9tRmllbGRdID09PSB0aGlzW1RoaXNNb2RlbC5pZEF0dHJpYnV0ZV1cbiAgICAgICAgICAgIClcbiAgICAgICAgICAgICAgICAudG9SZWZBcnJheSgpXG4gICAgICAgICAgICAgICAgLm1hcChyZWYgPT4gcmVmW3RvRmllbGRdKTtcblxuICAgICAgICAgICAgY29uc3QgZGlmZkFjdGlvbnMgPSBhcnJheURpZmZBY3Rpb25zKGN1cnJlbnRJZHMsIG5vcm1hbGl6ZWROZXdJZHMpO1xuXG4gICAgICAgICAgICBpZiAoZGlmZkFjdGlvbnMpIHtcbiAgICAgICAgICAgICAgICBjb25zdCB7IGRlbGV0ZTogaWRzVG9EZWxldGUsIGFkZDogaWRzVG9BZGQgfSA9IGRpZmZBY3Rpb25zO1xuICAgICAgICAgICAgICAgIGlmIChpZHNUb0RlbGV0ZS5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXNbZmllbGQuYXMgfHwgbmFtZV0ucmVtb3ZlKC4uLmlkc1RvRGVsZXRlKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpZiAoaWRzVG9BZGQubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzW2ZpZWxkLmFzIHx8IG5hbWVdLmFkZCguLi5pZHNUb0FkZCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAcmV0dXJuIHt1bmRlZmluZWR9XG4gICAgICogQHByaXZhdGVcbiAgICAgKi9cbiAgICBfb25EZWxldGUoKSB7XG4gICAgICAgIGNvbnN0IHsgdmlydHVhbEZpZWxkcyB9ID0gdGhpcy5nZXRDbGFzcygpO1xuICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZ3VhcmQtZm9yLWluLCBuby1yZXN0cmljdGVkLXN5bnRheFxuICAgICAgICBmb3IgKGNvbnN0IGtleSBpbiB2aXJ0dWFsRmllbGRzKSB7XG4gICAgICAgICAgICBjb25zdCBmaWVsZCA9IHZpcnR1YWxGaWVsZHNba2V5XTtcbiAgICAgICAgICAgIGlmIChmaWVsZCBpbnN0YW5jZW9mIE1hbnlUb01hbnkpIHtcbiAgICAgICAgICAgICAgICAvLyBEZWxldGUgYW55IG1hbnktdG8tbWFueSByb3dzIHRoZSBlbnRpdHkgaXMgaW5jbHVkZWQgaW4uXG4gICAgICAgICAgICAgICAgY29uc3QgZGVzY3JpcHRvcktleSA9IGZpZWxkLmFzIHx8IGtleTtcbiAgICAgICAgICAgICAgICB0aGlzW2Rlc2NyaXB0b3JLZXldLmNsZWFyKCk7XG4gICAgICAgICAgICB9IGVsc2UgaWYgKGZpZWxkIGluc3RhbmNlb2YgRm9yZWlnbktleSkge1xuICAgICAgICAgICAgICAgIGNvbnN0IHJlbGF0ZWRRcyA9IHRoaXNba2V5XTtcbiAgICAgICAgICAgICAgICBpZiAocmVsYXRlZFFzLmV4aXN0cygpKSB7XG4gICAgICAgICAgICAgICAgICAgIHJlbGF0ZWRRcy51cGRhdGUoeyBbZmllbGQucmVsYXRlZE5hbWVdOiBudWxsIH0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gZWxzZSBpZiAoZmllbGQgaW5zdGFuY2VvZiBPbmVUb09uZSkge1xuICAgICAgICAgICAgICAgIC8vIFNldCBudWxsIHRvIGFueSBmb3JlaWduIGtleXMgb3Igb25lIHRvIG9uZXMgcG9pbnRlZCB0b1xuICAgICAgICAgICAgICAgIC8vIHRoaXMgaW5zdGFuY2UuXG4gICAgICAgICAgICAgICAgaWYgKHRoaXNba2V5XSAhPT0gbnVsbCkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzW2tleV1bZmllbGQucmVsYXRlZE5hbWVdID0gbnVsbDtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBERVBSRUNBVEVEIEFORCBSRU1PVkVEIE1FVEhPRFNcblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBib29sZWFuIGluZGljYXRpbmcgaWYgYW4gZW50aXR5XG4gICAgICogd2l0aCB0aGUgaWQgYGlkYCBleGlzdHMgaW4gdGhlIHN0YXRlLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7Kn0gIGlkIC0gYSB2YWx1ZSBjb3JyZXNwb25kaW5nIHRvIHRoZSBpZCBhdHRyaWJ1dGUgb2YgdGhlIHtAbGluayBNb2RlbH0gY2xhc3MuXG4gICAgICogQHJldHVybiB7Qm9vbGVhbn0gYSBib29sZWFuIGluZGljYXRpbmcgaWYgZW50aXR5IHdpdGggYGlkYCBleGlzdHMgaW4gdGhlIHN0YXRlXG4gICAgICogQGRlcHJlY2F0ZWQgUGxlYXNlIHVzZSB7QGxpbmsgTW9kZWwuaWRFeGlzdHN9IGluc3RlYWQuXG4gICAgICovXG4gICAgc3RhdGljIGhhc0lkKGlkKSB7XG4gICAgICAgIGNvbnNvbGUud2FybihcbiAgICAgICAgICAgIFwiYE1vZGVsLmhhc0lkYCBoYXMgYmVlbiBkZXByZWNhdGVkLiBQbGVhc2UgdXNlIGBNb2RlbC5pZEV4aXN0c2AgaW5zdGVhZC5cIlxuICAgICAgICApO1xuICAgICAgICByZXR1cm4gdGhpcy5pZEV4aXN0cyhpZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQGRlcHJlY2F0ZWQgU2VlIHRoZSAwLjkgbWlncmF0aW9uIGd1aWRlIG9uIHRoZSBHaXRIdWIgcmVwby5cbiAgICAgKiBAdGhyb3dzIHtFcnJvcn0gRHVlIHRvIGRlcHJlY2F0aW9uLlxuICAgICAqL1xuICAgIGdldE5leHRTdGF0ZSgpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgXCJgTW9kZWwucHJvdG90eXBlLmdldE5leHRTdGF0ZWAgaGFzIGJlZW4gcmVtb3ZlZC4gU2VlIHRoZSAwLjkgXCIgK1xuICAgICAgICAgICAgICAgIFwibWlncmF0aW9uIGd1aWRlIG9uIHRoZSBHaXRIdWIgcmVwby5cIlxuICAgICAgICApO1xuICAgIH1cbn07XG5cbk1vZGVsLmZpZWxkcyA9IHtcbiAgICBpZDogYXR0cigpLFxufTtcbk1vZGVsLnZpcnR1YWxGaWVsZHMgPSB7fTtcbk1vZGVsLnF1ZXJ5U2V0Q2xhc3MgPSBRdWVyeVNldDtcblxuZXhwb3J0IGRlZmF1bHQgTW9kZWw7XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./src/Model.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _Session__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Session */ \\\"./src/Session.js\\\");\\n/* harmony import */ var _QuerySet__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./QuerySet */ \\\"./src/QuerySet.js\\\");\\n/* harmony import */ var _fields__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./fields */ \\\"./src/fields/index.js\\\");\\n/* harmony import */ var _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./fields/ForeignKey */ \\\"./src/fields/ForeignKey.js\\\");\\n/* harmony import */ var _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./fields/ManyToMany */ \\\"./src/fields/ManyToMany.js\\\");\\n/* harmony import */ var _fields_OneToOne__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./fields/OneToOne */ \\\"./src/fields/OneToOne.js\\\");\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./constants */ \\\"./src/constants.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n/**\\n * Generates a query specification to get the instance's\\n * corresponding table row using its primary key.\\n *\\n * @private\\n * @returns {Object}\\n */\\n\\nfunction getByIdQuery(modelInstance) {\\n  const modelClass = modelInstance.getClass();\\n  const {\\n    idAttribute,\\n    modelName\\n  } = modelClass;\\n  return {\\n    table: modelName,\\n    clauses: [{\\n      type: _constants__WEBPACK_IMPORTED_MODULE_7__[\\\"FILTER\\\"],\\n      payload: {\\n        [idAttribute]: modelInstance.getId()\\n      }\\n    }]\\n  };\\n}\\n/**\\n * The heart of an ORM, the data model.\\n *\\n * The fields you specify to the Model will be used to generate\\n * a schema to the database, related property accessors, and\\n * possibly through models.\\n *\\n * In each {@link Session} you instantiate from an {@link ORM} instance,\\n * you will receive a session-specific subclass of this Model. The methods\\n * you define here will be available to you in sessions.\\n *\\n * An instance of {@link Model} represents a record in the database, though\\n * it is possible to generate multiple instances from the same record in the database.\\n *\\n * To create data models in your schema, subclass {@link Model}. To define\\n * information about the data model, override static class methods. Define instance\\n * logic by defining prototype methods (without `static` keyword).\\n */\\n\\n\\nconst Model = /*#__PURE__*/function () {\\n  /**\\n   * Creates a Model instance from it's properties.\\n   * Don't use this to create a new record; Use the static method {@link Model#create}.\\n   * @param  {Object} props - the properties to instantiate with\\n   */\\n  function Model(props) {\\n    this._initFields(props);\\n  }\\n\\n  var _proto = Model.prototype;\\n\\n  _proto._initFields = function _initFields(props) {\\n    const propsObj = Object(props);\\n    this._fields = { ...propsObj\\n    };\\n    Object.keys(propsObj).forEach(fieldName => {\\n      // In this case, we got a prop that wasn't defined as a field.\\n      // Assuming it's an arbitrary data field, making an instance-specific\\n      // descriptor for it.\\n      // Using the in operator as the property could be defined anywhere\\n      // on the prototype chain.\\n      if (!(fieldName in this)) {\\n        Object.defineProperty(this, fieldName, {\\n          get: () => this._fields[fieldName],\\n          set: value => this.set(fieldName, value),\\n          configurable: true,\\n          enumerable: true\\n        });\\n      }\\n    });\\n  };\\n\\n  Model.toString = function toString() {\\n    return `ModelClass: ${this.modelName}`;\\n  }\\n  /**\\n   * Returns the options object passed to the database for the table that represents\\n   * this Model class.\\n   *\\n   * Returns an empty object by default, which means the database\\n   * will use default options. You can either override this function to return the options\\n   * you want to use, or assign the options object as a static property of the same name to the\\n   * Model class.\\n   *\\n   * @return {Object} the options object passed to the database for the table\\n   *                  representing this Model class.\\n   */\\n  ;\\n\\n  Model.options = function options() {\\n    return {};\\n  }\\n  /**\\n   * Manually mark individual instances as accessed.\\n   * This allows invalidating selector memoization within mutable sessions.\\n   *\\n   * @param {Array.<*>} ids - Array of primary key values\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  Model.markAccessed = function markAccessed(ids) {\\n    if (typeof this._session === \\\"undefined\\\") {\\n      throw new Error([`Tried to mark rows of the ${this.modelName} model as accessed without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].markAccessed\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    this.session.markAccessed(this.modelName, ids);\\n  }\\n  /**\\n   * Manually mark this model's table as scanned.\\n   * This allows invalidating selector memoization within mutable sessions.\\n   *\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  Model.markFullTableScanned = function markFullTableScanned() {\\n    if (typeof this._session === \\\"undefined\\\") {\\n      throw new Error([`Tried to mark the ${this.modelName} model as full table scanned without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].markFullTableScanned\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    this.session.markFullTableScanned(this.modelName);\\n  }\\n  /**\\n   * Manually mark indexes as accessed.\\n   * This allows invalidating selector memoization within mutable sessions.\\n   *\\n   * @param {Array.<Array.<*,*>>} indexes - Array of column-value pairs\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  Model.markAccessedIndexes = function markAccessedIndexes(indexes) {\\n    if (typeof this._session === \\\"undefined\\\") {\\n      throw new Error([`Tried to mark indexes for the ${this.modelName} model as accessed without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].markAccessedIndexes\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    this.session.markAccessedIndexes(indexes.map(([attribute, value]) => [this.modelName, attribute, value]));\\n  }\\n  /**\\n   * Returns the id attribute of this {@link Model}.\\n   *\\n   * @return {string} The id attribute of this {@link Model}.\\n   */\\n  ;\\n\\n  /**\\n   * Connect the model class to a {@link Session}.\\n   *\\n   * @private\\n   * @param  {Session} session - The session to connect to.\\n   */\\n  Model.connect = function connect(session) {\\n    if (!(session instanceof _Session__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"])) {\\n      throw new Error(\\\"A model can only be connected to instances of Session.\\\");\\n    }\\n\\n    this._session = session;\\n  }\\n  /**\\n   * Get the current {@link Session} instance.\\n   *\\n   * @private\\n   * @return {Session} The current {@link Session} instance.\\n   */\\n  ;\\n\\n  /**\\n   * Returns an instance of the model's `querySetClass` field.\\n   * By default, this will be an empty {@link QuerySet}.\\n   *\\n   * @return {Object} An instance of the model's `querySetClass`.\\n   */\\n  Model.getQuerySet = function getQuerySet() {\\n    const {\\n      querySetClass: QuerySetClass\\n    } = this;\\n    return new QuerySetClass(this);\\n  }\\n  /**\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  Model.invalidateClassCache = function invalidateClassCache() {\\n    this.isSetUp = undefined;\\n    this.virtualFields = {};\\n  }\\n  /**\\n   * @see {@link Model.getQuerySet}\\n   */\\n  ;\\n\\n  /**\\n   * Returns parameters to be passed to {@link Table} instance.\\n   *\\n   * @private\\n   */\\n  Model.tableOptions = function tableOptions() {\\n    if (typeof this.backend === \\\"function\\\") {\\n      Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"warnDeprecated\\\"])(\\\"`Model.backend` has been deprecated. Please rename to `.options`.\\\");\\n      return this.backend();\\n    }\\n\\n    if (this.backend) {\\n      Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"warnDeprecated\\\"])(\\\"`Model.backend` has been deprecated. Please rename to `.options`.\\\");\\n      return this.backend;\\n    }\\n\\n    if (typeof this.options === \\\"function\\\") {\\n      return this.options();\\n    }\\n\\n    return this.options;\\n  }\\n  /**\\n   * Creates a new record in the database, instantiates a {@link Model} and returns it.\\n   *\\n   * If you pass values for many-to-many fields, instances are created on the through\\n   * model as well.\\n   *\\n   * @param  {Object} userProps - the new {@link Model}'s properties.\\n   * @return {Model} a new {@link Model} instance.\\n   */\\n  ;\\n\\n  Model.create = function create(userProps) {\\n    if (typeof this._session === \\\"undefined\\\") {\\n      throw new Error([`Tried to create a ${this.modelName} model instance without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].create\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    const props = { ...userProps\\n    };\\n    const m2mRelations = {};\\n    const declaredFieldNames = Object.keys(this.fields);\\n    const declaredVirtualFieldNames = Object.keys(this.virtualFields);\\n    declaredFieldNames.forEach(key => {\\n      const field = this.fields[key];\\n      const valuePassed = userProps.hasOwnProperty(key);\\n\\n      if (!(field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"])) {\\n        if (valuePassed) {\\n          const value = userProps[key];\\n          props[key] = Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"normalizeEntity\\\"])(value);\\n        } else if (field.getDefault) {\\n          props[key] = field.getDefault(userProps);\\n        }\\n      } else if (valuePassed) {\\n        // Save for later processing\\n        m2mRelations[key] = userProps[key];\\n\\n        if (!field.as) {\\n          /**\\n           * The relationship does not have an accessor\\n           * Discard the value from props as the field will be populated later with instances\\n           * from the target models when refreshing the M2M relations.\\n           * If the relationship does have an accessor (`as`) field then we do want to keep this\\n           * original value in the props to expose the raw list of IDs from the instance.\\n           */\\n          delete props[key];\\n        }\\n      }\\n    }); // add backward many-many if required\\n\\n    declaredVirtualFieldNames.forEach(key => {\\n      if (!m2mRelations.hasOwnProperty(key)) {\\n        const field = this.virtualFields[key];\\n\\n        if (userProps.hasOwnProperty(key) && field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n          // If a value is supplied for a ManyToMany field,\\n          // discard them from props and save for later processing.\\n          m2mRelations[key] = userProps[key];\\n          delete props[key];\\n        }\\n      }\\n    });\\n    const newEntry = this.session.applyUpdate({\\n      action: _constants__WEBPACK_IMPORTED_MODULE_7__[\\\"CREATE\\\"],\\n      table: this.modelName,\\n      payload: props\\n    });\\n    const ThisModel = this;\\n    const instance = new ThisModel(newEntry);\\n\\n    instance._refreshMany2Many(m2mRelations); // eslint-disable-line no-underscore-dangle\\n\\n\\n    return instance;\\n  }\\n  /**\\n   * Creates a new or update existing record in the database, instantiates a {@link Model} and returns it.\\n   *\\n   * If you pass values for many-to-many fields, instances are created on the through\\n   * model as well.\\n   *\\n   * @param  {Object} userProps - the required {@link Model}'s properties.\\n   * @return {Model} a {@link Model} instance.\\n   */\\n  ;\\n\\n  Model.upsert = function upsert(userProps) {\\n    if (typeof this.session === \\\"undefined\\\") {\\n      throw new Error([`Tried to upsert a ${this.modelName} model instance without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].upsert\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    const {\\n      idAttribute\\n    } = this;\\n\\n    if (userProps.hasOwnProperty(idAttribute)) {\\n      const id = userProps[idAttribute];\\n\\n      if (this.idExists(id)) {\\n        const model = this.withId(id);\\n        model.update(userProps);\\n        return model;\\n      }\\n    }\\n\\n    return this.create(userProps);\\n  }\\n  /**\\n   * Returns a {@link Model} instance for the object with id `id`.\\n   * Returns `null` if the model has no instance with id `id`.\\n   *\\n   * You can use {@link Model#idExists} to check for existence instead.\\n   *\\n   * @param  {*} id - the `id` of the object to get\\n   * @throws If object with id `id` doesn't exist\\n   * @return {Model|null} {@link Model} instance with id `id`\\n   */\\n  ;\\n\\n  Model.withId = function withId(id) {\\n    return this.get({\\n      [this.idAttribute]: id\\n    });\\n  }\\n  /**\\n   * Returns a boolean indicating if an entity\\n   * with the id `id` exists in the state.\\n   *\\n   * @param  {*}  id - a value corresponding to the id attribute of the {@link Model} class.\\n   * @return {Boolean} a boolean indicating if entity with `id` exists in the state\\n   *\\n   * @since 0.11.0\\n   */\\n  ;\\n\\n  Model.idExists = function idExists(id) {\\n    return this.exists({\\n      [this.idAttribute]: id\\n    });\\n  }\\n  /**\\n   * Returns a boolean indicating if an entity\\n   * with the given props exists in the state.\\n   *\\n   * @param  {*}  props - a key-value that {@link Model} instances should have to be considered as existing.\\n   * @return {Boolean} a boolean indicating if entity with `props` exists in the state\\n   */\\n  ;\\n\\n  Model.exists = function exists(lookupObj) {\\n    if (typeof this.session === \\\"undefined\\\") {\\n      throw new Error([`Tried to check if a ${this.modelName} model instance exists without a session. `, \\\"Create a session using `session = orm.session()` and call \\\", `\\\\`session[\\\"${this.modelName}\\\"].exists\\\\` instead.`].join(\\\"\\\"));\\n    }\\n\\n    return Boolean(this._findDatabaseRows(lookupObj).length);\\n  }\\n  /**\\n   * Gets the {@link Model} instance that matches properties in `lookupObj`.\\n   * Throws an error if {@link Model} if multiple records match\\n   * the properties.\\n   *\\n   * @param  {Object} lookupObj - the properties used to match a single entity.\\n   * @throws {Error} If more than one entity matches the properties in `lookupObj`.\\n   * @return {Model} a {@link Model} instance that matches the properties in `lookupObj`.\\n   */\\n  ;\\n\\n  Model.get = function get(lookupObj) {\\n    const ThisModel = this;\\n\\n    const rows = this._findDatabaseRows(lookupObj);\\n\\n    if (rows.length === 0) {\\n      return null;\\n    }\\n\\n    if (rows.length > 1) {\\n      throw new Error(`Expected to find a single row in \\\\`${this.modelName}.get\\\\`. Found ${rows.length}.`);\\n    }\\n\\n    return new ThisModel(rows[0]);\\n  }\\n  /**\\n   * Gets the {@link Model} class or subclass constructor (the class that\\n   * instantiated this instance).\\n   *\\n   * @return {Model} The {@link Model} class or subclass constructor used to instantiate\\n   *                 this instance.\\n   */\\n  ;\\n\\n  _proto.getClass = function getClass() {\\n    return this.constructor;\\n  }\\n  /**\\n   * Gets the id value of the current instance by looking up the id attribute.\\n   * @return {*} The id value of the current instance.\\n   */\\n  ;\\n\\n  _proto.getId = function getId() {\\n    return this._fields[this.getClass().idAttribute];\\n  }\\n  /**\\n   * Returns a reference to the plain JS object in the store.\\n   * It contains all the properties that you pass when creating the model,\\n   * except for primary keys of many-to-many relationships with a custom accessor.\\n   *\\n   * Make sure never to mutate this.\\n   *\\n   * @return {Object} a reference to the plain JS object in the store\\n   */\\n  ;\\n\\n  /**\\n   * Finds all rows in this model's table that match the given `lookupObj`.\\n   * If no `lookupObj` is passed, all rows in the model's table will be returned.\\n   *\\n   * @param  {*}  props - a key-value that {@link Model} instances should have to be considered as existing.\\n   * @return {Boolean} a boolean indicating if entity with `props` exists in the state\\n   * @private\\n   */\\n  Model._findDatabaseRows = function _findDatabaseRows(lookupObj) {\\n    const querySpec = {\\n      table: this.modelName\\n    };\\n\\n    if (lookupObj) {\\n      querySpec.clauses = [{\\n        type: _constants__WEBPACK_IMPORTED_MODULE_7__[\\\"FILTER\\\"],\\n        payload: lookupObj\\n      }];\\n    }\\n\\n    return this.session.query(querySpec).rows;\\n  }\\n  /**\\n   * Returns a string representation of the {@link Model} instance.\\n   *\\n   * @return {string} A string representation of this {@link Model} instance.\\n   */\\n  ;\\n\\n  _proto.toString = function toString() {\\n    const ThisModel = this.getClass();\\n    const className = ThisModel.modelName;\\n    const fieldNames = Object.keys(ThisModel.fields);\\n    const fields = fieldNames.map(fieldName => {\\n      const field = ThisModel.fields[fieldName];\\n\\n      if (field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n        const ids = this[fieldName].toModelArray().map(model => model.getId());\\n        return `${fieldName}: [${ids.join(\\\", \\\")}]`;\\n      }\\n\\n      const val = this._fields[fieldName];\\n      return `${fieldName}: ${val}`;\\n    }).join(\\\", \\\");\\n    return `${className}: {${fields}}`;\\n  }\\n  /**\\n   * Returns a boolean indicating if `otherModel` equals this {@link Model} instance.\\n   * Equality is determined by shallow comparing their attributes.\\n   *\\n   * This equality is used when you call {@link Model#update}.\\n   * You can prevent model updates by returning `true` here.\\n   * However, a model will always be updated if its relationships are changed.\\n   *\\n   * @param  {Model} otherModel - a {@link Model} instance to compare\\n   * @return {Boolean} a boolean indicating if the {@link Model} instance's are equal.\\n   */\\n  ;\\n\\n  _proto.equals = function equals(otherModel) {\\n    // eslint-disable-next-line no-underscore-dangle\\n    return Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"objectShallowEquals\\\"])(this._fields, otherModel._fields);\\n  }\\n  /**\\n   * Updates a property name to given value for this {@link Model} instance.\\n   * The values are immediately committed to the database.\\n   *\\n   * @param {string} propertyName - name of the property to set\\n   * @param {*} value - value assigned to the property\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.set = function set(propertyName, value) {\\n    this.update({\\n      [propertyName]: value\\n    });\\n  }\\n  /**\\n   * Assigns multiple fields and corresponding values to this {@link Model} instance.\\n   * The updates are immediately committed to the database.\\n   *\\n   * @param  {Object} userMergeObj - an object that will be merged with this instance.\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.update = function update(userMergeObj) {\\n    const ThisModel = this.getClass();\\n\\n    if (typeof ThisModel.session === \\\"undefined\\\") {\\n      throw new Error([`Tried to update a ${ThisModel.modelName} model instance without a session. `, \\\"You cannot call `.update` on an instance that you did not receive from the database.\\\"].join(\\\"\\\"));\\n    }\\n\\n    const mergeObj = { ...userMergeObj\\n    };\\n    const {\\n      fields,\\n      virtualFields\\n    } = ThisModel;\\n    const m2mRelations = {}; // If an array of entities or id's is supplied for a\\n    // many-to-many related field, clear the old relations\\n    // and add the new ones.\\n    // eslint-disable-next-line guard-for-in, no-restricted-syntax\\n\\n    for (const mergeKey in mergeObj) {\\n      const isRealField = fields.hasOwnProperty(mergeKey);\\n\\n      if (isRealField) {\\n        const field = fields[mergeKey];\\n\\n        if (field instanceof _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"] || field instanceof _fields_OneToOne__WEBPACK_IMPORTED_MODULE_6__[\\\"default\\\"]) {\\n          // update one-one/fk relations\\n          mergeObj[mergeKey] = Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"normalizeEntity\\\"])(mergeObj[mergeKey]);\\n        } else if (field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n          // field is forward relation\\n          m2mRelations[mergeKey] = mergeObj[mergeKey];\\n\\n          if (!field.as) {\\n            /**\\n             * The relationship does not have an accessor\\n             * Discard the value from props as the field will be populated later with instances\\n             * from the target models when refreshing the M2M relations.\\n             * If the relationship does have an accessor (`as`) field then we do want to keep this\\n             * original value in the props to expose the raw list of IDs from the instance.\\n             */\\n            delete mergeObj[mergeKey];\\n          }\\n        }\\n      } else if (virtualFields.hasOwnProperty(mergeKey)) {\\n        const field = virtualFields[mergeKey];\\n\\n        if (field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n          // field is backward relation\\n          m2mRelations[mergeKey] = mergeObj[mergeKey];\\n          delete mergeObj[mergeKey];\\n        }\\n      }\\n    }\\n\\n    const mergedFields = { ...this._fields,\\n      ...mergeObj\\n    };\\n    const updatedModel = new ThisModel(mergedFields); // only update fields if they have changed (referentially)\\n\\n    if (!this.equals(updatedModel)) {\\n      this._initFields(mergedFields);\\n\\n      ThisModel.session.applyUpdate({\\n        action: _constants__WEBPACK_IMPORTED_MODULE_7__[\\\"UPDATE\\\"],\\n        query: getByIdQuery(this),\\n        payload: mergeObj\\n      });\\n    } // update virtual fields\\n\\n\\n    this._refreshMany2Many(m2mRelations);\\n  }\\n  /**\\n   * Updates {@link Model} instance attributes to reflect the\\n   * database state in the current session.\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.refreshFromState = function refreshFromState() {\\n    this._initFields(this.ref);\\n  }\\n  /**\\n   * Deletes the record for this {@link Model} instance.\\n   * You'll still be able to access fields and values on the instance.\\n   *\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.delete = function _delete() {\\n    const ThisModel = this.getClass();\\n\\n    if (typeof ThisModel.session === \\\"undefined\\\") {\\n      throw new Error([`Tried to delete a ${ThisModel.modelName} model instance without a session. `, \\\"You cannot call `.delete` on an instance that you did not receive from the database.\\\"].join(\\\"\\\"));\\n    }\\n\\n    this._onDelete();\\n\\n    ThisModel.session.applyUpdate({\\n      action: _constants__WEBPACK_IMPORTED_MODULE_7__[\\\"DELETE\\\"],\\n      query: getByIdQuery(this)\\n    });\\n  }\\n  /**\\n   * Update many-many relations for model.\\n   * @param relations\\n   * @return undefined\\n   * @private\\n   */\\n  ;\\n\\n  _proto._refreshMany2Many = function _refreshMany2Many(relations) {\\n    const ThisModel = this.getClass();\\n    const {\\n      fields,\\n      virtualFields,\\n      modelName\\n    } = ThisModel;\\n    Object.keys(relations).forEach(name => {\\n      const reverse = !fields.hasOwnProperty(name);\\n      const field = virtualFields[name];\\n      const values = relations[name];\\n\\n      if (!Array.isArray(values)) {\\n        throw new TypeError(`Failed to resolve many-to-many relationship: ${modelName}[${name}] must be an array (passed: ${values})`);\\n      }\\n\\n      const normalizedNewIds = values.map(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"normalizeEntity\\\"]);\\n      const uniqueIds = [...new Set(normalizedNewIds)];\\n\\n      if (normalizedNewIds.length !== uniqueIds.length) {\\n        throw new Error(`Found duplicate id(s) when passing \\\"${normalizedNewIds}\\\" to ${ThisModel.modelName}.${name} value`);\\n      }\\n\\n      const throughModelName = field.through || Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"m2mName\\\"])(ThisModel.modelName, name);\\n      const ThroughModel = ThisModel.session[throughModelName];\\n      let fromField;\\n      let toField;\\n\\n      if (!reverse) {\\n        ({\\n          from: fromField,\\n          to: toField\\n        } = field.throughFields);\\n      } else {\\n        ({\\n          from: toField,\\n          to: fromField\\n        } = field.throughFields);\\n      }\\n\\n      const currentIds = ThroughModel.filter(through => through[fromField] === this[ThisModel.idAttribute]).toRefArray().map(ref => ref[toField]);\\n      const diffActions = Object(_utils__WEBPACK_IMPORTED_MODULE_8__[\\\"arrayDiffActions\\\"])(currentIds, normalizedNewIds);\\n\\n      if (diffActions) {\\n        const {\\n          delete: idsToDelete,\\n          add: idsToAdd\\n        } = diffActions;\\n\\n        if (idsToDelete.length > 0) {\\n          this[field.as || name].remove(...idsToDelete);\\n        }\\n\\n        if (idsToAdd.length > 0) {\\n          this[field.as || name].add(...idsToAdd);\\n        }\\n      }\\n    });\\n  }\\n  /**\\n   * @return {undefined}\\n   * @private\\n   */\\n  ;\\n\\n  _proto._onDelete = function _onDelete() {\\n    const {\\n      virtualFields\\n    } = this.getClass(); // eslint-disable-next-line guard-for-in, no-restricted-syntax\\n\\n    for (const key in virtualFields) {\\n      const field = virtualFields[key];\\n\\n      if (field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n        // Delete any many-to-many rows the entity is included in.\\n        const descriptorKey = field.as || key;\\n        this[descriptorKey].clear();\\n      } else if (field instanceof _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"]) {\\n        const relatedQs = this[key];\\n\\n        if (relatedQs.exists()) {\\n          relatedQs.update({\\n            [field.relatedName]: null\\n          });\\n        }\\n      } else if (field instanceof _fields_OneToOne__WEBPACK_IMPORTED_MODULE_6__[\\\"default\\\"]) {\\n        // Set null to any foreign keys or one to ones pointed to\\n        // this instance.\\n        if (this[key] !== null) {\\n          this[key][field.relatedName] = null;\\n        }\\n      }\\n    }\\n  } // DEPRECATED AND REMOVED METHODS\\n\\n  /**\\n   * Returns a boolean indicating if an entity\\n   * with the id `id` exists in the state.\\n   *\\n   * @param  {*}  id - a value corresponding to the id attribute of the {@link Model} class.\\n   * @return {Boolean} a boolean indicating if entity with `id` exists in the state\\n   * @deprecated Please use {@link Model.idExists} instead.\\n   */\\n  ;\\n\\n  Model.hasId = function hasId(id) {\\n    console.warn(\\\"`Model.hasId` has been deprecated. Please use `Model.idExists` instead.\\\");\\n    return this.idExists(id);\\n  }\\n  /**\\n   * @deprecated See the 0.9 migration guide on the GitHub repo.\\n   * @throws {Error} Due to deprecation.\\n   */\\n  ;\\n\\n  _proto.getNextState = function getNextState() {\\n    throw new Error(\\\"`Model.prototype.getNextState` has been removed. See the 0.9 \\\" + \\\"migration guide on the GitHub repo.\\\");\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(Model, [{\\n    key: \\\"ref\\\",\\n    get: function () {\\n      const ThisModel = this.getClass(); // eslint-disable-next-line no-underscore-dangle\\n\\n      return ThisModel._findDatabaseRows({\\n        [ThisModel.idAttribute]: this.getId()\\n      })[0];\\n    }\\n  }], [{\\n    key: \\\"idAttribute\\\",\\n    get: function () {\\n      if (typeof this._session === \\\"undefined\\\") {\\n        throw new Error([`Tried to get the ${this.modelName} model's id attribute without a session. `, \\\"Create a session using `session = orm.session()` and access \\\", `\\\\`session[\\\"${this.modelName}\\\"].idAttribute\\\\` instead.`].join(\\\"\\\"));\\n      }\\n\\n      return this.session.db.describe(this.modelName).idAttribute;\\n    }\\n  }, {\\n    key: \\\"session\\\",\\n    get: function () {\\n      return this._session;\\n    }\\n  }, {\\n    key: \\\"query\\\",\\n    get: function () {\\n      return this.getQuerySet();\\n    }\\n  }]);\\n\\n  return Model;\\n}();\\n\\nModel.fields = {\\n  id: Object(_fields__WEBPACK_IMPORTED_MODULE_3__[\\\"attr\\\"])()\\n};\\nModel.virtualFields = {};\\nModel.querySetClass = _QuerySet__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"];\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (Model);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9Nb2RlbC5qcz9mYzVkIl0sIm5hbWVzIjpbImdldEJ5SWRRdWVyeSIsIm1vZGVsSW5zdGFuY2UiLCJtb2RlbENsYXNzIiwiZ2V0Q2xhc3MiLCJpZEF0dHJpYnV0ZSIsIm1vZGVsTmFtZSIsInRhYmxlIiwiY2xhdXNlcyIsInR5cGUiLCJGSUxURVIiLCJwYXlsb2FkIiwiZ2V0SWQiLCJNb2RlbCIsInByb3BzIiwiX2luaXRGaWVsZHMiLCJwcm9wc09iaiIsIk9iamVjdCIsIl9maWVsZHMiLCJrZXlzIiwiZm9yRWFjaCIsImZpZWxkTmFtZSIsImRlZmluZVByb3BlcnR5IiwiZ2V0Iiwic2V0IiwidmFsdWUiLCJjb25maWd1cmFibGUiLCJlbnVtZXJhYmxlIiwidG9TdHJpbmciLCJvcHRpb25zIiwibWFya0FjY2Vzc2VkIiwiaWRzIiwiX3Nlc3Npb24iLCJFcnJvciIsImpvaW4iLCJzZXNzaW9uIiwibWFya0Z1bGxUYWJsZVNjYW5uZWQiLCJtYXJrQWNjZXNzZWRJbmRleGVzIiwiaW5kZXhlcyIsIm1hcCIsImF0dHJpYnV0ZSIsImNvbm5lY3QiLCJTZXNzaW9uIiwiZ2V0UXVlcnlTZXQiLCJxdWVyeVNldENsYXNzIiwiUXVlcnlTZXRDbGFzcyIsImludmFsaWRhdGVDbGFzc0NhY2hlIiwiaXNTZXRVcCIsInVuZGVmaW5lZCIsInZpcnR1YWxGaWVsZHMiLCJ0YWJsZU9wdGlvbnMiLCJiYWNrZW5kIiwid2FybkRlcHJlY2F0ZWQiLCJjcmVhdGUiLCJ1c2VyUHJvcHMiLCJtMm1SZWxhdGlvbnMiLCJkZWNsYXJlZEZpZWxkTmFtZXMiLCJmaWVsZHMiLCJkZWNsYXJlZFZpcnR1YWxGaWVsZE5hbWVzIiwia2V5IiwiZmllbGQiLCJ2YWx1ZVBhc3NlZCIsImhhc093blByb3BlcnR5IiwiTWFueVRvTWFueSIsIm5vcm1hbGl6ZUVudGl0eSIsImdldERlZmF1bHQiLCJhcyIsIm5ld0VudHJ5IiwiYXBwbHlVcGRhdGUiLCJhY3Rpb24iLCJDUkVBVEUiLCJUaGlzTW9kZWwiLCJpbnN0YW5jZSIsIl9yZWZyZXNoTWFueTJNYW55IiwidXBzZXJ0IiwiaWQiLCJpZEV4aXN0cyIsIm1vZGVsIiwid2l0aElkIiwidXBkYXRlIiwiZXhpc3RzIiwibG9va3VwT2JqIiwiQm9vbGVhbiIsIl9maW5kRGF0YWJhc2VSb3dzIiwibGVuZ3RoIiwicm93cyIsImNvbnN0cnVjdG9yIiwicXVlcnlTcGVjIiwicXVlcnkiLCJjbGFzc05hbWUiLCJmaWVsZE5hbWVzIiwidG9Nb2RlbEFycmF5IiwidmFsIiwiZXF1YWxzIiwib3RoZXJNb2RlbCIsIm9iamVjdFNoYWxsb3dFcXVhbHMiLCJwcm9wZXJ0eU5hbWUiLCJ1c2VyTWVyZ2VPYmoiLCJtZXJnZU9iaiIsIm1lcmdlS2V5IiwiaXNSZWFsRmllbGQiLCJGb3JlaWduS2V5IiwiT25lVG9PbmUiLCJtZXJnZWRGaWVsZHMiLCJ1cGRhdGVkTW9kZWwiLCJVUERBVEUiLCJyZWZyZXNoRnJvbVN0YXRlIiwicmVmIiwiZGVsZXRlIiwiX29uRGVsZXRlIiwiREVMRVRFIiwicmVsYXRpb25zIiwibmFtZSIsInJldmVyc2UiLCJ2YWx1ZXMiLCJBcnJheSIsImlzQXJyYXkiLCJUeXBlRXJyb3IiLCJub3JtYWxpemVkTmV3SWRzIiwidW5pcXVlSWRzIiwiU2V0IiwidGhyb3VnaE1vZGVsTmFtZSIsInRocm91Z2giLCJtMm1OYW1lIiwiVGhyb3VnaE1vZGVsIiwiZnJvbUZpZWxkIiwidG9GaWVsZCIsImZyb20iLCJ0byIsInRocm91Z2hGaWVsZHMiLCJjdXJyZW50SWRzIiwiZmlsdGVyIiwidG9SZWZBcnJheSIsImRpZmZBY3Rpb25zIiwiYXJyYXlEaWZmQWN0aW9ucyIsImlkc1RvRGVsZXRlIiwiYWRkIiwiaWRzVG9BZGQiLCJyZW1vdmUiLCJkZXNjcmlwdG9yS2V5IiwiY2xlYXIiLCJyZWxhdGVkUXMiLCJyZWxhdGVkTmFtZSIsImhhc0lkIiwiY29uc29sZSIsIndhcm4iLCJnZXROZXh0U3RhdGUiLCJkYiIsImRlc2Ny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SSxxQ0FGRixDQUFOO0FBSUgsR0E1dUJNOztBQUFBO0FBQUE7QUFBQSxTQTRhUCxZQUFVO0FBQ04sWUFBTXNDLFNBQVMsR0FBRyxLQUFLbkUsUUFBTCxFQUFsQixDQURNLENBR047O0FBQ0EsYUFBT21FLFNBQVMsQ0FBQ1ksaUJBQVYsQ0FBNEI7QUFDL0IsU0FBQ1osU0FBUyxDQUFDbEUsV0FBWCxHQUF5QixLQUFLTyxLQUFMO0FBRE0sT0FBNUIsRUFFSixDQUZJLENBQVA7QUFHSDtBQW5iTTtBQUFBO0FBQUEsU0F5SFAsWUFBeUI7QUFDckIsVUFBSSxPQUFPLEtBQUtvQixRQUFaLEtBQXlCLFdBQTdCLEVBQTBDO0FBQ3RDLGNBQU0sSUFBSUMsS0FBSixDQUNGLENBQ0ssb0JBQW1CLEtBQUszQixTQUFVLDJDQUR2QyxFQUVJLDhEQUZKLEVBR0ssY0FBYSxLQUFLQSxTQUFVLDJCQUhqQyxFQUlFNEIsSUFKRixDQUlPLEVBSlAsQ0FERSxDQUFOO0FBT0g7O0FBQ0QsYUFBTyxLQUFLQyxPQUFMLENBQWFnSCxFQUFiLENBQWdCQyxRQUFoQixDQUF5QixLQUFLOUksU0FBOUIsRUFBeUNELFdBQWhEO0FBQ0g7QUFwSU07QUFBQTtBQUFBLFNBMkpQLFlBQXFCO0FBQ2pCLGFBQU8sS0FBSzJCLFFBQVo7QUFDSDtBQTdKTTtBQUFBO0FBQUEsU0FxTFAsWUFBbUI7QUFDZixhQUFPLEtBQUtXLFdBQUwsRUFBUDtBQUNIO0FBdkxNOztBQUFBO0FBQUEsR0FBWDs7QUErdUJBOUIsS0FBSyxDQUFDNEMsTUFBTixHQUFlO0FBQ1hrQixJQUFFLEVBQUUwRSxvREFBSTtBQURHLENBQWY7QUFHQXhJLEtBQUssQ0FBQ29DLGFBQU4sR0FBc0IsRUFBdEI7QUFDQXBDLEtBQUssQ0FBQytCLGFBQU4sR0FBc0IwRyxpREFBdEI7QUFFZXpJLG9FQUFmIiwiZmlsZSI6Ii4vc3JjL01vZGVsLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFNlc3Npb24gZnJvbSBcIi4vU2Vzc2lvblwiO1xuaW1wb3J0IFF1ZXJ5U2V0IGZyb20gXCIuL1F1ZXJ5U2V0XCI7XG5cbmltcG9ydCB7IGF0dHIgfSBmcm9tIFwiLi9maWVsZHNcIjtcbmltcG9ydCBGb3JlaWduS2V5IGZyb20gXCIuL2ZpZWxkcy9Gb3JlaWduS2V5XCI7XG5pbXBvcnQgTWFueVRvTWFueSBmcm9tIFwiLi9maWVsZHMvTWFueVRvTWFueVwiO1xuaW1wb3J0IE9uZVRvT25lIGZyb20gXCIuL2ZpZWxkcy9PbmVUb09uZVwiO1xuXG5pbXBvcnQgeyBDUkVBVEUsIFVQREFURSwgREVMRVRFLCBGSUxURVIgfSBmcm9tIFwiLi9jb25zdGFudHNcIjtcbmltcG9ydCB7XG4gICAgbm9ybWFsaXplRW50aXR5LFxuICAgIGFycmF5RGlmZkFjdGlvbnMsXG4gICAgb2JqZWN0U2hhbGxvd0VxdWFscyxcbiAgICB3YXJuRGVwcmVjYXRlZCxcbiAgICBtMm1OYW1lLFxufSBmcm9tIFwiLi91dGlsc1wiO1xuXG4vKipcbiAqIEdlbmVyYXRlcyBhIHF1ZXJ5IHNwZWNpZmljYXRpb24gdG8gZ2V0IHRoZSBpbnN0YW5jZSdzXG4gKiBjb3JyZXNwb25kaW5nIHRhYmxlIHJvdyB1c2luZyBpdHMgcHJpbWFyeSBrZXkuXG4gKlxuICogQHByaXZhdGVcbiAqIEByZXR1cm5zIHtPYmplY3R9XG4gKi9cbmZ1bmN0aW9uIGdldEJ5SWRRdWVyeShtb2RlbEluc3RhbmNlKSB7XG4gICAgY29uc3QgbW9kZWxDbGFzcyA9IG1vZGVsSW5zdGFuY2UuZ2V0Q2xhc3MoKTtcbiAgICBjb25zdCB7IGlkQXR0cmlidXRlLCBtb2RlbE5hbWUgfSA9IG1vZGVsQ2xhc3M7XG5cbiAgICByZXR1cm4ge1xuICAgICAgICB0YWJsZTogbW9kZWxOYW1lLFxuICAgICAgICBjbGF1c2VzOiBbXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgdHlwZTogRklMVEVSLFxuICAgICAgICAgICAgICAgIHBheWxvYWQ6IHtcbiAgICAgICAgICAgICAgICAgICAgW2lkQXR0cmlidXRlXTogbW9kZWxJbnN0YW5jZS5nZXRJZCgpLFxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICB9LFxuICAgICAgICBdLFxuICAgIH07XG59XG5cbi8qKlxuICogVGhlIGhlYXJ0IG9mIGFuIE9STSwgdGhlIGRhdGEgbW9kZWwuXG4gKlxuICogVGhlIGZpZWxkcyB5b3Ugc3BlY2lmeSB0byB0aGUgTW9kZWwgd2lsbCBiZSB1c2VkIHRvIGdlbmVyYXRlXG4gKiBhIHNjaGVtYSB0byB0aGUgZGF0YWJhc2UsIHJlbGF0ZWQgcHJvcGVydHkgYWNjZXNzb3JzLCBhbmRcbiAqIHBvc3NpYmx5IHRocm91Z2ggbW9kZWxzLlxuICpcbiAqIEluIGVhY2gge0BsaW5rIFNlc3Npb259IHlvdSBpbnN0YW50aWF0ZSBmcm9tIGFuIHtAbGluayBPUk19IGluc3RhbmNlLFxuICogeW91IHdpbGwgcmVjZWl2ZSBhIHNlc3Npb24tc3BlY2lmaWMgc3ViY2xhc3Mgb2YgdGhpcyBNb2RlbC4gVGhlIG1ldGhvZHNcbiAqIHlvdSBkZWZpbmUgaGVyZSB3aWxsIGJlIGF2YWlsYWJsZSB0byB5b3UgaW4gc2Vzc2lvbnMuXG4gKlxuICogQW4gaW5zdGFuY2Ugb2Yge0BsaW5rIE1vZGVsfSByZXByZXNlbnRzIGEgcmVjb3JkIGluIHRoZSBkYXRhYmFzZSwgdGhvdWdoXG4gKiBpdCBpcyBwb3NzaWJsZSB0byBnZW5lcmF0ZSBtdWx0aXBsZSBpbnN0YW5jZXMgZnJvbSB0aGUgc2FtZSByZWNvcmQgaW4gdGhlIGRhdGFiYXNlLlxuICpcbiAqIFRvIGNyZWF0ZSBkYXRhIG1vZGVscyBpbiB5b3VyIHNjaGVtYSwgc3ViY2xhc3Mge0BsaW5rIE1vZGVsfS4gVG8gZGVmaW5lXG4gKiBpbmZvcm1hdGlvbiBhYm91dCB0aGUgZGF0YSBtb2RlbCwgb3ZlcnJpZGUgc3RhdGljIGNsYXNzIG1ldGhvZHMuIERlZmluZSBpbnN0YW5jZVxuICogbG9naWMgYnkgZGVmaW5pbmcgcHJvdG90eXBlIG1ldGhvZHMgKHdpdGhvdXQgYHN0YXRpY2Aga2V5d29yZCkuXG4gKi9cbmNvbnN0IE1vZGVsID0gY2xhc3MgTW9kZWwge1xuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBNb2RlbCBpbnN0YW5jZSBmcm9tIGl0J3MgcHJvcGVydGllcy5cbiAgICAgKiBEb24ndCB1c2UgdGhpcyB0byBjcmVhdGUgYSBuZXcgcmVjb3JkOyBVc2UgdGhlIHN0YXRpYyBtZXRob2Qge0BsaW5rIE1vZGVsI2NyZWF0ZX0uXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBwcm9wcyAtIHRoZSBwcm9wZXJ0aWVzIHRvIGluc3RhbnRpYXRlIHdpdGhcbiAgICAgKi9cbiAgICBjb25zdHJ1Y3Rvcihwcm9wcykge1xuICAgICAgICB0aGlzLl9pbml0RmllbGRzKHByb3BzKTtcbiAgICB9XG5cbiAgICBfaW5pdEZpZWxkcyhwcm9wcykge1xuICAgICAgICBjb25zdCBwcm9wc09iaiA9IE9iamVjdChwcm9wcyk7XG4gICAgICAgIHRoaXMuX2ZpZWxkcyA9IHsgLi4ucHJvcHNPYmogfTtcblxuICAgICAgICBPYmplY3Qua2V5cyhwcm9wc09iaikuZm9yRWFjaCgoZmllbGROYW1lKSA9PiB7XG4gICAgICAgICAgICAvLyBJbiB0aGlzIGNhc2UsIHdlIGdvdCBhIHByb3AgdGhhdCB3YXNuJ3QgZGVmaW5lZCBhcyBhIGZpZWxkLlxuICAgICAgICAgICAgLy8gQXNzdW1pbmcgaXQncyBhbiBhcmJpdHJhcnkgZGF0YSBmaWVsZCwgbWFraW5nIGFuIGluc3RhbmNlLXNwZWNpZmljXG4gICAgICAgICAgICAvLyBkZXNjcmlwdG9yIGZvciBpdC5cbiAgICAgICAgICAgIC8vIFVzaW5nIHRoZSBpbiBvcGVyYXRvciBhcyB0aGUgcHJvcGVydHkgY291bGQgYmUgZGVmaW5lZCBhbnl3aGVyZVxuICAgICAgICAgICAgLy8gb24gdGhlIHByb3RvdHlwZSBjaGFpbi5cbiAgICAgICAgICAgIGlmICghKGZpZWxkTmFtZSBpbiB0aGlzKSkge1xuICAgICAgICAgICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCBmaWVsZE5hbWUsIHtcbiAgICAgICAgICAgICAgICAgICAgZ2V0OiAoKSA9PiB0aGlzLl9maWVsZHNbZmllbGROYW1lXSxcbiAgICAgICAgICAgICAgICAgICAgc2V0OiAodmFsdWUpID0+IHRoaXMuc2V0KGZpZWxkTmFtZSwgdmFsdWUpLFxuICAgICAgICAgICAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgICAgICAgICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIHN0YXRpYyB0b1N0cmluZygpIHtcbiAgICAgICAgcmV0dXJuIGBNb2RlbENsYXNzOiAke3RoaXMubW9kZWxOYW1lfWA7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgb3B0aW9ucyBvYmplY3QgcGFzc2VkIHRvIHRoZSBkYXRhYmFzZSBmb3IgdGhlIHRhYmxlIHRoYXQgcmVwcmVzZW50c1xuICAgICAqIHRoaXMgTW9kZWwgY2xhc3MuXG4gICAgICpcbiAgICAgKiBSZXR1cm5zIGFuIGVtcHR5IG9iamVjdCBieSBkZWZhdWx0LCB3aGljaCBtZWFucyB0aGUgZGF0YWJhc2VcbiAgICAgKiB3aWxsIHVzZSBkZWZhdWx0IG9wdGlvbnMuIFlvdSBjYW4gZWl0aGVyIG92ZXJyaWRlIHRoaXMgZnVuY3Rpb24gdG8gcmV0dXJuIHRoZSBvcHRpb25zXG4gICAgICogeW91IHdhbnQgdG8gdXNlLCBvciBhc3NpZ24gdGhlIG9wdGlvbnMgb2JqZWN0IGFzIGEgc3RhdGljIHByb3BlcnR5IG9mIHRoZSBzYW1lIG5hbWUgdG8gdGhlXG4gICAgICogTW9kZWwgY2xhc3MuXG4gICAgICpcbiAgICAgKiBAcmV0dXJuIHtPYmplY3R9IHRoZSBvcHRpb25zIG9iamVjdCBwYXNzZWQgdG8gdGhlIGRhdGFiYXNlIGZvciB0aGUgdGFibGVcbiAgICAgKiAgICAgICAgICAgICAgICAgIHJlcHJlc2VudGluZyB0aGlzIE1vZGVsIGNsYXNzLlxuICAgICAqL1xuICAgIHN0YXRpYyBvcHRpb25zKCkge1xuICAgICAgICByZXR1cm4ge307XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogTWFudWFsbHkgbWFyayBpbmRpdmlkdWFsIGluc3RhbmNlcyBhcyBhY2Nlc3NlZC5cbiAgICAgKiBUaGlzIGFsbG93cyBpbnZhbGlkYXRpbmcgc2VsZWN0b3IgbWVtb2l6YXRpb24gd2l0aGluIG11dGFibGUgc2Vzc2lvbnMuXG4gICAgICpcbiAgICAgKiBAcGFyYW0ge0FycmF5LjwqPn0gaWRzIC0gQXJyYXkgb2YgcHJpbWFyeSBrZXkgdmFsdWVzXG4gICAgICogQHJldHVybiB7dW5kZWZpbmVkfVxuICAgICAqL1xuICAgIHN0YXRpYyBtYXJrQWNjZXNzZWQoaWRzKSB7XG4gICAgICAgIGlmICh0eXBlb2YgdGhpcy5fc2Vzc2lvbiA9PT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIFtcbiAgICAgICAgICAgICAgICAgICAgYFRyaWVkIHRvIG1hcmsgcm93cyBvZiB0aGUgJHt0aGlzLm1vZGVsTmFtZX0gbW9kZWwgYXMgYWNjZXNzZWQgd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBjYWxsIFwiLFxuICAgICAgICAgICAgICAgICAgICBgXFxgc2Vzc2lvbltcIiR7dGhpcy5tb2RlbE5hbWV9XCJdLm1hcmtBY2Nlc3NlZFxcYCBpbnN0ZWFkLmAsXG4gICAgICAgICAgICAgICAgXS5qb2luKFwiXCIpXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuc2Vzc2lvbi5tYXJrQWNjZXNzZWQodGhpcy5tb2RlbE5hbWUsIGlkcyk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogTWFudWFsbHkgbWFyayB0aGlzIG1vZGVsJ3MgdGFibGUgYXMgc2Nhbm5lZC5cbiAgICAgKiBUaGlzIGFsbG93cyBpbnZhbGlkYXRpbmcgc2VsZWN0b3IgbWVtb2l6YXRpb24gd2l0aGluIG11dGFibGUgc2Vzc2lvbnMuXG4gICAgICpcbiAgICAgKiBAcmV0dXJuIHt1bmRlZmluZWR9XG4gICAgICovXG4gICAgc3RhdGljIG1hcmtGdWxsVGFibGVTY2FubmVkKCkge1xuICAgICAgICBpZiAodHlwZW9mIHRoaXMuX3Nlc3Npb24gPT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBbXG4gICAgICAgICAgICAgICAgICAgIGBUcmllZCB0byBtYXJrIHRoZSAke3RoaXMubW9kZWxOYW1lfSBtb2RlbCBhcyBmdWxsIHRhYmxlIHNjYW5uZWQgd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBjYWxsIFwiLFxuICAgICAgICAgICAgICAgICAgICBgXFxgc2Vzc2lvbltcIiR7dGhpcy5tb2RlbE5hbWV9XCJdLm1hcmtGdWxsVGFibGVTY2FubmVkXFxgIGluc3RlYWQuYCxcbiAgICAgICAgICAgICAgICBdLmpvaW4oXCJcIilcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5zZXNzaW9uLm1hcmtGdWxsVGFibGVTY2FubmVkKHRoaXMubW9kZWxOYW1lKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBNYW51YWxseSBtYXJrIGluZGV4ZXMgYXMgYWNjZXNzZWQuXG4gICAgICogVGhpcyBhbGxvd3MgaW52YWxpZGF0aW5nIHNlbGVjdG9yIG1lbW9pemF0aW9uIHdpdGhpbiBtdXRhYmxlIHNlc3Npb25zLlxuICAgICAqXG4gICAgICogQHBhcmFtIHtBcnJheS48QXJyYXkuPCosKj4+fSBpbmRleGVzIC0gQXJyYXkgb2YgY29sdW1uLXZhbHVlIHBhaXJzXG4gICAgICogQHJldHVybiB7dW5kZWZpbmVkfVxuICAgICAqL1xuICAgIHN0YXRpYyBtYXJrQWNjZXNzZWRJbmRleGVzKGluZGV4ZXMpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLl9zZXNzaW9uID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgW1xuICAgICAgICAgICAgICAgICAgICBgVHJpZWQgdG8gbWFyayBpbmRleGVzIGZvciB0aGUgJHt0aGlzLm1vZGVsTmFtZX0gbW9kZWwgYXMgYWNjZXNzZWQgd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBjYWxsIFwiLFxuICAgICAgICAgICAgICAgICAgICBgXFxgc2Vzc2lvbltcIiR7dGhpcy5tb2RlbE5hbWV9XCJdLm1hcmtBY2Nlc3NlZEluZGV4ZXNcXGAgaW5zdGVhZC5gLFxuICAgICAgICAgICAgICAgIF0uam9pbihcIlwiKVxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLnNlc3Npb24ubWFya0FjY2Vzc2VkSW5kZXhlcyhcbiAgICAgICAgICAgIGluZGV4ZXMubWFwKChbYXR0cmlidXRlLCB2YWx1ZV0pID0+IFtcbiAgICAgICAgICAgICAgICB0aGlzLm1vZGVsTmFtZSxcbiAgICAgICAgICAgICAgICBhdHRyaWJ1dGUsXG4gICAgICAgICAgICAgICAgdmFsdWUsXG4gICAgICAgICAgICBdKVxuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGlkIGF0dHJpYnV0ZSBvZiB0aGlzIHtAbGluayBNb2RlbH0uXG4gICAgICpcbiAgICAgKiBAcmV0dXJuIHtzdHJpbmd9IFRoZSBpZCBhdHRyaWJ1dGUgb2YgdGhpcyB7QGxpbmsgTW9kZWx9LlxuICAgICAqL1xuICAgIHN0YXRpYyBnZXQgaWRBdHRyaWJ1dGUoKSB7XG4gICAgICAgIGlmICh0eXBlb2YgdGhpcy5fc2Vzc2lvbiA9PT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIFtcbiAgICAgICAgICAgICAgICAgICAgYFRyaWVkIHRvIGdldCB0aGUgJHt0aGlzLm1vZGVsTmFtZX0gbW9kZWwncyBpZCBhdHRyaWJ1dGUgd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBhY2Nlc3MgXCIsXG4gICAgICAgICAgICAgICAgICAgIGBcXGBzZXNzaW9uW1wiJHt0aGlzLm1vZGVsTmFtZX1cIl0uaWRBdHRyaWJ1dGVcXGAgaW5zdGVhZC5gLFxuICAgICAgICAgICAgICAgIF0uam9pbihcIlwiKVxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5zZXNzaW9uLmRiLmRlc2NyaWJlKHRoaXMubW9kZWxOYW1lKS5pZEF0dHJpYnV0ZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDb25uZWN0IHRoZSBtb2RlbCBjbGFzcyB0byBhIHtAbGluayBTZXNzaW9ufS5cbiAgICAgKlxuICAgICAqIEBwcml2YXRlXG4gICAgICogQHBhcmFtICB7U2Vzc2lvbn0gc2Vzc2lvbiAtIFRoZSBzZXNzaW9uIHRvIGNvbm5lY3QgdG8uXG4gICAgICovXG4gICAgc3RhdGljIGNvbm5lY3Qoc2Vzc2lvbikge1xuICAgICAgICBpZiAoIShzZXNzaW9uIGluc3RhbmNlb2YgU2Vzc2lvbikpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBcIkEgbW9kZWwgY2FuIG9ubHkgYmUgY29ubmVjdGVkIHRvIGluc3RhbmNlcyBvZiBTZXNzaW9uLlwiXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuX3Nlc3Npb24gPSBzZXNzaW9uO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEdldCB0aGUgY3VycmVudCB7QGxpbmsgU2Vzc2lvbn0gaW5zdGFuY2UuXG4gICAgICpcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqIEByZXR1cm4ge1Nlc3Npb259IFRoZSBjdXJyZW50IHtAbGluayBTZXNzaW9ufSBpbnN0YW5jZS5cbiAgICAgKi9cbiAgICBzdGF0aWMgZ2V0IHNlc3Npb24oKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9zZXNzaW9uO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYW4gaW5zdGFuY2Ugb2YgdGhlIG1vZGVsJ3MgYHF1ZXJ5U2V0Q2xhc3NgIGZpZWxkLlxuICAgICAqIEJ5IGRlZmF1bHQsIHRoaXMgd2lsbCBiZSBhbiBlbXB0eSB7QGxpbmsgUXVlcnlTZXR9LlxuICAgICAqXG4gICAgICogQHJldHVybiB7T2JqZWN0fSBBbiBpbnN0YW5jZSBvZiB0aGUgbW9kZWwncyBgcXVlcnlTZXRDbGFzc2AuXG4gICAgICovXG4gICAgc3RhdGljIGdldFF1ZXJ5U2V0KCkge1xuICAgICAgICBjb25zdCB7IHF1ZXJ5U2V0Q2xhc3M6IFF1ZXJ5U2V0Q2xhc3MgfSA9IHRoaXM7XG4gICAgICAgIHJldHVybiBuZXcgUXVlcnlTZXRDbGFzcyh0aGlzKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAcmV0dXJuIHt1bmRlZmluZWR9XG4gICAgICovXG4gICAgc3RhdGljIGludmFsaWRhdGVDbGFzc0NhY2hlKCkge1xuICAgICAgICB0aGlzLmlzU2V0VXAgPSB1bmRlZmluZWQ7XG4gICAgICAgIHRoaXMudmlydHVhbEZpZWxkcyA9IHt9O1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEBzZWUge0BsaW5rIE1vZGVsLmdldFF1ZXJ5U2V0fVxuICAgICAqL1xuICAgIHN0YXRpYyBnZXQgcXVlcnkoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldFF1ZXJ5U2V0KCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBwYXJhbWV0ZXJzIHRvIGJlIHBhc3NlZCB0byB7QGxpbmsgVGFibGV9IGluc3RhbmNlLlxuICAgICAqXG4gICAgICogQHByaXZhdGVcbiAgICAgKi9cbiAgICBzdGF0aWMgdGFibGVPcHRpb25zKCkge1xuICAgICAgICBpZiAodHlwZW9mIHRoaXMuYmFja2VuZCA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgICB3YXJuRGVwcmVjYXRlZChcbiAgICAgICAgICAgICAgICBcImBNb2RlbC5iYWNrZW5kYCBoYXMgYmVlbiBkZXByZWNhdGVkLiBQbGVhc2UgcmVuYW1lIHRvIGAub3B0aW9uc2AuXCJcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5iYWNrZW5kKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuYmFja2VuZCkge1xuICAgICAgICAgICAgd2FybkRlcHJlY2F0ZWQoXG4gICAgICAgICAgICAgICAgXCJgTW9kZWwuYmFja2VuZGAgaGFzIGJlZW4gZGVwcmVjYXRlZC4gUGxlYXNlIHJlbmFtZSB0byBgLm9wdGlvbnNgLlwiXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuYmFja2VuZDtcbiAgICAgICAgfVxuICAgICAgICBpZiAodHlwZW9mIHRoaXMub3B0aW9ucyA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zKCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucztcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IHJlY29yZCBpbiB0aGUgZGF0YWJhc2UsIGluc3RhbnRpYXRlcyBhIHtAbGluayBNb2RlbH0gYW5kIHJldHVybnMgaXQuXG4gICAgICpcbiAgICAgKiBJZiB5b3UgcGFzcyB2YWx1ZXMgZm9yIG1hbnktdG8tbWFueSBmaWVsZHMsIGluc3RhbmNlcyBhcmUgY3JlYXRlZCBvbiB0aGUgdGhyb3VnaFxuICAgICAqIG1vZGVsIGFzIHdlbGwuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IHVzZXJQcm9wcyAtIHRoZSBuZXcge0BsaW5rIE1vZGVsfSdzIHByb3BlcnRpZXMuXG4gICAgICogQHJldHVybiB7TW9kZWx9IGEgbmV3IHtAbGluayBNb2RlbH0gaW5zdGFuY2UuXG4gICAgICovXG4gICAgc3RhdGljIGNyZWF0ZSh1c2VyUHJvcHMpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLl9zZXNzaW9uID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgW1xuICAgICAgICAgICAgICAgICAgICBgVHJpZWQgdG8gY3JlYXRlIGEgJHt0aGlzLm1vZGVsTmFtZX0gbW9kZWwgaW5zdGFuY2Ugd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBjYWxsIFwiLFxuICAgICAgICAgICAgICAgICAgICBgXFxgc2Vzc2lvbltcIiR7dGhpcy5tb2RlbE5hbWV9XCJdLmNyZWF0ZVxcYCBpbnN0ZWFkLmAsXG4gICAgICAgICAgICAgICAgXS5qb2luKFwiXCIpXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHByb3BzID0geyAuLi51c2VyUHJvcHMgfTtcblxuICAgICAgICBjb25zdCBtMm1SZWxhdGlvbnMgPSB7fTtcblxuICAgICAgICBjb25zdCBkZWNsYXJlZEZpZWxkTmFtZXMgPSBPYmplY3Qua2V5cyh0aGlzLmZpZWxkcyk7XG4gICAgICAgIGNvbnN0IGRlY2xhcmVkVmlydHVhbEZpZWxkTmFtZXMgPSBPYmplY3Qua2V5cyh0aGlzLnZpcnR1YWxGaWVsZHMpO1xuXG4gICAgICAgIGRlY2xhcmVkRmllbGROYW1lcy5mb3JFYWNoKChrZXkpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGZpZWxkID0gdGhpcy5maWVsZHNba2V5XTtcbiAgICAgICAgICAgIGNvbnN0IHZhbHVlUGFzc2VkID0gdXNlclByb3BzLmhhc093blByb3BlcnR5KGtleSk7XG4gICAgICAgICAgICBpZiAoIShmaWVsZCBpbnN0YW5jZW9mIE1hbnlUb01hbnkpKSB7XG4gICAgICAgICAgICAgICAgaWYgKHZhbHVlUGFzc2VkKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHZhbHVlID0gdXNlclByb3BzW2tleV07XG4gICAgICAgICAgICAgICAgICAgIHByb3BzW2tleV0gPSBub3JtYWxpemVFbnRpdHkodmFsdWUpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAoZmllbGQuZ2V0RGVmYXVsdCkge1xuICAgICAgICAgICAgICAgICAgICBwcm9wc1trZXldID0gZmllbGQuZ2V0RGVmYXVsdCh1c2VyUHJvcHMpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gZWxzZSBpZiAodmFsdWVQYXNzZWQpIHtcbiAgICAgICAgICAgICAgICAvLyBTYXZlIGZvciBsYXRlciBwcm9jZXNzaW5nXG4gICAgICAgICAgICAgICAgbTJtUmVsYXRpb25zW2tleV0gPSB1c2VyUHJvcHNba2V5XTtcblxuICAgICAgICAgICAgICAgIGlmICghZmllbGQuYXMpIHtcbiAgICAgICAgICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgICAgICAgICAqIFRoZSByZWxhdGlvbnNoaXAgZG9lcyBub3QgaGF2ZSBhbiBhY2Nlc3NvclxuICAgICAgICAgICAgICAgICAgICAgKiBEaXNjYXJkIHRoZSB2YWx1ZSBmcm9tIHByb3BzIGFzIHRoZSBmaWVsZCB3aWxsIGJlIHBvcHVsYXRlZCBsYXRlciB3aXRoIGluc3RhbmNlc1xuICAgICAgICAgICAgICAgICAgICAgKiBmcm9tIHRoZSB0YXJnZXQgbW9kZWxzIHdoZW4gcmVmcmVzaGluZyB0aGUgTTJNIHJlbGF0aW9ucy5cbiAgICAgICAgICAgICAgICAgICAgICogSWYgdGhlIHJlbGF0aW9uc2hpcCBkb2VzIGhhdmUgYW4gYWNjZXNzb3IgKGBhc2ApIGZpZWxkIHRoZW4gd2UgZG8gd2FudCB0byBrZWVwIHRoaXNcbiAgICAgICAgICAgICAgICAgICAgICogb3JpZ2luYWwgdmFsdWUgaW4gdGhlIHByb3BzIHRvIGV4cG9zZSB0aGUgcmF3IGxpc3Qgb2YgSURzIGZyb20gdGhlIGluc3RhbmNlLlxuICAgICAgICAgICAgICAgICAgICAgKi9cbiAgICAgICAgICAgICAgICAgICAgZGVsZXRlIHByb3BzW2tleV07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcblxuICAgICAgICAvLyBhZGQgYmFja3dhcmQgbWFueS1tYW55IGlmIHJlcXVpcmVkXG4gICAgICAgIGRlY2xhcmVkVmlydHVhbEZpZWxkTmFtZXMuZm9yRWFjaCgoa2V5KSA9PiB7XG4gICAgICAgICAgICBpZiAoIW0ybVJlbGF0aW9ucy5oYXNPd25Qcm9wZXJ0eShrZXkpKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgZmllbGQgPSB0aGlzLnZpcnR1YWxGaWVsZHNba2V5XTtcbiAgICAgICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgICAgICAgIHVzZXJQcm9wcy5oYXNPd25Qcm9wZXJ0eShrZXkpICYmXG4gICAgICAgICAgICAgICAgICAgIGZpZWxkIGluc3RhbmNlb2YgTWFueVRvTWFueVxuICAgICAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAgICAgICAvLyBJZiBhIHZhbHVlIGlzIHN1cHBsaWVkIGZvciBhIE1hbnlUb01hbnkgZmllbGQsXG4gICAgICAgICAgICAgICAgICAgIC8vIGRpc2NhcmQgdGhlbSBmcm9tIHByb3BzIGFuZCBzYXZlIGZvciBsYXRlciBwcm9jZXNzaW5nLlxuICAgICAgICAgICAgICAgICAgICBtMm1SZWxhdGlvbnNba2V5XSA9IHVzZXJQcm9wc1trZXldO1xuICAgICAgICAgICAgICAgICAgICBkZWxldGUgcHJvcHNba2V5XTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuXG4gICAgICAgIGNvbnN0IG5ld0VudHJ5ID0gdGhpcy5zZXNzaW9uLmFwcGx5VXBkYXRlKHtcbiAgICAgICAgICAgIGFjdGlvbjogQ1JFQVRFLFxuICAgICAgICAgICAgdGFibGU6IHRoaXMubW9kZWxOYW1lLFxuICAgICAgICAgICAgcGF5bG9hZDogcHJvcHMsXG4gICAgICAgIH0pO1xuXG4gICAgICAgIGNvbnN0IFRoaXNNb2RlbCA9IHRoaXM7XG4gICAgICAgIGNvbnN0IGluc3RhbmNlID0gbmV3IFRoaXNNb2RlbChuZXdFbnRyeSk7XG4gICAgICAgIGluc3RhbmNlLl9yZWZyZXNoTWFueTJNYW55KG0ybVJlbGF0aW9ucyk7IC8vIGVzbGludC1kaXNhYmxlLWxpbmUgbm8tdW5kZXJzY29yZS1kYW5nbGVcbiAgICAgICAgcmV0dXJuIGluc3RhbmNlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgb3IgdXBkYXRlIGV4aXN0aW5nIHJlY29yZCBpbiB0aGUgZGF0YWJhc2UsIGluc3RhbnRpYXRlcyBhIHtAbGluayBNb2RlbH0gYW5kIHJldHVybnMgaXQuXG4gICAgICpcbiAgICAgKiBJZiB5b3UgcGFzcyB2YWx1ZXMgZm9yIG1hbnktdG8tbWFueSBmaWVsZHMsIGluc3RhbmNlcyBhcmUgY3JlYXRlZCBvbiB0aGUgdGhyb3VnaFxuICAgICAqIG1vZGVsIGFzIHdlbGwuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IHVzZXJQcm9wcyAtIHRoZSByZXF1aXJlZCB7QGxpbmsgTW9kZWx9J3MgcHJvcGVydGllcy5cbiAgICAgKiBAcmV0dXJuIHtNb2RlbH0gYSB7QGxpbmsgTW9kZWx9IGluc3RhbmNlLlxuICAgICAqL1xuICAgIHN0YXRpYyB1cHNlcnQodXNlclByb3BzKSB7XG4gICAgICAgIGlmICh0eXBlb2YgdGhpcy5zZXNzaW9uID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgW1xuICAgICAgICAgICAgICAgICAgICBgVHJpZWQgdG8gdXBzZXJ0IGEgJHt0aGlzLm1vZGVsTmFtZX0gbW9kZWwgaW5zdGFuY2Ugd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBjYWxsIFwiLFxuICAgICAgICAgICAgICAgICAgICBgXFxgc2Vzc2lvbltcIiR7dGhpcy5tb2RlbE5hbWV9XCJdLnVwc2VydFxcYCBpbnN0ZWFkLmAsXG4gICAgICAgICAgICAgICAgXS5qb2luKFwiXCIpXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgeyBpZEF0dHJpYnV0ZSB9ID0gdGhpcztcbiAgICAgICAgaWYgKHVzZXJQcm9wcy5oYXNPd25Qcm9wZXJ0eShpZEF0dHJpYnV0ZSkpIHtcbiAgICAgICAgICAgIGNvbnN0IGlkID0gdXNlclByb3BzW2lkQXR0cmlidXRlXTtcbiAgICAgICAgICAgIGlmICh0aGlzLmlkRXhpc3RzKGlkKSkge1xuICAgICAgICAgICAgICAgIGNvbnN0IG1vZGVsID0gdGhpcy53aXRoSWQoaWQpO1xuICAgICAgICAgICAgICAgIG1vZGVsLnVwZGF0ZSh1c2VyUHJvcHMpO1xuICAgICAgICAgICAgICAgIHJldHVybiBtb2RlbDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLmNyZWF0ZSh1c2VyUHJvcHMpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSB7QGxpbmsgTW9kZWx9IGluc3RhbmNlIGZvciB0aGUgb2JqZWN0IHdpdGggaWQgYGlkYC5cbiAgICAgKiBSZXR1cm5zIGBudWxsYCBpZiB0aGUgbW9kZWwgaGFzIG5vIGluc3RhbmNlIHdpdGggaWQgYGlkYC5cbiAgICAgKlxuICAgICAqIFlvdSBjYW4gdXNlIHtAbGluayBNb2RlbCNpZEV4aXN0c30gdG8gY2hlY2sgZm9yIGV4aXN0ZW5jZSBpbnN0ZWFkLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7Kn0gaWQgLSB0aGUgYGlkYCBvZiB0aGUgb2JqZWN0IHRvIGdldFxuICAgICAqIEB0aHJvd3MgSWYgb2JqZWN0IHdpdGggaWQgYGlkYCBkb2Vzbid0IGV4aXN0XG4gICAgICogQHJldHVybiB7TW9kZWx8bnVsbH0ge0BsaW5rIE1vZGVsfSBpbnN0YW5jZSB3aXRoIGlkIGBpZGBcbiAgICAgKi9cbiAgICBzdGF0aWMgd2l0aElkKGlkKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldCh7XG4gICAgICAgICAgICBbdGhpcy5pZEF0dHJpYnV0ZV06IGlkLFxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIGEgYm9vbGVhbiBpbmRpY2F0aW5nIGlmIGFuIGVudGl0eVxuICAgICAqIHdpdGggdGhlIGlkIGBpZGAgZXhpc3RzIGluIHRoZSBzdGF0ZS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAgeyp9ICBpZCAtIGEgdmFsdWUgY29ycmVzcG9uZGluZyB0byB0aGUgaWQgYXR0cmlidXRlIG9mIHRoZSB7QGxpbmsgTW9kZWx9IGNsYXNzLlxuICAgICAqIEByZXR1cm4ge0Jvb2xlYW59IGEgYm9vbGVhbiBpbmRpY2F0aW5nIGlmIGVudGl0eSB3aXRoIGBpZGAgZXhpc3RzIGluIHRoZSBzdGF0ZVxuICAgICAqXG4gICAgICogQHNpbmNlIDAuMTEuMFxuICAgICAqL1xuICAgIHN0YXRpYyBpZEV4aXN0cyhpZCkge1xuICAgICAgICByZXR1cm4gdGhpcy5leGlzdHMoe1xuICAgICAgICAgICAgW3RoaXMuaWRBdHRyaWJ1dGVdOiBpZCxcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhIGJvb2xlYW4gaW5kaWNhdGluZyBpZiBhbiBlbnRpdHlcbiAgICAgKiB3aXRoIHRoZSBnaXZlbiBwcm9wcyBleGlzdHMgaW4gdGhlIHN0YXRlLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7Kn0gIHByb3BzIC0gYSBrZXktdmFsdWUgdGhhdCB7QGxpbmsgTW9kZWx9IGluc3RhbmNlcyBzaG91bGQgaGF2ZSB0byBiZSBjb25zaWRlcmVkIGFzIGV4aXN0aW5nLlxuICAgICAqIEByZXR1cm4ge0Jvb2xlYW59IGEgYm9vbGVhbiBpbmRpY2F0aW5nIGlmIGVudGl0eSB3aXRoIGBwcm9wc2AgZXhpc3RzIGluIHRoZSBzdGF0ZVxuICAgICAqL1xuICAgIHN0YXRpYyBleGlzdHMobG9va3VwT2JqKSB7XG4gICAgICAgIGlmICh0eXBlb2YgdGhpcy5zZXNzaW9uID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgW1xuICAgICAgICAgICAgICAgICAgICBgVHJpZWQgdG8gY2hlY2sgaWYgYSAke3RoaXMubW9kZWxOYW1lfSBtb2RlbCBpbnN0YW5jZSBleGlzdHMgd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCBjYWxsIFwiLFxuICAgICAgICAgICAgICAgICAgICBgXFxgc2Vzc2lvbltcIiR7dGhpcy5tb2RlbE5hbWV9XCJdLmV4aXN0c1xcYCBpbnN0ZWFkLmAsXG4gICAgICAgICAgICAgICAgXS5qb2luKFwiXCIpXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIEJvb2xlYW4odGhpcy5fZmluZERhdGFiYXNlUm93cyhsb29rdXBPYmopLmxlbmd0aCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogR2V0cyB0aGUge0BsaW5rIE1vZGVsfSBpbnN0YW5jZSB0aGF0IG1hdGNoZXMgcHJvcGVydGllcyBpbiBgbG9va3VwT2JqYC5cbiAgICAgKiBUaHJvd3MgYW4gZXJyb3IgaWYge0BsaW5rIE1vZGVsfSBpZiBtdWx0aXBsZSByZWNvcmRzIG1hdGNoXG4gICAgICogdGhlIHByb3BlcnRpZXMuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGxvb2t1cE9iaiAtIHRoZSBwcm9wZXJ0aWVzIHVzZWQgdG8gbWF0Y2ggYSBzaW5nbGUgZW50aXR5LlxuICAgICAqIEB0aHJvd3Mge0Vycm9yfSBJZiBtb3JlIHRoYW4gb25lIGVudGl0eSBtYXRjaGVzIHRoZSBwcm9wZXJ0aWVzIGluIGBsb29rdXBPYmpgLlxuICAgICAqIEByZXR1cm4ge01vZGVsfSBhIHtAbGluayBNb2RlbH0gaW5zdGFuY2UgdGhhdCBtYXRjaGVzIHRoZSBwcm9wZXJ0aWVzIGluIGBsb29rdXBPYmpgLlxuICAgICAqL1xuICAgIHN0YXRpYyBnZXQobG9va3VwT2JqKSB7XG4gICAgICAgIGNvbnN0IFRoaXNNb2RlbCA9IHRoaXM7XG5cbiAgICAgICAgY29uc3Qgcm93cyA9IHRoaXMuX2ZpbmREYXRhYmFzZVJvd3MobG9va3VwT2JqKTtcbiAgICAgICAgaWYgKHJvd3MubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgfVxuICAgICAgICBpZiAocm93cy5sZW5ndGggPiAxKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgYEV4cGVjdGVkIHRvIGZpbmQgYSBzaW5nbGUgcm93IGluIFxcYCR7dGhpcy5tb2RlbE5hbWV9LmdldFxcYC4gRm91bmQgJHtyb3dzLmxlbmd0aH0uYFxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBuZXcgVGhpc01vZGVsKHJvd3NbMF0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEdldHMgdGhlIHtAbGluayBNb2RlbH0gY2xhc3Mgb3Igc3ViY2xhc3MgY29uc3RydWN0b3IgKHRoZSBjbGFzcyB0aGF0XG4gICAgICogaW5zdGFudGlhdGVkIHRoaXMgaW5zdGFuY2UpLlxuICAgICAqXG4gICAgICogQHJldHVybiB7TW9kZWx9IFRoZSB7QGxpbmsgTW9kZWx9IGNsYXNzIG9yIHN1YmNsYXNzIGNvbnN0cnVjdG9yIHVzZWQgdG8gaW5zdGFudGlhdGVcbiAgICAgKiAgICAgICAgICAgICAgICAgdGhpcyBpbnN0YW5jZS5cbiAgICAgKi9cbiAgICBnZXRDbGFzcygpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuY29uc3RydWN0b3I7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogR2V0cyB0aGUgaWQgdmFsdWUgb2YgdGhlIGN1cnJlbnQgaW5zdGFuY2UgYnkgbG9va2luZyB1cCB0aGUgaWQgYXR0cmlidXRlLlxuICAgICAqIEByZXR1cm4geyp9IFRoZSBpZCB2YWx1ZSBvZiB0aGUgY3VycmVudCBpbnN0YW5jZS5cbiAgICAgKi9cbiAgICBnZXRJZCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2ZpZWxkc1t0aGlzLmdldENsYXNzKCkuaWRBdHRyaWJ1dGVdO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSByZWZlcmVuY2UgdG8gdGhlIHBsYWluIEpTIG9iamVjdCBpbiB0aGUgc3RvcmUuXG4gICAgICogSXQgY29udGFpbnMgYWxsIHRoZSBwcm9wZXJ0aWVzIHRoYXQgeW91IHBhc3Mgd2hlbiBjcmVhdGluZyB0aGUgbW9kZWwsXG4gICAgICogZXhjZXB0IGZvciBwcmltYXJ5IGtleXMgb2YgbWFueS10by1tYW55IHJlbGF0aW9uc2hpcHMgd2l0aCBhIGN1c3RvbSBhY2Nlc3Nvci5cbiAgICAgKlxuICAgICAqIE1ha2Ugc3VyZSBuZXZlciB0byBtdXRhdGUgdGhpcy5cbiAgICAgKlxuICAgICAqIEByZXR1cm4ge09iamVjdH0gYSByZWZlcmVuY2UgdG8gdGhlIHBsYWluIEpTIG9iamVjdCBpbiB0aGUgc3RvcmVcbiAgICAgKi9cbiAgICBnZXQgcmVmKCkge1xuICAgICAgICBjb25zdCBUaGlzTW9kZWwgPSB0aGlzLmdldENsYXNzKCk7XG5cbiAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLXVuZGVyc2NvcmUtZGFuZ2xlXG4gICAgICAgIHJldHVybiBUaGlzTW9kZWwuX2ZpbmREYXRhYmFzZVJvd3Moe1xuICAgICAgICAgICAgW1RoaXNNb2RlbC5pZEF0dHJpYnV0ZV06IHRoaXMuZ2V0SWQoKSxcbiAgICAgICAgfSlbMF07XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRmluZHMgYWxsIHJvd3MgaW4gdGhpcyBtb2RlbCdzIHRhYmxlIHRoYXQgbWF0Y2ggdGhlIGdpdmVuIGBsb29rdXBPYmpgLlxuICAgICAqIElmIG5vIGBsb29rdXBPYmpgIGlzIHBhc3NlZCwgYWxsIHJvd3MgaW4gdGhlIG1vZGVsJ3MgdGFibGUgd2lsbCBiZSByZXR1cm5lZC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAgeyp9ICBwcm9wcyAtIGEga2V5LXZhbHVlIHRoYXQge0BsaW5rIE1vZGVsfSBpbnN0YW5jZXMgc2hvdWxkIGhhdmUgdG8gYmUgY29uc2lkZXJlZCBhcyBleGlzdGluZy5cbiAgICAgKiBAcmV0dXJuIHtCb29sZWFufSBhIGJvb2xlYW4gaW5kaWNhdGluZyBpZiBlbnRpdHkgd2l0aCBgcHJvcHNgIGV4aXN0cyBpbiB0aGUgc3RhdGVcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqL1xuICAgIHN0YXRpYyBfZmluZERhdGFiYXNlUm93cyhsb29rdXBPYmopIHtcbiAgICAgICAgY29uc3QgcXVlcnlTcGVjID0ge1xuICAgICAgICAgICAgdGFibGU6IHRoaXMubW9kZWxOYW1lLFxuICAgICAgICB9O1xuICAgICAgICBpZiAobG9va3VwT2JqKSB7XG4gICAgICAgICAgICBxdWVyeVNwZWMuY2xhdXNlcyA9IFtcbiAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgIHR5cGU6IEZJTFRFUixcbiAgICAgICAgICAgICAgICAgICAgcGF5bG9hZDogbG9va3VwT2JqLFxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBdO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLnNlc3Npb24ucXVlcnkocXVlcnlTcGVjKS5yb3dzO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBzdHJpbmcgcmVwcmVzZW50YXRpb24gb2YgdGhlIHtAbGluayBNb2RlbH0gaW5zdGFuY2UuXG4gICAgICpcbiAgICAgKiBAcmV0dXJuIHtzdHJpbmd9IEEgc3RyaW5nIHJlcHJlc2VudGF0aW9uIG9mIHRoaXMge0BsaW5rIE1vZGVsfSBpbnN0YW5jZS5cbiAgICAgKi9cbiAgICB0b1N0cmluZygpIHtcbiAgICAgICAgY29uc3QgVGhpc01vZGVsID0gdGhpcy5nZXRDbGFzcygpO1xuICAgICAgICBjb25zdCBjbGFzc05hbWUgPSBUaGlzTW9kZWwubW9kZWxOYW1lO1xuICAgICAgICBjb25zdCBmaWVsZE5hbWVzID0gT2JqZWN0LmtleXMoVGhpc01vZGVsLmZpZWxkcyk7XG4gICAgICAgIGNvbnN0IGZpZWxkcyA9IGZpZWxkTmFtZXNcbiAgICAgICAgICAgIC5tYXAoKGZpZWxkTmFtZSkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGZpZWxkID0gVGhpc01vZGVsLmZpZWxkc1tmaWVsZE5hbWVdO1xuICAgICAgICAgICAgICAgIGlmIChmaWVsZCBpbnN0YW5jZW9mIE1hbnlUb01hbnkpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgaWRzID0gdGhpc1tmaWVsZE5hbWVdXG4gICAgICAgICAgICAgICAgICAgICAgICAudG9Nb2RlbEFycmF5KClcbiAgICAgICAgICAgICAgICAgICAgICAgIC5tYXAoKG1vZGVsKSA9PiBtb2RlbC5nZXRJZCgpKTtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGAke2ZpZWxkTmFtZX06IFske2lkcy5qb2luKFwiLCBcIil9XWA7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGNvbnN0IHZhbCA9IHRoaXMuX2ZpZWxkc1tmaWVsZE5hbWVdO1xuICAgICAgICAgICAgICAgIHJldHVybiBgJHtmaWVsZE5hbWV9OiAke3ZhbH1gO1xuICAgICAgICAgICAgfSlcbiAgICAgICAgICAgIC5qb2luKFwiLCBcIik7XG4gICAgICAgIHJldHVybiBgJHtjbGFzc05hbWV9OiB7JHtmaWVsZHN9fWA7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhIGJvb2xlYW4gaW5kaWNhdGluZyBpZiBgb3RoZXJNb2RlbGAgZXF1YWxzIHRoaXMge0BsaW5rIE1vZGVsfSBpbnN0YW5jZS5cbiAgICAgKiBFcXVhbGl0eSBpcyBkZXRlcm1pbmVkIGJ5IHNoYWxsb3cgY29tcGFyaW5nIHRoZWlyIGF0dHJpYnV0ZXMuXG4gICAgICpcbiAgICAgKiBUaGlzIGVxdWFsaXR5IGlzIHVzZWQgd2hlbiB5b3UgY2FsbCB7QGxpbmsgTW9kZWwjdXBkYXRlfS5cbiAgICAgKiBZb3UgY2FuIHByZXZlbnQgbW9kZWwgdXBkYXRlcyBieSByZXR1cm5pbmcgYHRydWVgIGhlcmUuXG4gICAgICogSG93ZXZlciwgYSBtb2RlbCB3aWxsIGFsd2F5cyBiZSB1cGRhdGVkIGlmIGl0cyByZWxhdGlvbnNoaXBzIGFyZSBjaGFuZ2VkLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7TW9kZWx9IG90aGVyTW9kZWwgLSBhIHtAbGluayBNb2RlbH0gaW5zdGFuY2UgdG8gY29tcGFyZVxuICAgICAqIEByZXR1cm4ge0Jvb2xlYW59IGEgYm9vbGVhbiBpbmRpY2F0aW5nIGlmIHRoZSB7QGxpbmsgTW9kZWx9IGluc3RhbmNlJ3MgYXJlIGVxdWFsLlxuICAgICAqL1xuICAgIGVxdWFscyhvdGhlck1vZGVsKSB7XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBuby11bmRlcnNjb3JlLWRhbmdsZVxuICAgICAgICByZXR1cm4gb2JqZWN0U2hhbGxvd0VxdWFscyh0aGlzLl9maWVsZHMsIG90aGVyTW9kZWwuX2ZpZWxkcyk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVXBkYXRlcyBhIHByb3BlcnR5IG5hbWUgdG8gZ2l2ZW4gdmFsdWUgZm9yIHRoaXMge0BsaW5rIE1vZGVsfSBpbnN0YW5jZS5cbiAgICAgKiBUaGUgdmFsdWVzIGFyZSBpbW1lZGlhdGVseSBjb21taXR0ZWQgdG8gdGhlIGRhdGFiYXNlLlxuICAgICAqXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IHByb3BlcnR5TmFtZSAtIG5hbWUgb2YgdGhlIHByb3BlcnR5IHRvIHNldFxuICAgICAqIEBwYXJhbSB7Kn0gdmFsdWUgLSB2YWx1ZSBhc3NpZ25lZCB0byB0aGUgcHJvcGVydHlcbiAgICAgKiBAcmV0dXJuIHt1bmRlZmluZWR9XG4gICAgICovXG4gICAgc2V0KHByb3BlcnR5TmFtZSwgdmFsdWUpIHtcbiAgICAgICAgdGhpcy51cGRhdGUoe1xuICAgICAgICAgICAgW3Byb3BlcnR5TmFtZV06IHZhbHVlLFxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBc3NpZ25zIG11bHRpcGxlIGZpZWxkcyBhbmQgY29ycmVzcG9uZGluZyB2YWx1ZXMgdG8gdGhpcyB7QGxpbmsgTW9kZWx9IGluc3RhbmNlLlxuICAgICAqIFRoZSB1cGRhdGVzIGFyZSBpbW1lZGlhdGVseSBjb21taXR0ZWQgdG8gdGhlIGRhdGFiYXNlLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSB1c2VyTWVyZ2VPYmogLSBhbiBvYmplY3QgdGhhdCB3aWxsIGJlIG1lcmdlZCB3aXRoIHRoaXMgaW5zdGFuY2UuXG4gICAgICogQHJldHVybiB7dW5kZWZpbmVkfVxuICAgICAqL1xuICAgIHVwZGF0ZSh1c2VyTWVyZ2VPYmopIHtcbiAgICAgICAgY29uc3QgVGhpc01vZGVsID0gdGhpcy5nZXRDbGFzcygpO1xuICAgICAgICBpZiAodHlwZW9mIFRoaXNNb2RlbC5zZXNzaW9uID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgW1xuICAgICAgICAgICAgICAgICAgICBgVHJpZWQgdG8gdXBkYXRlIGEgJHtUaGlzTW9kZWwubW9kZWxOYW1lfSBtb2RlbCBpbnN0YW5jZSB3aXRob3V0IGEgc2Vzc2lvbi4gYCxcbiAgICAgICAgICAgICAgICAgICAgXCJZb3UgY2Fubm90IGNhbGwgYC51cGRhdGVgIG9uIGFuIGluc3RhbmNlIHRoYXQgeW91IGRpZCBub3QgcmVjZWl2ZSBmcm9tIHRoZSBkYXRhYmFzZS5cIixcbiAgICAgICAgICAgICAgICBdLmpvaW4oXCJcIilcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBtZXJnZU9iaiA9IHsgLi4udXNlck1lcmdlT2JqIH07XG5cbiAgICAgICAgY29uc3QgeyBmaWVsZHMsIHZpcnR1YWxGaWVsZHMgfSA9IFRoaXNNb2RlbDtcblxuICAgICAgICBjb25zdCBtMm1SZWxhdGlvbnMgPSB7fTtcblxuICAgICAgICAvLyBJZiBhbiBhcnJheSBvZiBlbnRpdGllcyBvciBpZCdzIGlzIHN1cHBsaWVkIGZvciBhXG4gICAgICAgIC8vIG1hbnktdG8tbWFueSByZWxhdGVkIGZpZWxkLCBjbGVhciB0aGUgb2xkIHJlbGF0aW9uc1xuICAgICAgICAvLyBhbmQgYWRkIHRoZSBuZXcgb25lcy5cbiAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGd1YXJkLWZvci1pbiwgbm8tcmVzdHJpY3RlZC1zeW50YXhcbiAgICAgICAgZm9yIChjb25zdCBtZXJnZUtleSBpbiBtZXJnZU9iaikge1xuICAgICAgICAgICAgY29uc3QgaXNSZWFsRmllbGQgPSBmaWVsZHMuaGFzT3duUHJvcGVydHkobWVyZ2VLZXkpO1xuXG4gICAgICAgICAgICBpZiAoaXNSZWFsRmllbGQpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBmaWVsZCA9IGZpZWxkc1ttZXJnZUtleV07XG5cbiAgICAgICAgICAgICAgICBpZiAoZmllbGQgaW5zdGFuY2VvZiBGb3JlaWduS2V5IHx8IGZpZWxkIGluc3RhbmNlb2YgT25lVG9PbmUpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gdXBkYXRlIG9uZS1vbmUvZmsgcmVsYXRpb25zXG4gICAgICAgICAgICAgICAgICAgIG1lcmdlT2JqW21lcmdlS2V5XSA9IG5vcm1hbGl6ZUVudGl0eShtZXJnZU9ialttZXJnZUtleV0pO1xuICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAoZmllbGQgaW5zdGFuY2VvZiBNYW55VG9NYW55KSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIGZpZWxkIGlzIGZvcndhcmQgcmVsYXRpb25cbiAgICAgICAgICAgICAgICAgICAgbTJtUmVsYXRpb25zW21lcmdlS2V5XSA9IG1lcmdlT2JqW21lcmdlS2V5XTtcblxuICAgICAgICAgICAgICAgICAgICBpZiAoIWZpZWxkLmFzKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAgICAgICAgICAgICAqIFRoZSByZWxhdGlvbnNoaXAgZG9lcyBub3QgaGF2ZSBhbiBhY2Nlc3NvclxuICAgICAgICAgICAgICAgICAgICAgICAgICogRGlzY2FyZCB0aGUgdmFsdWUgZnJvbSBwcm9wcyBhcyB0aGUgZmllbGQgd2lsbCBiZSBwb3B1bGF0ZWQgbGF0ZXIgd2l0aCBpbnN0YW5jZXNcbiAgICAgICAgICAgICAgICAgICAgICAgICAqIGZyb20gdGhlIHRhcmdldCBtb2RlbHMgd2hlbiByZWZyZXNoaW5nIHRoZSBNMk0gcmVsYXRpb25zLlxuICAgICAgICAgICAgICAgICAgICAgICAgICogSWYgdGhlIHJlbGF0aW9uc2hpcCBkb2VzIGhhdmUgYW4gYWNjZXNzb3IgKGBhc2ApIGZpZWxkIHRoZW4gd2UgZG8gd2FudCB0byBrZWVwIHRoaXNcbiAgICAgICAgICAgICAgICAgICAgICAgICAqIG9yaWdpbmFsIHZhbHVlIGluIHRoZSBwcm9wcyB0byBleHBvc2UgdGhlIHJhdyBsaXN0IG9mIElEcyBmcm9tIHRoZSBpbnN0YW5jZS5cbiAgICAgICAgICAgICAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgICAgICAgICAgICAgZGVsZXRlIG1lcmdlT2JqW21lcmdlS2V5XTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gZWxzZSBpZiAodmlydHVhbEZpZWxkcy5oYXNPd25Qcm9wZXJ0eShtZXJnZUtleSkpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBmaWVsZCA9IHZpcnR1YWxGaWVsZHNbbWVyZ2VLZXldO1xuICAgICAgICAgICAgICAgIGlmIChmaWVsZCBpbnN0YW5jZW9mIE1hbnlUb01hbnkpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gZmllbGQgaXMgYmFja3dhcmQgcmVsYXRpb25cbiAgICAgICAgICAgICAgICAgICAgbTJtUmVsYXRpb25zW21lcmdlS2V5XSA9IG1lcmdlT2JqW21lcmdlS2V5XTtcbiAgICAgICAgICAgICAgICAgICAgZGVsZXRlIG1lcmdlT2JqW21lcmdlS2V5XTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBtZXJnZWRGaWVsZHMgPSB7XG4gICAgICAgICAgICAuLi50aGlzLl9maWVsZHMsXG4gICAgICAgICAgICAuLi5tZXJnZU9iaixcbiAgICAgICAgfTtcblxuICAgICAgICBjb25zdCB1cGRhdGVkTW9kZWwgPSBuZXcgVGhpc01vZGVsKG1lcmdlZEZpZWxkcyk7XG4gICAgICAgIC8vIG9ubHkgdXBkYXRlIGZpZWxkcyBpZiB0aGV5IGhhdmUgY2hhbmdlZCAocmVmZXJlbnRpYWxseSlcbiAgICAgICAgaWYgKCF0aGlzLmVxdWFscyh1cGRhdGVkTW9kZWwpKSB7XG4gICAgICAgICAgICB0aGlzLl9pbml0RmllbGRzKG1lcmdlZEZpZWxkcyk7XG4gICAgICAgICAgICBUaGlzTW9kZWwuc2Vzc2lvbi5hcHBseVVwZGF0ZSh7XG4gICAgICAgICAgICAgICAgYWN0aW9uOiBVUERBVEUsXG4gICAgICAgICAgICAgICAgcXVlcnk6IGdldEJ5SWRRdWVyeSh0aGlzKSxcbiAgICAgICAgICAgICAgICBwYXlsb2FkOiBtZXJnZU9iaixcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gdXBkYXRlIHZpcnR1YWwgZmllbGRzXG4gICAgICAgIHRoaXMuX3JlZnJlc2hNYW55Mk1hbnkobTJtUmVsYXRpb25zKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBVcGRhdGVzIHtAbGluayBNb2RlbH0gaW5zdGFuY2UgYXR0cmlidXRlcyB0byByZWZsZWN0IHRoZVxuICAgICAqIGRhdGFiYXNlIHN0YXRlIGluIHRoZSBjdXJyZW50IHNlc3Npb24uXG4gICAgICogQHJldHVybiB7dW5kZWZpbmVkfVxuICAgICAqL1xuICAgIHJlZnJlc2hGcm9tU3RhdGUoKSB7XG4gICAgICAgIHRoaXMuX2luaXRGaWVsZHModGhpcy5yZWYpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIERlbGV0ZXMgdGhlIHJlY29yZCBmb3IgdGhpcyB7QGxpbmsgTW9kZWx9IGluc3RhbmNlLlxuICAgICAqIFlvdSdsbCBzdGlsbCBiZSBhYmxlIHRvIGFjY2VzcyBmaWVsZHMgYW5kIHZhbHVlcyBvbiB0aGUgaW5zdGFuY2UuXG4gICAgICpcbiAgICAgKiBAcmV0dXJuIHt1bmRlZmluZWR9XG4gICAgICovXG4gICAgZGVsZXRlKCkge1xuICAgICAgICBjb25zdCBUaGlzTW9kZWwgPSB0aGlzLmdldENsYXNzKCk7XG4gICAgICAgIGlmICh0eXBlb2YgVGhpc01vZGVsLnNlc3Npb24gPT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBbXG4gICAgICAgICAgICAgICAgICAgIGBUcmllZCB0byBkZWxldGUgYSAke1RoaXNNb2RlbC5tb2RlbE5hbWV9IG1vZGVsIGluc3RhbmNlIHdpdGhvdXQgYSBzZXNzaW9uLiBgLFxuICAgICAgICAgICAgICAgICAgICBcIllvdSBjYW5ub3QgY2FsbCBgLmRlbGV0ZWAgb24gYW4gaW5zdGFuY2UgdGhhdCB5b3UgZGlkIG5vdCByZWNlaXZlIGZyb20gdGhlIGRhdGFiYXNlLlwiLFxuICAgICAgICAgICAgICAgIF0uam9pbihcIlwiKVxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuX29uRGVsZXRlKCk7XG4gICAgICAgIFRoaXNNb2RlbC5zZXNzaW9uLmFwcGx5VXBkYXRlKHtcbiAgICAgICAgICAgIGFjdGlvbjogREVMRVRFLFxuICAgICAgICAgICAgcXVlcnk6IGdldEJ5SWRRdWVyeSh0aGlzKSxcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVXBkYXRlIG1hbnktbWFueSByZWxhdGlvbnMgZm9yIG1vZGVsLlxuICAgICAqIEBwYXJhbSByZWxhdGlvbnNcbiAgICAgKiBAcmV0dXJuIHVuZGVmaW5lZFxuICAgICAqIEBwcml2YXRlXG4gICAgICovXG4gICAgX3JlZnJlc2hNYW55Mk1hbnkocmVsYXRpb25zKSB7XG4gICAgICAgIGNvbnN0IFRoaXNNb2RlbCA9IHRoaXMuZ2V0Q2xhc3MoKTtcbiAgICAgICAgY29uc3QgeyBmaWVsZHMsIHZpcnR1YWxGaWVsZHMsIG1vZGVsTmFtZSB9ID0gVGhpc01vZGVsO1xuXG4gICAgICAgIE9iamVjdC5rZXlzKHJlbGF0aW9ucykuZm9yRWFjaCgobmFtZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcmV2ZXJzZSA9ICFmaWVsZHMuaGFzT3duUHJvcGVydHkobmFtZSk7XG4gICAgICAgICAgICBjb25zdCBmaWVsZCA9IHZpcnR1YWxGaWVsZHNbbmFtZV07XG4gICAgICAgICAgICBjb25zdCB2YWx1ZXMgPSByZWxhdGlvbnNbbmFtZV07XG5cbiAgICAgICAgICAgIGlmICghQXJyYXkuaXNBcnJheSh2YWx1ZXMpKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcbiAgICAgICAgICAgICAgICAgICAgYEZhaWxlZCB0byByZXNvbHZlIG1hbnktdG8tbWFueSByZWxhdGlvbnNoaXA6ICR7bW9kZWxOYW1lfVske25hbWV9XSBtdXN0IGJlIGFuIGFycmF5IChwYXNzZWQ6ICR7dmFsdWVzfSlgXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY29uc3Qgbm9ybWFsaXplZE5ld0lkcyA9IHZhbHVlcy5tYXAobm9ybWFsaXplRW50aXR5KTtcbiAgICAgICAgICAgIGNvbnN0IHVuaXF1ZUlkcyA9IFsuLi5uZXcgU2V0KG5vcm1hbGl6ZWROZXdJZHMpXTtcblxuICAgICAgICAgICAgaWYgKG5vcm1hbGl6ZWROZXdJZHMubGVuZ3RoICE9PSB1bmlxdWVJZHMubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgICAgICBgRm91bmQgZHVwbGljYXRlIGlkKHMpIHdoZW4gcGFzc2luZyBcIiR7bm9ybWFsaXplZE5ld0lkc31cIiB0byAke1RoaXNNb2RlbC5tb2RlbE5hbWV9LiR7bmFtZX0gdmFsdWVgXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY29uc3QgdGhyb3VnaE1vZGVsTmFtZSA9XG4gICAgICAgICAgICAgICAgZmllbGQudGhyb3VnaCB8fCBtMm1OYW1lKFRoaXNNb2RlbC5tb2RlbE5hbWUsIG5hbWUpO1xuICAgICAgICAgICAgY29uc3QgVGhyb3VnaE1vZGVsID0gVGhpc01vZGVsLnNlc3Npb25bdGhyb3VnaE1vZGVsTmFtZV07XG5cbiAgICAgICAgICAgIGxldCBmcm9tRmllbGQ7XG4gICAgICAgICAgICBsZXQgdG9GaWVsZDtcblxuICAgICAgICAgICAgaWYgKCFyZXZlcnNlKSB7XG4gICAgICAgICAgICAgICAgKHsgZnJvbTogZnJvbUZpZWxkLCB0bzogdG9GaWVsZCB9ID0gZmllbGQudGhyb3VnaEZpZWxkcyk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICh7IGZyb206IHRvRmllbGQsIHRvOiBmcm9tRmllbGQgfSA9IGZpZWxkLnRocm91Z2hGaWVsZHMpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjb25zdCBjdXJyZW50SWRzID0gVGhyb3VnaE1vZGVsLmZpbHRlcihcbiAgICAgICAgICAgICAgICAodGhyb3VnaCkgPT4gdGhyb3VnaFtmcm9tRmllbGRdID09PSB0aGlzW1RoaXNNb2RlbC5pZEF0dHJpYnV0ZV1cbiAgICAgICAgICAgIClcbiAgICAgICAgICAgICAgICAudG9SZWZBcnJheSgpXG4gICAgICAgICAgICAgICAgLm1hcCgocmVmKSA9PiByZWZbdG9GaWVsZF0pO1xuXG4gICAgICAgICAgICBjb25zdCBkaWZmQWN0aW9ucyA9IGFycmF5RGlmZkFjdGlvbnMoY3VycmVudElkcywgbm9ybWFsaXplZE5ld0lkcyk7XG5cbiAgICAgICAgICAgIGlmIChkaWZmQWN0aW9ucykge1xuICAgICAgICAgICAgICAgIGNvbnN0IHsgZGVsZXRlOiBpZHNUb0RlbGV0ZSwgYWRkOiBpZHNUb0FkZCB9ID0gZGlmZkFjdGlvbnM7XG4gICAgICAgICAgICAgICAgaWYgKGlkc1RvRGVsZXRlLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpc1tmaWVsZC5hcyB8fCBuYW1lXS5yZW1vdmUoLi4uaWRzVG9EZWxldGUpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGlmIChpZHNUb0FkZC5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXNbZmllbGQuYXMgfHwgbmFtZV0uYWRkKC4uLmlkc1RvQWRkKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEByZXR1cm4ge3VuZGVmaW5lZH1cbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqL1xuICAgIF9vbkRlbGV0ZSgpIHtcbiAgICAgICAgY29uc3QgeyB2aXJ0dWFsRmllbGRzIH0gPSB0aGlzLmdldENsYXNzKCk7XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBndWFyZC1mb3ItaW4sIG5vLXJlc3RyaWN0ZWQtc3ludGF4XG4gICAgICAgIGZvciAoY29uc3Qga2V5IGluIHZpcnR1YWxGaWVsZHMpIHtcbiAgICAgICAgICAgIGNvbnN0IGZpZWxkID0gdmlydHVhbEZpZWxkc1trZXldO1xuICAgICAgICAgICAgaWYgKGZpZWxkIGluc3RhbmNlb2YgTWFueVRvTWFueSkge1xuICAgICAgICAgICAgICAgIC8vIERlbGV0ZSBhbnkgbWFueS10by1tYW55IHJvd3MgdGhlIGVudGl0eSBpcyBpbmNsdWRlZCBpbi5cbiAgICAgICAgICAgICAgICBjb25zdCBkZXNjcmlwdG9yS2V5ID0gZmllbGQuYXMgfHwga2V5O1xuICAgICAgICAgICAgICAgIHRoaXNbZGVzY3JpcHRvcktleV0uY2xlYXIoKTtcbiAgICAgICAgICAgIH0gZWxzZSBpZiAoZmllbGQgaW5zdGFuY2VvZiBGb3JlaWduS2V5KSB7XG4gICAgICAgICAgICAgICAgY29uc3QgcmVsYXRlZFFzID0gdGhpc1trZXldO1xuICAgICAgICAgICAgICAgIGlmIChyZWxhdGVkUXMuZXhpc3RzKCkpIHtcbiAgICAgICAgICAgICAgICAgICAgcmVsYXRlZFFzLnVwZGF0ZSh7IFtmaWVsZC5yZWxhdGVkTmFtZV06IG51bGwgfSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBlbHNlIGlmIChmaWVsZCBpbnN0YW5jZW9mIE9uZVRvT25lKSB7XG4gICAgICAgICAgICAgICAgLy8gU2V0IG51bGwgdG8gYW55IGZvcmVpZ24ga2V5cyBvciBvbmUgdG8gb25lcyBwb2ludGVkIHRvXG4gICAgICAgICAgICAgICAgLy8gdGhpcyBpbnN0YW5jZS5cbiAgICAgICAgICAgICAgICBpZiAodGhpc1trZXldICE9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXNba2V5XVtmaWVsZC5yZWxhdGVkTmFtZV0gPSBudWxsO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIERFUFJFQ0FURUQgQU5EIFJFTU9WRUQgTUVUSE9EU1xuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhIGJvb2xlYW4gaW5kaWNhdGluZyBpZiBhbiBlbnRpdHlcbiAgICAgKiB3aXRoIHRoZSBpZCBgaWRgIGV4aXN0cyBpbiB0aGUgc3RhdGUuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHsqfSAgaWQgLSBhIHZhbHVlIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGlkIGF0dHJpYnV0ZSBvZiB0aGUge0BsaW5rIE1vZGVsfSBjbGFzcy5cbiAgICAgKiBAcmV0dXJuIHtCb29sZWFufSBhIGJvb2xlYW4gaW5kaWNhdGluZyBpZiBlbnRpdHkgd2l0aCBgaWRgIGV4aXN0cyBpbiB0aGUgc3RhdGVcbiAgICAgKiBAZGVwcmVjYXRlZCBQbGVhc2UgdXNlIHtAbGluayBNb2RlbC5pZEV4aXN0c30gaW5zdGVhZC5cbiAgICAgKi9cbiAgICBzdGF0aWMgaGFzSWQoaWQpIHtcbiAgICAgICAgY29uc29sZS53YXJuKFxuICAgICAgICAgICAgXCJgTW9kZWwuaGFzSWRgIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFBsZWFzZSB1c2UgYE1vZGVsLmlkRXhpc3RzYCBpbnN0ZWFkLlwiXG4gICAgICAgICk7XG4gICAgICAgIHJldHVybiB0aGlzLmlkRXhpc3RzKGlkKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAZGVwcmVjYXRlZCBTZWUgdGhlIDAuOSBtaWdyYXRpb24gZ3VpZGUgb24gdGhlIEdpdEh1YiByZXBvLlxuICAgICAqIEB0aHJvd3Mge0Vycm9yfSBEdWUgdG8gZGVwcmVjYXRpb24uXG4gICAgICovXG4gICAgZ2V0TmV4dFN0YXRlKCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBcImBNb2RlbC5wcm90b3R5cGUuZ2V0TmV4dFN0YXRlYCBoYXMgYmVlbiByZW1vdmVkLiBTZWUgdGhlIDAuOSBcIiArXG4gICAgICAgICAgICAgICAgXCJtaWdyYXRpb24gZ3VpZGUgb24gdGhlIEdpdEh1YiByZXBvLlwiXG4gICAgICAgICk7XG4gICAgfVxufTtcblxuTW9kZWwuZmllbGRzID0ge1xuICAgIGlkOiBhdHRyKCksXG59O1xuTW9kZWwudmlydHVhbEZpZWxkcyA9IHt9O1xuTW9kZWwucXVlcnlTZXRDbGFzcyA9IFF1ZXJ5U2V0O1xuXG5leHBvcnQgZGVmYXVsdCBNb2RlbDtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/Model.js\\n\");\n \n /***/ }),\n \n@@ -4462,7 +4484,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _bab\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"DeprecatedSchema\\\", function() { return DeprecatedSchema; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"ORM\\\", function() { return ORM; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _Session__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Session */ \\\"./src/Session.js\\\");\\n/* harmony import */ var _Model__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./Model */ \\\"./src/Model.js\\\");\\n/* harmony import */ var _db__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./db */ \\\"./src/db/index.js\\\");\\n/* harmony import */ var _fields__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./fields */ \\\"./src/fields/index.js\\\");\\n/* harmony import */ var _fields_Field__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./fields/Field */ \\\"./src/fields/Field.js\\\");\\n/* harmony import */ var _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./fields/ForeignKey */ \\\"./src/fields/ForeignKey.js\\\");\\n/* harmony import */ var _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./fields/ManyToMany */ \\\"./src/fields/ManyToMany.js\\\");\\n/* harmony import */ var _selectors__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./selectors */ \\\"./src/selectors/index.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n/* eslint-disable max-classes-per-file */\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nconst ORM_DEFAULTS = {\\n  createDatabase: _db__WEBPACK_IMPORTED_MODULE_4__[\\\"createDatabase\\\"]\\n};\\nconst RESERVED_TABLE_OPTIONS = [\\\"indexes\\\", \\\"meta\\\"];\\n\\nconst isReservedTableOption = word => RESERVED_TABLE_OPTIONS.includes(word);\\n/**\\n * ORM - the Object Relational Mapper.\\n *\\n * Use instances of this class to:\\n *\\n * - Register your {@link Model} classes using {@link ORM#register}\\n * - Get the empty state for the underlying database with {@link ORM#getEmptyState}\\n * - Start an immutable database session with {@link ORM#session}\\n * - Start a mutating database session with {@link ORM#mutableSession}\\n *\\n * Internally, this class handles generating a schema specification from models\\n * to the database.\\n */\\n\\n\\nlet ORM = /*#__PURE__*/function () {\\n  /**\\n   * Creates a new ORM instance.\\n   *\\n   * @param {Object} [opts]\\n   * @param {Function} [opts.stateSelector] - function that given a Redux state tree\\n   *                                          will return the ORM state's subtree,\\n   *                                          e.g. `state => state.orm`\\n   *                                          (necessary if you want to use selectors)\\n   * @param {Function} [opts.createDatabase] - function that creates a database\\n   */\\n  function ORM(opts) {\\n    const {\\n      createDatabase\\n    } = { ...ORM_DEFAULTS,\\n      ...(opts || {})\\n    };\\n    this.createDatabase = createDatabase;\\n    this.registry = [];\\n    this.implicitThroughModels = [];\\n    this.installedFields = {};\\n    this.stateSelector = opts ? opts.stateSelector : null;\\n  }\\n  /**\\n   * Registers a {@link Model} class to the ORM.\\n   *\\n   * If the model has declared any ManyToMany fields, their\\n   * through models will be generated and registered with\\n   * this call, unless a custom through model has been specified.\\n   *\\n   * @param  {...Model} models - a {@link Model} class to register\\n   * @return {undefined}\\n   */\\n\\n\\n  var _proto = ORM.prototype;\\n\\n  _proto.register = function register(...models) {\\n    models.forEach(model => {\\n      if (model.modelName === undefined) {\\n        throw new Error(\\\"A model was passed that doesn't have a modelName set\\\");\\n      }\\n\\n      model.invalidateClassCache();\\n      this.registerManyToManyModelsFor(model);\\n      this.registry.push(model);\\n      Object.defineProperty(this, model.modelName, {\\n        get: () => {\\n          // make sure virtualFields are set up\\n          this._setupModelPrototypes(this.registry);\\n\\n          return Object(_selectors__WEBPACK_IMPORTED_MODULE_9__[\\\"createModelSelectorSpec\\\"])({\\n            model,\\n            orm: this\\n          });\\n        }\\n      });\\n    });\\n  };\\n\\n  _proto.registerManyToManyModelsFor = function registerManyToManyModelsFor(model) {\\n    const {\\n      fields\\n    } = model;\\n    const thisModelName = model.modelName;\\n    Object.entries(fields).forEach(([fieldName, fieldInstance]) => {\\n      if (!(fieldInstance instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_8__[\\\"default\\\"])) {\\n        return;\\n      }\\n\\n      let toModelName;\\n\\n      if (fieldInstance.toModelName === \\\"this\\\") {\\n        toModelName = thisModelName;\\n      } else {\\n        toModelName = fieldInstance.toModelName; // eslint-disable-line prefer-destructuring\\n      }\\n\\n      const selfReferencing = thisModelName === toModelName;\\n      const fromFieldName = Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"m2mFromFieldName\\\"])(thisModelName);\\n      const toFieldName = Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"m2mToFieldName\\\"])(toModelName);\\n\\n      if (fieldInstance.through) {\\n        if (selfReferencing && !fieldInstance.throughFields) {\\n          throw new Error(\\\"Self-referencing many-to-many relationship at \\\" + `\\\"${thisModelName}.${fieldName}\\\" using custom ` + `model \\\"${fieldInstance.through}\\\" has no ` + \\\"throughFields key. Cannot determine which \\\" + \\\"fields reference the instances partaking \\\" + \\\"in the relationship.\\\");\\n        }\\n      } else {\\n        const Through = /*#__PURE__*/function (_Model) {\\n          _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(ThroughModel, _Model);\\n\\n          function ThroughModel() {\\n            return _Model.apply(this, arguments) || this;\\n          }\\n\\n          return ThroughModel;\\n        }(_Model__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]);\\n\\n        Through.modelName = Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"m2mName\\\"])(thisModelName, fieldName);\\n\\n        const PlainForeignKey = /*#__PURE__*/function (_ForeignKey) {\\n          _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(PlainForeignKey, _ForeignKey);\\n\\n          function PlainForeignKey() {\\n            return _ForeignKey.apply(this, arguments) || this;\\n          }\\n\\n          _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(PlainForeignKey, [{\\n            key: \\\"installsBackwardsVirtualField\\\",\\n            get: function () {\\n              return false;\\n            }\\n          }, {\\n            key: \\\"installsBackwardsDescriptor\\\",\\n            get: function () {\\n              return false;\\n            }\\n          }]);\\n\\n          return PlainForeignKey;\\n        }(_fields_ForeignKey__WEBPACK_IMPORTED_MODULE_7__[\\\"default\\\"]);\\n\\n        const ForeignKeyClass = selfReferencing ? PlainForeignKey : _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_7__[\\\"default\\\"];\\n        Through.fields = {\\n          id: Object(_fields__WEBPACK_IMPORTED_MODULE_5__[\\\"attr\\\"])(),\\n          [fromFieldName]: new ForeignKeyClass(thisModelName),\\n          [toFieldName]: new ForeignKeyClass(toModelName)\\n        };\\n        Through.invalidateClassCache();\\n        this.implicitThroughModels.push(Through);\\n      }\\n    });\\n  }\\n  /**\\n   * Gets a {@link Model} class by its name from the registry.\\n   * @param  {string} modelName - the name of the {@link Model} class to get\\n   * @throws If {@link Model} class is not found.\\n   * @return {Model} the {@link Model} class, if found\\n   */\\n  ;\\n\\n  _proto.get = function get(modelName) {\\n    const allModels = this.registry.concat(this.implicitThroughModels);\\n    const found = Object.values(allModels).find(model => model.modelName === modelName);\\n\\n    if (typeof found === \\\"undefined\\\") {\\n      throw new Error(`Did not find model ${modelName} from registry.`);\\n    }\\n\\n    return found;\\n  };\\n\\n  _proto.getModelClasses = function getModelClasses() {\\n    this._setupModelPrototypes(this.registry);\\n\\n    this._setupModelPrototypes(this.implicitThroughModels);\\n\\n    return this.registry.concat(this.implicitThroughModels);\\n  };\\n\\n  _proto.generateSchemaSpec = function generateSchemaSpec() {\\n    const models = this.getModelClasses();\\n    const tables = models.reduce((spec, modelClass) => {\\n      const tableName = modelClass.modelName;\\n      const tableSpec = modelClass.tableOptions();\\n      Object.keys(tableSpec).filter(isReservedTableOption).forEach(key => {\\n        throw new Error(`Reserved keyword \\\\`${key}\\\\` used in ${tableName}.options.`);\\n      });\\n      spec[tableName] = {\\n        fields: { ...modelClass.fields\\n        },\\n        ...tableSpec\\n      };\\n      return spec;\\n    }, {});\\n    return {\\n      tables\\n    };\\n  };\\n\\n  _proto.getDatabase = function getDatabase() {\\n    if (!this.db) {\\n      this.db = this.createDatabase(this.generateSchemaSpec());\\n    }\\n\\n    return this.db;\\n  }\\n  /**\\n   * Returns the empty database state.\\n   * @return {Object} the empty state\\n   */\\n  ;\\n\\n  _proto.getEmptyState = function getEmptyState() {\\n    return this.getDatabase().getEmptyState();\\n  }\\n  /**\\n   * Begins an immutable database session.\\n   *\\n   * @param  {Object} state  - the state the database manages\\n   * @return {Session} a new {@link Session} instance\\n   */\\n  ;\\n\\n  _proto.session = function session(state) {\\n    return new _Session__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"](this, this.getDatabase(), state);\\n  }\\n  /**\\n   * Begins a mutable database session.\\n   *\\n   * @param  {Object} state  - the state the database manages\\n   * @return {Session} a new {@link Session} instance\\n   */\\n  ;\\n\\n  _proto.mutableSession = function mutableSession(state) {\\n    return new _Session__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"](this, this.getDatabase(), state, true);\\n  }\\n  /**\\n   * @private\\n   */\\n  ;\\n\\n  _proto._setupModelPrototypes = function _setupModelPrototypes(models) {\\n    models.filter(model => !model.isSetUp).forEach(model => {\\n      const {\\n        fields,\\n        modelName,\\n        querySetClass\\n      } = model;\\n      Object.entries(fields).forEach(([fieldName, field]) => {\\n        if (!(field instanceof _fields_Field__WEBPACK_IMPORTED_MODULE_6__[\\\"default\\\"])) {\\n          throw new Error(`${modelName}.${fieldName} is of type \\\"${typeof field}\\\" ` + \\\"but must be an instance of Field. Please use the \\\" + \\\"`attr`, `fk`, `oneToOne` and `many` \\\" + \\\"functions to define fields.\\\");\\n        }\\n\\n        if (!this._isFieldInstalled(modelName, fieldName)) {\\n          this._installField(field, fieldName, model);\\n\\n          this._setFieldInstalled(modelName, fieldName);\\n        }\\n      });\\n      Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"attachQuerySetMethods\\\"])(model, querySetClass);\\n      model.isSetUp = true;\\n    });\\n  }\\n  /**\\n   * @private\\n   */\\n  ;\\n\\n  _proto._isFieldInstalled = function _isFieldInstalled(modelName, fieldName) {\\n    return this.installedFields.hasOwnProperty(modelName) ? !!this.installedFields[modelName][fieldName] : false;\\n  }\\n  /**\\n   * @private\\n   */\\n  ;\\n\\n  _proto._setFieldInstalled = function _setFieldInstalled(modelName, fieldName) {\\n    if (!this.installedFields.hasOwnProperty(modelName)) {\\n      this.installedFields[modelName] = {};\\n    }\\n\\n    this.installedFields[modelName][fieldName] = true;\\n  }\\n  /**\\n   * Installs a field on a model and its related models if necessary.\\n   * @private\\n   */\\n  ;\\n\\n  _proto._installField = function _installField(field, fieldName, model) {\\n    const FieldInstaller = field.installerClass;\\n    new FieldInstaller({\\n      field,\\n      fieldName,\\n      model,\\n      orm: this\\n    }).run();\\n  } // DEPRECATED AND REMOVED METHODS\\n\\n  /**\\n   * @deprecated Use {@link ORM#mutableSession} instead.\\n   */\\n  ;\\n\\n  _proto.withMutations = function withMutations(state) {\\n    Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"warnDeprecated\\\"])(\\\"`ORM.prototype.withMutations` has been deprecated. \\\" + \\\"Use `ORM.prototype.mutableSession` instead.\\\");\\n    return this.mutableSession(state);\\n  }\\n  /**\\n   * @deprecated Use {@link ORM#session} instead.\\n   */\\n  ;\\n\\n  _proto.from = function from(state) {\\n    Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"warnDeprecated\\\"])(\\\"`ORM.prototype.from` has been deprecated. \\\" + \\\"Use `ORM.prototype.session` instead.\\\");\\n    return this.session(state);\\n  }\\n  /**\\n   * @deprecated Use {@link ORM#getEmptyState} instead.\\n   */\\n  ;\\n\\n  _proto.getDefaultState = function getDefaultState() {\\n    Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"warnDeprecated\\\"])(\\\"`ORM.prototype.getDefaultState` has been deprecated. Use \\\" + \\\"`ORM.prototype.getEmptyState` instead.\\\");\\n    return this.getEmptyState();\\n  }\\n  /**\\n   * @deprecated Define a Model class instead.\\n   */\\n  ;\\n\\n  _proto.define = function define() {\\n    throw new Error(\\\"`ORM.prototype.define` has been removed. Please define a Model class.\\\");\\n  };\\n\\n  return ORM;\\n}();\\n\\nfunction DeprecatedSchema() {\\n  throw new Error(\\\"Schema has been renamed to ORM. Please import ORM instead of Schema \\\" + \\\"from Redux-ORM.\\\");\\n}\\n\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (ORM);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9PUk0uanM/YWE0ZSJdLCJuYW1lcyI6WyJPUk1fREVGQVVMVFMiLCJjcmVhdGVEYXRhYmFzZSIsImRlZmF1bHRDcmVhdGVEYXRhYmFzZSIsIlJFU0VSVkVEX1RBQkxFX09QVElPTlMiLCJpc1Jlc2VydmVkVGFibGVPcHRpb24iLCJ3b3JkIiwiaW5jbHVkZXMiLCJPUk0iLCJvcHRzIiwicmVnaXN0cnkiLCJpbXBsaWNpdFRocm91Z2hNb2RlbHMiLCJpbnN0YWxsZWRGaWVsZHMiLCJzdGF0ZVNlbGVjdG9yIiwicmVnaXN0ZXIiLCJtb2RlbHMiLCJmb3JFYWNoIiwibW9kZWwiLCJtb2RlbE5hbWUiLCJ1bmRlZmluZWQiLCJFcnJvciIsImludmFsaWRhdGVDbGFzc0NhY2hlIiwicmVnaXN0ZXJNYW55VG9NYW55TW9kZWxzRm9yIiwicHVzaCIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZ2V0IiwiX3NldHVwTW9kZWxQcm90b3R5cGVzIiwiY3JlYXRlTW9kZWxTZWxlY3RvclNwZWMiLCJvcm0iLCJmaWVsZHMiLCJ0aGlzTW9kZWxOYW1lIiwiZW50cmllcyIsImZpZWxkTmFtZSIsImZpZWxkSW5zdGFuY2UiLCJNYW55VG9NYW55IiwidG9Nb2RlbE5hbWUiLCJzZWxmUmVmZXJlbmNpbmciLCJmcm9tRmllbGROYW1lIiwibTJtRnJvbUZpZWxkTmFtZSIsInRvRmllbGROYW1lIiwibTJtVG9GaWVsZE5hbWUiLCJ0aHJvdWdoIiwidGhyb3VnaEZpZWxkcyIsIlRocm91Z2giLCJNb2RlbCIsIm0ybU5hbWUiLCJQbGFpbkZvcmVpZ25LZXkiLCJGb3JlaWduS2V5IiwiRm9yZWlnbktleUNsYXNzIiwiaWQiLCJhdHRyIiwiYWxsTW9kZWxzIiwiY29uY2F0IiwiZm91bmQiLCJ2YWx1ZXMiLCJmaW5kIiwiZ2V0TW9kZWxDbGFzc2VzIiwiZ2VuZXJhdGVTY2hlbWFTcGVjIiwidGFibGVzIiwicmVkdWNlIiwic3BlYyIsIm1vZGVsQ2xhc3MiLCJ0YWJsZU5hbWUiLCJ0YWJsZVNwZWMiLCJ0YWJsZU9wdGlvbnMiLCJrZXlzIiwiZmlsdGVyIiwia2V5IiwiZ2V0RGF0YWJhc2UiLCJkYiIsImdldEVtcHR5U3RhdGUiLCJzZXNzaW9uIiwic3RhdGUiLCJTZXNzaW9uIiwibXV0YWJsZVNlc3Npb24iLCJpc1NldFVwIiwicXVlcnlTZXRDbGFzcyIsImZpZWxkIiwiRmllbGQiLCJfaXNGaWVsZEluc3RhbGxlZCIsIl9pbnN0YWxsRmllbGQiLCJfc2V0RmllbGRJbnN0YWxsZWQiLCJhdHRhY2hRdWVyeVNldE1ldGhvZHMiLCJoYXNPd25Qcm9wZXJ0eSIsIkZpZWxkSW5zdGFsbGVyIiwiaW5zdGFsbGVyQ2xhc3MiLCJydW4iLCJ3aXRoTXV0YXRpb25zIiwid2FybkRlcHJlY2F0ZWQiLCJmcm9tIiwiZ2V0RGVmYXVsdFN0YXRlIiwiZGVmaW5lIiwiRGVwcmVjYXRlZFNjaGVtYSJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFFQTtBQUVBO0FBUUEsTUFBTUEsWUFBWSxHQUFHO0FBQ2pCQyxnQkFBYyxFQUFFQyxrREFBcUJBO0FBRHBCLENBQXJCO0FBSUEsTUFBTUMsc0JBQXNCLEdBQUcsQ0FBQyxTQUFELEVBQVksTUFBWixDQUEvQjs7QUFDQSxNQUFNQyxxQkFBcUIsR0FBR0MsSUFBSSxJQUFJRixzQkFBc0IsQ0FBQ0csUUFBdkIsQ0FBZ0NELElBQWhDLENBQXRDO0FBRUE7Ozs7Ozs7Ozs7Ozs7OztJQWFNRSxHO0FBQ0Y7Ozs7Ozs7Ozs7QUFVQSxlQUFZQyxJQUFaLEVBQWtCO0FBQ2QsVUFBTTtBQUFFUDtBQUFGLFFBQXFCLEVBQUUsR0FBR0QsWUFBTDtBQUFtQixVQUFJUSxJQUFJLElBQUksRUFBWjtBQUFuQixLQUEzQjtBQUNBLFNBQUtQLGNBQUwsR0FBc0JBLGNBQXRCO0FBQ0EsU0FBS1EsUUFBTCxHQUFnQixFQUFoQjtBQUNBLFNBQUtDLHFCQUFMLEdB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eUVBQ0ksaUJBRkYsQ0FBTjtBQUlIO0FBRUQ7QUFFZVosa0VBQWYiLCJmaWxlIjoiLi9zcmMvT1JNLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiLyogZXNsaW50LWRpc2FibGUgbWF4LWNsYXNzZXMtcGVyLWZpbGUgKi9cbmltcG9ydCBTZXNzaW9uIGZyb20gXCIuL1Nlc3Npb25cIjtcbmltcG9ydCBNb2RlbCBmcm9tIFwiLi9Nb2RlbFwiO1xuaW1wb3J0IHsgY3JlYXRlRGF0YWJhc2UgYXMgZGVmYXVsdENyZWF0ZURhdGFiYXNlIH0gZnJvbSBcIi4vZGJcIjtcbmltcG9ydCB7IGF0dHIgfSBmcm9tIFwiLi9maWVsZHNcIjtcbmltcG9ydCBGaWVsZCBmcm9tIFwiLi9maWVsZHMvRmllbGRcIjtcbmltcG9ydCBGb3JlaWduS2V5IGZyb20gXCIuL2ZpZWxkcy9Gb3JlaWduS2V5XCI7XG5pbXBvcnQgTWFueVRvTWFueSBmcm9tIFwiLi9maWVsZHMvTWFueVRvTWFueVwiO1xuXG5pbXBvcnQgeyBjcmVhdGVNb2RlbFNlbGVjdG9yU3BlYyB9IGZyb20gXCIuL3NlbGVjdG9yc1wiO1xuXG5pbXBvcnQge1xuICAgIG0ybU5hbWUsXG4gICAgYXR0YWNoUXVlcnlTZXRNZXRob2RzLFxuICAgIG0ybVRvRmllbGROYW1lLFxuICAgIG0ybUZyb21GaWVsZE5hbWUsXG4gICAgd2FybkRlcHJlY2F0ZWQsXG59IGZyb20gXCIuL3V0aWxzXCI7XG5cbmNvbnN0IE9STV9ERUZBVUxUUyA9IHtcbiAgICBjcmVhdGVEYXRhYmFzZTogZGVmYXVsdENyZWF0ZURhdGFiYXNlLFxufTtcblxuY29uc3QgUkVTRVJWRURfVEFCTEVfT1BUSU9OUyA9IFtcImluZGV4ZXNcIiwgXCJtZXRhXCJdO1xuY29uc3QgaXNSZXNlcnZlZFRhYmxlT3B0aW9uID0gd29yZCA9PiBSRVNFUlZFRF9UQUJMRV9PUFRJT05TLmluY2x1ZGVzKHdvcmQpO1xuXG4vKipcbiAqIE9STSAtIHRoZSBPYmplY3QgUmVsYXRpb25hbCBNYXBwZXIuXG4gKlxuICogVXNlIGluc3RhbmNlcyBvZiB0aGlzIGNsYXNzIHRvOlxuICpcbiAqIC0gUmVnaXN0ZXIgeW91ciB7QGxpbmsgTW9kZWx9IGNsYXNzZXMgdXNpbmcge0BsaW5rIE9STSNyZWdpc3Rlcn1cbiAqIC0gR2V0IHRoZSBlbXB0eSBzdGF0ZSBmb3IgdGhlIHVuZGVybHlpbmcgZGF0YWJhc2Ugd2l0aCB7QGxpbmsgT1JNI2dldEVtcHR5U3RhdGV9XG4gKiAtIFN0YXJ0IGFuIGltbXV0YWJsZSBkYXRhYmFzZSBzZXNzaW9uIHdpdGgge0BsaW5rIE9STSNzZXNzaW9ufVxuICogLSBTdGFydCBhIG11dGF0aW5nIGRhdGFiYXNlIHNlc3Npb24gd2l0aCB7QGxpbmsgT1JNI211dGFibGVTZXNzaW9ufVxuICpcbiAqIEludGVybmFsbHksIHRoaXMgY2xhc3MgaGFuZGxlcyBnZW5lcmF0aW5nIGEgc2NoZW1hIHNwZWNpZmljYXRpb24gZnJvbSBtb2RlbHNcbiAqIHRvIHRoZSBkYXRhYmFzZS5cbiAqL1xuY2xhc3MgT1JNIHtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IE9STSBpbnN0YW5jZS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB7T2JqZWN0fSBbb3B0c11cbiAgICAgKiBAcGFyYW0ge0Z1bmN0aW9ufSBbb3B0cy5zdGF0ZVNlbGVjdG9yXSAtIGZ1bmN0aW9uIHRoYXQgZ2l2ZW4gYSBSZWR1eCBzdGF0ZSB0cmVlXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIHJldHVybiB0aGUgT1JNIHN0YXRlJ3Mgc3VidHJlZSxcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGUuZy4gYHN0YXRlID0+IHN0YXRlLm9ybWBcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChuZWNlc3NhcnkgaWYgeW91IHdhbnQgdG8gdXNlIHNlbGVjdG9ycylcbiAgICAgKiBAcGFyYW0ge0Z1bmN0aW9ufSBbb3B0cy5jcmVhdGVEYXRhYmFzZV0gLSBmdW5jdGlvbiB0aGF0IGNyZWF0ZXMgYSBkYXRhYmFzZVxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKG9wdHMpIHtcbiAgICAgICAgY29uc3QgeyBjcmVhdGVEYXRhYmFzZSB9ID0geyAuLi5PUk1fREVGQVVMVFMsIC4uLihvcHRzIHx8IHt9KSB9O1xuICAgICAgICB0aGlzLmNyZWF0ZURhdGFiYXNlID0gY3JlYXRlRGF0YWJhc2U7XG4gICAgICAgIHRoaXMucmVnaXN0cnkgPSBbXTtcbiAgICAgICAgdGhpcy5pbXBsaWNpdFRocm91Z2hNb2RlbHMgPSBbXTtcbiAgICAgICAgdGhpcy5pbnN0YWxsZWRGaWVsZHMgPSB7fTtcbiAgICAgICAgdGhpcy5zdGF0ZVNlbGVjdG9yID0gb3B0cyA/IG9wdHMuc3RhdGVTZWxlY3RvciA6IG51bGw7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVnaXN0ZXJzIGEge0BsaW5rIE1vZGVsfSBjbGFzcyB0byB0aGUgT1JNLlxuICAgICAqXG4gICAgICogSWYgdGhlIG1vZGVsIGhhcyBkZWNsYXJlZCBhbnkgTWFueVRvTWFueSBmaWVsZHMsIHRoZWlyXG4gICAgICogdGhyb3VnaCBtb2RlbHMgd2lsbCBiZSBnZW5lcmF0ZWQgYW5kIHJlZ2lzdGVyZWQgd2l0aFxuICAgICAqIHRoaXMgY2FsbCwgdW5sZXNzIGEgY3VzdG9tIHRocm91Z2ggbW9kZWwgaGFzIGJlZW4gc3BlY2lmaWVkLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7Li4uTW9kZWx9IG1vZGVscyAtIGEge0BsaW5rIE1vZGVsfSBjbGFzcyB0byByZWdpc3RlclxuICAgICAqIEByZXR1cm4ge3VuZGVmaW5lZH1cbiAgICAgKi9cbiAgICByZWdpc3RlciguLi5tb2RlbHMpIHtcbiAgICAgICAgbW9kZWxzLmZvckVhY2gobW9kZWwgPT4ge1xuICAgICAgICAgICAgaWYgKG1vZGVsLm1vZGVsTmFtZSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgICAgICBcIkEgbW9kZWwgd2FzIHBhc3NlZCB0aGF0IGRvZXNuJ3QgaGF2ZSBhIG1vZGVsTmFtZSBzZXRcIlxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIG1vZGVsLmludmFsaWRhdGVDbGFzc0NhY2hlKCk7XG5cbiAgICAgICAgICAgIHRoaXMucmVnaXN0ZXJNYW55VG9NYW55TW9kZWxzRm9yKG1vZGVsKTtcbiAgICAgICAgICAgIHRoaXMucmVnaXN0cnkucHVzaChtb2RlbCk7XG5cbiAgICAgICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCBtb2RlbC5tb2RlbE5hbWUsIHtcbiAgICAgICAgICAgICAgICBnZXQ6ICgpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgLy8gbWFrZSBzdXJlIHZpcnR1YWxGaWVsZHMgYXJlIHNldCB1cFxuICAgICAgICAgICAgICAgICAgICB0aGlzLl9zZXR1cE1vZGVsUHJvdG90eXBlcyh0aGlzLnJlZ2lzdHJ5KTtcblxuICAgICAgICAgICAgICAgICAgICByZXR1cm4gY3JlYXRlTW9kZWxTZWxlY3RvclNwZWMoe1xuICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwsXG4gICAgICAgICAgICAgICAgICAgICAgICBvcm06IHRoaXMsXG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgcmVnaXN0ZXJNYW55VG9NYW55TW9kZWxzRm9yKG1vZGVsKSB7XG4gICAgICAgIGNvbnN0IHsgZmllbGRzIH0gPSBtb2RlbDtcbiAgICAgICAgY29uc3QgdGhpc01vZGVsTmFtZSA9IG1vZGVsLm1vZGVsTmFtZTtcblxuICAgICAgICBPYmplY3QuZW50cmllcyhmaWVsZHMpLmZvckVhY2goKFtmaWVsZE5hbWUsIGZpZWxkSW5zdGFuY2VdKSA9PiB7XG4gICAgICAgICAgICBpZiAoIShmaWVsZEluc3RhbmNlIGluc3RhbmNlb2YgTWFueVRvTWFueSkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGxldCB0b01vZGVsTmFtZTtcbiAgICAgICAgICAgIGlmIChmaWVsZEluc3RhbmNlLnRvTW9kZWxOYW1lID09PSBcInRoaXNcIikge1xuICAgICAgICAgICAgICAgIHRvTW9kZWxOYW1lID0gdGhpc01vZGVsTmFtZTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgdG9Nb2RlbE5hbWUgPSBmaWVsZEluc3RhbmNlLnRvTW9kZWxOYW1lOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIHByZWZlci1kZXN0cnVjdHVyaW5nXG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGNvbnN0IHNlbGZSZWZlcmVuY2luZyA9IHRoaXNNb2RlbE5hbWUgPT09IHRvTW9kZWxOYW1lO1xuICAgICAgICAgICAgY29uc3QgZnJvbUZpZWxkTmFtZSA9IG0ybUZyb21GaWVsZE5hbWUodGhpc01vZGVsTmFtZSk7XG4gICAgICAgICAgICBjb25zdCB0b0ZpZWxkTmFtZSA9IG0ybVRvRmllbGROYW1lKHRvTW9kZWxOYW1lKTtcblxuICAgICAgICAgICAgaWYgKGZpZWxkSW5zdGFuY2UudGhyb3VnaCkge1xuICAgICAgICAgICAgICAgIGlmIChzZWxmUmVmZXJlbmNpbmcgJiYgIWZpZWxkSW5zdGFuY2UudGhyb3VnaEZpZWxkcykge1xuICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgICAgICAgICBcIlNlbGYtcmVmZXJlbmNpbmcgbWFueS10by1tYW55IHJlbGF0aW9uc2hpcCBhdCBcIiArXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgYFwiJHt0aGlzTW9kZWxOYW1lfS4ke2ZpZWxkTmFtZX1cIiB1c2luZyBjdXN0b20gYCArXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgYG1vZGVsIFwiJHtmaWVsZEluc3RhbmNlLnRocm91Z2h9XCIgaGFzIG5vIGAgK1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwidGhyb3VnaEZpZWxkcyBrZXkuIENhbm5vdCBkZXRlcm1pbmUgd2hpY2ggXCIgK1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwiZmllbGRzIHJlZmVyZW5jZSB0aGUgaW5zdGFuY2VzIHBhcnRha2luZyBcIiArXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJpbiB0aGUgcmVsYXRpb25zaGlwLlwiXG4gICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBjb25zdCBUaHJvdWdoID0gY2xhc3MgVGhyb3VnaE1vZGVsIGV4dGVuZHMgTW9kZWwge307XG5cbiAgICAgICAgICAgICAgICBUaHJvdWdoLm1vZGVsTmFtZSA9IG0ybU5hbWUodGhpc01vZGVsTmFtZSwgZmllbGROYW1lKTtcblxuICAgICAgICAgICAgICAgIGNvbnN0IFBsYWluRm9yZWlnbktleSA9IGNsYXNzIFBsYWluRm9yZWlnbktleSBleHRlbmRzIEZvcmVpZ25LZXkge1xuICAgICAgICAgICAgICAgICAgICBnZXQgaW5zdGFsbHNCYWNrd2FyZHNWaXJ0dWFsRmllbGQoKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICBnZXQgaW5zdGFsbHNCYWNrd2FyZHNEZXNjcmlwdG9yKCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICBjb25zdCBGb3JlaWduS2V5Q2xhc3MgPSBzZWxmUmVmZXJlbmNpbmdcbiAgICAgICAgICAgICAgICAgICAgPyBQbGFpbkZvcmVpZ25LZXlcbiAgICAgICAgICAgICAgICAgICAgOiBGb3JlaWduS2V5O1xuICAgICAgICAgICAgICAgIFRocm91Z2guZmllbGRzID0ge1xuICAgICAgICAgICAgICAgICAgICBpZDogYXR0cigpLFxuICAgICAgICAgICAgICAgICAgICBbZnJvbUZpZWxkTmFtZV06IG5ldyBGb3JlaWduS2V5Q2xhc3ModGhpc01vZGVsTmFtZSksXG4gICAgICAgICAgICAgICAgICAgIFt0b0ZpZWxkTmFtZV06IG5ldyBGb3JlaWduS2V5Q2xhc3ModG9Nb2RlbE5hbWUpLFxuICAgICAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgICAgICBUaHJvdWdoLmludmFsaWRhdGVDbGFzc0NhY2hlKCk7XG4gICAgICAgICAgICAgICAgdGhpcy5pbXBsaWNpdFRocm91Z2hNb2RlbHMucHVzaChUaHJvdWdoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogR2V0cyBhIHtAbGluayBNb2RlbH0gY2xhc3MgYnkgaXRzIG5hbWUgZnJvbSB0aGUgcmVnaXN0cnkuXG4gICAgICogQHBhcmFtICB7c3RyaW5nfSBtb2RlbE5hbWUgLSB0aGUgbmFtZSBvZiB0aGUge0BsaW5rIE1vZGVsfSBjbGFzcyB0byBnZXRcbiAgICAgKiBAdGhyb3dzIElmIHtAbGluayBNb2RlbH0gY2xhc3MgaXMgbm90IGZvdW5kLlxuICAgICAqIEByZXR1cm4ge01vZGVsfSB0aGUge0BsaW5rIE1vZGVsfSBjbGFzcywgaWYgZm91bmRcbiAgICAgKi9cbiAgICBnZXQobW9kZWxOYW1lKSB7XG4gICAgICAgIGNvbnN0IGFsbE1vZGVscyA9IHRoaXMucmVnaXN0cnkuY29uY2F0KHRoaXMuaW1wbGljaXRUaHJvdWdoTW9kZWxzKTtcbiAgICAgICAgY29uc3QgZm91bmQgPSBPYmplY3QudmFsdWVzKGFsbE1vZGVscykuZmluZChcbiAgICAgICAgICAgIG1vZGVsID0+IG1vZGVsLm1vZGVsTmFtZSA9PT0gbW9kZWxOYW1lXG4gICAgICAgICk7XG5cbiAgICAgICAgaWYgKHR5cGVvZiBmb3VuZCA9PT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBEaWQgbm90IGZpbmQgbW9kZWwgJHttb2RlbE5hbWV9IGZyb20gcmVnaXN0cnkuYCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGZvdW5kO1xuICAgIH1cblxuICAgIGdldE1vZGVsQ2xhc3NlcygpIHtcbiAgICAgICAgdGhpcy5fc2V0dXBNb2RlbFByb3RvdHlwZXModGhpcy5yZWdpc3RyeSk7XG4gICAgICAgIHRoaXMuX3NldHVwTW9kZWxQcm90b3R5cGVzKHRoaXMuaW1wbGljaXRUaHJvdWdoTW9kZWxzKTtcbiAgICAgICAgcmV0dXJuIHRoaXMucmVnaXN0cnkuY29uY2F0KHRoaXMuaW1wbGljaXRUaHJvdWdoTW9kZWxzKTtcbiAgICB9XG5cbiAgICBnZW5lcmF0ZVNjaGVtYVNwZWMoKSB7XG4gICAgICAgIGNvbnN0IG1vZGVscyA9IHRoaXMuZ2V0TW9kZWxDbGFzc2VzKCk7XG4gICAgICAgIGNvbnN0IHRhYmxlcyA9IG1vZGVscy5yZWR1Y2UoKHNwZWMsIG1vZGVsQ2xhc3MpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRhYmxlTmFtZSA9IG1vZGVsQ2xhc3MubW9kZWxOYW1lO1xuICAgICAgICAgICAgY29uc3QgdGFibGVTcGVjID0gbW9kZWxDbGFzcy50YWJsZU9wdGlvbnMoKTtcbiAgICAgICAgICAgIE9iamVjdC5rZXlzKHRhYmxlU3BlYylcbiAgICAgICAgICAgICAgICAuZmlsdGVyKGlzUmVzZXJ2ZWRUYWJsZU9wdGlvbilcbiAgICAgICAgICAgICAgICAuZm9yRWFjaChrZXkgPT4ge1xuICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgICAgICAgICBgUmVzZXJ2ZWQga2V5d29yZCBcXGAke2tleX1cXGAgdXNlZCBpbiAke3RhYmxlTmFtZX0ub3B0aW9ucy5gXG4gICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBzcGVjW3RhYmxlTmFtZV0gPSB7XG4gICAgICAgICAgICAgICAgZmllbGRzOiB7IC4uLm1vZGVsQ2xhc3MuZmllbGRzIH0sXG4gICAgICAgICAgICAgICAgLi4udGFibGVTcGVjLFxuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJldHVybiBzcGVjO1xuICAgICAgICB9LCB7fSk7XG4gICAgICAgIHJldHVybiB7IHRhYmxlcyB9O1xuICAgIH1cblxuICAgIGdldERhdGFiYXNlKCkge1xuICAgICAgICBpZiAoIXRoaXMuZGIpIHtcbiAgICAgICAgICAgIHRoaXMuZGIgPSB0aGlzLmNyZWF0ZURhdGFiYXNlKHRoaXMuZ2VuZXJhdGVTY2hlbWFTcGVjKCkpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLmRiO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGVtcHR5IGRhdGFiYXNlIHN0YXRlLlxuICAgICAqIEByZXR1cm4ge09iamVjdH0gdGhlIGVtcHR5IHN0YXRlXG4gICAgICovXG4gICAgZ2V0RW1wdHlTdGF0ZSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0RGF0YWJhc2UoKS5nZXRFbXB0eVN0YXRlKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQmVnaW5zIGFuIGltbXV0YWJsZSBkYXRhYmFzZSBzZXNzaW9uLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBzdGF0ZSAgLSB0aGUgc3RhdGUgdGhlIGRhdGFiYXNlIG1hbmFnZXNcbiAgICAgKiBAcmV0dXJuIHtTZXNzaW9ufSBhIG5ldyB7QGxpbmsgU2Vzc2lvbn0gaW5zdGFuY2VcbiAgICAgKi9cbiAgICBzZXNzaW9uKHN0YXRlKSB7XG4gICAgICAgIHJldHVybiBuZXcgU2Vzc2lvbih0aGlzLCB0aGlzLmdldERhdGFiYXNlKCksIHN0YXRlKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBCZWdpbnMgYSBtdXRhYmxlIGRhdGFiYXNlIHNlc3Npb24uXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IHN0YXRlICAtIHRoZSBzdGF0ZSB0aGUgZGF0YWJhc2UgbWFuYWdlc1xuICAgICAqIEByZXR1cm4ge1Nlc3Npb259IGEgbmV3IHtAbGluayBTZXNzaW9ufSBpbnN0YW5jZVxuICAgICAqL1xuICAgIG11dGFibGVTZXNzaW9uKHN0YXRlKSB7XG4gICAgICAgIHJldHVybiBuZXcgU2Vzc2lvbih0aGlzLCB0aGlzLmdldERhdGFiYXNlKCksIHN0YXRlLCB0cnVlKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqL1xuICAgIF9zZXR1cE1vZGVsUHJvdG90eXBlcyhtb2RlbHMpIHtcbiAgICAgICAgbW9kZWxzXG4gICAgICAgICAgICAuZmlsdGVyKG1vZGVsID0+ICFtb2RlbC5pc1NldFVwKVxuICAgICAgICAgICAgLmZvckVhY2gobW9kZWwgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IHsgZmllbGRzLCBtb2RlbE5hbWUsIHF1ZXJ5U2V0Q2xhc3MgfSA9IG1vZGVsO1xuICAgICAgICAgICAgICAgIE9iamVjdC5lbnRyaWVzKGZpZWxkcykuZm9yRWFjaCgoW2ZpZWxkTmFtZSwgZmllbGRdKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGlmICghKGZpZWxkIGluc3RhbmNlb2YgRmllbGQpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgYCR7bW9kZWxOYW1lfS4ke2ZpZWxkTmFtZX0gaXMgb2YgdHlwZSBcIiR7dHlwZW9mIGZpZWxkfVwiIGAgK1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcImJ1dCBtdXN0IGJlIGFuIGluc3RhbmNlIG9mIEZpZWxkLiBQbGVhc2UgdXNlIHRoZSBcIiArXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwiYGF0dHJgLCBgZmtgLCBgb25lVG9PbmVgIGFuZCBgbWFueWAgXCIgK1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcImZ1bmN0aW9ucyB0byBkZWZpbmUgZmllbGRzLlwiXG4gICAgICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGlmICghdGhpcy5faXNGaWVsZEluc3RhbGxlZChtb2RlbE5hbWUsIGZpZWxkTmFtZSkpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuX2luc3RhbGxGaWVsZChmaWVsZCwgZmllbGROYW1lLCBtb2RlbCk7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLl9zZXRGaWVsZEluc3RhbGxlZChtb2RlbE5hbWUsIGZpZWxkTmFtZSk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICBhdHRhY2hRdWVyeVNldE1ldGhvZHMobW9kZWwsIHF1ZXJ5U2V0Q2xhc3MpO1xuICAgICAgICAgICAgICAgIG1vZGVsLmlzU2V0VXAgPSB0cnVlO1xuICAgICAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQHByaXZhdGVcbiAgICAgKi9cbiAgICBfaXNGaWVsZEluc3RhbGxlZChtb2RlbE5hbWUsIGZpZWxkTmFtZSkge1xuICAgICAgICByZXR1cm4gdGhpcy5pbnN0YWxsZWRGaWVsZHMuaGFzT3duUHJvcGVydHkobW9kZWxOYW1lKVxuICAgICAgICAgICAgPyAhIXRoaXMuaW5zdGFsbGVkRmllbGRzW21vZGVsTmFtZV1bZmllbGROYW1lXVxuICAgICAgICAgICAgOiBmYWxzZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqL1xuICAgIF9zZXRGaWVsZEluc3RhbGxlZChtb2RlbE5hbWUsIGZpZWxkTmFtZSkge1xuICAgICAgICBpZiAoIXRoaXMuaW5zdGFsbGVkRmllbGRzLmhhc093blByb3BlcnR5KG1vZGVsTmFtZSkpIHtcbiAgICAgICAgICAgIHRoaXMuaW5zdGFsbGVkRmllbGRzW21vZGVsTmFtZV0gPSB7fTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLmluc3RhbGxlZEZpZWxkc1ttb2RlbE5hbWVdW2ZpZWxkTmFtZV0gPSB0cnVlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEluc3RhbGxzIGEgZmllbGQgb24gYSBtb2RlbCBhbmQgaXRzIHJlbGF0ZWQgbW9kZWxzIGlmIG5lY2Vzc2FyeS5cbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqL1xuICAgIF9pbnN0YWxsRmllbGQoZmllbGQsIGZpZWxkTmFtZSwgbW9kZWwpIHtcbiAgICAgICAgY29uc3QgRmllbGRJbnN0YWxsZXIgPSBmaWVsZC5pbnN0YWxsZXJDbGFzcztcbiAgICAgICAgbmV3IEZpZWxkSW5zdGFsbGVyKHtcbiAgICAgICAgICAgIGZpZWxkLFxuICAgICAgICAgICAgZmllbGROYW1lLFxuICAgICAgICAgICAgbW9kZWwsXG4gICAgICAgICAgICBvcm06IHRoaXMsXG4gICAgICAgIH0pLnJ1bigpO1xuICAgIH1cblxuICAgIC8vIERFUFJFQ0FURUQgQU5EIFJFTU9WRUQgTUVUSE9EU1xuXG4gICAgLyoqXG4gICAgICogQGRlcHJlY2F0ZWQgVXNlIHtAbGluayBPUk0jbXV0YWJsZVNlc3Npb259IGluc3RlYWQuXG4gICAgICovXG4gICAgd2l0aE11dGF0aW9ucyhzdGF0ZSkge1xuICAgICAgICB3YXJuRGVwcmVjYXRlZChcbiAgICAgICAgICAgIFwiYE9STS5wcm90b3R5cGUud2l0aE11dGF0aW9uc2AgaGFzIGJlZW4gZGVwcmVjYXRlZC4gXCIgK1xuICAgICAgICAgICAgICAgIFwiVXNlIGBPUk0ucHJvdG90eXBlLm11dGFibGVTZXNzaW9uYCBpbnN0ZWFkLlwiXG4gICAgICAgICk7XG4gICAgICAgIHJldHVybiB0aGlzLm11dGFibGVTZXNzaW9uKHN0YXRlKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAZGVwcmVjYXRlZCBVc2Uge0BsaW5rIE9STSNzZXNzaW9ufSBpbnN0ZWFkLlxuICAgICAqL1xuICAgIGZyb20oc3RhdGUpIHtcbiAgICAgICAgd2FybkRlcHJlY2F0ZWQoXG4gICAgICAgICAgICBcImBPUk0ucHJvdG90eXBlLmZyb21gIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFwiICtcbiAgICAgICAgICAgICAgICBcIlVzZSBgT1JNLnByb3RvdHlwZS5zZXNzaW9uYCBpbnN0ZWFkLlwiXG4gICAgICAgICk7XG4gICAgICAgIHJldHVybiB0aGlzLnNlc3Npb24oc3RhdGUpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEBkZXByZWNhdGVkIFVzZSB7QGxpbmsgT1JNI2dldEVtcHR5U3RhdGV9IGluc3RlYWQuXG4gICAgICovXG4gICAgZ2V0RGVmYXVsdFN0YXRlKCkge1xuICAgICAgICB3YXJuRGVwcmVjYXRlZChcbiAgICAgICAgICAgIFwiYE9STS5wcm90b3R5cGUuZ2V0RGVmYXVsdFN0YXRlYCBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgXCIgK1xuICAgICAgICAgICAgICAgIFwiYE9STS5wcm90b3R5cGUuZ2V0RW1wdHlTdGF0ZWAgaW5zdGVhZC5cIlxuICAgICAgICApO1xuICAgICAgICByZXR1cm4gdGhpcy5nZXRFbXB0eVN0YXRlKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQGRlcHJlY2F0ZWQgRGVmaW5lIGEgTW9kZWwgY2xhc3MgaW5zdGVhZC5cbiAgICAgKi9cbiAgICBkZWZpbmUoKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIFwiYE9STS5wcm90b3R5cGUuZGVmaW5lYCBoYXMgYmVlbiByZW1vdmVkLiBQbGVhc2UgZGVmaW5lIGEgTW9kZWwgY2xhc3MuXCJcbiAgICAgICAgKTtcbiAgICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBEZXByZWNhdGVkU2NoZW1hKCkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgXCJTY2hlbWEgaGFzIGJlZW4gcmVuYW1lZCB0byBPUk0uIFBsZWFzZSBpbXBvcnQgT1JNIGluc3RlYWQgb2YgU2NoZW1hIFwiICtcbiAgICAgICAgICAgIFwiZnJvbSBSZWR1eC1PUk0uXCJcbiAgICApO1xufVxuXG5leHBvcnQgeyBPUk0gfTtcblxuZXhwb3J0IGRlZmF1bHQgT1JNO1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/ORM.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"DeprecatedSchema\\\", function() { return DeprecatedSchema; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"ORM\\\", function() { return ORM; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _Session__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Session */ \\\"./src/Session.js\\\");\\n/* harmony import */ var _Model__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./Model */ \\\"./src/Model.js\\\");\\n/* harmony import */ var _db__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./db */ \\\"./src/db/index.js\\\");\\n/* harmony import */ var _fields__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./fields */ \\\"./src/fields/index.js\\\");\\n/* harmony import */ var _fields_Field__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./fields/Field */ \\\"./src/fields/Field.js\\\");\\n/* harmony import */ var _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./fields/ForeignKey */ \\\"./src/fields/ForeignKey.js\\\");\\n/* harmony import */ var _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./fields/ManyToMany */ \\\"./src/fields/ManyToMany.js\\\");\\n/* harmony import */ var _selectors__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./selectors */ \\\"./src/selectors/index.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n/* eslint-disable max-classes-per-file */\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nconst ORM_DEFAULTS = {\\n  createDatabase: _db__WEBPACK_IMPORTED_MODULE_4__[\\\"createDatabase\\\"]\\n};\\nconst RESERVED_TABLE_OPTIONS = [\\\"indexes\\\", \\\"meta\\\"];\\n\\nconst isReservedTableOption = word => RESERVED_TABLE_OPTIONS.includes(word);\\n/**\\n * ORM - the Object Relational Mapper.\\n *\\n * Use instances of this class to:\\n *\\n * - Register your {@link Model} classes using {@link ORM#register}\\n * - Get the empty state for the underlying database with {@link ORM#getEmptyState}\\n * - Start an immutable database session with {@link ORM#session}\\n * - Start a mutating database session with {@link ORM#mutableSession}\\n *\\n * Internally, this class handles generating a schema specification from models\\n * to the database.\\n */\\n\\n\\nlet ORM = /*#__PURE__*/function () {\\n  /**\\n   * Creates a new ORM instance.\\n   *\\n   * @param {Object} [opts]\\n   * @param {Function} [opts.stateSelector] - function that given a Redux state tree\\n   *                                          will return the ORM state's subtree,\\n   *                                          e.g. `state => state.orm`\\n   *                                          (necessary if you want to use selectors)\\n   * @param {Function} [opts.createDatabase] - function that creates a database\\n   */\\n  function ORM(opts) {\\n    const {\\n      createDatabase\\n    } = { ...ORM_DEFAULTS,\\n      ...(opts || {})\\n    };\\n    this.createDatabase = createDatabase;\\n    this.registry = [];\\n    this.implicitThroughModels = [];\\n    this.installedFields = {};\\n    this.stateSelector = opts ? opts.stateSelector : null;\\n  }\\n  /**\\n   * Registers a {@link Model} class to the ORM.\\n   *\\n   * If the model has declared any ManyToMany fields, their\\n   * through models will be generated and registered with\\n   * this call, unless a custom through model has been specified.\\n   *\\n   * @param  {...Model} models - a {@link Model} class to register\\n   * @return {undefined}\\n   */\\n\\n\\n  var _proto = ORM.prototype;\\n\\n  _proto.register = function register(...models) {\\n    models.forEach(model => {\\n      if (model.modelName === undefined) {\\n        throw new Error(\\\"A model was passed that doesn't have a modelName set\\\");\\n      }\\n\\n      model.invalidateClassCache();\\n      this.registerManyToManyModelsFor(model);\\n      this.registry.push(model);\\n      Object.defineProperty(this, model.modelName, {\\n        get: () => {\\n          // make sure virtualFields are set up\\n          this._setupModelPrototypes(this.registry);\\n\\n          return Object(_selectors__WEBPACK_IMPORTED_MODULE_9__[\\\"createModelSelectorSpec\\\"])({\\n            model,\\n            orm: this\\n          });\\n        }\\n      });\\n    });\\n  };\\n\\n  _proto.registerManyToManyModelsFor = function registerManyToManyModelsFor(model) {\\n    const {\\n      fields\\n    } = model;\\n    const thisModelName = model.modelName;\\n    Object.entries(fields).forEach(([fieldName, fieldInstance]) => {\\n      if (!(fieldInstance instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_8__[\\\"default\\\"])) {\\n        return;\\n      }\\n\\n      let toModelName;\\n\\n      if (fieldInstance.toModelName === \\\"this\\\") {\\n        toModelName = thisModelName;\\n      } else {\\n        toModelName = fieldInstance.toModelName; // eslint-disable-line prefer-destructuring\\n      }\\n\\n      const selfReferencing = thisModelName === toModelName;\\n      const fromFieldName = Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"m2mFromFieldName\\\"])(thisModelName);\\n      const toFieldName = Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"m2mToFieldName\\\"])(toModelName);\\n\\n      if (fieldInstance.through) {\\n        if (selfReferencing && !fieldInstance.throughFields) {\\n          throw new Error(\\\"Self-referencing many-to-many relationship at \\\" + `\\\"${thisModelName}.${fieldName}\\\" using custom ` + `model \\\"${fieldInstance.through}\\\" has no ` + \\\"throughFields key. Cannot determine which \\\" + \\\"fields reference the instances partaking \\\" + \\\"in the relationship.\\\");\\n        }\\n      } else {\\n        const Through = /*#__PURE__*/function (_Model) {\\n          _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(ThroughModel, _Model);\\n\\n          function ThroughModel() {\\n            return _Model.apply(this, arguments) || this;\\n          }\\n\\n          return ThroughModel;\\n        }(_Model__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]);\\n\\n        Through.modelName = Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"m2mName\\\"])(thisModelName, fieldName);\\n\\n        const PlainForeignKey = /*#__PURE__*/function (_ForeignKey) {\\n          _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(PlainForeignKey, _ForeignKey);\\n\\n          function PlainForeignKey() {\\n            return _ForeignKey.apply(this, arguments) || this;\\n          }\\n\\n          _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(PlainForeignKey, [{\\n            key: \\\"installsBackwardsVirtualField\\\",\\n            get: function () {\\n              return false;\\n            }\\n          }, {\\n            key: \\\"installsBackwardsDescriptor\\\",\\n            get: function () {\\n              return false;\\n            }\\n          }]);\\n\\n          return PlainForeignKey;\\n        }(_fields_ForeignKey__WEBPACK_IMPORTED_MODULE_7__[\\\"default\\\"]);\\n\\n        const ForeignKeyClass = selfReferencing ? PlainForeignKey : _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_7__[\\\"default\\\"];\\n        Through.fields = {\\n          id: Object(_fields__WEBPACK_IMPORTED_MODULE_5__[\\\"attr\\\"])(),\\n          [fromFieldName]: new ForeignKeyClass(thisModelName),\\n          [toFieldName]: new ForeignKeyClass(toModelName)\\n        };\\n        Through.invalidateClassCache();\\n        this.implicitThroughModels.push(Through);\\n      }\\n    });\\n  }\\n  /**\\n   * Gets a {@link Model} class by its name from the registry.\\n   * @param  {string} modelName - the name of the {@link Model} class to get\\n   * @throws If {@link Model} class is not found.\\n   * @return {Model} the {@link Model} class, if found\\n   */\\n  ;\\n\\n  _proto.get = function get(modelName) {\\n    const allModels = this.registry.concat(this.implicitThroughModels);\\n    const found = Object.values(allModels).find(model => model.modelName === modelName);\\n\\n    if (typeof found === \\\"undefined\\\") {\\n      throw new Error(`Did not find model ${modelName} from registry.`);\\n    }\\n\\n    return found;\\n  };\\n\\n  _proto.getModelClasses = function getModelClasses() {\\n    this._setupModelPrototypes(this.registry);\\n\\n    this._setupModelPrototypes(this.implicitThroughModels);\\n\\n    return this.registry.concat(this.implicitThroughModels);\\n  };\\n\\n  _proto.generateSchemaSpec = function generateSchemaSpec() {\\n    const models = this.getModelClasses();\\n    const tables = models.reduce((spec, modelClass) => {\\n      const tableName = modelClass.modelName;\\n      const tableSpec = modelClass.tableOptions();\\n      Object.keys(tableSpec).filter(isReservedTableOption).forEach(key => {\\n        throw new Error(`Reserved keyword \\\\`${key}\\\\` used in ${tableName}.options.`);\\n      });\\n      spec[tableName] = {\\n        fields: { ...modelClass.fields\\n        },\\n        ...tableSpec\\n      };\\n      return spec;\\n    }, {});\\n    return {\\n      tables\\n    };\\n  };\\n\\n  _proto.getDatabase = function getDatabase() {\\n    if (!this.db) {\\n      this.db = this.createDatabase(this.generateSchemaSpec());\\n    }\\n\\n    return this.db;\\n  }\\n  /**\\n   * Returns the empty database state.\\n   * @return {Object} the empty state\\n   */\\n  ;\\n\\n  _proto.getEmptyState = function getEmptyState() {\\n    return this.getDatabase().getEmptyState();\\n  }\\n  /**\\n   * Begins an immutable database session.\\n   *\\n   * @param  {Object} state  - the state the database manages\\n   * @return {Session} a new {@link Session} instance\\n   */\\n  ;\\n\\n  _proto.session = function session(state) {\\n    return new _Session__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"](this, this.getDatabase(), state);\\n  }\\n  /**\\n   * Begins a mutable database session.\\n   *\\n   * @param  {Object} state  - the state the database manages\\n   * @return {Session} a new {@link Session} instance\\n   */\\n  ;\\n\\n  _proto.mutableSession = function mutableSession(state) {\\n    return new _Session__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"](this, this.getDatabase(), state, true);\\n  }\\n  /**\\n   * @private\\n   */\\n  ;\\n\\n  _proto._setupModelPrototypes = function _setupModelPrototypes(models) {\\n    models.filter(model => !model.isSetUp).forEach(model => {\\n      const {\\n        fields,\\n        modelName,\\n        querySetClass\\n      } = model;\\n      Object.entries(fields).forEach(([fieldName, field]) => {\\n        if (!(field instanceof _fields_Field__WEBPACK_IMPORTED_MODULE_6__[\\\"default\\\"])) {\\n          throw new Error(`${modelName}.${fieldName} is of type \\\"${typeof field}\\\" ` + \\\"but must be an instance of Field. Please use the \\\" + \\\"`attr`, `fk`, `oneToOne` and `many` \\\" + \\\"functions to define fields.\\\");\\n        }\\n\\n        if (!this._isFieldInstalled(modelName, fieldName)) {\\n          this._installField(field, fieldName, model);\\n\\n          this._setFieldInstalled(modelName, fieldName);\\n        }\\n      });\\n      Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"attachQuerySetMethods\\\"])(model, querySetClass);\\n      model.isSetUp = true;\\n    });\\n  }\\n  /**\\n   * @private\\n   */\\n  ;\\n\\n  _proto._isFieldInstalled = function _isFieldInstalled(modelName, fieldName) {\\n    return this.installedFields.hasOwnProperty(modelName) ? !!this.installedFields[modelName][fieldName] : false;\\n  }\\n  /**\\n   * @private\\n   */\\n  ;\\n\\n  _proto._setFieldInstalled = function _setFieldInstalled(modelName, fieldName) {\\n    if (!this.installedFields.hasOwnProperty(modelName)) {\\n      this.installedFields[modelName] = {};\\n    }\\n\\n    this.installedFields[modelName][fieldName] = true;\\n  }\\n  /**\\n   * Installs a field on a model and its related models if necessary.\\n   * @private\\n   */\\n  ;\\n\\n  _proto._installField = function _installField(field, fieldName, model) {\\n    const FieldInstaller = field.installerClass;\\n    new FieldInstaller({\\n      field,\\n      fieldName,\\n      model,\\n      orm: this\\n    }).run();\\n  } // DEPRECATED AND REMOVED METHODS\\n\\n  /**\\n   * @deprecated Use {@link ORM#mutableSession} instead.\\n   */\\n  ;\\n\\n  _proto.withMutations = function withMutations(state) {\\n    Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"warnDeprecated\\\"])(\\\"`ORM.prototype.withMutations` has been deprecated. \\\" + \\\"Use `ORM.prototype.mutableSession` instead.\\\");\\n    return this.mutableSession(state);\\n  }\\n  /**\\n   * @deprecated Use {@link ORM#session} instead.\\n   */\\n  ;\\n\\n  _proto.from = function from(state) {\\n    Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"warnDeprecated\\\"])(\\\"`ORM.prototype.from` has been deprecated. \\\" + \\\"Use `ORM.prototype.session` instead.\\\");\\n    return this.session(state);\\n  }\\n  /**\\n   * @deprecated Use {@link ORM#getEmptyState} instead.\\n   */\\n  ;\\n\\n  _proto.getDefaultState = function getDefaultState() {\\n    Object(_utils__WEBPACK_IMPORTED_MODULE_10__[\\\"warnDeprecated\\\"])(\\\"`ORM.prototype.getDefaultState` has been deprecated. Use \\\" + \\\"`ORM.prototype.getEmptyState` instead.\\\");\\n    return this.getEmptyState();\\n  }\\n  /**\\n   * @deprecated Define a Model class instead.\\n   */\\n  ;\\n\\n  _proto.define = function define() {\\n    throw new Error(\\\"`ORM.prototype.define` has been removed. Please define a Model class.\\\");\\n  };\\n\\n  return ORM;\\n}();\\n\\nfunction DeprecatedSchema() {\\n  throw new Error(\\\"Schema has been renamed to ORM. Please import ORM instead of Schema \\\" + \\\"from Redux-ORM.\\\");\\n}\\n\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (ORM);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9PUk0uanM/YWE0ZSJdLCJuYW1lcyI6WyJPUk1fREVGQVVMVFMiLCJjcmVhdGVEYXRhYmFzZSIsImRlZmF1bHRDcmVhdGVEYXRhYmFzZSIsIlJFU0VSVkVEX1RBQkxFX09QVElPTlMiLCJpc1Jlc2VydmVkVGFibGVPcHRpb24iLCJ3b3JkIiwiaW5jbHVkZXMiLCJPUk0iLCJvcHRzIiwicmVnaXN0cnkiLCJpbXBsaWNpdFRocm91Z2hNb2RlbHMiLCJpbnN0YWxsZWRGaWVsZHMiLCJzdGF0ZVNlbGVjdG9yIiwicmVnaXN0ZXIiLCJtb2RlbHMiLCJmb3JFYWNoIiwibW9kZWwiLCJtb2RlbE5hbWUiLCJ1bmRlZmluZWQiLCJFcnJvciIsImludmFsaWRhdGVDbGFzc0NhY2hlIiwicmVnaXN0ZXJNYW55VG9NYW55TW9kZWxzRm9yIiwicHVzaCIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZ2V0IiwiX3NldHVwTW9kZWxQcm90b3R5cGVzIiwiY3JlYXRlTW9kZWxTZWxlY3RvclNwZWMiLCJvcm0iLCJmaWVsZHMiLCJ0aGlzTW9kZWxOYW1lIiwiZW50cmllcyIsImZpZWxkTmFtZSIsImZpZWxkSW5zdGFuY2UiLCJNYW55VG9NYW55IiwidG9Nb2RlbE5hbWUiLCJzZWxmUmVmZXJlbmNpbmciLCJmcm9tRmllbGROYW1lIiwibTJtRnJvbUZpZWxkTmFtZSIsInRvRmllbGROYW1lIiwibTJtVG9GaWVsZE5hbWUiLCJ0aHJvdWdoIiwidGhyb3VnaEZpZWxkcyIsIlRocm91Z2giLCJNb2RlbCIsIm0ybU5hbWUiLCJQbGFpbkZvcmVpZ25LZXkiLCJGb3JlaWduS2V5IiwiRm9yZWlnbktleUNsYXNzIiwiaWQiLCJhdHRyIiwiYWxsTW9kZWxzIiwiY29uY2F0IiwiZm91bmQiLCJ2YWx1ZXMiLCJmaW5kIiwiZ2V0TW9kZWxDbGFzc2VzIiwiZ2VuZXJhdGVTY2hlbWFTcGVjIiwidGFibGVzIiwicmVkdWNlIiwic3BlYyIsIm1vZGVsQ2xhc3MiLCJ0YWJsZU5hbWUiLCJ0YWJsZVNwZWMiLCJ0YWJsZU9wdGlvbnMiLCJrZXlzIiwiZmlsdGVyIiwia2V5IiwiZ2V0RGF0YWJhc2UiLCJkYiIsImdldEVtcHR5U3RhdGUiLCJzZXNzaW9uIiwic3RhdGUiLCJTZXNzaW9uIiwibXV0YWJsZVNlc3Npb24iLCJpc1NldFVwIiwicXVlcnlTZXRDbGFzcyIsImZpZWxkIiwiRmllbGQiLCJfaXNGaWVsZEluc3RhbGxlZCIsIl9pbnN0YWxsRmllbGQiLCJfc2V0RmllbGRJbnN0YWxsZWQiLCJhdHRhY2hRdWVyeVNldE1ldGhvZHMiLCJoYXNPd25Qcm9wZXJ0eSIsIkZpZWxkSW5zdGFsbGVyIiwiaW5zdGFsbGVyQ2xhc3MiLCJydW4iLCJ3aXRoTXV0YXRpb25zIiwid2FybkRlcHJlY2F0ZWQiLCJmcm9tIiwiZ2V0RGVmYXVsdFN0YXRlIiwiZGVmaW5lIiwiRGVwcmVjYXRlZFNjaGVtYSJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFFQTtBQUVBO0FBUUEsTUFBTUEsWUFBWSxHQUFHO0FBQ2pCQyxnQkFBYyxFQUFFQyxrREFBcUJBO0FBRHBCLENBQXJCO0FBSUEsTUFBTUMsc0JBQXNCLEdBQUcsQ0FBQyxTQUFELEVBQVksTUFBWixDQUEvQjs7QUFDQSxNQUFNQyxxQkFBcUIsR0FBSUMsSUFBRCxJQUFVRixzQkFBc0IsQ0FBQ0csUUFBdkIsQ0FBZ0NELElBQWhDLENBQXhDO0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztJQUNNRSxHO0FBQ0Y7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDSSxlQUFZQyxJQUFaLEVBQWtCO0FBQ2QsVUFBTTtBQUFFUDtBQUFGLFFBQXFCLEVBQUUsR0FBR0QsWUFBTDtBQUFtQixVQUFJUSxJQUFJLElBQUksRUFBWjtBQUFu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QUFLakIsT0FBTCxDQUFhQyxLQUFiLENBQVA7QUFDSDtBQUVEO0FBQ0o7QUFDQTs7O1NBQ0lrQixlLEdBQUEsMkJBQWtCO0FBQ2RGLGtFQUFjLENBQ1YsOERBQ0ksd0NBRk0sQ0FBZDtBQUlBLFdBQU8sS0FBS2xCLGFBQUwsRUFBUDtBQUNIO0FBRUQ7QUFDSjtBQUNBOzs7U0FDSXFCLE0sR0FBQSxrQkFBUztBQUNMLFVBQU0sSUFBSXhFLEtBQUosQ0FDRix1RUFERSxDQUFOO0FBR0gsRzs7Ozs7QUFHRSxTQUFTeUUsZ0JBQVQsR0FBNEI7QUFDL0IsUUFBTSxJQUFJekUsS0FBSixDQUNGLHlFQUNJLGlCQUZGLENBQU47QUFJSDtBQUVEO0FBRWVaLGtFQUFmIiwiZmlsZSI6Ii4vc3JjL09STS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbIi8qIGVzbGludC1kaXNhYmxlIG1heC1jbGFzc2VzLXBlci1maWxlICovXG5pbXBvcnQgU2Vzc2lvbiBmcm9tIFwiLi9TZXNzaW9uXCI7XG5pbXBvcnQgTW9kZWwgZnJvbSBcIi4vTW9kZWxcIjtcbmltcG9ydCB7IGNyZWF0ZURhdGFiYXNlIGFzIGRlZmF1bHRDcmVhdGVEYXRhYmFzZSB9IGZyb20gXCIuL2RiXCI7XG5pbXBvcnQgeyBhdHRyIH0gZnJvbSBcIi4vZmllbGRzXCI7XG5pbXBvcnQgRmllbGQgZnJvbSBcIi4vZmllbGRzL0ZpZWxkXCI7XG5pbXBvcnQgRm9yZWlnbktleSBmcm9tIFwiLi9maWVsZHMvRm9yZWlnbktleVwiO1xuaW1wb3J0IE1hbnlUb01hbnkgZnJvbSBcIi4vZmllbGRzL01hbnlUb01hbnlcIjtcblxuaW1wb3J0IHsgY3JlYXRlTW9kZWxTZWxlY3RvclNwZWMgfSBmcm9tIFwiLi9zZWxlY3RvcnNcIjtcblxuaW1wb3J0IHtcbiAgICBtMm1OYW1lLFxuICAgIGF0dGFjaFF1ZXJ5U2V0TWV0aG9kcyxcbiAgICBtMm1Ub0ZpZWxkTmFtZSxcbiAgICBtMm1Gcm9tRmllbGROYW1lLFxuICAgIHdhcm5EZXByZWNhdGVkLFxufSBmcm9tIFwiLi91dGlsc1wiO1xuXG5jb25zdCBPUk1fREVGQVVMVFMgPSB7XG4gICAgY3JlYXRlRGF0YWJhc2U6IGRlZmF1bHRDcmVhdGVEYXRhYmFzZSxcbn07XG5cbmNvbnN0IFJFU0VSVkVEX1RBQkxFX09QVElPTlMgPSBbXCJpbmRleGVzXCIsIFwibWV0YVwiXTtcbmNvbnN0IGlzUmVzZXJ2ZWRUYWJsZU9wdGlvbiA9ICh3b3JkKSA9PiBSRVNFUlZFRF9UQUJMRV9PUFRJT05TLmluY2x1ZGVzKHdvcmQpO1xuXG4vKipcbiAqIE9STSAtIHRoZSBPYmplY3QgUmVsYXRpb25hbCBNYXBwZXIuXG4gKlxuICogVXNlIGluc3RhbmNlcyBvZiB0aGlzIGNsYXNzIHRvOlxuICpcbiAqIC0gUmVnaXN0ZXIgeW91ciB7QGxpbmsgTW9kZWx9IGNsYXNzZXMgdXNpbmcge0BsaW5rIE9STSNyZWdpc3Rlcn1cbiAqIC0gR2V0IHRoZSBlbXB0eSBzdGF0ZSBmb3IgdGhlIHVuZGVybHlpbmcgZGF0YWJhc2Ugd2l0aCB7QGxpbmsgT1JNI2dldEVtcHR5U3RhdGV9XG4gKiAtIFN0YXJ0IGFuIGltbXV0YWJsZSBkYXRhYmFzZSBzZXNzaW9uIHdpdGgge0BsaW5rIE9STSNzZXNzaW9ufVxuICogLSBTdGFydCBhIG11dGF0aW5nIGRhdGFiYXNlIHNlc3Npb24gd2l0aCB7QGxpbmsgT1JNI211dGFibGVTZXNzaW9ufVxuICpcbiAqIEludGVybmFsbHksIHRoaXMgY2xhc3MgaGFuZGxlcyBnZW5lcmF0aW5nIGEgc2NoZW1hIHNwZWNpZmljYXRpb24gZnJvbSBtb2RlbHNcbiAqIHRvIHRoZSBkYXRhYmFzZS5cbiAqL1xuY2xhc3MgT1JNIHtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IE9STSBpbnN0YW5jZS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB7T2JqZWN0fSBbb3B0c11cbiAgICAgKiBAcGFyYW0ge0Z1bmN0aW9ufSBbb3B0cy5zdGF0ZVNlbGVjdG9yXSAtIGZ1bmN0aW9uIHRoYXQgZ2l2ZW4gYSBSZWR1eCBzdGF0ZSB0cmVlXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIHJldHVybiB0aGUgT1JNIHN0YXRlJ3Mgc3VidHJlZSxcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGUuZy4gYHN0YXRlID0+IHN0YXRlLm9ybWBcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChuZWNlc3NhcnkgaWYgeW91IHdhbnQgdG8gdXNlIHNlbGVjdG9ycylcbiAgICAgKiBAcGFyYW0ge0Z1bmN0aW9ufSBbb3B0cy5jcmVhdGVEYXRhYmFzZV0gLSBmdW5jdGlvbiB0aGF0IGNyZWF0ZXMgYSBkYXRhYmFzZVxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKG9wdHMpIHtcbiAgICAgICAgY29uc3QgeyBjcmVhdGVEYXRhYmFzZSB9ID0geyAuLi5PUk1fREVGQVVMVFMsIC4uLihvcHRzIHx8IHt9KSB9O1xuICAgICAgICB0aGlzLmNyZWF0ZURhdGFiYXNlID0gY3JlYXRlRGF0YWJhc2U7XG4gICAgICAgIHRoaXMucmVnaXN0cnkgPSBbXTtcbiAgICAgICAgdGhpcy5pbXBsaWNpdFRocm91Z2hNb2RlbHMgPSBbXTtcbiAgICAgICAgdGhpcy5pbnN0YWxsZWRGaWVsZHMgPSB7fTtcbiAgICAgICAgdGhpcy5zdGF0ZVNlbGVjdG9yID0gb3B0cyA/IG9wdHMuc3RhdGVTZWxlY3RvciA6IG51bGw7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVnaXN0ZXJzIGEge0BsaW5rIE1vZGVsfSBjbGFzcyB0byB0aGUgT1JNLlxuICAgICAqXG4gICAgICogSWYgdGhlIG1vZGVsIGhhcyBkZWNsYXJlZCBhbnkgTWFueVRvTWFueSBmaWVsZHMsIHRoZWlyXG4gICAgICogdGhyb3VnaCBtb2RlbHMgd2lsbCBiZSBnZW5lcmF0ZWQgYW5kIHJlZ2lzdGVyZWQgd2l0aFxuICAgICAqIHRoaXMgY2FsbCwgdW5sZXNzIGEgY3VzdG9tIHRocm91Z2ggbW9kZWwgaGFzIGJlZW4gc3BlY2lmaWVkLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7Li4uTW9kZWx9IG1vZGVscyAtIGEge0BsaW5rIE1vZGVsfSBjbGFzcyB0byByZWdpc3RlclxuICAgICAqIEByZXR1cm4ge3VuZGVmaW5lZH1cbiAgICAgKi9cbiAgICByZWdpc3RlciguLi5tb2RlbHMpIHtcbiAgICAgICAgbW9kZWxzLmZvckVhY2goKG1vZGVsKSA9PiB7XG4gICAgICAgICAgICBpZiAobW9kZWwubW9kZWxOYW1lID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgICAgIFwiQSBtb2RlbCB3YXMgcGFzc2VkIHRoYXQgZG9lc24ndCBoYXZlIGEgbW9kZWxOYW1lIHNldFwiXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgbW9kZWwuaW52YWxpZGF0ZUNsYXNzQ2FjaGUoKTtcblxuICAgICAgICAgICAgdGhpcy5yZWdpc3Rlck1hbnlUb01hbnlNb2RlbHNGb3IobW9kZWwpO1xuICAgICAgICAgICAgdGhpcy5yZWdpc3RyeS5wdXNoKG1vZGVsKTtcblxuICAgICAgICAgICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsIG1vZGVsLm1vZGVsTmFtZSwge1xuICAgICAgICAgICAgICAgIGdldDogKCkgPT4ge1xuICAgICAgICAgICAgICAgICAgICAvLyBtYWtlIHN1cmUgdmlydHVhbEZpZWxkcyBhcmUgc2V0IHVwXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuX3NldHVwTW9kZWxQcm90b3R5cGVzKHRoaXMucmVnaXN0cnkpO1xuXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBjcmVhdGVNb2RlbFNlbGVjdG9yU3BlYyh7XG4gICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgICAgIG9ybTogdGhpcyxcbiAgICAgICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICByZWdpc3Rlck1hbnlUb01hbnlNb2RlbHNGb3IobW9kZWwpIHtcbiAgICAgICAgY29uc3QgeyBmaWVsZHMgfSA9IG1vZGVsO1xuICAgICAgICBjb25zdCB0aGlzTW9kZWxOYW1lID0gbW9kZWwubW9kZWxOYW1lO1xuXG4gICAgICAgIE9iamVjdC5lbnRyaWVzKGZpZWxkcykuZm9yRWFjaCgoW2ZpZWxkTmFtZSwgZmllbGRJbnN0YW5jZV0pID0+IHtcbiAgICAgICAgICAgIGlmICghKGZpZWxkSW5zdGFuY2UgaW5zdGFuY2VvZiBNYW55VG9NYW55KSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgbGV0IHRvTW9kZWxOYW1lO1xuICAgICAgICAgICAgaWYgKGZpZWxkSW5zdGFuY2UudG9Nb2RlbE5hbWUgPT09IFwidGhpc1wiKSB7XG4gICAgICAgICAgICAgICAgdG9Nb2RlbE5hbWUgPSB0aGlzTW9kZWxOYW1lO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICB0b01vZGVsTmFtZSA9IGZpZWxkSW5zdGFuY2UudG9Nb2RlbE5hbWU7IC8vIGVzbGludC1kaXNhYmxlLWxpbmUgcHJlZmVyLWRlc3RydWN0dXJpbmdcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY29uc3Qgc2VsZlJlZmVyZW5jaW5nID0gdGhpc01vZGVsTmFtZSA9PT0gdG9Nb2RlbE5hbWU7XG4gICAgICAgICAgICBjb25zdCBmcm9tRmllbGROYW1lID0gbTJtRnJvbUZpZWxkTmFtZSh0aGlzTW9kZWxOYW1lKTtcbiAgICAgICAgICAgIGNvbnN0IHRvRmllbGROYW1lID0gbTJtVG9GaWVsZE5hbWUodG9Nb2RlbE5hbWUpO1xuXG4gICAgICAgICAgICBpZiAoZmllbGRJbnN0YW5jZS50aHJvdWdoKSB7XG4gICAgICAgICAgICAgICAgaWYgKHNlbGZSZWZlcmVuY2luZyAmJiAhZmllbGRJbnN0YW5jZS50aHJvdWdoRmllbGRzKSB7XG4gICAgICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICAgICAgICAgIFwiU2VsZi1yZWZlcmVuY2luZyBtYW55LXRvLW1hbnkgcmVsYXRpb25zaGlwIGF0IFwiICtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBgXCIke3RoaXNNb2RlbE5hbWV9LiR7ZmllbGROYW1lfVwiIHVzaW5nIGN1c3RvbSBgICtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBgbW9kZWwgXCIke2ZpZWxkSW5zdGFuY2UudGhyb3VnaH1cIiBoYXMgbm8gYCArXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJ0aHJvdWdoRmllbGRzIGtleS4gQ2Fubm90IGRldGVybWluZSB3aGljaCBcIiArXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJmaWVsZHMgcmVmZXJlbmNlIHRoZSBpbnN0YW5jZXMgcGFydGFraW5nIFwiICtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBcImluIHRoZSByZWxhdGlvbnNoaXAuXCJcbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGNvbnN0IFRocm91Z2ggPSBjbGFzcyBUaHJvdWdoTW9kZWwgZXh0ZW5kcyBNb2RlbCB7fTtcblxuICAgICAgICAgICAgICAgIFRocm91Z2gubW9kZWxOYW1lID0gbTJtTmFtZSh0aGlzTW9kZWxOYW1lLCBmaWVsZE5hbWUpO1xuXG4gICAgICAgICAgICAgICAgY29uc3QgUGxhaW5Gb3JlaWduS2V5ID0gY2xhc3MgUGxhaW5Gb3JlaWduS2V5IGV4dGVuZHMgRm9yZWlnbktleSB7XG4gICAgICAgICAgICAgICAgICAgIGdldCBpbnN0YWxsc0JhY2t3YXJkc1ZpcnR1YWxGaWVsZCgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgIGdldCBpbnN0YWxsc0JhY2t3YXJkc0Rlc2NyaXB0b3IoKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICAgIGNvbnN0IEZvcmVpZ25LZXlDbGFzcyA9IHNlbGZSZWZlcmVuY2luZ1xuICAgICAgICAgICAgICAgICAgICA/IFBsYWluRm9yZWlnbktleVxuICAgICAgICAgICAgICAgICAgICA6IEZvcmVpZ25LZXk7XG4gICAgICAgICAgICAgICAgVGhyb3VnaC5maWVsZHMgPSB7XG4gICAgICAgICAgICAgICAgICAgIGlkOiBhdHRyKCksXG4gICAgICAgICAgICAgICAgICAgIFtmcm9tRmllbGROYW1lXTogbmV3IEZvcmVpZ25LZXlDbGFzcyh0aGlzTW9kZWxOYW1lKSxcbiAgICAgICAgICAgICAgICAgICAgW3RvRmllbGROYW1lXTogbmV3IEZvcmVpZ25LZXlDbGFzcyh0b01vZGVsTmFtZSksXG4gICAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICAgIFRocm91Z2guaW52YWxpZGF0ZUNsYXNzQ2FjaGUoKTtcbiAgICAgICAgICAgICAgICB0aGlzLmltcGxpY2l0VGhyb3VnaE1vZGVscy5wdXNoKFRocm91Z2gpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXRzIGEge0BsaW5rIE1vZGVsfSBjbGFzcyBieSBpdHMgbmFtZSBmcm9tIHRoZSByZWdpc3RyeS5cbiAgICAgKiBAcGFyYW0gIHtzdHJpbmd9IG1vZGVsTmFtZSAtIHRoZSBuYW1lIG9mIHRoZSB7QGxpbmsgTW9kZWx9IGNsYXNzIHRvIGdldFxuICAgICAqIEB0aHJvd3MgSWYge0BsaW5rIE1vZGVsfSBjbGFzcyBpcyBub3QgZm91bmQuXG4gICAgICogQHJldHVybiB7TW9kZWx9IHRoZSB7QGxpbmsgTW9kZWx9IGNsYXNzLCBpZiBmb3VuZFxuICAgICAqL1xuICAgIGdldChtb2RlbE5hbWUpIHtcbiAgICAgICAgY29uc3QgYWxsTW9kZWxzID0gdGhpcy5yZWdpc3RyeS5jb25jYXQodGhpcy5pbXBsaWNpdFRocm91Z2hNb2RlbHMpO1xuICAgICAgICBjb25zdCBmb3VuZCA9IE9iamVjdC52YWx1ZXMoYWxsTW9kZWxzKS5maW5kKFxuICAgICAgICAgICAgKG1vZGVsKSA9PiBtb2RlbC5tb2RlbE5hbWUgPT09IG1vZGVsTmFtZVxuICAgICAgICApO1xuXG4gICAgICAgIGlmICh0eXBlb2YgZm91bmQgPT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgRGlkIG5vdCBmaW5kIG1vZGVsICR7bW9kZWxOYW1lfSBmcm9tIHJlZ2lzdHJ5LmApO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBmb3VuZDtcbiAgICB9XG5cbiAgICBnZXRNb2RlbENsYXNzZXMoKSB7XG4gICAgICAgIHRoaXMuX3NldHVwTW9kZWxQcm90b3R5cGVzKHRoaXMucmVnaXN0cnkpO1xuICAgICAgICB0aGlzLl9zZXR1cE1vZGVsUHJvdG90eXBlcyh0aGlzLmltcGxpY2l0VGhyb3VnaE1vZGVscyk7XG4gICAgICAgIHJldHVybiB0aGlzLnJlZ2lzdHJ5LmNvbmNhdCh0aGlzLmltcGxpY2l0VGhyb3VnaE1vZGVscyk7XG4gICAgfVxuXG4gICAgZ2VuZXJhdGVTY2hlbWFTcGVjKCkge1xuICAgICAgICBjb25zdCBtb2RlbHMgPSB0aGlzLmdldE1vZGVsQ2xhc3NlcygpO1xuICAgICAgICBjb25zdCB0YWJsZXMgPSBtb2RlbHMucmVkdWNlKChzcGVjLCBtb2RlbENsYXNzKSA9PiB7XG4gICAgICAgICAgICBjb25zdCB0YWJsZU5hbWUgPSBtb2RlbENsYXNzLm1vZGVsTmFtZTtcbiAgICAgICAgICAgIGNvbnN0IHRhYmxlU3BlYyA9IG1vZGVsQ2xhc3MudGFibGVPcHRpb25zKCk7XG4gICAgICAgICAgICBPYmplY3Qua2V5cyh0YWJsZVNwZWMpXG4gICAgICAgICAgICAgICAgLmZpbHRlcihpc1Jlc2VydmVkVGFibGVPcHRpb24pXG4gICAgICAgICAgICAgICAgLmZvckVhY2goKGtleSkgPT4ge1xuICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgICAgICAgICBgUmVzZXJ2ZWQga2V5d29yZCBcXGAke2tleX1cXGAgdXNlZCBpbiAke3RhYmxlTmFtZX0ub3B0aW9ucy5gXG4gICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBzcGVjW3RhYmxlTmFtZV0gPSB7XG4gICAgICAgICAgICAgICAgZmllbGRzOiB7IC4uLm1vZGVsQ2xhc3MuZmllbGRzIH0sXG4gICAgICAgICAgICAgICAgLi4udGFibGVTcGVjLFxuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJldHVybiBzcGVjO1xuICAgICAgICB9LCB7fSk7XG4gICAgICAgIHJldHVybiB7IHRhYmxlcyB9O1xuICAgIH1cblxuICAgIGdldERhdGFiYXNlKCkge1xuICAgICAgICBpZiAoIXRoaXMuZGIpIHtcbiAgICAgICAgICAgIHRoaXMuZGIgPSB0aGlzLmNyZWF0ZURhdGFiYXNlKHRoaXMuZ2VuZXJhdGVTY2hlbWFTcGVjKCkpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLmRiO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGVtcHR5IGRhdGFiYXNlIHN0YXRlLlxuICAgICAqIEByZXR1cm4ge09iamVjdH0gdGhlIGVtcHR5IHN0YXRlXG4gICAgICovXG4gICAgZ2V0RW1wdHlTdGF0ZSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0RGF0YWJhc2UoKS5nZXRFbXB0eVN0YXRlKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQmVnaW5zIGFuIGltbXV0YWJsZSBkYXRhYmFzZSBzZXNzaW9uLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBzdGF0ZSAgLSB0aGUgc3RhdGUgdGhlIGRhdGFiYXNlIG1hbmFnZXNcbiAgICAgKiBAcmV0dXJuIHtTZXNzaW9ufSBhIG5ldyB7QGxpbmsgU2Vzc2lvbn0gaW5zdGFuY2VcbiAgICAgKi9cbiAgICBzZXNzaW9uKHN0YXRlKSB7XG4gICAgICAgIHJldHVybiBuZXcgU2Vzc2lvbih0aGlzLCB0aGlzLmdldERhdGFiYXNlKCksIHN0YXRlKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBCZWdpbnMgYSBtdXRhYmxlIGRhdGFiYXNlIHNlc3Npb24uXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IHN0YXRlICAtIHRoZSBzdGF0ZSB0aGUgZGF0YWJhc2UgbWFuYWdlc1xuICAgICAqIEByZXR1cm4ge1Nlc3Npb259IGEgbmV3IHtAbGluayBTZXNzaW9ufSBpbnN0YW5jZVxuICAgICAqL1xuICAgIG11dGFibGVTZXNzaW9uKHN0YXRlKSB7XG4gICAgICAgIHJldHVybiBuZXcgU2Vzc2lvbih0aGlzLCB0aGlzLmdldERhdGFiYXNlKCksIHN0YXRlLCB0cnVlKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqL1xuICAgIF9zZXR1cE1vZGVsUHJvdG90eXBlcyhtb2RlbHMpIHtcbiAgICAgICAgbW9kZWxzXG4gICAgICAgICAgICAuZmlsdGVyKChtb2RlbCkgPT4gIW1vZGVsLmlzU2V0VXApXG4gICAgICAgICAgICAuZm9yRWFjaCgobW9kZWwpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCB7IGZpZWxkcywgbW9kZWxOYW1lLCBxdWVyeVNldENsYXNzIH0gPSBtb2RlbDtcbiAgICAgICAgICAgICAgICBPYmplY3QuZW50cmllcyhmaWVsZHMpLmZvckVhY2goKFtmaWVsZE5hbWUsIGZpZWxkXSkgPT4ge1xuICAgICAgICAgICAgICAgICAgICBpZiAoIShmaWVsZCBpbnN0YW5jZW9mIEZpZWxkKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGAke21vZGVsTmFtZX0uJHtmaWVsZE5hbWV9IGlzIG9mIHR5cGUgXCIke3R5cGVvZiBmaWVsZH1cIiBgICtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJidXQgbXVzdCBiZSBhbiBpbnN0YW5jZSBvZiBGaWVsZC4gUGxlYXNlIHVzZSB0aGUgXCIgK1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcImBhdHRyYCwgYGZrYCwgYG9uZVRvT25lYCBhbmQgYG1hbnlgIFwiICtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJmdW5jdGlvbnMgdG8gZGVmaW5lIGZpZWxkcy5cIlxuICAgICAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBpZiAoIXRoaXMuX2lzRmllbGRJbnN0YWxsZWQobW9kZWxOYW1lLCBmaWVsZE5hbWUpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLl9pbnN0YWxsRmllbGQoZmllbGQsIGZpZWxkTmFtZSwgbW9kZWwpO1xuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5fc2V0RmllbGRJbnN0YWxsZWQobW9kZWxOYW1lLCBmaWVsZE5hbWUpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgYXR0YWNoUXVlcnlTZXRNZXRob2RzKG1vZGVsLCBxdWVyeVNldENsYXNzKTtcbiAgICAgICAgICAgICAgICBtb2RlbC5pc1NldFVwID0gdHJ1ZTtcbiAgICAgICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEBwcml2YXRlXG4gICAgICovXG4gICAgX2lzRmllbGRJbnN0YWxsZWQobW9kZWxOYW1lLCBmaWVsZE5hbWUpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuaW5zdGFsbGVkRmllbGRzLmhhc093blByb3BlcnR5KG1vZGVsTmFtZSlcbiAgICAgICAgICAgID8gISF0aGlzLmluc3RhbGxlZEZpZWxkc1ttb2RlbE5hbWVdW2ZpZWxkTmFtZV1cbiAgICAgICAgICAgIDogZmFsc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQHByaXZhdGVcbiAgICAgKi9cbiAgICBfc2V0RmllbGRJbnN0YWxsZWQobW9kZWxOYW1lLCBmaWVsZE5hbWUpIHtcbiAgICAgICAgaWYgKCF0aGlzLmluc3RhbGxlZEZpZWxkcy5oYXNPd25Qcm9wZXJ0eShtb2RlbE5hbWUpKSB7XG4gICAgICAgICAgICB0aGlzLmluc3RhbGxlZEZpZWxkc1ttb2RlbE5hbWVdID0ge307XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5pbnN0YWxsZWRGaWVsZHNbbW9kZWxOYW1lXVtmaWVsZE5hbWVdID0gdHJ1ZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBJbnN0YWxscyBhIGZpZWxkIG9uIGEgbW9kZWwgYW5kIGl0cyByZWxhdGVkIG1vZGVscyBpZiBuZWNlc3NhcnkuXG4gICAgICogQHByaXZhdGVcbiAgICAgKi9cbiAgICBfaW5zdGFsbEZpZWxkKGZpZWxkLCBmaWVsZE5hbWUsIG1vZGVsKSB7XG4gICAgICAgIGNvbnN0IEZpZWxkSW5zdGFsbGVyID0gZmllbGQuaW5zdGFsbGVyQ2xhc3M7XG4gICAgICAgIG5ldyBGaWVsZEluc3RhbGxlcih7XG4gICAgICAgICAgICBmaWVsZCxcbiAgICAgICAgICAgIGZpZWxkTmFtZSxcbiAgICAgICAgICAgIG1vZGVsLFxuICAgICAgICAgICAgb3JtOiB0aGlzLFxuICAgICAgICB9KS5ydW4oKTtcbiAgICB9XG5cbiAgICAvLyBERVBSRUNBVEVEIEFORCBSRU1PVkVEIE1FVEhPRFNcblxuICAgIC8qKlxuICAgICAqIEBkZXByZWNhdGVkIFVzZSB7QGxpbmsgT1JNI211dGFibGVTZXNzaW9ufSBpbnN0ZWFkLlxuICAgICAqL1xuICAgIHdpdGhNdXRhdGlvbnMoc3RhdGUpIHtcbiAgICAgICAgd2FybkRlcHJlY2F0ZWQoXG4gICAgICAgICAgICBcImBPUk0ucHJvdG90eXBlLndpdGhNdXRhdGlvbnNgIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFwiICtcbiAgICAgICAgICAgICAgICBcIlVzZSBgT1JNLnByb3RvdHlwZS5tdXRhYmxlU2Vzc2lvbmAgaW5zdGVhZC5cIlxuICAgICAgICApO1xuICAgICAgICByZXR1cm4gdGhpcy5tdXRhYmxlU2Vzc2lvbihzdGF0ZSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQGRlcHJlY2F0ZWQgVXNlIHtAbGluayBPUk0jc2Vzc2lvbn0gaW5zdGVhZC5cbiAgICAgKi9cbiAgICBmcm9tKHN0YXRlKSB7XG4gICAgICAgIHdhcm5EZXByZWNhdGVkKFxuICAgICAgICAgICAgXCJgT1JNLnByb3RvdHlwZS5mcm9tYCBoYXMgYmVlbiBkZXByZWNhdGVkLiBcIiArXG4gICAgICAgICAgICAgICAgXCJVc2UgYE9STS5wcm90b3R5cGUuc2Vzc2lvbmAgaW5zdGVhZC5cIlxuICAgICAgICApO1xuICAgICAgICByZXR1cm4gdGhpcy5zZXNzaW9uKHN0YXRlKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAZGVwcmVjYXRlZCBVc2Uge0BsaW5rIE9STSNnZXRFbXB0eVN0YXRlfSBpbnN0ZWFkLlxuICAgICAqL1xuICAgIGdldERlZmF1bHRTdGF0ZSgpIHtcbiAgICAgICAgd2FybkRlcHJlY2F0ZWQoXG4gICAgICAgICAgICBcImBPUk0ucHJvdG90eXBlLmdldERlZmF1bHRTdGF0ZWAgaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIFwiICtcbiAgICAgICAgICAgICAgICBcImBPUk0ucHJvdG90eXBlLmdldEVtcHR5U3RhdGVgIGluc3RlYWQuXCJcbiAgICAgICAgKTtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0RW1wdHlTdGF0ZSgpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEBkZXByZWNhdGVkIERlZmluZSBhIE1vZGVsIGNsYXNzIGluc3RlYWQuXG4gICAgICovXG4gICAgZGVmaW5lKCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBcImBPUk0ucHJvdG90eXBlLmRlZmluZWAgaGFzIGJlZW4gcmVtb3ZlZC4gUGxlYXNlIGRlZmluZSBhIE1vZGVsIGNsYXNzLlwiXG4gICAgICAgICk7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gRGVwcmVjYXRlZFNjaGVtYSgpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIFwiU2NoZW1hIGhhcyBiZWVuIHJlbmFtZWQgdG8gT1JNLiBQbGVhc2UgaW1wb3J0IE9STSBpbnN0ZWFkIG9mIFNjaGVtYSBcIiArXG4gICAgICAgICAgICBcImZyb20gUmVkdXgtT1JNLlwiXG4gICAgKTtcbn1cblxuZXhwb3J0IHsgT1JNIH07XG5cbmV4cG9ydCBkZWZhdWx0IE9STTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/ORM.js\\n\");\n \n /***/ }),\n \n@@ -4474,7 +4496,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ \\\"./src/utils.js\\\");\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./constants */ \\\"./src/constants.js\\\");\\n\\n\\n\\n/**\\n * This class is used to build and make queries to the database\\n * and operating the resulting set (such as updating attributes\\n * or deleting the records).\\n *\\n * The queries are built lazily. For example:\\n *\\n * ```javascript\\n * const qs = Book.all()\\n *     .filter(book => book.releaseYear > 1999)\\n *     .orderBy('name');\\n * ```\\n *\\n * Doesn't execute a query. The query is executed only when\\n * you need information from the query result, such as {@link QuerySet#count},\\n * {@link QuerySet#toRefArray}. After the query is executed, the resulting\\n * set is cached in the QuerySet instance.\\n *\\n * QuerySet instances also return copies, so chaining filters doesn't\\n * mutate the previous instances.\\n */\\n\\nconst QuerySet = /*#__PURE__*/function () {\\n  /**\\n   * Creates a QuerySet. The constructor is mainly for internal use;\\n   * You should access QuerySet instances from {@link Model}.\\n   *\\n   * @param  {Model} modelClass - the model class of objects in this QuerySet.\\n   * @param  {any[]} clauses - query clauses needed to evaluate the set.\\n   * @param {Object} [opts] - additional options\\n   */\\n  function QuerySet(modelClass, clauses, opts) {\\n    Object.assign(this, {\\n      modelClass,\\n      clauses: clauses || []\\n    });\\n    this._opts = opts;\\n  }\\n\\n  QuerySet.addSharedMethod = function addSharedMethod(methodName) {\\n    this.sharedMethods = this.sharedMethods.concat(methodName);\\n  };\\n\\n  var _proto = QuerySet.prototype;\\n\\n  _proto._new = function _new(clauses, userOpts) {\\n    const opts = { ...this._opts,\\n      ...userOpts\\n    };\\n    return new this.constructor(this.modelClass, clauses, opts);\\n  };\\n\\n  _proto.toString = function toString() {\\n    this._evaluate();\\n\\n    const contents = this.rows.map(({\\n      id\\n    }) => this.modelClass.withId(id).toString()).join(\\\"\\\\n    - \\\");\\n    return `QuerySet contents:\\\\n    - ${contents}`;\\n  }\\n  /**\\n   * Returns an array of the plain objects represented by the QuerySet.\\n   * The plain objects are direct references to the store.\\n   *\\n   * @return {Object[]} references to the plain JS objects represented by\\n   *                    the QuerySet\\n   */\\n  ;\\n\\n  _proto.toRefArray = function toRefArray() {\\n    return this._evaluate();\\n  }\\n  /**\\n   * Returns an array of {@link Model} instances represented by the QuerySet.\\n   * @return {Model[]} model instances represented by the QuerySet\\n   */\\n  ;\\n\\n  _proto.toModelArray = function toModelArray() {\\n    const {\\n      modelClass: ModelClass\\n    } = this;\\n    return this._evaluate().map(props => new ModelClass(props));\\n  }\\n  /**\\n   * Returns the number of {@link Model} instances represented by the QuerySet.\\n   *\\n   * @return {number} length of the QuerySet\\n   */\\n  ;\\n\\n  _proto.count = function count() {\\n    this._evaluate();\\n\\n    return this.rows.length;\\n  }\\n  /**\\n   * Checks if the {@link QuerySet} instance has any records matching the query\\n   * in the database.\\n   *\\n   * @return {Boolean} `true` if the {@link QuerySet} instance contains entities, else `false`.\\n   */\\n  ;\\n\\n  _proto.exists = function exists() {\\n    return Boolean(this.count());\\n  }\\n  /**\\n   * Returns the {@link Model} instance at index `index` in the {@link QuerySet} instance if\\n   * `withRefs` flag is set to `false`, or a reference to the plain JavaScript\\n   * object in the model state if `true`.\\n   *\\n   * @param  {number} index - index of the model instance to get\\n   * @return {Model|undefined} a {@link Model} instance at index\\n   *                           `index` in the {@link QuerySet} instance,\\n   *                           or undefined if the index is out of bounds.\\n   */\\n  ;\\n\\n  _proto.at = function at(index) {\\n    const {\\n      modelClass: ModelClass\\n    } = this;\\n\\n    const rows = this._evaluate();\\n\\n    if (index >= 0 && index < rows.length) {\\n      return new ModelClass(rows[index]);\\n    }\\n\\n    return undefined;\\n  }\\n  /**\\n   * Returns the {@link Model} instance at index 0 in the {@link QuerySet} instance.\\n   * @return {Model}\\n   */\\n  ;\\n\\n  _proto.first = function first() {\\n    return this.at(0);\\n  }\\n  /**\\n   * Returns the {@link Model} instance at index `QuerySet.count() - 1`\\n   * @return {Model}\\n   */\\n  ;\\n\\n  _proto.last = function last() {\\n    const rows = this._evaluate();\\n\\n    return this.at(rows.length - 1);\\n  }\\n  /**\\n   * Returns a new {@link QuerySet} instance with the same entities.\\n   * @return {QuerySet} a new QuerySet with the same entities.\\n   */\\n  ;\\n\\n  _proto.all = function all() {\\n    return this._new(this.clauses);\\n  }\\n  /**\\n   * Returns a new {@link QuerySet} instance with entities that match properties in `lookupObj`.\\n   *\\n   * @param  {Object} lookupObj - the properties to match objects with. Can also be a function.\\n   *                              It works the same as [Lodash filter](https://lodash.com/docs/#filter).\\n   * @return {QuerySet} a new {@link QuerySet} instance with objects that passed the filter.\\n   */\\n  ;\\n\\n  _proto.filter = function filter(lookupObj) {\\n    /**\\n     * allow foreign keys to be specified as model instances,\\n     * transform model instances to their primary keys\\n     */\\n    const normalizedLookupObj = typeof lookupObj === \\\"object\\\" ? Object(_utils__WEBPACK_IMPORTED_MODULE_1__[\\\"mapValues\\\"])(lookupObj, _utils__WEBPACK_IMPORTED_MODULE_1__[\\\"normalizeEntity\\\"]) : lookupObj;\\n    const filterDescriptor = {\\n      type: _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"FILTER\\\"],\\n      payload: normalizedLookupObj\\n    };\\n    /**\\n     * create a new QuerySet\\n     * including only rows matching the lookupObj\\n     */\\n\\n    return this._new(this.clauses.concat(filterDescriptor));\\n  }\\n  /**\\n   * Returns a new {@link QuerySet} instance with entities that do not match\\n   * properties in `lookupObj`.\\n   *\\n   * @param  {Object} lookupObj - the properties to unmatch objects with. Can also be a function.\\n   *                              It works the same as [Lodash reject](https://lodash.com/docs/#reject).\\n   * @return {QuerySet} a new {@link QuerySet} instance with objects that did not pass the filter.\\n   */\\n  ;\\n\\n  _proto.exclude = function exclude(lookupObj) {\\n    /**\\n     * allow foreign keys to be specified as model instances,\\n     * transform model instances to their primary keys\\n     */\\n    const normalizedLookupObj = typeof lookupObj === \\\"object\\\" ? Object(_utils__WEBPACK_IMPORTED_MODULE_1__[\\\"mapValues\\\"])(lookupObj, _utils__WEBPACK_IMPORTED_MODULE_1__[\\\"normalizeEntity\\\"]) : lookupObj;\\n    const excludeDescriptor = {\\n      type: _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"EXCLUDE\\\"],\\n      payload: normalizedLookupObj\\n    };\\n    /**\\n     * create a new QuerySet\\n     * excluding all rows matching the lookupObj\\n     */\\n\\n    return this._new(this.clauses.concat(excludeDescriptor));\\n  }\\n  /**\\n   * Performs the actual database query.\\n   * @private\\n   * @return {Array} rows corresponding to the QuerySet's clauses\\n   */\\n  ;\\n\\n  _proto._evaluate = function _evaluate() {\\n    if (typeof this.modelClass.session === \\\"undefined\\\") {\\n      throw new Error([`Tried to query the ${this.modelClass.modelName} model's table without a session. `, \\\"Create a session using `session = orm.session()` and use \\\", `\\\\`session[\\\"${this.modelClass.modelName}\\\"]\\\\` for querying instead.`].join(\\\"\\\"));\\n    }\\n\\n    if (!this._evaluated) {\\n      const {\\n        session,\\n        modelName: table\\n      } = this.modelClass;\\n      const querySpec = {\\n        table,\\n        clauses: this.clauses\\n      };\\n      this.rows = session.query(querySpec).rows;\\n      this._evaluated = true;\\n    }\\n\\n    return this.rows;\\n  }\\n  /**\\n   * Returns a new {@link QuerySet} instance with entities ordered by `iteratees` in ascending\\n   * order, unless otherwise specified. Delegates to [Lodash orderBy](https://lodash.com/docs/#orderBy).\\n   *\\n   * @param  {string[]|Function[]} iteratees - an array where each item can be a string or a\\n   *                                           function. If a string is supplied, it should\\n   *                                           correspond to property on the entity that will\\n   *                                           determine the order. If a function is supplied,\\n   *                                           it should return the value to order by.\\n   * @param {Array<Boolean|'asc'|'desc'>} [orders] - the sort orders of `iteratees`. If unspecified, all iteratees\\n   *                               will be sorted in ascending order. `true` and `'asc'`\\n   *                               correspond to ascending order, and `false` and `'desc'`\\n   *                               to descending order.\\n   * @return {QuerySet} a new {@link QuerySet} with objects ordered by `iteratees`.\\n   */\\n  ;\\n\\n  _proto.orderBy = function orderBy(iteratees, orders) {\\n    const orderByDescriptor = {\\n      type: _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"ORDER_BY\\\"],\\n      payload: [iteratees, orders]\\n    };\\n    /**\\n     * create a new QuerySet\\n     * sorting all rows according to the passed arguments\\n     */\\n\\n    return this._new(this.clauses.concat(orderByDescriptor));\\n  }\\n  /**\\n   * Records an update specified with `mergeObj` to all the objects\\n   * in the {@link QuerySet} instance.\\n   *\\n   * @param  {Object} mergeObj - an object to merge with all the objects in this\\n   *                             queryset.\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.update = function update(mergeObj) {\\n    const {\\n      session,\\n      modelName: table\\n    } = this.modelClass;\\n    session.applyUpdate({\\n      action: _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"UPDATE\\\"],\\n      query: {\\n        table,\\n        clauses: this.clauses\\n      },\\n      payload: mergeObj\\n    });\\n    this._evaluated = false;\\n  }\\n  /**\\n   * Records a deletion of all the objects in this {@link QuerySet} instance.\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.delete = function _delete() {\\n    const {\\n      session,\\n      modelName: table\\n    } = this.modelClass;\\n    this.toModelArray().forEach(model => model._onDelete() // eslint-disable-line no-underscore-dangle\\n    );\\n    session.applyUpdate({\\n      action: _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"DELETE\\\"],\\n      query: {\\n        table,\\n        clauses: this.clauses\\n      }\\n    });\\n    this._evaluated = false;\\n  } // DEPRECATED AND REMOVED METHODS\\n\\n  /**\\n   * @deprecated\\n   * Use {@link QuerySet#toModelArray} or predicate functions that\\n   * instantiate Models from refs, e.g. `new Model(ref)`.\\n   */\\n  ;\\n\\n  /**\\n   * @deprecated\\n   * Call {@link QuerySet#toModelArray} or {@link QuerySet#toRefArray} first to map.\\n   */\\n  _proto.map = function map() {\\n    throw new Error(\\\"`QuerySet.prototype.map` has been removed. \\\" + \\\"Call `.toModelArray()` or `.toRefArray()` first to map.\\\");\\n  }\\n  /**\\n   * @deprecated\\n   * Call {@link QuerySet#toModelArray} or {@link QuerySet#toRefArray} first to iterate.\\n   */\\n  ;\\n\\n  _proto.forEach = function forEach() {\\n    throw new Error(\\\"`QuerySet.prototype.forEach` has been removed. \\\" + \\\"Call `.toModelArray()` or `.toRefArray()` first to iterate.\\\");\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(QuerySet, [{\\n    key: \\\"withModels\\\",\\n    get: function () {\\n      throw new Error(\\\"`QuerySet.prototype.withModels` has been removed. \\\" + \\\"Use `.toModelArray()` or predicate functions that \\\" + \\\"instantiate Models from refs, e.g. `new Model(ref)`.\\\");\\n    }\\n    /**\\n     * @deprecated Query building operates on refs only now.\\n     */\\n\\n  }, {\\n    key: \\\"withRefs\\\",\\n    get: function () {\\n      Object(_utils__WEBPACK_IMPORTED_MODULE_1__[\\\"warnDeprecated\\\"])(\\\"`QuerySet.prototype.withRefs` has been deprecated. \\\" + \\\"Query building operates on refs only now.\\\");\\n      return undefined;\\n    }\\n  }]);\\n\\n  return QuerySet;\\n}();\\n\\nQuerySet.sharedMethods = [\\\"count\\\", \\\"at\\\", \\\"all\\\", \\\"last\\\", \\\"first\\\", \\\"filter\\\", \\\"exclude\\\", \\\"orderBy\\\", \\\"update\\\", \\\"delete\\\"];\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (QuerySet);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9RdWVyeVNldC5qcz9kODM0Il0sIm5hbWVzIjpbIlF1ZXJ5U2V0IiwibW9kZWxDbGFzcyIsImNsYXVzZXMiLCJvcHRzIiwiT2JqZWN0IiwiYXNzaWduIiwiX29wdHMiLCJhZGRTaGFyZWRNZXRob2QiLCJtZXRob2ROYW1lIiwic2hhcmVkTWV0aG9kcyIsImNvbmNhdCIsIl9uZXciLCJ1c2VyT3B0cyIsImNvbnN0cnVjdG9yIiwidG9TdHJpbmciLCJfZXZhbHVhdGUiLCJjb250ZW50cyIsInJvd3MiLCJtYXAiLCJpZCIsIndpdGhJZCIsImpvaW4iLCJ0b1JlZkFycmF5IiwidG9Nb2RlbEFycmF5IiwiTW9kZWxDbGFzcyIsInByb3BzIiwiY291bnQiLCJsZW5ndGgiLCJleGlzdHMiLCJCb29sZWFuIiwiYXQiLCJpbmRleCIsInVuZGVmaW5lZCIsImZpcnN0IiwibGFzdCIsImFsbCIsImZpbHRlciIsImxvb2t1cE9iaiIsIm5vcm1hbGl6ZWRMb29rdXBPYmoiLCJtYXBWYWx1ZXMiLCJub3JtYWxpemVFbnRpdHkiLCJmaWx0ZXJEZXNjcmlwdG9yIiwidHlwZSIsIkZJTFRFUiIsInBheWxvYWQiLCJleGNsdWRlIiwiZXhjbHVkZURlc2NyaXB0b3IiLCJFWENMVURFIiwic2Vzc2lvbiIsIkVycm9yIiwibW9kZWxOYW1lIiwiX2V2YWx1YXRlZCIsInRhYmxlIiwicXVlcnlTcGVjIiwicXVlcnkiLCJvcmRlckJ5IiwiaXRlcmF0ZWVzIiwib3JkZXJzIiwib3JkZXJCeURlc2NyaXB0b3IiLCJPUkRFUl9CWSIsInVwZGF0ZSIsIm1lcmdlT2JqIiwiYXBwbHlVcGRhdGUiLCJhY3Rpb24iLCJVUERBVEUiLCJkZWxldGUiLCJmb3JFYWNoIiwibW9kZWwiLCJfb25EZWxldGUiLCJERUxFVEUiLCJ3YXJuRGVwcmVjYXRlZCJdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUE7QUFFQTtBQUVBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBcUJB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RUFPckIsU0FQcUIsRUFRckIsU0FScUIsRUFTckIsUUFUcUIsRUFVckIsUUFWcUIsQ0FBekI7QUFhZVQsdUVBQWYiLCJmaWxlIjoiLi9zcmMvUXVlcnlTZXQuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBub3JtYWxpemVFbnRpdHksIHdhcm5EZXByZWNhdGVkLCBtYXBWYWx1ZXMgfSBmcm9tIFwiLi91dGlsc1wiO1xuXG5pbXBvcnQgeyBVUERBVEUsIERFTEVURSwgRklMVEVSLCBFWENMVURFLCBPUkRFUl9CWSB9IGZyb20gXCIuL2NvbnN0YW50c1wiO1xuXG4vKipcbiAqIFRoaXMgY2xhc3MgaXMgdXNlZCB0byBidWlsZCBhbmQgbWFrZSBxdWVyaWVzIHRvIHRoZSBkYXRhYmFzZVxuICogYW5kIG9wZXJhdGluZyB0aGUgcmVzdWx0aW5nIHNldCAoc3VjaCBhcyB1cGRhdGluZyBhdHRyaWJ1dGVzXG4gKiBvciBkZWxldGluZyB0aGUgcmVjb3JkcykuXG4gKlxuICogVGhlIHF1ZXJpZXMgYXJlIGJ1aWx0IGxhemlseS4gRm9yIGV4YW1wbGU6XG4gKlxuICogYGBgamF2YXNjcmlwdFxuICogY29uc3QgcXMgPSBCb29rLmFsbCgpXG4gKiAgICAgLmZpbHRlcihib29rID0+IGJvb2sucmVsZWFzZVllYXIgPiAxOTk5KVxuICogICAgIC5vcmRlckJ5KCduYW1lJyk7XG4gKiBgYGBcbiAqXG4gKiBEb2Vzbid0IGV4ZWN1dGUgYSBxdWVyeS4gVGhlIHF1ZXJ5IGlzIGV4ZWN1dGVkIG9ubHkgd2hlblxuICogeW91IG5lZWQgaW5mb3JtYXRpb24gZnJvbSB0aGUgcXVlcnkgcmVzdWx0LCBzdWNoIGFzIHtAbGluayBRdWVyeVNldCNjb3VudH0sXG4gKiB7QGxpbmsgUXVlcnlTZXQjdG9SZWZBcnJheX0uIEFmdGVyIHRoZSBxdWVyeSBpcyBleGVjdXRlZCwgdGhlIHJlc3VsdGluZ1xuICogc2V0IGlzIGNhY2hlZCBpbiB0aGUgUXVlcnlTZXQgaW5zdGFuY2UuXG4gKlxuICogUXVlcnlTZXQgaW5zdGFuY2VzIGFsc28gcmV0dXJuIGNvcGllcywgc28gY2hhaW5pbmcgZmlsdGVycyBkb2Vzbid0XG4gKiBtdXRhdGUgdGhlIHByZXZpb3VzIGluc3RhbmNlcy5cbiAqL1xuY29uc3QgUXVlcnlTZXQgPSBjbGFzcyBRdWVyeVNldCB7XG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIFF1ZXJ5U2V0LiBUaGUgY29uc3RydWN0b3IgaXMgbWFpbmx5IGZvciBpbnRlcm5hbCB1c2U7XG4gICAgICogWW91IHNob3VsZCBhY2Nlc3MgUXVlcnlTZXQgaW5zdGFuY2VzIGZyb20ge0BsaW5rIE1vZGVsfS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge01vZGVsfSBtb2RlbENsYXNzIC0gdGhlIG1vZGVsIGNsYXNzIG9mIG9iamVjdHMgaW4gdGhpcyBRdWVyeVNldC5cbiAgICAgKiBAcGFyYW0gIHthbnlbXX0gY2xhdXNlcyAtIHF1ZXJ5IGNsYXVzZXMgbmVlZGVkIHRvIGV2YWx1YXRlIHRoZSBzZXQuXG4gICAgICogQHBhcmFtIHtPYmplY3R9IFtvcHRzXSAtIGFkZGl0aW9uYWwgb3B0aW9uc1xuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKG1vZGVsQ2xhc3MsIGNsYXVzZXMsIG9wdHMpIHtcbiAgICAgICAgT2JqZWN0LmFzc2lnbih0aGlzLCB7XG4gICAgICAgICAgICBtb2RlbENsYXNzLFxuICAgICAgICAgICAgY2xhdXNlczogY2xhdXNlcyB8fCBbXSxcbiAgICAgICAgfSk7XG5cbiAgICAgICAgdGhpcy5fb3B0cyA9IG9wdHM7XG4gICAgfVxuXG4gICAgc3RhdGljIGFkZFNoYXJlZE1ldGhvZChtZXRob2ROYW1lKSB7XG4gICAgICAgIHRoaXMuc2hhcmVkTWV0aG9kcyA9IHRoaXMuc2hhcmVkTWV0aG9kcy5jb25jYXQobWV0aG9kTmFtZSk7XG4gICAgfVxuXG4gICAgX25ldyhjbGF1c2VzLCB1c2VyT3B0cykge1xuICAgICAgICBjb25zdCBvcHRzID0geyAuLi50aGlzLl9vcHRzLCAuLi51c2VyT3B0cyB9O1xuICAgICAgICByZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy5tb2RlbENsYXNzLCBjbGF1c2VzLCBvcHRzKTtcbiAgICB9XG5cbiAgICB0b1N0cmluZygpIHtcbiAgICAgICAgdGhpcy5fZXZhbHVhdGUoKTtcbiAgICAgICAgY29uc3QgY29udGVudHMgPSB0aGlzLnJvd3NcbiAgICAgICAgICAgIC5tYXAoKHsgaWQgfSkgPT4gdGhpcy5tb2RlbENsYXNzLndpdGhJZChpZCkudG9TdHJpbmcoKSlcbiAgICAgICAgICAgIC5qb2luKFwiXFxuICAgIC0gXCIpO1xuICAgICAgICByZXR1cm4gYFF1ZXJ5U2V0IGNvbnRlbnRzOlxcbiAgICAtICR7Y29udGVudHN9YDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIGFuIGFycmF5IG9mIHRoZSBwbGFpbiBvYmplY3RzIHJlcHJlc2VudGVkIGJ5IHRoZSBRdWVyeVNldC5cbiAgICAgKiBUaGUgcGxhaW4gb2JqZWN0cyBhcmUgZGlyZWN0IHJlZmVyZW5jZXMgdG8gdGhlIHN0b3JlLlxuICAgICAqXG4gICAgICogQHJldHVybiB7T2JqZWN0W119IHJlZmVyZW5jZXMgdG8gdGhlIHBsYWluIEpTIG9iamVjdHMgcmVwcmVzZW50ZWQgYnlcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgdGhlIFF1ZXJ5U2V0XG4gICAgICovXG4gICAgdG9SZWZBcnJheSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2V2YWx1YXRlKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhbiBhcnJheSBvZiB7QGxpbmsgTW9kZWx9IGluc3RhbmNlcyByZXByZXNlbnRlZCBieSB0aGUgUXVlcnlTZXQuXG4gICAgICogQHJldHVybiB7TW9kZWxbXX0gbW9kZWwgaW5zdGFuY2VzIHJlcHJlc2VudGVkIGJ5IHRoZSBRdWVyeVNldFxuICAgICAqL1xuICAgIHRvTW9kZWxBcnJheSgpIHtcbiAgICAgICAgY29uc3QgeyBtb2RlbENsYXNzOiBNb2RlbENsYXNzIH0gPSB0aGlzO1xuICAgICAgICByZXR1cm4gdGhpcy5fZXZhbHVhdGUoKS5tYXAocHJvcHMgPT4gbmV3IE1vZGVsQ2xhc3MocHJvcHMpKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBudW1iZXIgb2Yge0BsaW5rIE1vZGVsfSBpbnN0YW5jZXMgcmVwcmVzZW50ZWQgYnkgdGhlIFF1ZXJ5U2V0LlxuICAgICAqXG4gICAgICogQHJldHVybiB7bnVtYmVyfSBsZW5ndGggb2YgdGhlIFF1ZXJ5U2V0XG4gICAgICovXG4gICAgY291bnQoKSB7XG4gICAgICAgIHRoaXMuX2V2YWx1YXRlKCk7XG4gICAgICAgIHJldHVybiB0aGlzLnJvd3MubGVuZ3RoO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENoZWNrcyBpZiB0aGUge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSBoYXMgYW55IHJlY29yZHMgbWF0Y2hpbmcgdGhlIHF1ZXJ5XG4gICAgICogaW4gdGhlIGRhdGFiYXNlLlxuICAgICAqXG4gICAgICogQHJldHVybiB7Qm9vbGVhbn0gYHRydWVgIGlmIHRoZSB7QGxpbmsgUXVlcnlTZXR9IGluc3RhbmNlIGNvbnRhaW5zIGVudGl0aWVzLCBlbHNlIGBmYWxzZWAuXG4gICAgICovXG4gICAgZXhpc3RzKCkge1xuICAgICAgICByZXR1cm4gQm9vbGVhbih0aGlzLmNvdW50KCkpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIHtAbGluayBNb2RlbH0gaW5zdGFuY2UgYXQgaW5kZXggYGluZGV4YCBpbiB0aGUge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSBpZlxuICAgICAqIGB3aXRoUmVmc2AgZmxhZyBpcyBzZXQgdG8gYGZhbHNlYCwgb3IgYSByZWZlcmVuY2UgdG8gdGhlIHBsYWluIEphdmFTY3JpcHRcbiAgICAgKiBvYmplY3QgaW4gdGhlIG1vZGVsIHN0YXRlIGlmIGB0cnVlYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge251bWJlcn0gaW5kZXggLSBpbmRleCBvZiB0aGUgbW9kZWwgaW5zdGFuY2UgdG8gZ2V0XG4gICAgICogQHJldHVybiB7TW9kZWx8dW5kZWZpbmVkfSBhIHtAbGluayBNb2RlbH0gaW5zdGFuY2UgYXQgaW5kZXhcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgIGBpbmRleGAgaW4gdGhlIHtAbGluayBRdWVyeVNldH0gaW5zdGFuY2UsXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICBvciB1bmRlZmluZWQgaWYgdGhlIGluZGV4IGlzIG91dCBvZiBib3VuZHMuXG4gICAgICovXG4gICAgYXQoaW5kZXgpIHtcbiAgICAgICAgY29uc3QgeyBtb2RlbENsYXNzOiBNb2RlbENsYXNzIH0gPSB0aGlzO1xuXG4gICAgICAgIGNvbnN0IHJvd3MgPSB0aGlzLl9ldmFsdWF0ZSgpO1xuICAgICAgICBpZiAoaW5kZXggPj0gMCAmJiBpbmRleCA8IHJvd3MubGVuZ3RoKSB7XG4gICAgICAgICAgICByZXR1cm4gbmV3IE1vZGVsQ2xhc3Mocm93c1tpbmRleF0pO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSB7QGxpbmsgTW9kZWx9IGluc3RhbmNlIGF0IGluZGV4IDAgaW4gdGhlIHtAbGluayBRdWVyeVNldH0gaW5zdGFuY2UuXG4gICAgICogQHJldHVybiB7TW9kZWx9XG4gICAgICovXG4gICAgZmlyc3QoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmF0KDApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIHtAbGluayBNb2RlbH0gaW5zdGFuY2UgYXQgaW5kZXggYFF1ZXJ5U2V0LmNvdW50KCkgLSAxYFxuICAgICAqIEByZXR1cm4ge01vZGVsfVxuICAgICAqL1xuICAgIGxhc3QoKSB7XG4gICAgICAgIGNvbnN0IHJvd3MgPSB0aGlzLl9ldmFsdWF0ZSgpO1xuICAgICAgICByZXR1cm4gdGhpcy5hdChyb3dzLmxlbmd0aCAtIDEpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBuZXcge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSB3aXRoIHRoZSBzYW1lIGVudGl0aWVzLlxuICAgICAqIEByZXR1cm4ge1F1ZXJ5U2V0fSBhIG5ldyBRdWVyeVNldCB3aXRoIHRoZSBzYW1lIGVudGl0aWVzLlxuICAgICAqL1xuICAgIGFsbCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX25ldyh0aGlzLmNsYXVzZXMpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBuZXcge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSB3aXRoIGVudGl0aWVzIHRoYXQgbWF0Y2ggcHJvcGVydGllcyBpbiBgbG9va3VwT2JqYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gbG9va3VwT2JqIC0gdGhlIHByb3BlcnRpZXMgdG8gbWF0Y2ggb2JqZWN0cyB3aXRoLiBDYW4gYWxzbyBiZSBhIGZ1bmN0aW9uLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSXQgd29ya3MgdGhlIHNhbWUgYXMgW0xvZGFzaCBmaWx0ZXJdKGh0dHBzOi8vbG9kYXNoLmNvbS9kb2NzLyNmaWx0ZXIpLlxuICAgICAqIEByZXR1cm4ge1F1ZXJ5U2V0fSBhIG5ldyB7QGxpbmsgUXVlcnlTZXR9IGluc3RhbmNlIHdpdGggb2JqZWN0cyB0aGF0IHBhc3NlZCB0aGUgZmlsdGVyLlxuICAgICAqL1xuICAgIGZpbHRlcihsb29rdXBPYmopIHtcbiAgICAgICAgLyoqXG4gICAgICAgICAqIGFsbG93IGZvcmVpZ24ga2V5cyB0byBiZSBzcGVjaWZpZWQgYXMgbW9kZWwgaW5zdGFuY2VzLFxuICAgICAgICAgKiB0cmFuc2Zvcm0gbW9kZWwgaW5zdGFuY2VzIHRvIHRoZWlyIHByaW1hcnkga2V5c1xuICAgICAgICAgKi9cbiAgICAgICAgY29uc3Qgbm9ybWFsaXplZExvb2t1cE9iaiA9XG4gICAgICAgICAgICB0eXBlb2YgbG9va3VwT2JqID09PSBcIm9iamVjdFwiXG4gICAgICAgICAgICAgICAgPyBtYXBWYWx1ZXMobG9va3VwT2JqLCBub3JtYWxpemVFbnRpdHkpXG4gICAgICAgICAgICAgICAgOiBsb29rdXBPYmo7XG5cbiAgICAgICAgY29uc3QgZmlsdGVyRGVzY3JpcHRvciA9IHtcbiAgICAgICAgICAgIHR5cGU6IEZJTFRFUixcbiAgICAgICAgICAgIHBheWxvYWQ6IG5vcm1hbGl6ZWRMb29rdXBPYmosXG4gICAgICAgIH07XG4gICAgICAgIC8qKlxuICAgICAgICAgKiBjcmVhdGUgYSBuZXcgUXVlcnlTZXRcbiAgICAgICAgICogaW5jbHVkaW5nIG9ubHkgcm93cyBtYXRjaGluZyB0aGUgbG9va3VwT2JqXG4gICAgICAgICAqL1xuICAgICAgICByZXR1cm4gdGhpcy5fbmV3KHRoaXMuY2xhdXNlcy5jb25jYXQoZmlsdGVyRGVzY3JpcHRvcikpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBuZXcge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSB3aXRoIGVudGl0aWVzIHRoYXQgZG8gbm90IG1hdGNoXG4gICAgICogcHJvcGVydGllcyBpbiBgbG9va3VwT2JqYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gbG9va3VwT2JqIC0gdGhlIHByb3BlcnRpZXMgdG8gdW5tYXRjaCBvYmplY3RzIHdpdGguIENhbiBhbHNvIGJlIGEgZnVuY3Rpb24uXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJdCB3b3JrcyB0aGUgc2FtZSBhcyBbTG9kYXNoIHJlamVjdF0oaHR0cHM6Ly9sb2Rhc2guY29tL2RvY3MvI3JlamVjdCkuXG4gICAgICogQHJldHVybiB7UXVlcnlTZXR9IGEgbmV3IHtAbGluayBRdWVyeVNldH0gaW5zdGFuY2Ugd2l0aCBvYmplY3RzIHRoYXQgZGlkIG5vdCBwYXNzIHRoZSBmaWx0ZXIuXG4gICAgICovXG4gICAgZXhjbHVkZShsb29rdXBPYmopIHtcbiAgICAgICAgLyoqXG4gICAgICAgICAqIGFsbG93IGZvcmVpZ24ga2V5cyB0byBiZSBzcGVjaWZpZWQgYXMgbW9kZWwgaW5zdGFuY2VzLFxuICAgICAgICAgKiB0cmFuc2Zvcm0gbW9kZWwgaW5zdGFuY2VzIHRvIHRoZWlyIHByaW1hcnkga2V5c1xuICAgICAgICAgKi9cbiAgICAgICAgY29uc3Qgbm9ybWFsaXplZExvb2t1cE9iaiA9XG4gICAgICAgICAgICB0eXBlb2YgbG9va3VwT2JqID09PSBcIm9iamVjdFwiXG4gICAgICAgICAgICAgICAgPyBtYXBWYWx1ZXMobG9va3VwT2JqLCBub3JtYWxpemVFbnRpdHkpXG4gICAgICAgICAgICAgICAgOiBsb29rdXBPYmo7XG4gICAgICAgIGNvbnN0IGV4Y2x1ZGVEZXNjcmlwdG9yID0ge1xuICAgICAgICAgICAgdHlwZTogRVhDTFVERSxcbiAgICAgICAgICAgIHBheWxvYWQ6IG5vcm1hbGl6ZWRMb29rdXBPYmosXG4gICAgICAgIH07XG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIGNyZWF0ZSBhIG5ldyBRdWVyeVNldFxuICAgICAgICAgKiBleGNsdWRpbmcgYWxsIHJvd3MgbWF0Y2hpbmcgdGhlIGxvb2t1cE9ialxuICAgICAgICAgKi9cbiAgICAgICAgcmV0dXJuIHRoaXMuX25ldyh0aGlzLmNsYXVzZXMuY29uY2F0KGV4Y2x1ZGVEZXNjcmlwdG9yKSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUGVyZm9ybXMgdGhlIGFjdHVhbCBkYXRhYmFzZSBxdWVyeS5cbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqIEByZXR1cm4ge0FycmF5fSByb3dzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIFF1ZXJ5U2V0J3MgY2xhdXNlc1xuICAgICAqL1xuICAgIF9ldmFsdWF0ZSgpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLm1vZGVsQ2xhc3Muc2Vzc2lvbiA9PT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIFtcbiAgICAgICAgICAgICAgICAgICAgYFRyaWVkIHRvIHF1ZXJ5IHRoZSAke3RoaXMubW9kZWxDbGFzcy5tb2RlbE5hbWV9IG1vZGVsJ3MgdGFibGUgd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCB1c2UgXCIsXG4gICAgICAgICAgICAgICAgICAgIGBcXGBzZXNzaW9uW1wiJHt0aGlzLm1vZGVsQ2xhc3MubW9kZWxOYW1lfVwiXVxcYCBmb3IgcXVlcnlpbmcgaW5zdGVhZC5gLFxuICAgICAgICAgICAgICAgIF0uam9pbihcIlwiKVxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoIXRoaXMuX2V2YWx1YXRlZCkge1xuICAgICAgICAgICAgY29uc3QgeyBzZXNzaW9uLCBtb2RlbE5hbWU6IHRhYmxlIH0gPSB0aGlzLm1vZGVsQ2xhc3M7XG4gICAgICAgICAgICBjb25zdCBxdWVyeVNwZWMgPSB7XG4gICAgICAgICAgICAgICAgdGFibGUsXG4gICAgICAgICAgICAgICAgY2xhdXNlczogdGhpcy5jbGF1c2VzLFxuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHRoaXMucm93cyA9IHNlc3Npb24ucXVlcnkocXVlcnlTcGVjKS5yb3dzO1xuICAgICAgICAgICAgdGhpcy5fZXZhbHVhdGVkID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5yb3dzO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBuZXcge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSB3aXRoIGVudGl0aWVzIG9yZGVyZWQgYnkgYGl0ZXJhdGVlc2AgaW4gYXNjZW5kaW5nXG4gICAgICogb3JkZXIsIHVubGVzcyBvdGhlcndpc2Ugc3BlY2lmaWVkLiBEZWxlZ2F0ZXMgdG8gW0xvZGFzaCBvcmRlckJ5XShodHRwczovL2xvZGFzaC5jb20vZG9jcy8jb3JkZXJCeSkuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtzdHJpbmdbXXxGdW5jdGlvbltdfSBpdGVyYXRlZXMgLSBhbiBhcnJheSB3aGVyZSBlYWNoIGl0ZW0gY2FuIGJlIGEgc3RyaW5nIG9yIGFcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbi4gSWYgYSBzdHJpbmcgaXMgc3VwcGxpZWQsIGl0IHNob3VsZFxuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvcnJlc3BvbmQgdG8gcHJvcGVydHkgb24gdGhlIGVudGl0eSB0aGF0IHdpbGxcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXRlcm1pbmUgdGhlIG9yZGVyLiBJZiBhIGZ1bmN0aW9uIGlzIHN1cHBsaWVkLFxuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0IHNob3VsZCByZXR1cm4gdGhlIHZhbHVlIHRvIG9yZGVyIGJ5LlxuICAgICAqIEBwYXJhbSB7QXJyYXk8Qm9vbGVhbnwnYXNjJ3wnZGVzYyc+fSBbb3JkZXJzXSAtIHRoZSBzb3J0IG9yZGVycyBvZiBgaXRlcmF0ZWVzYC4gSWYgdW5zcGVjaWZpZWQsIGFsbCBpdGVyYXRlZXNcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHNvcnRlZCBpbiBhc2NlbmRpbmcgb3JkZXIuIGB0cnVlYCBhbmQgYCdhc2MnYFxuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvcnJlc3BvbmQgdG8gYXNjZW5kaW5nIG9yZGVyLCBhbmQgYGZhbHNlYCBhbmQgYCdkZXNjJ2BcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0byBkZXNjZW5kaW5nIG9yZGVyLlxuICAgICAqIEByZXR1cm4ge1F1ZXJ5U2V0fSBhIG5ldyB7QGxpbmsgUXVlcnlTZXR9IHdpdGggb2JqZWN0cyBvcmRlcmVkIGJ5IGBpdGVyYXRlZXNgLlxuICAgICAqL1xuICAgIG9yZGVyQnkoaXRlcmF0ZWVzLCBvcmRlcnMpIHtcbiAgICAgICAgY29uc3Qgb3JkZXJCeURlc2NyaXB0b3IgPSB7XG4gICAgICAgICAgICB0eXBlOiBPUkRFUl9CWSxcbiAgICAgICAgICAgIHBheWxvYWQ6IFtpdGVyYXRlZXMsIG9yZGVyc10sXG4gICAgICAgIH07XG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIGNyZWF0ZSBhIG5ldyBRdWVyeVNldFxuICAgICAgICAgKiBzb3J0aW5nIGFsbCByb3dzIGFjY29yZGluZyB0byB0aGUgcGFzc2VkIGFyZ3VtZW50c1xuICAgICAgICAgKi9cbiAgICAgICAgcmV0dXJuIHRoaXMuX25ldyh0aGlzLmNsYXVzZXMuY29uY2F0KG9yZGVyQnlEZXNjcmlwdG9yKSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVjb3JkcyBhbiB1cGRhdGUgc3BlY2lmaWVkIHdpdGggYG1lcmdlT2JqYCB0byBhbGwgdGhlIG9iamVjdHNcbiAgICAgKiBpbiB0aGUge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gbWVyZ2VPYmogLSBhbiBvYmplY3QgdG8gbWVyZ2Ugd2l0aCBhbGwgdGhlIG9iamVjdHMgaW4gdGhpc1xuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVyeXNldC5cbiAgICAgKiBAcmV0dXJuIHt1bmRlZmluZWR9XG4gICAgICovXG4gICAgdXBkYXRlKG1lcmdlT2JqKSB7XG4gICAgICAgIGNvbnN0IHsgc2Vzc2lvbiwgbW9kZWxOYW1lOiB0YWJsZSB9ID0gdGhpcy5tb2RlbENsYXNzO1xuXG4gICAgICAgIHNlc3Npb24uYXBwbHlVcGRhdGUoe1xuICAgICAgICAgICAgYWN0aW9uOiBVUERBVEUsXG4gICAgICAgICAgICBxdWVyeToge1xuICAgICAgICAgICAgICAgIHRhYmxlLFxuICAgICAgICAgICAgICAgIGNsYXVzZXM6IHRoaXMuY2xhdXNlcyxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBwYXlsb2FkOiBtZXJnZU9iaixcbiAgICAgICAgfSk7XG5cbiAgICAgICAgdGhpcy5fZXZhbHVhdGVkID0gZmFsc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVjb3JkcyBhIGRlbGV0aW9uIG9mIGFsbCB0aGUgb2JqZWN0cyBpbiB0aGlzIHtAbGluayBRdWVyeVNldH0gaW5zdGFuY2UuXG4gICAgICogQHJldHVybiB7dW5kZWZpbmVkfVxuICAgICAqL1xuICAgIGRlbGV0ZSgpIHtcbiAgICAgICAgY29uc3QgeyBzZXNzaW9uLCBtb2RlbE5hbWU6IHRhYmxlIH0gPSB0aGlzLm1vZGVsQ2xhc3M7XG5cbiAgICAgICAgdGhpcy50b01vZGVsQXJyYXkoKS5mb3JFYWNoKFxuICAgICAgICAgICAgbW9kZWwgPT4gbW9kZWwuX29uRGVsZXRlKCkgLy8gZXNsaW50LWRpc2FibGUtbGluZSBuby11bmRlcnNjb3JlLWRhbmdsZVxuICAgICAgICApO1xuXG4gICAgICAgIHNlc3Npb24uYXBwbHlVcGRhdGUoe1xuICAgICAgICAgICAgYWN0aW9uOiBERUxFVEUsXG4gICAgICAgICAgICBxdWVyeToge1xuICAgICAgICAgICAgICAgIHRhYmxlLFxuICAgICAgICAgICAgICAgIGNsYXVzZXM6IHRoaXMuY2xhdXNlcyxcbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0pO1xuXG4gICAgICAgIHRoaXMuX2V2YWx1YXRlZCA9IGZhbHNlO1xuICAgIH1cblxuICAgIC8vIERFUFJFQ0FURUQgQU5EIFJFTU9WRUQgTUVUSE9EU1xuXG4gICAgLyoqXG4gICAgICogQGRlcHJlY2F0ZWRcbiAgICAgKiBVc2Uge0BsaW5rIFF1ZXJ5U2V0I3RvTW9kZWxBcnJheX0gb3IgcHJlZGljYXRlIGZ1bmN0aW9ucyB0aGF0XG4gICAgICogaW5zdGFudGlhdGUgTW9kZWxzIGZyb20gcmVmcywgZS5nLiBgbmV3IE1vZGVsKHJlZilgLlxuICAgICAqL1xuICAgIGdldCB3aXRoTW9kZWxzKCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBcImBRdWVyeVNldC5wcm90b3R5cGUud2l0aE1vZGVsc2AgaGFzIGJlZW4gcmVtb3ZlZC4gXCIgK1xuICAgICAgICAgICAgICAgIFwiVXNlIGAudG9Nb2RlbEFycmF5KClgIG9yIHByZWRpY2F0ZSBmdW5jdGlvbnMgdGhhdCBcIiArXG4gICAgICAgICAgICAgICAgXCJpbnN0YW50aWF0ZSBNb2RlbHMgZnJvbSByZWZzLCBlLmcuIGBuZXcgTW9kZWwocmVmKWAuXCJcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAZGVwcmVjYXRlZCBRdWVyeSBidWlsZGluZyBvcGVyYXRlcyBvbiByZWZzIG9ubHkgbm93LlxuICAgICAqL1xuICAgIGdldCB3aXRoUmVmcygpIHtcbiAgICAgICAgd2FybkRlcHJlY2F0ZWQoXG4gICAgICAgICAgICBcImBRdWVyeVNldC5wcm90b3R5cGUud2l0aFJlZnNgIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFwiICtcbiAgICAgICAgICAgICAgICBcIlF1ZXJ5IGJ1aWxkaW5nIG9wZXJhdGVzIG9uIHJlZnMgb25seSBub3cuXCJcbiAgICAgICAgKTtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAZGVwcmVjYXRlZFxuICAgICAqIENhbGwge0BsaW5rIFF1ZXJ5U2V0I3RvTW9kZWxBcnJheX0gb3Ige0BsaW5rIFF1ZXJ5U2V0I3RvUmVmQXJyYXl9IGZpcnN0IHRvIG1hcC5cbiAgICAgKi9cbiAgICBtYXAoKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIFwiYFF1ZXJ5U2V0LnByb3RvdHlwZS5tYXBgIGhhcyBiZWVuIHJlbW92ZWQuIFwiICtcbiAgICAgICAgICAgICAgICBcIkNhbGwgYC50b01vZGVsQXJyYXkoKWAgb3IgYC50b1JlZkFycmF5KClgIGZpcnN0IHRvIG1hcC5cIlxuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEBkZXByZWNhdGVkXG4gICAgICogQ2FsbCB7QGxpbmsgUXVlcnlTZXQjdG9Nb2RlbEFycmF5fSBvciB7QGxpbmsgUXVlcnlTZXQjdG9SZWZBcnJheX0gZmlyc3QgdG8gaXRlcmF0ZS5cbiAgICAgKi9cbiAgICBmb3JFYWNoKCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBcImBRdWVyeVNldC5wcm90b3R5cGUuZm9yRWFjaGAgaGFzIGJlZW4gcmVtb3ZlZC4gXCIgK1xuICAgICAgICAgICAgICAgIFwiQ2FsbCBgLnRvTW9kZWxBcnJheSgpYCBvciBgLnRvUmVmQXJyYXkoKWAgZmlyc3QgdG8gaXRlcmF0ZS5cIlxuICAgICAgICApO1xuICAgIH1cbn07XG5cblF1ZXJ5U2V0LnNoYXJlZE1ldGhvZHMgPSBbXG4gICAgXCJjb3VudFwiLFxuICAgIFwiYXRcIixcbiAgICBcImFsbFwiLFxuICAgIFwibGFzdFwiLFxuICAgIFwiZmlyc3RcIixcbiAgICBcImZpbHRlclwiLFxuICAgIFwiZXhjbHVkZVwiLFxuICAgIFwib3JkZXJCeVwiLFxuICAgIFwidXBkYXRlXCIsXG4gICAgXCJkZWxldGVcIixcbl07XG5cbmV4cG9ydCBkZWZhdWx0IFF1ZXJ5U2V0O1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/QuerySet.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ \\\"./src/utils.js\\\");\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./constants */ \\\"./src/constants.js\\\");\\n\\n\\n\\n/**\\n * This class is used to build and make queries to the database\\n * and operating the resulting set (such as updating attributes\\n * or deleting the records).\\n *\\n * The queries are built lazily. For example:\\n *\\n * ```javascript\\n * const qs = Book.all()\\n *     .filter(book => book.releaseYear > 1999)\\n *     .orderBy('name');\\n * ```\\n *\\n * Doesn't execute a query. The query is executed only when\\n * you need information from the query result, such as {@link QuerySet#count},\\n * {@link QuerySet#toRefArray}. After the query is executed, the resulting\\n * set is cached in the QuerySet instance.\\n *\\n * QuerySet instances also return copies, so chaining filters doesn't\\n * mutate the previous instances.\\n */\\n\\nconst QuerySet = /*#__PURE__*/function () {\\n  /**\\n   * Creates a QuerySet. The constructor is mainly for internal use;\\n   * You should access QuerySet instances from {@link Model}.\\n   *\\n   * @param  {Model} modelClass - the model class of objects in this QuerySet.\\n   * @param  {any[]} clauses - query clauses needed to evaluate the set.\\n   * @param {Object} [opts] - additional options\\n   */\\n  function QuerySet(modelClass, clauses, opts) {\\n    Object.assign(this, {\\n      modelClass,\\n      clauses: clauses || []\\n    });\\n    this._opts = opts;\\n  }\\n\\n  QuerySet.addSharedMethod = function addSharedMethod(methodName) {\\n    this.sharedMethods = this.sharedMethods.concat(methodName);\\n  };\\n\\n  var _proto = QuerySet.prototype;\\n\\n  _proto._new = function _new(clauses, userOpts) {\\n    const opts = { ...this._opts,\\n      ...userOpts\\n    };\\n    return new this.constructor(this.modelClass, clauses, opts);\\n  };\\n\\n  _proto.toString = function toString() {\\n    this._evaluate();\\n\\n    const contents = this.rows.map(({\\n      id\\n    }) => this.modelClass.withId(id).toString()).join(\\\"\\\\n    - \\\");\\n    return `QuerySet contents:\\\\n    - ${contents}`;\\n  }\\n  /**\\n   * Returns an array of the plain objects represented by the QuerySet.\\n   * The plain objects are direct references to the store.\\n   *\\n   * @return {Object[]} references to the plain JS objects represented by\\n   *                    the QuerySet\\n   */\\n  ;\\n\\n  _proto.toRefArray = function toRefArray() {\\n    return this._evaluate();\\n  }\\n  /**\\n   * Returns an array of {@link Model} instances represented by the QuerySet.\\n   * @return {Model[]} model instances represented by the QuerySet\\n   */\\n  ;\\n\\n  _proto.toModelArray = function toModelArray() {\\n    const {\\n      modelClass: ModelClass\\n    } = this;\\n    return this._evaluate().map(props => new ModelClass(props));\\n  }\\n  /**\\n   * Returns the number of {@link Model} instances represented by the QuerySet.\\n   *\\n   * @return {number} length of the QuerySet\\n   */\\n  ;\\n\\n  _proto.count = function count() {\\n    this._evaluate();\\n\\n    return this.rows.length;\\n  }\\n  /**\\n   * Checks if the {@link QuerySet} instance has any records matching the query\\n   * in the database.\\n   *\\n   * @return {Boolean} `true` if the {@link QuerySet} instance contains entities, else `false`.\\n   */\\n  ;\\n\\n  _proto.exists = function exists() {\\n    return Boolean(this.count());\\n  }\\n  /**\\n   * Returns the {@link Model} instance at index `index` in the {@link QuerySet} instance if\\n   * `withRefs` flag is set to `false`, or a reference to the plain JavaScript\\n   * object in the model state if `true`.\\n   *\\n   * @param  {number} index - index of the model instance to get\\n   * @return {Model|undefined} a {@link Model} instance at index\\n   *                           `index` in the {@link QuerySet} instance,\\n   *                           or undefined if the index is out of bounds.\\n   */\\n  ;\\n\\n  _proto.at = function at(index) {\\n    const {\\n      modelClass: ModelClass\\n    } = this;\\n\\n    const rows = this._evaluate();\\n\\n    if (index >= 0 && index < rows.length) {\\n      return new ModelClass(rows[index]);\\n    }\\n\\n    return undefined;\\n  }\\n  /**\\n   * Returns the {@link Model} instance at index 0 in the {@link QuerySet} instance.\\n   * @return {Model}\\n   */\\n  ;\\n\\n  _proto.first = function first() {\\n    return this.at(0);\\n  }\\n  /**\\n   * Returns the {@link Model} instance at index `QuerySet.count() - 1`\\n   * @return {Model}\\n   */\\n  ;\\n\\n  _proto.last = function last() {\\n    const rows = this._evaluate();\\n\\n    return this.at(rows.length - 1);\\n  }\\n  /**\\n   * Returns a new {@link QuerySet} instance with the same entities.\\n   * @return {QuerySet} a new QuerySet with the same entities.\\n   */\\n  ;\\n\\n  _proto.all = function all() {\\n    return this._new(this.clauses);\\n  }\\n  /**\\n   * Returns a new {@link QuerySet} instance with entities that match properties in `lookupObj`.\\n   *\\n   * @param  {Object} lookupObj - the properties to match objects with. Can also be a function.\\n   *                              It works the same as [Lodash filter](https://lodash.com/docs/#filter).\\n   * @return {QuerySet} a new {@link QuerySet} instance with objects that passed the filter.\\n   */\\n  ;\\n\\n  _proto.filter = function filter(lookupObj) {\\n    /**\\n     * allow foreign keys to be specified as model instances,\\n     * transform model instances to their primary keys\\n     */\\n    const normalizedLookupObj = typeof lookupObj === \\\"object\\\" ? Object(_utils__WEBPACK_IMPORTED_MODULE_1__[\\\"mapValues\\\"])(lookupObj, _utils__WEBPACK_IMPORTED_MODULE_1__[\\\"normalizeEntity\\\"]) : lookupObj;\\n    const filterDescriptor = {\\n      type: _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"FILTER\\\"],\\n      payload: normalizedLookupObj\\n    };\\n    /**\\n     * create a new QuerySet\\n     * including only rows matching the lookupObj\\n     */\\n\\n    return this._new(this.clauses.concat(filterDescriptor));\\n  }\\n  /**\\n   * Returns a new {@link QuerySet} instance with entities that do not match\\n   * properties in `lookupObj`.\\n   *\\n   * @param  {Object} lookupObj - the properties to unmatch objects with. Can also be a function.\\n   *                              It works the same as [Lodash reject](https://lodash.com/docs/#reject).\\n   * @return {QuerySet} a new {@link QuerySet} instance with objects that did not pass the filter.\\n   */\\n  ;\\n\\n  _proto.exclude = function exclude(lookupObj) {\\n    /**\\n     * allow foreign keys to be specified as model instances,\\n     * transform model instances to their primary keys\\n     */\\n    const normalizedLookupObj = typeof lookupObj === \\\"object\\\" ? Object(_utils__WEBPACK_IMPORTED_MODULE_1__[\\\"mapValues\\\"])(lookupObj, _utils__WEBPACK_IMPORTED_MODULE_1__[\\\"normalizeEntity\\\"]) : lookupObj;\\n    const excludeDescriptor = {\\n      type: _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"EXCLUDE\\\"],\\n      payload: normalizedLookupObj\\n    };\\n    /**\\n     * create a new QuerySet\\n     * excluding all rows matching the lookupObj\\n     */\\n\\n    return this._new(this.clauses.concat(excludeDescriptor));\\n  }\\n  /**\\n   * Performs the actual database query.\\n   * @private\\n   * @return {Array} rows corresponding to the QuerySet's clauses\\n   */\\n  ;\\n\\n  _proto._evaluate = function _evaluate() {\\n    if (typeof this.modelClass.session === \\\"undefined\\\") {\\n      throw new Error([`Tried to query the ${this.modelClass.modelName} model's table without a session. `, \\\"Create a session using `session = orm.session()` and use \\\", `\\\\`session[\\\"${this.modelClass.modelName}\\\"]\\\\` for querying instead.`].join(\\\"\\\"));\\n    }\\n\\n    if (!this._evaluated) {\\n      const {\\n        session,\\n        modelName: table\\n      } = this.modelClass;\\n      const querySpec = {\\n        table,\\n        clauses: this.clauses\\n      };\\n      this.rows = session.query(querySpec).rows;\\n      this._evaluated = true;\\n    }\\n\\n    return this.rows;\\n  }\\n  /**\\n   * Returns a new {@link QuerySet} instance with entities ordered by `iteratees` in ascending\\n   * order, unless otherwise specified. Delegates to [Lodash orderBy](https://lodash.com/docs/#orderBy).\\n   *\\n   * @param  {string[]|Function[]} iteratees - an array where each item can be a string or a\\n   *                                           function. If a string is supplied, it should\\n   *                                           correspond to property on the entity that will\\n   *                                           determine the order. If a function is supplied,\\n   *                                           it should return the value to order by.\\n   * @param {Array<Boolean|'asc'|'desc'>} [orders] - the sort orders of `iteratees`. If unspecified, all iteratees\\n   *                               will be sorted in ascending order. `true` and `'asc'`\\n   *                               correspond to ascending order, and `false` and `'desc'`\\n   *                               to descending order.\\n   * @return {QuerySet} a new {@link QuerySet} with objects ordered by `iteratees`.\\n   */\\n  ;\\n\\n  _proto.orderBy = function orderBy(iteratees, orders) {\\n    const orderByDescriptor = {\\n      type: _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"ORDER_BY\\\"],\\n      payload: [iteratees, orders]\\n    };\\n    /**\\n     * create a new QuerySet\\n     * sorting all rows according to the passed arguments\\n     */\\n\\n    return this._new(this.clauses.concat(orderByDescriptor));\\n  }\\n  /**\\n   * Records an update specified with `mergeObj` to all the objects\\n   * in the {@link QuerySet} instance.\\n   *\\n   * @param  {Object} mergeObj - an object to merge with all the objects in this\\n   *                             queryset.\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.update = function update(mergeObj) {\\n    const {\\n      session,\\n      modelName: table\\n    } = this.modelClass;\\n    session.applyUpdate({\\n      action: _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"UPDATE\\\"],\\n      query: {\\n        table,\\n        clauses: this.clauses\\n      },\\n      payload: mergeObj\\n    });\\n    this._evaluated = false;\\n  }\\n  /**\\n   * Records a deletion of all the objects in this {@link QuerySet} instance.\\n   * @return {undefined}\\n   */\\n  ;\\n\\n  _proto.delete = function _delete() {\\n    const {\\n      session,\\n      modelName: table\\n    } = this.modelClass;\\n    this.toModelArray().forEach(model => model._onDelete() // eslint-disable-line no-underscore-dangle\\n    );\\n    session.applyUpdate({\\n      action: _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"DELETE\\\"],\\n      query: {\\n        table,\\n        clauses: this.clauses\\n      }\\n    });\\n    this._evaluated = false;\\n  } // DEPRECATED AND REMOVED METHODS\\n\\n  /**\\n   * @deprecated\\n   * Use {@link QuerySet#toModelArray} or predicate functions that\\n   * instantiate Models from refs, e.g. `new Model(ref)`.\\n   */\\n  ;\\n\\n  /**\\n   * @deprecated\\n   * Call {@link QuerySet#toModelArray} or {@link QuerySet#toRefArray} first to map.\\n   */\\n  _proto.map = function map() {\\n    throw new Error(\\\"`QuerySet.prototype.map` has been removed. \\\" + \\\"Call `.toModelArray()` or `.toRefArray()` first to map.\\\");\\n  }\\n  /**\\n   * @deprecated\\n   * Call {@link QuerySet#toModelArray} or {@link QuerySet#toRefArray} first to iterate.\\n   */\\n  ;\\n\\n  _proto.forEach = function forEach() {\\n    throw new Error(\\\"`QuerySet.prototype.forEach` has been removed. \\\" + \\\"Call `.toModelArray()` or `.toRefArray()` first to iterate.\\\");\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(QuerySet, [{\\n    key: \\\"withModels\\\",\\n    get: function () {\\n      throw new Error(\\\"`QuerySet.prototype.withModels` has been removed. \\\" + \\\"Use `.toModelArray()` or predicate functions that \\\" + \\\"instantiate Models from refs, e.g. `new Model(ref)`.\\\");\\n    }\\n    /**\\n     * @deprecated Query building operates on refs only now.\\n     */\\n\\n  }, {\\n    key: \\\"withRefs\\\",\\n    get: function () {\\n      Object(_utils__WEBPACK_IMPORTED_MODULE_1__[\\\"warnDeprecated\\\"])(\\\"`QuerySet.prototype.withRefs` has been deprecated. \\\" + \\\"Query building operates on refs only now.\\\");\\n      return undefined;\\n    }\\n  }]);\\n\\n  return QuerySet;\\n}();\\n\\nQuerySet.sharedMethods = [\\\"count\\\", \\\"at\\\", \\\"all\\\", \\\"last\\\", \\\"first\\\", \\\"filter\\\", \\\"exclude\\\", \\\"orderBy\\\", \\\"update\\\", \\\"delete\\\"];\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (QuerySet);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9RdWVyeVNldC5qcz9kODM0Il0sIm5hbWVzIjpbIlF1ZXJ5U2V0IiwibW9kZWxDbGFzcyIsImNsYXVzZXMiLCJvcHRzIiwiT2JqZWN0IiwiYXNzaWduIiwiX29wdHMiLCJhZGRTaGFyZWRNZXRob2QiLCJtZXRob2ROYW1lIiwic2hhcmVkTWV0aG9kcyIsImNvbmNhdCIsIl9uZXciLCJ1c2VyT3B0cyIsImNvbnN0cnVjdG9yIiwidG9TdHJpbmciLCJfZXZhbHVhdGUiLCJjb250ZW50cyIsInJvd3MiLCJtYXAiLCJpZCIsIndpdGhJZCIsImpvaW4iLCJ0b1JlZkFycmF5IiwidG9Nb2RlbEFycmF5IiwiTW9kZWxDbGFzcyIsInByb3BzIiwiY291bnQiLCJsZW5ndGgiLCJleGlzdHMiLCJCb29sZWFuIiwiYXQiLCJpbmRleCIsInVuZGVmaW5lZCIsImZpcnN0IiwibGFzdCIsImFsbCIsImZpbHRlciIsImxvb2t1cE9iaiIsIm5vcm1hbGl6ZWRMb29rdXBPYmoiLCJtYXBWYWx1ZXMiLCJub3JtYWxpemVFbnRpdHkiLCJmaWx0ZXJEZXNjcmlwdG9yIiwidHlwZSIsIkZJTFRFUiIsInBheWxvYWQiLCJleGNsdWRlIiwiZXhjbHVkZURlc2NyaXB0b3IiLCJFWENMVURFIiwic2Vzc2lvbiIsIkVycm9yIiwibW9kZWxOYW1lIiwiX2V2YWx1YXRlZCIsInRhYmxlIiwicXVlcnlTcGVjIiwicXVlcnkiLCJvcmRlckJ5IiwiaXRlcmF0ZWVzIiwib3JkZXJzIiwib3JkZXJCeURlc2NyaXB0b3IiLCJPUkRFUl9CWSIsInVwZGF0ZSIsIm1lcmdlT2JqIiwiYXBwbHlVcGRhdGUiLCJhY3Rpb24iLCJVUERBVEUiLCJkZWxldGUiLCJmb3JFYWNoIiwibW9kZWwiLCJfb25EZWxldGUiLCJERUxFVEUiLCJ3YXJuRGVwcmVjYXRlZCJdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUE7QUFFQTtBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtB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QTVSYzs7QUFnVFY7QUFDSjtBQUNBO0FBQ0E7QUFuVGMsU0FvVFZqQyxHQXBUVSxHQW9UVixlQUFNO0FBQ0YsVUFBTSxJQUFJK0IsS0FBSixDQUNGLGdEQUNJLHlEQUZGLENBQU47QUFJSDtBQUVEO0FBQ0o7QUFDQTtBQUNBO0FBOVRjOztBQUFBLFNBK1RWaUIsT0EvVFUsR0ErVFYsbUJBQVU7QUFDTixVQUFNLElBQUlqQixLQUFKLENBQ0Ysb0RBQ0ksNkRBRkYsQ0FBTjtBQUlILEdBcFVTOztBQUFBO0FBQUE7QUFBQSxTQTZSVixZQUFpQjtBQUNiLFlBQU0sSUFBSUEsS0FBSixDQUNGLHVEQUNJLG9EQURKLEdBRUksc0RBSEYsQ0FBTjtBQUtIO0FBRUQ7QUFDSjtBQUNBOztBQXZTYztBQUFBO0FBQUEsU0F3U1YsWUFBZTtBQUNYcUIsbUVBQWMsQ0FDVix3REFDSSwyQ0FGTSxDQUFkO0FBSUEsYUFBT3RDLFNBQVA7QUFDSDtBQTlTUzs7QUFBQTtBQUFBLEdBQWQ7O0FBdVVBaEMsUUFBUSxDQUFDUyxhQUFULEdBQXlCLENBQ3JCLE9BRHFCLEVBRXJCLElBRnFCLEVBR3JCLEtBSHFCLEVBSXJCLE1BSnFCLEVBS3JCLE9BTHFCLEVBTXJCLFFBTnFCLEVBT3JCLFNBUHFCLEVBUXJCLFNBUnFCLEVBU3JCLFFBVHFCLEVBVXJCLFFBVnFCLENBQXpCO0FBYWVULHVFQUFmIiwiZmlsZSI6Ii4vc3JjL1F1ZXJ5U2V0LmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgbm9ybWFsaXplRW50aXR5LCB3YXJuRGVwcmVjYXRlZCwgbWFwVmFsdWVzIH0gZnJvbSBcIi4vdXRpbHNcIjtcblxuaW1wb3J0IHsgVVBEQVRFLCBERUxFVEUsIEZJTFRFUiwgRVhDTFVERSwgT1JERVJfQlkgfSBmcm9tIFwiLi9jb25zdGFudHNcIjtcblxuLyoqXG4gKiBUaGlzIGNsYXNzIGlzIHVzZWQgdG8gYnVpbGQgYW5kIG1ha2UgcXVlcmllcyB0byB0aGUgZGF0YWJhc2VcbiAqIGFuZCBvcGVyYXRpbmcgdGhlIHJlc3VsdGluZyBzZXQgKHN1Y2ggYXMgdXBkYXRpbmcgYXR0cmlidXRlc1xuICogb3IgZGVsZXRpbmcgdGhlIHJlY29yZHMpLlxuICpcbiAqIFRoZSBxdWVyaWVzIGFyZSBidWlsdCBsYXppbHkuIEZvciBleGFtcGxlOlxuICpcbiAqIGBgYGphdmFzY3JpcHRcbiAqIGNvbnN0IHFzID0gQm9vay5hbGwoKVxuICogICAgIC5maWx0ZXIoYm9vayA9PiBib29rLnJlbGVhc2VZZWFyID4gMTk5OSlcbiAqICAgICAub3JkZXJCeSgnbmFtZScpO1xuICogYGBgXG4gKlxuICogRG9lc24ndCBleGVjdXRlIGEgcXVlcnkuIFRoZSBxdWVyeSBpcyBleGVjdXRlZCBvbmx5IHdoZW5cbiAqIHlvdSBuZWVkIGluZm9ybWF0aW9uIGZyb20gdGhlIHF1ZXJ5IHJlc3VsdCwgc3VjaCBhcyB7QGxpbmsgUXVlcnlTZXQjY291bnR9LFxuICoge0BsaW5rIFF1ZXJ5U2V0I3RvUmVmQXJyYXl9LiBBZnRlciB0aGUgcXVlcnkgaXMgZXhlY3V0ZWQsIHRoZSByZXN1bHRpbmdcbiAqIHNldCBpcyBjYWNoZWQgaW4gdGhlIFF1ZXJ5U2V0IGluc3RhbmNlLlxuICpcbiAqIFF1ZXJ5U2V0IGluc3RhbmNlcyBhbHNvIHJldHVybiBjb3BpZXMsIHNvIGNoYWluaW5nIGZpbHRlcnMgZG9lc24ndFxuICogbXV0YXRlIHRoZSBwcmV2aW91cyBpbnN0YW5jZXMuXG4gKi9cbmNvbnN0IFF1ZXJ5U2V0ID0gY2xhc3MgUXVlcnlTZXQge1xuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBRdWVyeVNldC4gVGhlIGNvbnN0cnVjdG9yIGlzIG1haW5seSBmb3IgaW50ZXJuYWwgdXNlO1xuICAgICAqIFlvdSBzaG91bGQgYWNjZXNzIFF1ZXJ5U2V0IGluc3RhbmNlcyBmcm9tIHtAbGluayBNb2RlbH0uXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtNb2RlbH0gbW9kZWxDbGFzcyAtIHRoZSBtb2RlbCBjbGFzcyBvZiBvYmplY3RzIGluIHRoaXMgUXVlcnlTZXQuXG4gICAgICogQHBhcmFtICB7YW55W119IGNsYXVzZXMgLSBxdWVyeSBjbGF1c2VzIG5lZWRlZCB0byBldmFsdWF0ZSB0aGUgc2V0LlxuICAgICAqIEBwYXJhbSB7T2JqZWN0fSBbb3B0c10gLSBhZGRpdGlvbmFsIG9wdGlvbnNcbiAgICAgKi9cbiAgICBjb25zdHJ1Y3Rvcihtb2RlbENsYXNzLCBjbGF1c2VzLCBvcHRzKSB7XG4gICAgICAgIE9iamVjdC5hc3NpZ24odGhpcywge1xuICAgICAgICAgICAgbW9kZWxDbGFzcyxcbiAgICAgICAgICAgIGNsYXVzZXM6IGNsYXVzZXMgfHwgW10sXG4gICAgICAgIH0pO1xuXG4gICAgICAgIHRoaXMuX29wdHMgPSBvcHRzO1xuICAgIH1cblxuICAgIHN0YXRpYyBhZGRTaGFyZWRNZXRob2QobWV0aG9kTmFtZSkge1xuICAgICAgICB0aGlzLnNoYXJlZE1ldGhvZHMgPSB0aGlzLnNoYXJlZE1ldGhvZHMuY29uY2F0KG1ldGhvZE5hbWUpO1xuICAgIH1cblxuICAgIF9uZXcoY2xhdXNlcywgdXNlck9wdHMpIHtcbiAgICAgICAgY29uc3Qgb3B0cyA9IHsgLi4udGhpcy5fb3B0cywgLi4udXNlck9wdHMgfTtcbiAgICAgICAgcmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKHRoaXMubW9kZWxDbGFzcywgY2xhdXNlcywgb3B0cyk7XG4gICAgfVxuXG4gICAgdG9TdHJpbmcoKSB7XG4gICAgICAgIHRoaXMuX2V2YWx1YXRlKCk7XG4gICAgICAgIGNvbnN0IGNvbnRlbnRzID0gdGhpcy5yb3dzXG4gICAgICAgICAgICAubWFwKCh7IGlkIH0pID0+IHRoaXMubW9kZWxDbGFzcy53aXRoSWQoaWQpLnRvU3RyaW5nKCkpXG4gICAgICAgICAgICAuam9pbihcIlxcbiAgICAtIFwiKTtcbiAgICAgICAgcmV0dXJuIGBRdWVyeVNldCBjb250ZW50czpcXG4gICAgLSAke2NvbnRlbnRzfWA7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhbiBhcnJheSBvZiB0aGUgcGxhaW4gb2JqZWN0cyByZXByZXNlbnRlZCBieSB0aGUgUXVlcnlTZXQuXG4gICAgICogVGhlIHBsYWluIG9iamVjdHMgYXJlIGRpcmVjdCByZWZlcmVuY2VzIHRvIHRoZSBzdG9yZS5cbiAgICAgKlxuICAgICAqIEByZXR1cm4ge09iamVjdFtdfSByZWZlcmVuY2VzIHRvIHRoZSBwbGFpbiBKUyBvYmplY3RzIHJlcHJlc2VudGVkIGJ5XG4gICAgICogICAgICAgICAgICAgICAgICAgIHRoZSBRdWVyeVNldFxuICAgICAqL1xuICAgIHRvUmVmQXJyYXkoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9ldmFsdWF0ZSgpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYW4gYXJyYXkgb2Yge0BsaW5rIE1vZGVsfSBpbnN0YW5jZXMgcmVwcmVzZW50ZWQgYnkgdGhlIFF1ZXJ5U2V0LlxuICAgICAqIEByZXR1cm4ge01vZGVsW119IG1vZGVsIGluc3RhbmNlcyByZXByZXNlbnRlZCBieSB0aGUgUXVlcnlTZXRcbiAgICAgKi9cbiAgICB0b01vZGVsQXJyYXkoKSB7XG4gICAgICAgIGNvbnN0IHsgbW9kZWxDbGFzczogTW9kZWxDbGFzcyB9ID0gdGhpcztcbiAgICAgICAgcmV0dXJuIHRoaXMuX2V2YWx1YXRlKCkubWFwKChwcm9wcykgPT4gbmV3IE1vZGVsQ2xhc3MocHJvcHMpKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBudW1iZXIgb2Yge0BsaW5rIE1vZGVsfSBpbnN0YW5jZXMgcmVwcmVzZW50ZWQgYnkgdGhlIFF1ZXJ5U2V0LlxuICAgICAqXG4gICAgICogQHJldHVybiB7bnVtYmVyfSBsZW5ndGggb2YgdGhlIFF1ZXJ5U2V0XG4gICAgICovXG4gICAgY291bnQoKSB7XG4gICAgICAgIHRoaXMuX2V2YWx1YXRlKCk7XG4gICAgICAgIHJldHVybiB0aGlzLnJvd3MubGVuZ3RoO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENoZWNrcyBpZiB0aGUge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSBoYXMgYW55IHJlY29yZHMgbWF0Y2hpbmcgdGhlIHF1ZXJ5XG4gICAgICogaW4gdGhlIGRhdGFiYXNlLlxuICAgICAqXG4gICAgICogQHJldHVybiB7Qm9vbGVhbn0gYHRydWVgIGlmIHRoZSB7QGxpbmsgUXVlcnlTZXR9IGluc3RhbmNlIGNvbnRhaW5zIGVudGl0aWVzLCBlbHNlIGBmYWxzZWAuXG4gICAgICovXG4gICAgZXhpc3RzKCkge1xuICAgICAgICByZXR1cm4gQm9vbGVhbih0aGlzLmNvdW50KCkpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIHtAbGluayBNb2RlbH0gaW5zdGFuY2UgYXQgaW5kZXggYGluZGV4YCBpbiB0aGUge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSBpZlxuICAgICAqIGB3aXRoUmVmc2AgZmxhZyBpcyBzZXQgdG8gYGZhbHNlYCwgb3IgYSByZWZlcmVuY2UgdG8gdGhlIHBsYWluIEphdmFTY3JpcHRcbiAgICAgKiBvYmplY3QgaW4gdGhlIG1vZGVsIHN0YXRlIGlmIGB0cnVlYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge251bWJlcn0gaW5kZXggLSBpbmRleCBvZiB0aGUgbW9kZWwgaW5zdGFuY2UgdG8gZ2V0XG4gICAgICogQHJldHVybiB7TW9kZWx8dW5kZWZpbmVkfSBhIHtAbGluayBNb2RlbH0gaW5zdGFuY2UgYXQgaW5kZXhcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgIGBpbmRleGAgaW4gdGhlIHtAbGluayBRdWVyeVNldH0gaW5zdGFuY2UsXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICBvciB1bmRlZmluZWQgaWYgdGhlIGluZGV4IGlzIG91dCBvZiBib3VuZHMuXG4gICAgICovXG4gICAgYXQoaW5kZXgpIHtcbiAgICAgICAgY29uc3QgeyBtb2RlbENsYXNzOiBNb2RlbENsYXNzIH0gPSB0aGlzO1xuXG4gICAgICAgIGNvbnN0IHJvd3MgPSB0aGlzLl9ldmFsdWF0ZSgpO1xuICAgICAgICBpZiAoaW5kZXggPj0gMCAmJiBpbmRleCA8IHJvd3MubGVuZ3RoKSB7XG4gICAgICAgICAgICByZXR1cm4gbmV3IE1vZGVsQ2xhc3Mocm93c1tpbmRleF0pO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSB7QGxpbmsgTW9kZWx9IGluc3RhbmNlIGF0IGluZGV4IDAgaW4gdGhlIHtAbGluayBRdWVyeVNldH0gaW5zdGFuY2UuXG4gICAgICogQHJldHVybiB7TW9kZWx9XG4gICAgICovXG4gICAgZmlyc3QoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmF0KDApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIHtAbGluayBNb2RlbH0gaW5zdGFuY2UgYXQgaW5kZXggYFF1ZXJ5U2V0LmNvdW50KCkgLSAxYFxuICAgICAqIEByZXR1cm4ge01vZGVsfVxuICAgICAqL1xuICAgIGxhc3QoKSB7XG4gICAgICAgIGNvbnN0IHJvd3MgPSB0aGlzLl9ldmFsdWF0ZSgpO1xuICAgICAgICByZXR1cm4gdGhpcy5hdChyb3dzLmxlbmd0aCAtIDEpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBuZXcge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSB3aXRoIHRoZSBzYW1lIGVudGl0aWVzLlxuICAgICAqIEByZXR1cm4ge1F1ZXJ5U2V0fSBhIG5ldyBRdWVyeVNldCB3aXRoIHRoZSBzYW1lIGVudGl0aWVzLlxuICAgICAqL1xuICAgIGFsbCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX25ldyh0aGlzLmNsYXVzZXMpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBuZXcge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSB3aXRoIGVudGl0aWVzIHRoYXQgbWF0Y2ggcHJvcGVydGllcyBpbiBgbG9va3VwT2JqYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gbG9va3VwT2JqIC0gdGhlIHByb3BlcnRpZXMgdG8gbWF0Y2ggb2JqZWN0cyB3aXRoLiBDYW4gYWxzbyBiZSBhIGZ1bmN0aW9uLlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSXQgd29ya3MgdGhlIHNhbWUgYXMgW0xvZGFzaCBmaWx0ZXJdKGh0dHBzOi8vbG9kYXNoLmNvbS9kb2NzLyNmaWx0ZXIpLlxuICAgICAqIEByZXR1cm4ge1F1ZXJ5U2V0fSBhIG5ldyB7QGxpbmsgUXVlcnlTZXR9IGluc3RhbmNlIHdpdGggb2JqZWN0cyB0aGF0IHBhc3NlZCB0aGUgZmlsdGVyLlxuICAgICAqL1xuICAgIGZpbHRlcihsb29rdXBPYmopIHtcbiAgICAgICAgLyoqXG4gICAgICAgICAqIGFsbG93IGZvcmVpZ24ga2V5cyB0byBiZSBzcGVjaWZpZWQgYXMgbW9kZWwgaW5zdGFuY2VzLFxuICAgICAgICAgKiB0cmFuc2Zvcm0gbW9kZWwgaW5zdGFuY2VzIHRvIHRoZWlyIHByaW1hcnkga2V5c1xuICAgICAgICAgKi9cbiAgICAgICAgY29uc3Qgbm9ybWFsaXplZExvb2t1cE9iaiA9XG4gICAgICAgICAgICB0eXBlb2YgbG9va3VwT2JqID09PSBcIm9iamVjdFwiXG4gICAgICAgICAgICAgICAgPyBtYXBWYWx1ZXMobG9va3VwT2JqLCBub3JtYWxpemVFbnRpdHkpXG4gICAgICAgICAgICAgICAgOiBsb29rdXBPYmo7XG5cbiAgICAgICAgY29uc3QgZmlsdGVyRGVzY3JpcHRvciA9IHtcbiAgICAgICAgICAgIHR5cGU6IEZJTFRFUixcbiAgICAgICAgICAgIHBheWxvYWQ6IG5vcm1hbGl6ZWRMb29rdXBPYmosXG4gICAgICAgIH07XG4gICAgICAgIC8qKlxuICAgICAgICAgKiBjcmVhdGUgYSBuZXcgUXVlcnlTZXRcbiAgICAgICAgICogaW5jbHVkaW5nIG9ubHkgcm93cyBtYXRjaGluZyB0aGUgbG9va3VwT2JqXG4gICAgICAgICAqL1xuICAgICAgICByZXR1cm4gdGhpcy5fbmV3KHRoaXMuY2xhdXNlcy5jb25jYXQoZmlsdGVyRGVzY3JpcHRvcikpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBuZXcge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSB3aXRoIGVudGl0aWVzIHRoYXQgZG8gbm90IG1hdGNoXG4gICAgICogcHJvcGVydGllcyBpbiBgbG9va3VwT2JqYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gbG9va3VwT2JqIC0gdGhlIHByb3BlcnRpZXMgdG8gdW5tYXRjaCBvYmplY3RzIHdpdGguIENhbiBhbHNvIGJlIGEgZnVuY3Rpb24uXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJdCB3b3JrcyB0aGUgc2FtZSBhcyBbTG9kYXNoIHJlamVjdF0oaHR0cHM6Ly9sb2Rhc2guY29tL2RvY3MvI3JlamVjdCkuXG4gICAgICogQHJldHVybiB7UXVlcnlTZXR9IGEgbmV3IHtAbGluayBRdWVyeVNldH0gaW5zdGFuY2Ugd2l0aCBvYmplY3RzIHRoYXQgZGlkIG5vdCBwYXNzIHRoZSBmaWx0ZXIuXG4gICAgICovXG4gICAgZXhjbHVkZShsb29rdXBPYmopIHtcbiAgICAgICAgLyoqXG4gICAgICAgICAqIGFsbG93IGZvcmVpZ24ga2V5cyB0byBiZSBzcGVjaWZpZWQgYXMgbW9kZWwgaW5zdGFuY2VzLFxuICAgICAgICAgKiB0cmFuc2Zvcm0gbW9kZWwgaW5zdGFuY2VzIHRvIHRoZWlyIHByaW1hcnkga2V5c1xuICAgICAgICAgKi9cbiAgICAgICAgY29uc3Qgbm9ybWFsaXplZExvb2t1cE9iaiA9XG4gICAgICAgICAgICB0eXBlb2YgbG9va3VwT2JqID09PSBcIm9iamVjdFwiXG4gICAgICAgICAgICAgICAgPyBtYXBWYWx1ZXMobG9va3VwT2JqLCBub3JtYWxpemVFbnRpdHkpXG4gICAgICAgICAgICAgICAgOiBsb29rdXBPYmo7XG4gICAgICAgIGNvbnN0IGV4Y2x1ZGVEZXNjcmlwdG9yID0ge1xuICAgICAgICAgICAgdHlwZTogRVhDTFVERSxcbiAgICAgICAgICAgIHBheWxvYWQ6IG5vcm1hbGl6ZWRMb29rdXBPYmosXG4gICAgICAgIH07XG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIGNyZWF0ZSBhIG5ldyBRdWVyeVNldFxuICAgICAgICAgKiBleGNsdWRpbmcgYWxsIHJvd3MgbWF0Y2hpbmcgdGhlIGxvb2t1cE9ialxuICAgICAgICAgKi9cbiAgICAgICAgcmV0dXJuIHRoaXMuX25ldyh0aGlzLmNsYXVzZXMuY29uY2F0KGV4Y2x1ZGVEZXNjcmlwdG9yKSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUGVyZm9ybXMgdGhlIGFjdHVhbCBkYXRhYmFzZSBxdWVyeS5cbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqIEByZXR1cm4ge0FycmF5fSByb3dzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIFF1ZXJ5U2V0J3MgY2xhdXNlc1xuICAgICAqL1xuICAgIF9ldmFsdWF0ZSgpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLm1vZGVsQ2xhc3Muc2Vzc2lvbiA9PT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIFtcbiAgICAgICAgICAgICAgICAgICAgYFRyaWVkIHRvIHF1ZXJ5IHRoZSAke3RoaXMubW9kZWxDbGFzcy5tb2RlbE5hbWV9IG1vZGVsJ3MgdGFibGUgd2l0aG91dCBhIHNlc3Npb24uIGAsXG4gICAgICAgICAgICAgICAgICAgIFwiQ3JlYXRlIGEgc2Vzc2lvbiB1c2luZyBgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKClgIGFuZCB1c2UgXCIsXG4gICAgICAgICAgICAgICAgICAgIGBcXGBzZXNzaW9uW1wiJHt0aGlzLm1vZGVsQ2xhc3MubW9kZWxOYW1lfVwiXVxcYCBmb3IgcXVlcnlpbmcgaW5zdGVhZC5gLFxuICAgICAgICAgICAgICAgIF0uam9pbihcIlwiKVxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoIXRoaXMuX2V2YWx1YXRlZCkge1xuICAgICAgICAgICAgY29uc3QgeyBzZXNzaW9uLCBtb2RlbE5hbWU6IHRhYmxlIH0gPSB0aGlzLm1vZGVsQ2xhc3M7XG4gICAgICAgICAgICBjb25zdCBxdWVyeVNwZWMgPSB7XG4gICAgICAgICAgICAgICAgdGFibGUsXG4gICAgICAgICAgICAgICAgY2xhdXNlczogdGhpcy5jbGF1c2VzLFxuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHRoaXMucm93cyA9IHNlc3Npb24ucXVlcnkocXVlcnlTcGVjKS5yb3dzO1xuICAgICAgICAgICAgdGhpcy5fZXZhbHVhdGVkID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5yb3dzO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBuZXcge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZSB3aXRoIGVudGl0aWVzIG9yZGVyZWQgYnkgYGl0ZXJhdGVlc2AgaW4gYXNjZW5kaW5nXG4gICAgICogb3JkZXIsIHVubGVzcyBvdGhlcndpc2Ugc3BlY2lmaWVkLiBEZWxlZ2F0ZXMgdG8gW0xvZGFzaCBvcmRlckJ5XShodHRwczovL2xvZGFzaC5jb20vZG9jcy8jb3JkZXJCeSkuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtzdHJpbmdbXXxGdW5jdGlvbltdfSBpdGVyYXRlZXMgLSBhbiBhcnJheSB3aGVyZSBlYWNoIGl0ZW0gY2FuIGJlIGEgc3RyaW5nIG9yIGFcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbi4gSWYgYSBzdHJpbmcgaXMgc3VwcGxpZWQsIGl0IHNob3VsZFxuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvcnJlc3BvbmQgdG8gcHJvcGVydHkgb24gdGhlIGVudGl0eSB0aGF0IHdpbGxcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXRlcm1pbmUgdGhlIG9yZGVyLiBJZiBhIGZ1bmN0aW9uIGlzIHN1cHBsaWVkLFxuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0IHNob3VsZCByZXR1cm4gdGhlIHZhbHVlIHRvIG9yZGVyIGJ5LlxuICAgICAqIEBwYXJhbSB7QXJyYXk8Qm9vbGVhbnwnYXNjJ3wnZGVzYyc+fSBbb3JkZXJzXSAtIHRoZSBzb3J0IG9yZGVycyBvZiBgaXRlcmF0ZWVzYC4gSWYgdW5zcGVjaWZpZWQsIGFsbCBpdGVyYXRlZXNcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHNvcnRlZCBpbiBhc2NlbmRpbmcgb3JkZXIuIGB0cnVlYCBhbmQgYCdhc2MnYFxuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvcnJlc3BvbmQgdG8gYXNjZW5kaW5nIG9yZGVyLCBhbmQgYGZhbHNlYCBhbmQgYCdkZXNjJ2BcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0byBkZXNjZW5kaW5nIG9yZGVyLlxuICAgICAqIEByZXR1cm4ge1F1ZXJ5U2V0fSBhIG5ldyB7QGxpbmsgUXVlcnlTZXR9IHdpdGggb2JqZWN0cyBvcmRlcmVkIGJ5IGBpdGVyYXRlZXNgLlxuICAgICAqL1xuICAgIG9yZGVyQnkoaXRlcmF0ZWVzLCBvcmRlcnMpIHtcbiAgICAgICAgY29uc3Qgb3JkZXJCeURlc2NyaXB0b3IgPSB7XG4gICAgICAgICAgICB0eXBlOiBPUkRFUl9CWSxcbiAgICAgICAgICAgIHBheWxvYWQ6IFtpdGVyYXRlZXMsIG9yZGVyc10sXG4gICAgICAgIH07XG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIGNyZWF0ZSBhIG5ldyBRdWVyeVNldFxuICAgICAgICAgKiBzb3J0aW5nIGFsbCByb3dzIGFjY29yZGluZyB0byB0aGUgcGFzc2VkIGFyZ3VtZW50c1xuICAgICAgICAgKi9cbiAgICAgICAgcmV0dXJuIHRoaXMuX25ldyh0aGlzLmNsYXVzZXMuY29uY2F0KG9yZGVyQnlEZXNjcmlwdG9yKSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVjb3JkcyBhbiB1cGRhdGUgc3BlY2lmaWVkIHdpdGggYG1lcmdlT2JqYCB0byBhbGwgdGhlIG9iamVjdHNcbiAgICAgKiBpbiB0aGUge0BsaW5rIFF1ZXJ5U2V0fSBpbnN0YW5jZS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gbWVyZ2VPYmogLSBhbiBvYmplY3QgdG8gbWVyZ2Ugd2l0aCBhbGwgdGhlIG9iamVjdHMgaW4gdGhpc1xuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVyeXNldC5cbiAgICAgKiBAcmV0dXJuIHt1bmRlZmluZWR9XG4gICAgICovXG4gICAgdXBkYXRlKG1lcmdlT2JqKSB7XG4gICAgICAgIGNvbnN0IHsgc2Vzc2lvbiwgbW9kZWxOYW1lOiB0YWJsZSB9ID0gdGhpcy5tb2RlbENsYXNzO1xuXG4gICAgICAgIHNlc3Npb24uYXBwbHlVcGRhdGUoe1xuICAgICAgICAgICAgYWN0aW9uOiBVUERBVEUsXG4gICAgICAgICAgICBxdWVyeToge1xuICAgICAgICAgICAgICAgIHRhYmxlLFxuICAgICAgICAgICAgICAgIGNsYXVzZXM6IHRoaXMuY2xhdXNlcyxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBwYXlsb2FkOiBtZXJnZU9iaixcbiAgICAgICAgfSk7XG5cbiAgICAgICAgdGhpcy5fZXZhbHVhdGVkID0gZmFsc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVjb3JkcyBhIGRlbGV0aW9uIG9mIGFsbCB0aGUgb2JqZWN0cyBpbiB0aGlzIHtAbGluayBRdWVyeVNldH0gaW5zdGFuY2UuXG4gICAgICogQHJldHVybiB7dW5kZWZpbmVkfVxuICAgICAqL1xuICAgIGRlbGV0ZSgpIHtcbiAgICAgICAgY29uc3QgeyBzZXNzaW9uLCBtb2RlbE5hbWU6IHRhYmxlIH0gPSB0aGlzLm1vZGVsQ2xhc3M7XG5cbiAgICAgICAgdGhpcy50b01vZGVsQXJyYXkoKS5mb3JFYWNoKFxuICAgICAgICAgICAgKG1vZGVsKSA9PiBtb2RlbC5fb25EZWxldGUoKSAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIG5vLXVuZGVyc2NvcmUtZGFuZ2xlXG4gICAgICAgICk7XG5cbiAgICAgICAgc2Vzc2lvbi5hcHBseVVwZGF0ZSh7XG4gICAgICAgICAgICBhY3Rpb246IERFTEVURSxcbiAgICAgICAgICAgIHF1ZXJ5OiB7XG4gICAgICAgICAgICAgICAgdGFibGUsXG4gICAgICAgICAgICAgICAgY2xhdXNlczogdGhpcy5jbGF1c2VzLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgfSk7XG5cbiAgICAgICAgdGhpcy5fZXZhbHVhdGVkID0gZmFsc2U7XG4gICAgfVxuXG4gICAgLy8gREVQUkVDQVRFRCBBTkQgUkVNT1ZFRCBNRVRIT0RTXG5cbiAgICAvKipcbiAgICAgKiBAZGVwcmVjYXRlZFxuICAgICAqIFVzZSB7QGxpbmsgUXVlcnlTZXQjdG9Nb2RlbEFycmF5fSBvciBwcmVkaWNhdGUgZnVuY3Rpb25zIHRoYXRcbiAgICAgKiBpbnN0YW50aWF0ZSBNb2RlbHMgZnJvbSByZWZzLCBlLmcuIGBuZXcgTW9kZWwocmVmKWAuXG4gICAgICovXG4gICAgZ2V0IHdpdGhNb2RlbHMoKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIFwiYFF1ZXJ5U2V0LnByb3RvdHlwZS53aXRoTW9kZWxzYCBoYXMgYmVlbiByZW1vdmVkLiBcIiArXG4gICAgICAgICAgICAgICAgXCJVc2UgYC50b01vZGVsQXJyYXkoKWAgb3IgcHJlZGljYXRlIGZ1bmN0aW9ucyB0aGF0IFwiICtcbiAgICAgICAgICAgICAgICBcImluc3RhbnRpYXRlIE1vZGVscyBmcm9tIHJlZnMsIGUuZy4gYG5ldyBNb2RlbChyZWYpYC5cIlxuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEBkZXByZWNhdGVkIFF1ZXJ5IGJ1aWxkaW5nIG9wZXJhdGVzIG9uIHJlZnMgb25seSBub3cuXG4gICAgICovXG4gICAgZ2V0IHdpdGhSZWZzKCkge1xuICAgICAgICB3YXJuRGVwcmVjYXRlZChcbiAgICAgICAgICAgIFwiYFF1ZXJ5U2V0LnByb3RvdHlwZS53aXRoUmVmc2AgaGFzIGJlZW4gZGVwcmVjYXRlZC4gXCIgK1xuICAgICAgICAgICAgICAgIFwiUXVlcnkgYnVpbGRpbmcgb3BlcmF0ZXMgb24gcmVmcyBvbmx5IG5vdy5cIlxuICAgICAgICApO1xuICAgICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEBkZXByZWNhdGVkXG4gICAgICogQ2FsbCB7QGxpbmsgUXVlcnlTZXQjdG9Nb2RlbEFycmF5fSBvciB7QGxpbmsgUXVlcnlTZXQjdG9SZWZBcnJheX0gZmlyc3QgdG8gbWFwLlxuICAgICAqL1xuICAgIG1hcCgpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgXCJgUXVlcnlTZXQucHJvdG90eXBlLm1hcGAgaGFzIGJlZW4gcmVtb3ZlZC4gXCIgK1xuICAgICAgICAgICAgICAgIFwiQ2FsbCBgLnRvTW9kZWxBcnJheSgpYCBvciBgLnRvUmVmQXJyYXkoKWAgZmlyc3QgdG8gbWFwLlwiXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQGRlcHJlY2F0ZWRcbiAgICAgKiBDYWxsIHtAbGluayBRdWVyeVNldCN0b01vZGVsQXJyYXl9IG9yIHtAbGluayBRdWVyeVNldCN0b1JlZkFycmF5fSBmaXJzdCB0byBpdGVyYXRlLlxuICAgICAqL1xuICAgIGZvckVhY2goKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIFwiYFF1ZXJ5U2V0LnByb3RvdHlwZS5mb3JFYWNoYCBoYXMgYmVlbiByZW1vdmVkLiBcIiArXG4gICAgICAgICAgICAgICAgXCJDYWxsIGAudG9Nb2RlbEFycmF5KClgIG9yIGAudG9SZWZBcnJheSgpYCBmaXJzdCB0byBpdGVyYXRlLlwiXG4gICAgICAgICk7XG4gICAgfVxufTtcblxuUXVlcnlTZXQuc2hhcmVkTWV0aG9kcyA9IFtcbiAgICBcImNvdW50XCIsXG4gICAgXCJhdFwiLFxuICAgIFwiYWxsXCIsXG4gICAgXCJsYXN0XCIsXG4gICAgXCJmaXJzdFwiLFxuICAgIFwiZmlsdGVyXCIsXG4gICAgXCJleGNsdWRlXCIsXG4gICAgXCJvcmRlckJ5XCIsXG4gICAgXCJ1cGRhdGVcIixcbiAgICBcImRlbGV0ZVwiLFxuXTtcblxuZXhwb3J0IGRlZmF1bHQgUXVlcnlTZXQ7XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./src/QuerySet.js\\n\");\n \n /***/ }),\n \n@@ -4486,7 +4508,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _bab\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var immutable_ops__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! immutable-ops */ \\\"./node_modules/immutable-ops/es/index.js\\\");\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./constants */ \\\"./src/constants.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n\\n\\nconst Session = /*#__PURE__*/function () {\\n  /**\\n   * Creates a new Session.\\n   *\\n   * @param  {Database} db - a {@link Database} instance\\n   * @param  {Object} state - the database state\\n   * @param  {Boolean} [withMutations] - whether the session should mutate data\\n   * @param  {Object} [batchToken] - used by the backend to identify objects that can be\\n   *                                 mutated.\\n   */\\n  function Session(schema, db, state, withMutations, batchToken) {\\n    this.schema = schema;\\n    this.db = db;\\n    this.state = state || db.getEmptyState();\\n    this.initialState = this.state;\\n    this.withMutations = Boolean(withMutations);\\n    this.batchToken = batchToken || Object(immutable_ops__WEBPACK_IMPORTED_MODULE_1__[\\\"getBatchToken\\\"])();\\n    this.modelData = {};\\n    this.models = schema.getModelClasses();\\n    this.sessionBoundModels = this.models.map(modelClass => {\\n      function SessionBoundModel() {\\n        return Reflect.construct(modelClass, arguments, SessionBoundModel); // eslint-disable-line prefer-rest-params\\n      }\\n\\n      Reflect.setPrototypeOf(SessionBoundModel.prototype, modelClass.prototype);\\n      Reflect.setPrototypeOf(SessionBoundModel, modelClass);\\n      Object.defineProperty(this, modelClass.modelName, {\\n        get: () => SessionBoundModel\\n      });\\n      SessionBoundModel.connect(this);\\n      return SessionBoundModel;\\n    });\\n  }\\n\\n  var _proto = Session.prototype;\\n\\n  _proto.getDataForModel = function getDataForModel(modelName) {\\n    if (!this.modelData[modelName]) {\\n      this.modelData[modelName] = {};\\n    }\\n\\n    return this.modelData[modelName];\\n  };\\n\\n  _proto.getModelData = function getModelData() {\\n    return this.modelData;\\n  };\\n\\n  _proto.markAccessed = function markAccessed(modelName, modelIds) {\\n    const data = this.getDataForModel(modelName);\\n\\n    if (!data.accessedInstances) {\\n      data.accessedInstances = {};\\n    }\\n\\n    modelIds.forEach(id => {\\n      data.accessedInstances[id] = true;\\n    });\\n  };\\n\\n  _proto.markFullTableScanned = function markFullTableScanned(modelName) {\\n    const data = this.getDataForModel(modelName);\\n    data.fullTableScanned = true;\\n  };\\n\\n  _proto.markAccessedIndexes = function markAccessedIndexes(indexes) {\\n    indexes.forEach(([table, attr, value]) => {\\n      const data = this.getDataForModel(table);\\n\\n      if (!data.accessedIndexes) {\\n        data.accessedIndexes = {};\\n      }\\n\\n      data.accessedIndexes[attr] = [...(data.accessedIndexes[attr] || []), value];\\n    });\\n  };\\n\\n  /**\\n   * Applies update to a model state.\\n   *\\n   * @private\\n   * @param {Object} update - the update object. Must have keys\\n   *                          `type`, `payload`.\\n   */\\n  _proto.applyUpdate = function applyUpdate(updateSpec) {\\n    const tx = this._getTransaction(updateSpec);\\n\\n    const result = this.db.update(updateSpec, tx, this.state);\\n    const {\\n      status,\\n      state,\\n      payload\\n    } = result;\\n\\n    if (status !== _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"SUCCESS\\\"]) {\\n      throw new Error(`Applying update failed with status ${status}. Payload: ${payload}`);\\n    }\\n\\n    this.state = state;\\n    return payload;\\n  };\\n\\n  _proto.query = function query(querySpec) {\\n    const result = this.db.query(querySpec, this.state);\\n\\n    this._markAccessedByQuery(querySpec, result);\\n\\n    return result;\\n  };\\n\\n  _proto._getTransaction = function _getTransaction(updateSpec) {\\n    const {\\n      withMutations\\n    } = this;\\n    const {\\n      action\\n    } = updateSpec;\\n    let {\\n      batchToken\\n    } = this;\\n\\n    if ([_constants__WEBPACK_IMPORTED_MODULE_2__[\\\"UPDATE\\\"], _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"DELETE\\\"]].includes(action)) {\\n      batchToken = Object(immutable_ops__WEBPACK_IMPORTED_MODULE_1__[\\\"getBatchToken\\\"])();\\n    }\\n\\n    return {\\n      batchToken,\\n      withMutations\\n    };\\n  };\\n\\n  _proto._markAccessedByQuery = function _markAccessedByQuery(querySpec, result) {\\n    const {\\n      table,\\n      clauses\\n    } = querySpec;\\n    const {\\n      rows\\n    } = result;\\n    const {\\n      idAttribute\\n    } = this[table];\\n    const accessedIds = new Set(rows.map(row => row[idAttribute]));\\n    const anyClauseFilteredByPk = clauses.some(clause => {\\n      if (!Object(_utils__WEBPACK_IMPORTED_MODULE_3__[\\\"clauseFiltersByAttribute\\\"])(clause, idAttribute)) {\\n        return false;\\n      }\\n      /**\\n       * We previously knew which row we wanted to access,\\n       * so there was no need to scan the entire table.\\n       */\\n\\n\\n      accessedIds.add(clause.payload[idAttribute]);\\n      return true;\\n    });\\n    const accessedIndexes = [];\\n    const {\\n      indexes\\n    } = this.state[table];\\n    clauses.forEach(clause => {\\n      Object.keys(indexes).forEach(attr => {\\n        if (!Object(_utils__WEBPACK_IMPORTED_MODULE_3__[\\\"clauseFiltersByAttribute\\\"])(clause, attr)) {\\n          return;\\n        }\\n\\n        const value = clause.payload[attr];\\n        accessedIndexes.push([table, attr, value]);\\n      });\\n    });\\n\\n    if (anyClauseFilteredByPk) {\\n      /**\\n       * The clauses have been ordered so that an indexed one was\\n       * the first to have been evaluated, and thus only the row\\n       * with the specified PK value has actually been accessed.\\n       */\\n      this.markAccessed(table, accessedIds);\\n    } else if (accessedIndexes.length) {\\n      /**\\n       * At least one clause was optimized using indexes.\\n       */\\n      this.markAccessed(table, accessedIds);\\n      this.markAccessedIndexes(accessedIndexes);\\n    } else {\\n      /**\\n       * At least one clause could not be efficiently optimized\\n       * or no clause was specified at all.\\n       */\\n      this.markFullTableScanned(table);\\n    }\\n  } // DEPRECATED AND REMOVED METHODS\\n\\n  /**\\n   * @deprecated Access {@link Session#state} instead.\\n   */\\n  ;\\n\\n  _proto.getNextState = function getNextState() {\\n    Object(_utils__WEBPACK_IMPORTED_MODULE_3__[\\\"warnDeprecated\\\"])(\\\"`Session.prototype.getNextState` has been deprecated. Access \\\" + \\\"the `Session.prototype.state` property instead.\\\");\\n    return this.state;\\n  }\\n  /**\\n   * @deprecated\\n   * The Redux integration API is now decoupled from ORM and Session.<br>\\n   * See the 0.9 migration guide in the GitHub repo.\\n   */\\n  ;\\n\\n  _proto.reduce = function reduce() {\\n    throw new Error(\\\"`Session.prototype.reduce` has been removed. The Redux integration API \\\" + \\\"is now decoupled from ORM and Session - see the 0.9 migration guide \\\" + \\\"in the GitHub repo.\\\");\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(Session, [{\\n    key: \\\"accessedModelInstances\\\",\\n    get: function () {\\n      return Object.entries(this.getModelData()).reduce((result, [key, value]) => {\\n        if (value.accessedInstances) {\\n          result[key] = value.accessedInstances;\\n        }\\n\\n        return result;\\n      }, {});\\n    }\\n  }, {\\n    key: \\\"fullTableScannedModels\\\",\\n    get: function () {\\n      return Object.entries(this.getModelData()).reduce((result, [key, value]) => {\\n        if (value.fullTableScanned) {\\n          result.push(key);\\n        }\\n\\n        return result;\\n      }, []);\\n    }\\n  }, {\\n    key: \\\"accessedIndexes\\\",\\n    get: function () {\\n      return Object.entries(this.getModelData()).reduce((result, [key, value]) => {\\n        if (value.accessedIndexes) {\\n          result[key] = value.accessedIndexes;\\n        }\\n\\n        return result;\\n      }, {});\\n    }\\n  }]);\\n\\n  return Session;\\n}();\\n\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (Session);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9TZXNzaW9uLmpzPzU5MWMiXSwibmFtZXMiOlsiU2Vzc2lvbiIsInNjaGVtYSIsImRiIiwic3RhdGUiLCJ3aXRoTXV0YXRpb25zIiwiYmF0Y2hUb2tlbiIsImdldEVtcHR5U3RhdGUiLCJpbml0aWFsU3RhdGUiLCJCb29sZWFuIiwiZ2V0QmF0Y2hUb2tlbiIsIm1vZGVsRGF0YSIsIm1vZGVscyIsImdldE1vZGVsQ2xhc3NlcyIsInNlc3Npb25Cb3VuZE1vZGVscyIsIm1hcCIsIm1vZGVsQ2xhc3MiLCJTZXNzaW9uQm91bmRNb2RlbCIsIlJlZmxlY3QiLCJjb25zdHJ1Y3QiLCJhcmd1bWVudHMiLCJzZXRQcm90b3R5cGVPZiIsInByb3RvdHlwZSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwibW9kZWxOYW1lIiwiZ2V0IiwiY29ubmVjdCIsImdldERhdGFGb3JNb2RlbCIsImdldE1vZGVsRGF0YSIsIm1hcmtBY2Nlc3NlZCIsIm1vZGVsSWRzIiwiZGF0YSIsImFjY2Vzc2VkSW5zdGFuY2VzIiwiZm9yRWFjaCIsImlkIiwibWFya0Z1bGxUYWJsZVNjYW5uZWQiLCJmdWxsVGFibGVTY2FubmVkIiwibWFya0FjY2Vzc2VkSW5kZXhlcyIsImluZGV4ZXMiLCJ0YWJsZSIsImF0dHIiLCJ2YWx1ZSIsImFjY2Vzc2VkSW5kZXhlcyIsImFwcGx5VXBkYXRlIiwidXBkYXRlU3BlYyIsInR4IiwiX2dldFRyYW5zYWN0aW9uIiwicmVzdWx0IiwidXBkYXRlIiwic3RhdHVzIiwicGF5bG9hZCIsIlNVQ0NFU1MiLCJFcnJvciIsInF1ZXJ5IiwicXVlcnlTcGVjIiwiX21hcmtBY2Nlc3NlZEJ5UXVlcnkiLCJhY3Rpb24iLCJVUERBVEUiLCJERUxFVEUiLCJpbmNsdWRlcyIsImNsYXVzZXMiLCJyb3dzIiwiaWRBdHRyaWJ1dGUiLCJhY2Nlc3NlZElkcyIsIlNldCIsInJvdyIsImFueUNsYXVzZUZpbHRlcmVkQnlQayIsInNvbWUiLCJjbGF1c2UiLCJjbGF1c2VGaWx0ZXJzQnlBdHRyaWJ1dGUiLCJhZGQiLCJrZXlzIiwicHVzaCIsImxlbmd0aCIsImdldE5leHRTdGF0ZSIsIndhcm5EZXByZWNhdGVkIiwicmVkdWNlIiwiZW50cmllcyIsImtleSJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFBO0FBRUE7QUFDQTs7QUFFQSxNQUFNQSxPQUFPO0FBQ1Q7Ozs7Ozs7OztBQVNBLG1CQUFZQyxNQUFaLEVBQW9CQyxFQUFwQixFQUF3QkMsS0FBeEIsRUFBK0JDLGFBQS9CLEVBQThDQyxVQUE5QyxFQUEwRDtBQUN0RCxTQUFLSixNQUFMLEdBQWNBLE1BQWQ7QUFDQSxTQUFLQyxFQUFMLEdBQVVBLEVBQVY7QUFDQSxTQUFLQyxLQUFMLEdBQWFBLEtBQUssSUFBSUQsRUFBRSxDQUFDSSxhQUFILEVBQXRCO0FBQ0EsU0FBS0MsWUFBTCxHQUFvQixLQUFLSixLQUF6QjtBQUVBLFNBQUtDLGFBQUwsR0FBcUJJLE9BQU8sQ0FBQ0osYUFBRCxDQUE1QjtBQUNBLFNBQUtDLFVBQUwsR0FBa0JBLFVBQVUsSUFBSUksbUVBQWEsRUFBN0M7QUFFQSxTQUFLQyxTQUFMLEdBQWlCLEVBQWpCO0FBRUEsU0FBS0MsTUFBTCxHQUFjVixNQUFNLENBQUNXLGVBQVAsRUFBZDtBQUVBLFNBQUtDLGtCQUFMLEdBQTBCLEtBQUtGLE1BQUwsQ0FBWUcsR0FBWixDQUFnQkMsVUFBVSxJQUFJO0FBQ3BELGVBQVNDLGlCQUFULEdBQTZCO0FBQ3pCLGVBQU9DLE9BQU8sQ0FBQ0MsU0FBUixDQUNISCxVQURHLEVBRUhJLFNBRkcsRUFHSEgsaUJBSEcsQ0FBUCxDQUR5QixDQUt0QjtBQUNOOztBQUNEQyxhQUFPLENBQUNHLGNBQVIsQ0FDSUosaUJBQWlCLENBQUNLLFNBRHRCLEVBRUlOLFVBQVUsQ0FBQ00sU0FGZjtBQUlBSixhQUFPLENBQUNHLGNBQVIsQ0FBdUJKLGlCQUF2QixFQUEwQ0QsVUFBMUM7QUFFQU8sWUFBTSxDQUFDQyxjQUFQLENBQXNCLElBQXRCLEVBQTRCUixV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L1Nlc3Npb24uanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBnZXRCYXRjaFRva2VuIH0gZnJvbSBcImltbXV0YWJsZS1vcHNcIjtcblxuaW1wb3J0IHsgU1VDQ0VTUywgVVBEQVRFLCBERUxFVEUgfSBmcm9tIFwiLi9jb25zdGFudHNcIjtcbmltcG9ydCB7IHdhcm5EZXByZWNhdGVkLCBjbGF1c2VGaWx0ZXJzQnlBdHRyaWJ1dGUgfSBmcm9tIFwiLi91dGlsc1wiO1xuXG5jb25zdCBTZXNzaW9uID0gY2xhc3MgU2Vzc2lvbiB7XG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyBTZXNzaW9uLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7RGF0YWJhc2V9IGRiIC0gYSB7QGxpbmsgRGF0YWJhc2V9IGluc3RhbmNlXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBzdGF0ZSAtIHRoZSBkYXRhYmFzZSBzdGF0ZVxuICAgICAqIEBwYXJhbSAge0Jvb2xlYW59IFt3aXRoTXV0YXRpb25zXSAtIHdoZXRoZXIgdGhlIHNlc3Npb24gc2hvdWxkIG11dGF0ZSBkYXRhXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBbYmF0Y2hUb2tlbl0gLSB1c2VkIGJ5IHRoZSBiYWNrZW5kIHRvIGlkZW50aWZ5IG9iamVjdHMgdGhhdCBjYW4gYmVcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZWQuXG4gICAgICovXG4gICAgY29uc3RydWN0b3Ioc2NoZW1hLCBkYiwgc3RhdGUsIHdpdGhNdXRhdGlvbnMsIGJhdGNoVG9rZW4pIHtcbiAgICAgICAgdGhpcy5zY2hlbWEgPSBzY2hlbWE7XG4gICAgICAgIHRoaXMuZGIgPSBkYjtcbiAgICAgICAgdGhpcy5zdGF0ZSA9IHN0YXRlIHx8IGRiLmdldEVtcHR5U3RhdGUoKTtcbiAgICAgICAgdGhpcy5pbml0aWFsU3RhdGUgPSB0aGlzLnN0YXRlO1xuXG4gICAgICAgIHRoaXMud2l0aE11dGF0aW9ucyA9IEJvb2xlYW4od2l0aE11dGF0aW9ucyk7XG4gICAgICAgIHRoaXMuYmF0Y2hUb2tlbiA9IGJhdGNoVG9rZW4gfHwgZ2V0QmF0Y2hUb2tlbigpO1xuXG4gICAgICAgIHRoaXMubW9kZWxEYXRhID0ge307XG5cbiAgICAgICAgdGhpcy5tb2RlbHMgPSBzY2hlbWEuZ2V0TW9kZWxDbGFzc2VzKCk7XG5cbiAgICAgICAgdGhpcy5zZXNzaW9uQm91bmRNb2RlbHMgPSB0aGlzLm1vZGVscy5tYXAobW9kZWxDbGFzcyA9PiB7XG4gICAgICAgICAgICBmdW5jdGlvbiBTZXNzaW9uQm91bmRNb2RlbCgpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gUmVmbGVjdC5jb25zdHJ1Y3QoXG4gICAgICAgICAgICAgICAgICAgIG1vZGVsQ2xhc3MsXG4gICAgICAgICAgICAgICAgICAgIGFyZ3VtZW50cyxcbiAgICAgICAgICAgICAgICAgICAgU2Vzc2lvbkJvdW5kTW9kZWxcbiAgICAgICAgICAgICAgICApOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIHByZWZlci1yZXN0LXBhcmFtc1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgUmVmbGVjdC5zZXRQcm90b3R5cGVPZihcbiAgICAgICAgICAgICAgICBTZXNzaW9uQm91bmRNb2RlbC5wcm90b3R5cGUsXG4gICAgICAgICAgICAgICAgbW9kZWxDbGFzcy5wcm90b3R5cGVcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICBSZWZsZWN0LnNldFByb3RvdHlwZU9mKFNlc3Npb25Cb3VuZE1vZGVsLCBtb2RlbENsYXNzKTtcblxuICAgICAgICAgICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsIG1vZGVsQ2xhc3MubW9kZWxOYW1lLCB7XG4gICAgICAgICAgICAgICAgZ2V0OiAoKSA9PiBTZXNzaW9uQm91bmRNb2RlbCxcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICBTZXNzaW9uQm91bmRNb2RlbC5jb25uZWN0KHRoaXMpO1xuICAgICAgICAgICAgcmV0dXJuIFNlc3Npb25Cb3VuZE1vZGVsO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBnZXREYXRhRm9yTW9kZWwobW9kZWxOYW1lKSB7XG4gICAgICAgIGlmICghdGhpcy5tb2RlbERhdGFbbW9kZWxOYW1lXSkge1xuICAgICAgICAgICAgdGhpcy5tb2RlbERhdGFbbW9kZWxOYW1lXSA9IHt9O1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLm1vZGVsRGF0YVttb2RlbE5hbWVdO1xuICAgIH1cblxuICAgIGdldE1vZGVsRGF0YSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMubW9kZWxEYXRhO1xuICAgIH1cblxuICAgIG1hcmtBY2Nlc3NlZChtb2RlbE5hbWUsIG1vZGVsSWRzKSB7XG4gICAgICAgIGNvbnN0IGRhdGEgPSB0aGlzLmdldERhdGFGb3JNb2RlbChtb2RlbE5hbWUpO1xuICAgICAgICBpZiAoIWRhdGEuYWNjZXNzZWRJbnN0YW5jZXMpIHtcbiAgICAgICAgICAgIGRhdGEuYWNjZXNzZWRJbnN0YW5jZXMgPSB7fTtcbiAgICAgICAgfVxuICAgICAgICBtb2RlbElkcy5mb3JFYWNoKGlkID0+IHtcbiAgICAgICAgICAgIGRhdGEuYWNjZXNzZWRJbnN0YW5jZXNbaWRdID0gdHJ1ZTtcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgZ2V0IGFjY2Vzc2VkTW9kZWxJbnN0YW5jZXMoKSB7XG4gICAgICAgIHJldHVybiBPYmplY3QuZW50cmllcyh0aGlzLmdldE1vZGVsRGF0YSgpKS5yZWR1Y2UoXG4gICAgICAgICAgICAocmVzdWx0LCBba2V5LCB2YWx1ZV0pID0+IHtcbiAgICAgICAgICAgICAgICBpZiAodmFsdWUuYWNjZXNzZWRJbnN0YW5jZXMpIHtcbiAgICAgICAgICAgICAgICAgICAgcmVzdWx0W2tleV0gPSB2YWx1ZS5hY2Nlc3NlZEluc3RhbmNlcztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICB7fVxuICAgICAgICApO1xuICAgIH1cblxuICAgIG1hcmtGdWxsVGFibGVTY2FubmVkKG1vZGVsTmFtZSkge1xuICAgICAgICBjb25zdCBkYXRhID0gdGhpcy5nZXREYXRhRm9yTW9kZWwobW9kZWxOYW1lKTtcbiAgICAgICAgZGF0YS5mdWxsVGFibGVTY2FubmVkID0gdHJ1ZTtcbiAgICB9XG5cbiAgICBnZXQgZnVsbFRhYmxlU2Nhbm5lZE1vZGVscygpIHtcbiAgICAgICAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKHRoaXMuZ2V0TW9kZWxEYXRhKCkpLnJlZHVjZShcbiAgICAgICAgICAgIChyZXN1bHQsIFtrZXksIHZhbHVlXSkgPT4ge1xuICAgICAgICAgICAgICAgIGlmICh2YWx1ZS5mdWxsVGFibGVTY2FubmVkKSB7XG4gICAgICAgICAgICAgICAgICAgIHJlc3VsdC5wdXNoKGtleSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgW11cbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBtYXJrQWNjZXNzZWRJbmRleGVzKGluZGV4ZXMpIHtcbiAgICAgICAgaW5kZXhlcy5mb3JFYWNoKChbdGFibGUsIGF0dHIsIHZhbHVlXSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgZGF0YSA9IHRoaXMuZ2V0RGF0YUZvck1vZGVsKHRhYmxlKTtcbiAgICAgICAgICAgIGlmICghZGF0YS5hY2Nlc3NlZEluZGV4ZXMpIHtcbiAgICAgICAgICAgICAgICBkYXRhLmFjY2Vzc2VkSW5kZXhlcyA9IHt9O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZGF0YS5hY2Nlc3NlZEluZGV4ZXNbYXR0cl0gPSBbXG4gICAgICAgICAgICAgICAgLi4uKGRhdGEuYWNjZXNzZWRJbmRleGVzW2F0dHJdIHx8IFtdKSxcbiAgICAgICAgICAgICAgICB2YWx1ZSxcbiAgICAgICAgICAgIF07XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIGdldCBhY2Nlc3NlZEluZGV4ZXMoKSB7XG4gICAgICAgIHJldHVybiBPYmplY3QuZW50cmllcyh0aGlzLmdldE1vZGVsRGF0YSgpKS5yZWR1Y2UoXG4gICAgICAgICAgICAocmVzdWx0LCBba2V5LCB2YWx1ZV0pID0+IHtcbiAgICAgICAgICAgICAgICBpZiAodmFsdWUuYWNjZXNzZWRJbmRleGVzKSB7XG4gICAgICAgICAgICAgICAgICAgIHJlc3VsdFtrZXldID0gdmFsdWUuYWNjZXNzZWRJbmRleGVzO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIHt9XG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQXBwbGllcyB1cGRhdGUgdG8gYSBtb2RlbCBzdGF0ZS5cbiAgICAgKlxuICAgICAqIEBwcml2YXRlXG4gICAgICogQHBhcmFtIHtPYmplY3R9IHVwZGF0ZSAtIHRoZSB1cGRhdGUgb2JqZWN0LiBNdXN0IGhhdmUga2V5c1xuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICBgdHlwZWAsIGBwYXlsb2FkYC5cbiAgICAgKi9cbiAgICBhcHBseVVwZGF0ZSh1cGRhdGVTcGVjKSB7XG4gICAgICAgIGNvbnN0IHR4ID0gdGhpcy5fZ2V0VHJhbnNhY3Rpb24odXBkYXRlU3BlYyk7XG4gICAgICAgIGNvbnN0IHJlc3VsdCA9IHRoaXMuZGIudXBkYXRlKHVwZGF0ZVNwZWMsIHR4LCB0aGlzLnN0YXRlKTtcbiAgICAgICAgY29uc3QgeyBzdGF0dXMsIHN0YXRlLCBwYXlsb2FkIH0gPSByZXN1bHQ7XG5cbiAgICAgICAgaWYgKHN0YXR1cyAhPT0gU1VDQ0VTUykge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIGBBcHBseWluZyB1cGRhdGUgZmFpbGVkIHdpdGggc3RhdHVzICR7c3RhdHVzfS4gUGF5bG9hZDogJHtwYXlsb2FkfWBcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnN0YXRlID0gc3RhdGU7XG5cbiAgICAgICAgcmV0dXJuIHBheWxvYWQ7XG4gICAgfVxuXG4gICAgcXVlcnkocXVlcnlTcGVjKSB7XG4gICAgICAgIGNvbnN0IHJlc3VsdCA9IHRoaXMuZGIucXVlcnkocXVlcnlTcGVjLCB0aGlzLnN0YXRlKTtcblxuICAgICAgICB0aGlzLl9tYXJrQWNjZXNzZWRCeVF1ZXJ5KHF1ZXJ5U3BlYywgcmVzdWx0KTtcblxuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH1cblxuICAgIF9nZXRUcmFuc2FjdGlvbih1cGRhdGVTcGVjKSB7XG4gICAgICAgIGNvbnN0IHsgd2l0aE11dGF0aW9ucyB9ID0gdGhpcztcbiAgICAgICAgY29uc3QgeyBhY3Rpb24gfSA9IHVwZGF0ZVNwZWM7XG4gICAgICAgIGxldCB7IGJhdGNoVG9rZW4gfSA9IHRoaXM7XG4gICAgICAgIGlmIChbVVBEQVRFLCBERUxFVEVdLmluY2x1ZGVzKGFjdGlvbikpIHtcbiAgICAgICAgICAgIGJhdGNoVG9rZW4gPSBnZXRCYXRjaFRva2VuKCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHsgYmF0Y2hUb2tlbiwgd2l0aE11dGF0aW9ucyB9O1xuICAgIH1cblxuICAgIF9tYXJrQWNjZXNzZWRCeVF1ZXJ5KHF1ZXJ5U3BlYywgcmVzdWx0KSB7XG4gICAgICAgIGNvbnN0IHsgdGFibGUsIGNsYXVzZXMgfSA9IHF1ZXJ5U3BlYztcbiAgICAgICAgY29uc3QgeyByb3dzIH0gPSByZXN1bHQ7XG5cbiAgICAgICAgY29uc3QgeyBpZEF0dHJpYnV0ZSB9ID0gdGhpc1t0YWJsZV07XG4gICAgICAgIGNvbnN0IGFjY2Vzc2VkSWRzID0gbmV3IFNldChyb3dzLm1hcChyb3cgPT4gcm93W2lkQXR0cmlidXRlXSkpO1xuXG4gICAgICAgIGNvbnN0IGFueUNsYXVzZUZpbHRlcmVkQnlQayA9IGNsYXVzZXMuc29tZShjbGF1c2UgPT4ge1xuICAgICAgICAgICAgaWYgKCFjbGF1c2VGaWx0ZXJzQnlBdHRyaWJ1dGUoY2xhdXNlLCBpZEF0dHJpYnV0ZSkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIFdlIHByZXZpb3VzbHkga25ldyB3aGljaCByb3cgd2Ugd2FudGVkIHRvIGFjY2VzcyxcbiAgICAgICAgICAgICAqIHNvIHRoZXJlIHdhcyBubyBuZWVkIHRvIHNjYW4gdGhlIGVudGlyZSB0YWJsZS5cbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgYWNjZXNzZWRJZHMuYWRkKGNsYXVzZS5wYXlsb2FkW2lkQXR0cmlidXRlXSk7XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfSk7XG5cbiAgICAgICAgY29uc3QgYWNjZXNzZWRJbmRleGVzID0gW107XG4gICAgICAgIGNvbnN0IHsgaW5kZXhlcyB9ID0gdGhpcy5zdGF0ZVt0YWJsZV07XG4gICAgICAgIGNsYXVzZXMuZm9yRWFjaChjbGF1c2UgPT4ge1xuICAgICAgICAgICAgT2JqZWN0LmtleXMoaW5kZXhlcykuZm9yRWFjaChhdHRyID0+IHtcbiAgICAgICAgICAgICAgICBpZiAoIWNsYXVzZUZpbHRlcnNCeUF0dHJpYnV0ZShjbGF1c2UsIGF0dHIpKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgY29uc3QgdmFsdWUgPSBjbGF1c2UucGF5bG9hZFthdHRyXTtcbiAgICAgICAgICAgICAgICBhY2Nlc3NlZEluZGV4ZXMucHVzaChbdGFibGUsIGF0dHIsIHZhbHVlXSk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSk7XG5cbiAgICAgICAgaWYgKGFueUNsYXVzZUZpbHRlcmVkQnlQaykge1xuICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgKiBUaGUgY2xhdXNlcyBoYXZlIGJlZW4gb3JkZXJlZCBzbyB0aGF0IGFuIGluZGV4ZWQgb25lIHdhc1xuICAgICAgICAgICAgICogdGhlIGZpcnN0IHRvIGhhdmUgYmVlbiBldmFsdWF0ZWQsIGFuZCB0aHVzIG9ubHkgdGhlIHJvd1xuICAgICAgICAgICAgICogd2l0aCB0aGUgc3BlY2lmaWVkIFBLIHZhbHVlIGhhcyBhY3R1YWxseSBiZWVuIGFjY2Vzc2VkLlxuICAgICAgICAgICAgICovXG4gICAgICAgICAgICB0aGlzLm1hcmtBY2Nlc3NlZCh0YWJsZSwgYWNjZXNzZWRJZHMpO1xuICAgICAgICB9IGVsc2UgaWYgKGFjY2Vzc2VkSW5kZXhlcy5sZW5ndGgpIHtcbiAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICogQXQgbGVhc3Qgb25lIGNsYXVzZSB3YXMgb3B0aW1pemVkIHVzaW5nIGluZGV4ZXMuXG4gICAgICAgICAgICAgKi9cbiAgICAgICAgICAgIHRoaXMubWFya0FjY2Vzc2VkKHRhYmxlLCBhY2Nlc3NlZElkcyk7XG4gICAgICAgICAgICB0aGlzLm1hcmtBY2Nlc3NlZEluZGV4ZXMoYWNjZXNzZWRJbmRleGVzKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICogQXQgbGVhc3Qgb25lIGNsYXVzZSBjb3VsZCBub3QgYmUgZWZmaWNpZW50bHkgb3B0aW1pemVkXG4gICAgICAgICAgICAgKiBvciBubyBjbGF1c2Ugd2FzIHNwZWNpZmllZCBhdCBhbGwuXG4gICAgICAgICAgICAgKi9cbiAgICAgICAgICAgIHRoaXMubWFya0Z1bGxUYWJsZVNjYW5uZWQodGFibGUpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLy8gREVQUkVDQVRFRCBBTkQgUkVNT1ZFRCBNRVRIT0RTXG5cbiAgICAvKipcbiAgICAgKiBAZGVwcmVjYXRlZCBBY2Nlc3Mge0BsaW5rIFNlc3Npb24jc3RhdGV9IGluc3RlYWQuXG4gICAgICovXG4gICAgZ2V0TmV4dFN0YXRlKCkge1xuICAgICAgICB3YXJuRGVwcmVjYXRlZChcbiAgICAgICAgICAgIFwiYFNlc3Npb24ucHJvdG90eXBlLmdldE5leHRTdGF0ZWAgaGFzIGJlZW4gZGVwcmVjYXRlZC4gQWNjZXNzIFwiICtcbiAgICAgICAgICAgICAgICBcInRoZSBgU2Vzc2lvbi5wcm90b3R5cGUuc3RhdGVgIHByb3BlcnR5IGluc3RlYWQuXCJcbiAgICAgICAgKTtcbiAgICAgICAgcmV0dXJuIHRoaXMuc3RhdGU7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQGRlcHJlY2F0ZWRcbiAgICAgKiBUaGUgUmVkdXggaW50ZWdyYXRpb24gQVBJIGlzIG5vdyBkZWNvdXBsZWQgZnJvbSBPUk0gYW5kIFNlc3Npb24uPGJyPlxuICAgICAqIFNlZSB0aGUgMC45IG1pZ3JhdGlvbiBndWlkZSBpbiB0aGUgR2l0SHViIHJlcG8uXG4gICAgICovXG4gICAgcmVkdWNlKCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBcImBTZXNzaW9uLnByb3RvdHlwZS5yZWR1Y2VgIGhhcyBiZWVuIHJlbW92ZWQuIFRoZSBSZWR1eCBpbnRlZ3JhdGlvbiBBUEkgXCIgK1xuICAgICAgICAgICAgICAgIFwiaXMgbm93IGRlY291cGxlZCBmcm9tIE9STSBhbmQgU2Vzc2lvbiAtIHNlZSB0aGUgMC45IG1pZ3JhdGlvbiBndWlkZSBcIiArXG4gICAgICAgICAgICAgICAgXCJpbiB0aGUgR2l0SHViIHJlcG8uXCJcbiAgICAgICAgKTtcbiAgICB9XG59O1xuXG5leHBvcnQgZGVmYXVsdCBTZXNzaW9uO1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/Session.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var immutable_ops__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! immutable-ops */ \\\"./node_modules/immutable-ops/es/index.js\\\");\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./constants */ \\\"./src/constants.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n\\n\\nconst Session = /*#__PURE__*/function () {\\n  /**\\n   * Creates a new Session.\\n   *\\n   * @param  {Database} db - a {@link Database} instance\\n   * @param  {Object} state - the database state\\n   * @param  {Boolean} [withMutations] - whether the session should mutate data\\n   * @param  {Object} [batchToken] - used by the backend to identify objects that can be\\n   *                                 mutated.\\n   */\\n  function Session(schema, db, state, withMutations, batchToken) {\\n    this.schema = schema;\\n    this.db = db;\\n    this.state = state || db.getEmptyState();\\n    this.initialState = this.state;\\n    this.withMutations = Boolean(withMutations);\\n    this.batchToken = batchToken || Object(immutable_ops__WEBPACK_IMPORTED_MODULE_1__[\\\"getBatchToken\\\"])();\\n    this.modelData = {};\\n    this.models = schema.getModelClasses();\\n    this.sessionBoundModels = this.models.map(modelClass => {\\n      function SessionBoundModel() {\\n        return Reflect.construct(modelClass, arguments, SessionBoundModel); // eslint-disable-line prefer-rest-params\\n      }\\n\\n      Reflect.setPrototypeOf(SessionBoundModel.prototype, modelClass.prototype);\\n      Reflect.setPrototypeOf(SessionBoundModel, modelClass);\\n      Object.defineProperty(this, modelClass.modelName, {\\n        get: () => SessionBoundModel\\n      });\\n      SessionBoundModel.connect(this);\\n      return SessionBoundModel;\\n    });\\n  }\\n\\n  var _proto = Session.prototype;\\n\\n  _proto.getDataForModel = function getDataForModel(modelName) {\\n    if (!this.modelData[modelName]) {\\n      this.modelData[modelName] = {};\\n    }\\n\\n    return this.modelData[modelName];\\n  };\\n\\n  _proto.getModelData = function getModelData() {\\n    return this.modelData;\\n  };\\n\\n  _proto.markAccessed = function markAccessed(modelName, modelIds) {\\n    const data = this.getDataForModel(modelName);\\n\\n    if (!data.accessedInstances) {\\n      data.accessedInstances = {};\\n    }\\n\\n    modelIds.forEach(id => {\\n      data.accessedInstances[id] = true;\\n    });\\n  };\\n\\n  _proto.markFullTableScanned = function markFullTableScanned(modelName) {\\n    const data = this.getDataForModel(modelName);\\n    data.fullTableScanned = true;\\n  };\\n\\n  _proto.markAccessedIndexes = function markAccessedIndexes(indexes) {\\n    indexes.forEach(([table, attr, value]) => {\\n      const data = this.getDataForModel(table);\\n\\n      if (!data.accessedIndexes) {\\n        data.accessedIndexes = {};\\n      }\\n\\n      data.accessedIndexes[attr] = [...(data.accessedIndexes[attr] || []), value];\\n    });\\n  };\\n\\n  /**\\n   * Applies update to a model state.\\n   *\\n   * @private\\n   * @param {Object} update - the update object. Must have keys\\n   *                          `type`, `payload`.\\n   */\\n  _proto.applyUpdate = function applyUpdate(updateSpec) {\\n    const tx = this._getTransaction(updateSpec);\\n\\n    const result = this.db.update(updateSpec, tx, this.state);\\n    const {\\n      status,\\n      state,\\n      payload\\n    } = result;\\n\\n    if (status !== _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"SUCCESS\\\"]) {\\n      throw new Error(`Applying update failed with status ${status}. Payload: ${payload}`);\\n    }\\n\\n    this.state = state;\\n    return payload;\\n  };\\n\\n  _proto.query = function query(querySpec) {\\n    const result = this.db.query(querySpec, this.state);\\n\\n    this._markAccessedByQuery(querySpec, result);\\n\\n    return result;\\n  };\\n\\n  _proto._getTransaction = function _getTransaction(updateSpec) {\\n    const {\\n      withMutations\\n    } = this;\\n    const {\\n      action\\n    } = updateSpec;\\n    let {\\n      batchToken\\n    } = this;\\n\\n    if ([_constants__WEBPACK_IMPORTED_MODULE_2__[\\\"UPDATE\\\"], _constants__WEBPACK_IMPORTED_MODULE_2__[\\\"DELETE\\\"]].includes(action)) {\\n      batchToken = Object(immutable_ops__WEBPACK_IMPORTED_MODULE_1__[\\\"getBatchToken\\\"])();\\n    }\\n\\n    return {\\n      batchToken,\\n      withMutations\\n    };\\n  };\\n\\n  _proto._markAccessedByQuery = function _markAccessedByQuery(querySpec, result) {\\n    const {\\n      table,\\n      clauses\\n    } = querySpec;\\n    const {\\n      rows\\n    } = result;\\n    const {\\n      idAttribute\\n    } = this[table];\\n    const accessedIds = new Set(rows.map(row => row[idAttribute]));\\n    const anyClauseFilteredByPk = clauses.some(clause => {\\n      if (!Object(_utils__WEBPACK_IMPORTED_MODULE_3__[\\\"clauseFiltersByAttribute\\\"])(clause, idAttribute)) {\\n        return false;\\n      }\\n      /**\\n       * We previously knew which row we wanted to access,\\n       * so there was no need to scan the entire table.\\n       */\\n\\n\\n      accessedIds.add(clause.payload[idAttribute]);\\n      return true;\\n    });\\n    const accessedIndexes = [];\\n    const {\\n      indexes\\n    } = this.state[table];\\n    clauses.forEach(clause => {\\n      Object.keys(indexes).forEach(attr => {\\n        if (!Object(_utils__WEBPACK_IMPORTED_MODULE_3__[\\\"clauseFiltersByAttribute\\\"])(clause, attr)) {\\n          return;\\n        }\\n\\n        const value = clause.payload[attr];\\n        accessedIndexes.push([table, attr, value]);\\n      });\\n    });\\n\\n    if (anyClauseFilteredByPk) {\\n      /**\\n       * The clauses have been ordered so that an indexed one was\\n       * the first to have been evaluated, and thus only the row\\n       * with the specified PK value has actually been accessed.\\n       */\\n      this.markAccessed(table, accessedIds);\\n    } else if (accessedIndexes.length) {\\n      /**\\n       * At least one clause was optimized using indexes.\\n       */\\n      this.markAccessed(table, accessedIds);\\n      this.markAccessedIndexes(accessedIndexes);\\n    } else {\\n      /**\\n       * At least one clause could not be efficiently optimized\\n       * or no clause was specified at all.\\n       */\\n      this.markFullTableScanned(table);\\n    }\\n  } // DEPRECATED AND REMOVED METHODS\\n\\n  /**\\n   * @deprecated Access {@link Session#state} instead.\\n   */\\n  ;\\n\\n  _proto.getNextState = function getNextState() {\\n    Object(_utils__WEBPACK_IMPORTED_MODULE_3__[\\\"warnDeprecated\\\"])(\\\"`Session.prototype.getNextState` has been deprecated. Access \\\" + \\\"the `Session.prototype.state` property instead.\\\");\\n    return this.state;\\n  }\\n  /**\\n   * @deprecated\\n   * The Redux integration API is now decoupled from ORM and Session.<br>\\n   * See the 0.9 migration guide in the GitHub repo.\\n   */\\n  ;\\n\\n  _proto.reduce = function reduce() {\\n    throw new Error(\\\"`Session.prototype.reduce` has been removed. The Redux integration API \\\" + \\\"is now decoupled from ORM and Session - see the 0.9 migration guide \\\" + \\\"in the GitHub repo.\\\");\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(Session, [{\\n    key: \\\"accessedModelInstances\\\",\\n    get: function () {\\n      return Object.entries(this.getModelData()).reduce((result, [key, value]) => {\\n        if (value.accessedInstances) {\\n          result[key] = value.accessedInstances;\\n        }\\n\\n        return result;\\n      }, {});\\n    }\\n  }, {\\n    key: \\\"fullTableScannedModels\\\",\\n    get: function () {\\n      return Object.entries(this.getModelData()).reduce((result, [key, value]) => {\\n        if (value.fullTableScanned) {\\n          result.push(key);\\n        }\\n\\n        return result;\\n      }, []);\\n    }\\n  }, {\\n    key: \\\"accessedIndexes\\\",\\n    get: function () {\\n      return Object.entries(this.getModelData()).reduce((result, [key, value]) => {\\n        if (value.accessedIndexes) {\\n          result[key] = value.accessedIndexes;\\n        }\\n\\n        return result;\\n      }, {});\\n    }\\n  }]);\\n\\n  return Session;\\n}();\\n\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (Session);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9TZXNzaW9uLmpzPzU5MWMiXSwibmFtZXMiOlsiU2Vzc2lvbiIsInNjaGVtYSIsImRiIiwic3RhdGUiLCJ3aXRoTXV0YXRpb25zIiwiYmF0Y2hUb2tlbiIsImdldEVtcHR5U3RhdGUiLCJpbml0aWFsU3RhdGUiLCJCb29sZWFuIiwiZ2V0QmF0Y2hUb2tlbiIsIm1vZGVsRGF0YSIsIm1vZGVscyIsImdldE1vZGVsQ2xhc3NlcyIsInNlc3Npb25Cb3VuZE1vZGVscyIsIm1hcCIsIm1vZGVsQ2xhc3MiLCJTZXNzaW9uQm91bmRNb2RlbCIsIlJlZmxlY3QiLCJjb25zdHJ1Y3QiLCJhcmd1bWVudHMiLCJzZXRQcm90b3R5cGVPZiIsInByb3RvdHlwZSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwibW9kZWxOYW1lIiwiZ2V0IiwiY29ubmVjdCIsImdldERhdGFGb3JNb2RlbCIsImdldE1vZGVsRGF0YSIsIm1hcmtBY2Nlc3NlZCIsIm1vZGVsSWRzIiwiZGF0YSIsImFjY2Vzc2VkSW5zdGFuY2VzIiwiZm9yRWFjaCIsImlkIiwibWFya0Z1bGxUYWJsZVNjYW5uZWQiLCJmdWxsVGFibGVTY2FubmVkIiwibWFya0FjY2Vzc2VkSW5kZXhlcyIsImluZGV4ZXMiLCJ0YWJsZSIsImF0dHIiLCJ2YWx1ZSIsImFjY2Vzc2VkSW5kZXhlcyIsImFwcGx5VXBkYXRlIiwidXBkYXRlU3BlYyIsInR4IiwiX2dldFRyYW5zYWN0aW9uIiwicmVzdWx0IiwidXBkYXRlIiwic3RhdHVzIiwicGF5bG9hZCIsIlNVQ0NFU1MiLCJFcnJvciIsInF1ZXJ5IiwicXVlcnlTcGVjIiwiX21hcmtBY2Nlc3NlZEJ5UXVlcnkiLCJhY3Rpb24iLCJVUERBVEUiLCJERUxFVEUiLCJpbmNsdWRlcyIsImNsYXVzZXMiLCJyb3dzIiwiaWRBdHRyaWJ1dGUiLCJhY2Nlc3NlZElkcyIsIlNldCIsInJvdyIsImFueUNsYXVzZUZpbHRlcmVkQnlQayIsInNvbWUiLCJjbGF1c2UiLCJjbGF1c2VGaWx0ZXJzQnlBdHRyaWJ1dGUiLCJhZGQiLCJrZXlzIiwicHVzaCIsImxlbmd0aCIsImdldE5leHRTdGF0ZSIsIndhcm5EZXByZWNhdGVkIiwicmVkdWNlIiwiZW50cmllcyIsImtleSJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFBO0FBRUE7QUFDQTs7QUFFQSxNQUFNQSxPQUFPO0FBQ1Q7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0ksbUJBQVlDLE1BQVosRUFBb0JDLEVBQXBCLEVBQXdCQyxLQUF4QixFQUErQkMsYUFBL0IsRUFBOENDLFVBQTlDLEVBQTBEO0FBQ3RELFNBQUtKLE1BQUwsR0FBY0EsTUFBZDtBQUNBLFNBQUtDLEVBQUwsR0FBVUEsRUFBVjtBQUNBLFNBQUtDLEtBQUwsR0FBYUEsS0FBSyxJQUFJRCxFQUFFLENBQUNJLGFBQUgsRUFBdEI7QUFDQSxTQUFLQyxZQUFMLEdBQW9CLEtBQUtKLEtBQXpCO0FBRUEsU0FBS0MsYUFBTCxHQUFxQkksT0FBTyxDQUFDSixhQUFELENBQTVCO0FBQ0EsU0FBS0MsVUFBTCxHQUFrQkEsVUFBVSxJQUFJSSxtRUFBYSxFQUE3QztBQUVBLFNBQUtDLFNBQUwsR0FBaUIsRUFBakI7QUFFQSxTQUFLQyxNQUFMLEdBQWNWLE1BQU0sQ0FBQ1csZUFBUCxFQUFkO0FBRUEsU0FBS0Msa0JBQUwsR0FBMEIsS0FBS0YsTUFBTCxDQUFZRyxHQUFaLENBQWlCQyxVQUFELElBQWdCO0FBQ3RELGVBQVNDLGlCQUFULEdBQTZCO0FBQ3pCLGVBQU9DLE9BQU8sQ0FBQ0MsU0FBUixDQUNISCxVQURHLEVBRUhJLFNBRkcsRUFHSEgsaUJBSEcsQ0FBUCxDQUR5QixDQUt0QjtBQUNOOztBQUNEQyxhQUFPLENBQUNHLGNBQVIsQ0FDSUosaUJBQWlCLENBQUNLLFNBRHRCLEVBRUlOLFVBQVUsQ0FBQ00sU0FGZjtBQUlBSixhQUFPLENBQUNHLGNBQVIsQ0FBdUJKLGlCQUF2QixFQUEwQ0QsVUFBMUM7QUFFQU8sWUFB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QUMrQixHQUFELENBQU4sR0FBY3JDLEtBQUssQ0FBQ0MsZUFBcEI7QUFDSDs7QUFDRCxlQUFPSyxNQUFQO0FBQ0gsT0FORSxFQU9ILEVBUEcsQ0FBUDtBQVNIO0FBdkhROztBQUFBO0FBQUEsR0FBYjs7QUFrUGUvQyxzRUFBZiIsImZpbGUiOiIuL3NyYy9TZXNzaW9uLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgZ2V0QmF0Y2hUb2tlbiB9IGZyb20gXCJpbW11dGFibGUtb3BzXCI7XG5cbmltcG9ydCB7IFNVQ0NFU1MsIFVQREFURSwgREVMRVRFIH0gZnJvbSBcIi4vY29uc3RhbnRzXCI7XG5pbXBvcnQgeyB3YXJuRGVwcmVjYXRlZCwgY2xhdXNlRmlsdGVyc0J5QXR0cmlidXRlIH0gZnJvbSBcIi4vdXRpbHNcIjtcblxuY29uc3QgU2Vzc2lvbiA9IGNsYXNzIFNlc3Npb24ge1xuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgU2Vzc2lvbi5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge0RhdGFiYXNlfSBkYiAtIGEge0BsaW5rIERhdGFiYXNlfSBpbnN0YW5jZVxuICAgICAqIEBwYXJhbSAge09iamVjdH0gc3RhdGUgLSB0aGUgZGF0YWJhc2Ugc3RhdGVcbiAgICAgKiBAcGFyYW0gIHtCb29sZWFufSBbd2l0aE11dGF0aW9uc10gLSB3aGV0aGVyIHRoZSBzZXNzaW9uIHNob3VsZCBtdXRhdGUgZGF0YVxuICAgICAqIEBwYXJhbSAge09iamVjdH0gW2JhdGNoVG9rZW5dIC0gdXNlZCBieSB0aGUgYmFja2VuZCB0byBpZGVudGlmeSBvYmplY3RzIHRoYXQgY2FuIGJlXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGVkLlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKHNjaGVtYSwgZGIsIHN0YXRlLCB3aXRoTXV0YXRpb25zLCBiYXRjaFRva2VuKSB7XG4gICAgICAgIHRoaXMuc2NoZW1hID0gc2NoZW1hO1xuICAgICAgICB0aGlzLmRiID0gZGI7XG4gICAgICAgIHRoaXMuc3RhdGUgPSBzdGF0ZSB8fCBkYi5nZXRFbXB0eVN0YXRlKCk7XG4gICAgICAgIHRoaXMuaW5pdGlhbFN0YXRlID0gdGhpcy5zdGF0ZTtcblxuICAgICAgICB0aGlzLndpdGhNdXRhdGlvbnMgPSBCb29sZWFuKHdpdGhNdXRhdGlvbnMpO1xuICAgICAgICB0aGlzLmJhdGNoVG9rZW4gPSBiYXRjaFRva2VuIHx8IGdldEJhdGNoVG9rZW4oKTtcblxuICAgICAgICB0aGlzLm1vZGVsRGF0YSA9IHt9O1xuXG4gICAgICAgIHRoaXMubW9kZWxzID0gc2NoZW1hLmdldE1vZGVsQ2xhc3NlcygpO1xuXG4gICAgICAgIHRoaXMuc2Vzc2lvbkJvdW5kTW9kZWxzID0gdGhpcy5tb2RlbHMubWFwKChtb2RlbENsYXNzKSA9PiB7XG4gICAgICAgICAgICBmdW5jdGlvbiBTZXNzaW9uQm91bmRNb2RlbCgpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gUmVmbGVjdC5jb25zdHJ1Y3QoXG4gICAgICAgICAgICAgICAgICAgIG1vZGVsQ2xhc3MsXG4gICAgICAgICAgICAgICAgICAgIGFyZ3VtZW50cyxcbiAgICAgICAgICAgICAgICAgICAgU2Vzc2lvbkJvdW5kTW9kZWxcbiAgICAgICAgICAgICAgICApOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIHByZWZlci1yZXN0LXBhcmFtc1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgUmVmbGVjdC5zZXRQcm90b3R5cGVPZihcbiAgICAgICAgICAgICAgICBTZXNzaW9uQm91bmRNb2RlbC5wcm90b3R5cGUsXG4gICAgICAgICAgICAgICAgbW9kZWxDbGFzcy5wcm90b3R5cGVcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICBSZWZsZWN0LnNldFByb3RvdHlwZU9mKFNlc3Npb25Cb3VuZE1vZGVsLCBtb2RlbENsYXNzKTtcblxuICAgICAgICAgICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsIG1vZGVsQ2xhc3MubW9kZWxOYW1lLCB7XG4gICAgICAgICAgICAgICAgZ2V0OiAoKSA9PiBTZXNzaW9uQm91bmRNb2RlbCxcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICBTZXNzaW9uQm91bmRNb2RlbC5jb25uZWN0KHRoaXMpO1xuICAgICAgICAgICAgcmV0dXJuIFNlc3Npb25Cb3VuZE1vZGVsO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBnZXREYXRhRm9yTW9kZWwobW9kZWxOYW1lKSB7XG4gICAgICAgIGlmICghdGhpcy5tb2RlbERhdGFbbW9kZWxOYW1lXSkge1xuICAgICAgICAgICAgdGhpcy5tb2RlbERhdGFbbW9kZWxOYW1lXSA9IHt9O1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLm1vZGVsRGF0YVttb2RlbE5hbWVdO1xuICAgIH1cblxuICAgIGdldE1vZGVsRGF0YSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMubW9kZWxEYXRhO1xuICAgIH1cblxuICAgIG1hcmtBY2Nlc3NlZChtb2RlbE5hbWUsIG1vZGVsSWRzKSB7XG4gICAgICAgIGNvbnN0IGRhdGEgPSB0aGlzLmdldERhdGFGb3JNb2RlbChtb2RlbE5hbWUpO1xuICAgICAgICBpZiAoIWRhdGEuYWNjZXNzZWRJbnN0YW5jZXMpIHtcbiAgICAgICAgICAgIGRhdGEuYWNjZXNzZWRJbnN0YW5jZXMgPSB7fTtcbiAgICAgICAgfVxuICAgICAgICBtb2RlbElkcy5mb3JFYWNoKChpZCkgPT4ge1xuICAgICAgICAgICAgZGF0YS5hY2Nlc3NlZEluc3RhbmNlc1tpZF0gPSB0cnVlO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBnZXQgYWNjZXNzZWRNb2RlbEluc3RhbmNlcygpIHtcbiAgICAgICAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKHRoaXMuZ2V0TW9kZWxEYXRhKCkpLnJlZHVjZShcbiAgICAgICAgICAgIChyZXN1bHQsIFtrZXksIHZhbHVlXSkgPT4ge1xuICAgICAgICAgICAgICAgIGlmICh2YWx1ZS5hY2Nlc3NlZEluc3RhbmNlcykge1xuICAgICAgICAgICAgICAgICAgICByZXN1bHRba2V5XSA9IHZhbHVlLmFjY2Vzc2VkSW5zdGFuY2VzO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIHt9XG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgbWFya0Z1bGxUYWJsZVNjYW5uZWQobW9kZWxOYW1lKSB7XG4gICAgICAgIGNvbnN0IGRhdGEgPSB0aGlzLmdldERhdGFGb3JNb2RlbChtb2RlbE5hbWUpO1xuICAgICAgICBkYXRhLmZ1bGxUYWJsZVNjYW5uZWQgPSB0cnVlO1xuICAgIH1cblxuICAgIGdldCBmdWxsVGFibGVTY2FubmVkTW9kZWxzKCkge1xuICAgICAgICByZXR1cm4gT2JqZWN0LmVudHJpZXModGhpcy5nZXRNb2RlbERhdGEoKSkucmVkdWNlKFxuICAgICAgICAgICAgKHJlc3VsdCwgW2tleSwgdmFsdWVdKSA9PiB7XG4gICAgICAgICAgICAgICAgaWYgKHZhbHVlLmZ1bGxUYWJsZVNjYW5uZWQpIHtcbiAgICAgICAgICAgICAgICAgICAgcmVzdWx0LnB1c2goa2V5KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBbXVxuICAgICAgICApO1xuICAgIH1cblxuICAgIG1hcmtBY2Nlc3NlZEluZGV4ZXMoaW5kZXhlcykge1xuICAgICAgICBpbmRleGVzLmZvckVhY2goKFt0YWJsZSwgYXR0ciwgdmFsdWVdKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBkYXRhID0gdGhpcy5nZXREYXRhRm9yTW9kZWwodGFibGUpO1xuICAgICAgICAgICAgaWYgKCFkYXRhLmFjY2Vzc2VkSW5kZXhlcykge1xuICAgICAgICAgICAgICAgIGRhdGEuYWNjZXNzZWRJbmRleGVzID0ge307XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBkYXRhLmFjY2Vzc2VkSW5kZXhlc1thdHRyXSA9IFtcbiAgICAgICAgICAgICAgICAuLi4oZGF0YS5hY2Nlc3NlZEluZGV4ZXNbYXR0cl0gfHwgW10pLFxuICAgICAgICAgICAgICAgIHZhbHVlLFxuICAgICAgICAgICAgXTtcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgZ2V0IGFjY2Vzc2VkSW5kZXhlcygpIHtcbiAgICAgICAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKHRoaXMuZ2V0TW9kZWxEYXRhKCkpLnJlZHVjZShcbiAgICAgICAgICAgIChyZXN1bHQsIFtrZXksIHZhbHVlXSkgPT4ge1xuICAgICAgICAgICAgICAgIGlmICh2YWx1ZS5hY2Nlc3NlZEluZGV4ZXMpIHtcbiAgICAgICAgICAgICAgICAgICAgcmVzdWx0W2tleV0gPSB2YWx1ZS5hY2Nlc3NlZEluZGV4ZXM7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAge31cbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBcHBsaWVzIHVwZGF0ZSB0byBhIG1vZGVsIHN0YXRlLlxuICAgICAqXG4gICAgICogQHByaXZhdGVcbiAgICAgKiBAcGFyYW0ge09iamVjdH0gdXBkYXRlIC0gdGhlIHVwZGF0ZSBvYmplY3QuIE11c3QgaGF2ZSBrZXlzXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgICAgIGB0eXBlYCwgYHBheWxvYWRgLlxuICAgICAqL1xuICAgIGFwcGx5VXBkYXRlKHVwZGF0ZVNwZWMpIHtcbiAgICAgICAgY29uc3QgdHggPSB0aGlzLl9nZXRUcmFuc2FjdGlvbih1cGRhdGVTcGVjKTtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gdGhpcy5kYi51cGRhdGUodXBkYXRlU3BlYywgdHgsIHRoaXMuc3RhdGUpO1xuICAgICAgICBjb25zdCB7IHN0YXR1cywgc3RhdGUsIHBheWxvYWQgfSA9IHJlc3VsdDtcblxuICAgICAgICBpZiAoc3RhdHVzICE9PSBTVUNDRVNTKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgYEFwcGx5aW5nIHVwZGF0ZSBmYWlsZWQgd2l0aCBzdGF0dXMgJHtzdGF0dXN9LiBQYXlsb2FkOiAke3BheWxvYWR9YFxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuc3RhdGUgPSBzdGF0ZTtcblxuICAgICAgICByZXR1cm4gcGF5bG9hZDtcbiAgICB9XG5cbiAgICBxdWVyeShxdWVyeVNwZWMpIHtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gdGhpcy5kYi5xdWVyeShxdWVyeVNwZWMsIHRoaXMuc3RhdGUpO1xuXG4gICAgICAgIHRoaXMuX21hcmtBY2Nlc3NlZEJ5UXVlcnkocXVlcnlTcGVjLCByZXN1bHQpO1xuXG4gICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfVxuXG4gICAgX2dldFRyYW5zYWN0aW9uKHVwZGF0ZVNwZWMpIHtcbiAgICAgICAgY29uc3QgeyB3aXRoTXV0YXRpb25zIH0gPSB0aGlzO1xuICAgICAgICBjb25zdCB7IGFjdGlvbiB9ID0gdXBkYXRlU3BlYztcbiAgICAgICAgbGV0IHsgYmF0Y2hUb2tlbiB9ID0gdGhpcztcbiAgICAgICAgaWYgKFtVUERBVEUsIERFTEVURV0uaW5jbHVkZXMoYWN0aW9uKSkge1xuICAgICAgICAgICAgYmF0Y2hUb2tlbiA9IGdldEJhdGNoVG9rZW4oKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4geyBiYXRjaFRva2VuLCB3aXRoTXV0YXRpb25zIH07XG4gICAgfVxuXG4gICAgX21hcmtBY2Nlc3NlZEJ5UXVlcnkocXVlcnlTcGVjLCByZXN1bHQpIHtcbiAgICAgICAgY29uc3QgeyB0YWJsZSwgY2xhdXNlcyB9ID0gcXVlcnlTcGVjO1xuICAgICAgICBjb25zdCB7IHJvd3MgfSA9IHJlc3VsdDtcblxuICAgICAgICBjb25zdCB7IGlkQXR0cmlidXRlIH0gPSB0aGlzW3RhYmxlXTtcbiAgICAgICAgY29uc3QgYWNjZXNzZWRJZHMgPSBuZXcgU2V0KHJvd3MubWFwKChyb3cpID0+IHJvd1tpZEF0dHJpYnV0ZV0pKTtcblxuICAgICAgICBjb25zdCBhbnlDbGF1c2VGaWx0ZXJlZEJ5UGsgPSBjbGF1c2VzLnNvbWUoKGNsYXVzZSkgPT4ge1xuICAgICAgICAgICAgaWYgKCFjbGF1c2VGaWx0ZXJzQnlBdHRyaWJ1dGUoY2xhdXNlLCBpZEF0dHJpYnV0ZSkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIFdlIHByZXZpb3VzbHkga25ldyB3aGljaCByb3cgd2Ugd2FudGVkIHRvIGFjY2VzcyxcbiAgICAgICAgICAgICAqIHNvIHRoZXJlIHdhcyBubyBuZWVkIHRvIHNjYW4gdGhlIGVudGlyZSB0YWJsZS5cbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgYWNjZXNzZWRJZHMuYWRkKGNsYXVzZS5wYXlsb2FkW2lkQXR0cmlidXRlXSk7XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfSk7XG5cbiAgICAgICAgY29uc3QgYWNjZXNzZWRJbmRleGVzID0gW107XG4gICAgICAgIGNvbnN0IHsgaW5kZXhlcyB9ID0gdGhpcy5zdGF0ZVt0YWJsZV07XG4gICAgICAgIGNsYXVzZXMuZm9yRWFjaCgoY2xhdXNlKSA9PiB7XG4gICAgICAgICAgICBPYmplY3Qua2V5cyhpbmRleGVzKS5mb3JFYWNoKChhdHRyKSA9PiB7XG4gICAgICAgICAgICAgICAgaWYgKCFjbGF1c2VGaWx0ZXJzQnlBdHRyaWJ1dGUoY2xhdXNlLCBhdHRyKSkge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGNvbnN0IHZhbHVlID0gY2xhdXNlLnBheWxvYWRbYXR0cl07XG4gICAgICAgICAgICAgICAgYWNjZXNzZWRJbmRleGVzLnB1c2goW3RhYmxlLCBhdHRyLCB2YWx1ZV0pO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIGlmIChhbnlDbGF1c2VGaWx0ZXJlZEJ5UGspIHtcbiAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICogVGhlIGNsYXVzZXMgaGF2ZSBiZWVuIG9yZGVyZWQgc28gdGhhdCBhbiBpbmRleGVkIG9uZSB3YXNcbiAgICAgICAgICAgICAqIHRoZSBmaXJzdCB0byBoYXZlIGJlZW4gZXZhbHVhdGVkLCBhbmQgdGh1cyBvbmx5IHRoZSByb3dcbiAgICAgICAgICAgICAqIHdpdGggdGhlIHNwZWNpZmllZCBQSyB2YWx1ZSBoYXMgYWN0dWFsbHkgYmVlbiBhY2Nlc3NlZC5cbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgdGhpcy5tYXJrQWNjZXNzZWQodGFibGUsIGFjY2Vzc2VkSWRzKTtcbiAgICAgICAgfSBlbHNlIGlmIChhY2Nlc3NlZEluZGV4ZXMubGVuZ3RoKSB7XG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIEF0IGxlYXN0IG9uZSBjbGF1c2Ugd2FzIG9wdGltaXplZCB1c2luZyBpbmRleGVzLlxuICAgICAgICAgICAgICovXG4gICAgICAgICAgICB0aGlzLm1hcmtBY2Nlc3NlZCh0YWJsZSwgYWNjZXNzZWRJZHMpO1xuICAgICAgICAgICAgdGhpcy5tYXJrQWNjZXNzZWRJbmRleGVzKGFjY2Vzc2VkSW5kZXhlcyk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIEF0IGxlYXN0IG9uZSBjbGF1c2UgY291bGQgbm90IGJlIGVmZmljaWVudGx5IG9wdGltaXplZFxuICAgICAgICAgICAgICogb3Igbm8gY2xhdXNlIHdhcyBzcGVjaWZpZWQgYXQgYWxsLlxuICAgICAgICAgICAgICovXG4gICAgICAgICAgICB0aGlzLm1hcmtGdWxsVGFibGVTY2FubmVkKHRhYmxlKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIERFUFJFQ0FURUQgQU5EIFJFTU9WRUQgTUVUSE9EU1xuXG4gICAgLyoqXG4gICAgICogQGRlcHJlY2F0ZWQgQWNjZXNzIHtAbGluayBTZXNzaW9uI3N0YXRlfSBpbnN0ZWFkLlxuICAgICAqL1xuICAgIGdldE5leHRTdGF0ZSgpIHtcbiAgICAgICAgd2FybkRlcHJlY2F0ZWQoXG4gICAgICAgICAgICBcImBTZXNzaW9uLnByb3RvdHlwZS5nZXROZXh0U3RhdGVgIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIEFjY2VzcyBcIiArXG4gICAgICAgICAgICAgICAgXCJ0aGUgYFNlc3Npb24ucHJvdG90eXBlLnN0YXRlYCBwcm9wZXJ0eSBpbnN0ZWFkLlwiXG4gICAgICAgICk7XG4gICAgICAgIHJldHVybiB0aGlzLnN0YXRlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEBkZXByZWNhdGVkXG4gICAgICogVGhlIFJlZHV4IGludGVncmF0aW9uIEFQSSBpcyBub3cgZGVjb3VwbGVkIGZyb20gT1JNIGFuZCBTZXNzaW9uLjxicj5cbiAgICAgKiBTZWUgdGhlIDAuOSBtaWdyYXRpb24gZ3VpZGUgaW4gdGhlIEdpdEh1YiByZXBvLlxuICAgICAqL1xuICAgIHJlZHVjZSgpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgXCJgU2Vzc2lvbi5wcm90b3R5cGUucmVkdWNlYCBoYXMgYmVlbiByZW1vdmVkLiBUaGUgUmVkdXggaW50ZWdyYXRpb24gQVBJIFwiICtcbiAgICAgICAgICAgICAgICBcImlzIG5vdyBkZWNvdXBsZWQgZnJvbSBPUk0gYW5kIFNlc3Npb24gLSBzZWUgdGhlIDAuOSBtaWdyYXRpb24gZ3VpZGUgXCIgK1xuICAgICAgICAgICAgICAgIFwiaW4gdGhlIEdpdEh1YiByZXBvLlwiXG4gICAgICAgICk7XG4gICAgfVxufTtcblxuZXhwb3J0IGRlZmF1bHQgU2Vzc2lvbjtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/Session.js\\n\");\n \n /***/ }),\n \n@@ -4510,7 +4532,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"createDatabase\\\", function() { return createDatabase; });\\n/* harmony import */ var immutable_ops__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! immutable-ops */ \\\"./node_modules/immutable-ops/es/index.js\\\");\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../constants */ \\\"./src/constants.js\\\");\\n/* harmony import */ var _Table__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Table */ \\\"./src/db/Table.js\\\");\\n\\n\\n\\nconst BASE_EMPTY_STATE = {};\\nObject.defineProperty(BASE_EMPTY_STATE, _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"STATE_FLAG\\\"], {\\n  enumerable: true,\\n  value: true\\n});\\n/** @private */\\n\\nfunction replaceTableState(tableName, newTableState, tx, state) {\\n  const {\\n    batchToken,\\n    withMutations\\n  } = tx;\\n\\n  if (withMutations) {\\n    state[tableName] = newTableState;\\n    return state;\\n  }\\n\\n  return immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.set(batchToken, tableName, newTableState, state);\\n}\\n/** @private */\\n\\n\\nfunction query(tables, querySpec, state) {\\n  const {\\n    table: tableName,\\n    clauses\\n  } = querySpec;\\n  const table = tables[tableName];\\n  const rows = table.query(state[tableName], clauses);\\n  return {\\n    rows\\n  };\\n}\\n/** @private */\\n\\n\\nfunction update(tables, updateSpec, tx, state) {\\n  const {\\n    action,\\n    payload\\n  } = updateSpec;\\n  let tableName;\\n  let nextTableState;\\n  let resultPayload;\\n\\n  if (action === _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"CREATE\\\"]) {\\n    ({\\n      table: tableName\\n    } = updateSpec);\\n    const table = tables[tableName];\\n    const currTableState = state[tableName];\\n    const result = table.insert(tx, currTableState, payload);\\n    nextTableState = result.state;\\n    resultPayload = result.created;\\n  } else {\\n    const {\\n      query: querySpec\\n    } = updateSpec;\\n    ({\\n      table: tableName\\n    } = querySpec);\\n    const {\\n      rows\\n    } = query(tables, querySpec, state);\\n    const table = tables[tableName];\\n    const currTableState = state[tableName];\\n\\n    if (action === _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"UPDATE\\\"]) {\\n      nextTableState = table.update(tx, currTableState, rows, payload); // return updated rows\\n\\n      resultPayload = query(tables, querySpec, state).rows;\\n    } else if (action === _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"DELETE\\\"]) {\\n      nextTableState = table.delete(tx, currTableState, rows); // return original rows that we just deleted\\n\\n      resultPayload = rows;\\n    } else {\\n      throw new Error(`Database received unknown update type: ${action}`);\\n    }\\n  }\\n\\n  const nextDBState = replaceTableState(tableName, nextTableState, tx, state);\\n  return {\\n    status: _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"SUCCESS\\\"],\\n    state: nextDBState,\\n    payload: resultPayload\\n  };\\n}\\n/**\\n * @memberof db\\n * @param {Object} schemaSpec\\n * @return Object database\\n */\\n\\n\\nfunction createDatabase(schemaSpec) {\\n  const {\\n    tables: tableSpecs\\n  } = schemaSpec;\\n  const tables = Object.entries(tableSpecs).reduce((map, [tableName, tableSpec]) => ({ ...map,\\n    [tableName]: new _Table__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"](tableSpec)\\n  }), {});\\n\\n  const getEmptyState = () => Object.entries(tables).reduce((map, [tableName, table]) => ({ ...map,\\n    [tableName]: table.getEmptyState()\\n  }), BASE_EMPTY_STATE);\\n\\n  return {\\n    getEmptyState,\\n    query: query.bind(null, tables),\\n    update: update.bind(null, tables),\\n    // Used to inspect the schema.\\n    describe: tableName => tables[tableName]\\n  };\\n}\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (createDatabase);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9kYi9EYXRhYmFzZS5qcz9lZjAxIl0sIm5hbWVzIjpbIkJBU0VfRU1QVFlfU1RBVEUiLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsIlNUQVRFX0ZMQUciLCJlbnVtZXJhYmxlIiwidmFsdWUiLCJyZXBsYWNlVGFibGVTdGF0ZSIsInRhYmxlTmFtZSIsIm5ld1RhYmxlU3RhdGUiLCJ0eCIsInN0YXRlIiwiYmF0Y2hUb2tlbiIsIndpdGhNdXRhdGlvbnMiLCJvcHMiLCJiYXRjaCIsInNldCIsInF1ZXJ5IiwidGFibGVzIiwicXVlcnlTcGVjIiwidGFibGUiLCJjbGF1c2VzIiwicm93cyIsInVwZGF0ZSIsInVwZGF0ZVNwZWMiLCJhY3Rpb24iLCJwYXlsb2FkIiwibmV4dFRhYmxlU3RhdGUiLCJyZXN1bHRQYXlsb2FkIiwiQ1JFQVRFIiwiY3VyclRhYmxlU3RhdGUiLCJyZXN1bHQiLCJpbnNlcnQiLCJjcmVhdGVkIiwiVVBEQVRFIiwiREVMRVRFIiwiZGVsZXRlIiwiRXJyb3IiLCJuZXh0REJTdGF0ZSIsInN0YXR1cyIsIlNVQ0NFU1MiLCJjcmVhdGVEYXRhYmFzZSIsInNjaGVtYVNwZWMiLCJ0YWJsZVNwZWNzIiwiZW50cmllcyIsInJlZHVjZSIsIm1hcCIsInRhYmxlU3BlYyIsIlRhYmxlIiwiZ2V0RW1wdHlTdGF0ZSIsImJpbmQiLCJkZXNjcmliZSJdLCJtYXBwaW5ncyI6IkFBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBRUE7QUFFQTtBQUVBLE1BQU1BLGdCQUFnQixHQUFHLEVBQXpCO0FBQ0FDLE1BQU0sQ0FBQ0MsY0FBUCxDQUFzQkYsZ0JBQXRCLEVBQXdDRyxxREFBeEMsRUFBb0Q7QUFDaERDLFlBQVUsRUFBRSxJQURvQztBQUVoREMsT0FBSyxFQUFFO0FBRnlDLENBQXBEO0FBS0E7O0FBQ0EsU0FBU0MsaUJBQVQsQ0FBMkJDLFNBQTNCLEVBQXNDQyxhQUF0QyxFQUFxREMsRUFBckQsRUFBeURD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bGUiOiIuL3NyYy9kYi9EYXRhYmFzZS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBvcHMgZnJvbSBcImltbXV0YWJsZS1vcHNcIjtcblxuaW1wb3J0IHsgQ1JFQVRFLCBVUERBVEUsIERFTEVURSwgU1VDQ0VTUywgU1RBVEVfRkxBRyB9IGZyb20gXCIuLi9jb25zdGFudHNcIjtcblxuaW1wb3J0IFRhYmxlIGZyb20gXCIuL1RhYmxlXCI7XG5cbmNvbnN0IEJBU0VfRU1QVFlfU1RBVEUgPSB7fTtcbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShCQVNFX0VNUFRZX1NUQVRFLCBTVEFURV9GTEFHLCB7XG4gICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICB2YWx1ZTogdHJ1ZSxcbn0pO1xuXG4vKiogQHByaXZhdGUgKi9cbmZ1bmN0aW9uIHJlcGxhY2VUYWJsZVN0YXRlKHRhYmxlTmFtZSwgbmV3VGFibGVTdGF0ZSwgdHgsIHN0YXRlKSB7XG4gICAgY29uc3QgeyBiYXRjaFRva2VuLCB3aXRoTXV0YXRpb25zIH0gPSB0eDtcblxuICAgIGlmICh3aXRoTXV0YXRpb25zKSB7XG4gICAgICAgIHN0YXRlW3RhYmxlTmFtZV0gPSBuZXdUYWJsZVN0YXRlO1xuICAgICAgICByZXR1cm4gc3RhdGU7XG4gICAgfVxuXG4gICAgcmV0dXJuIG9wcy5iYXRjaC5zZXQoYmF0Y2hUb2tlbiwgdGFibGVOYW1lLCBuZXdUYWJsZVN0YXRlLCBzdGF0ZSk7XG59XG5cbi8qKiBAcHJpdmF0ZSAqL1xuZnVuY3Rpb24gcXVlcnkodGFibGVzLCBxdWVyeVNwZWMsIHN0YXRlKSB7XG4gICAgY29uc3QgeyB0YWJsZTogdGFibGVOYW1lLCBjbGF1c2VzIH0gPSBxdWVyeVNwZWM7XG4gICAgY29uc3QgdGFibGUgPSB0YWJsZXNbdGFibGVOYW1lXTtcbiAgICBjb25zdCByb3dzID0gdGFibGUucXVlcnkoc3RhdGVbdGFibGVOYW1lXSwgY2xhdXNlcyk7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgcm93cyxcbiAgICB9O1xufVxuXG4vKiogQHByaXZhdGUgKi9cbmZ1bmN0aW9uIHVwZGF0ZSh0YWJsZXMsIHVwZGF0ZVNwZWMsIHR4LCBzdGF0ZSkge1xuICAgIGNvbnN0IHsgYWN0aW9uLCBwYXlsb2FkIH0gPSB1cGRhdGVTcGVjO1xuXG4gICAgbGV0IHRhYmxlTmFtZTtcbiAgICBsZXQgbmV4dFRhYmxlU3RhdGU7XG4gICAgbGV0IHJlc3VsdFBheWxvYWQ7XG5cbiAgICBpZiAoYWN0aW9uID09PSBDUkVBVEUpIHtcbiAgICAgICAgKHsgdGFibGU6IHRhYmxlTmFtZSB9ID0gdXBkYXRlU3BlYyk7XG4gICAgICAgIGNvbnN0IHRhYmxlID0gdGFibGVzW3RhYmxlTmFtZV07XG4gICAgICAgIGNvbnN0IGN1cnJUYWJsZVN0YXRlID0gc3RhdGVbdGFibGVOYW1lXTtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gdGFibGUuaW5zZXJ0KHR4LCBjdXJyVGFibGVTdGF0ZSwgcGF5bG9hZCk7XG4gICAgICAgIG5leHRUYWJsZVN0YXRlID0gcmVzdWx0LnN0YXRlO1xuICAgICAgICByZXN1bHRQYXlsb2FkID0gcmVzdWx0LmNyZWF0ZWQ7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgY29uc3QgeyBxdWVyeTogcXVlcnlTcGVjIH0gPSB1cGRhdGVTcGVjO1xuICAgICAgICAoeyB0YWJsZTogdGFibGVOYW1lIH0gPSBxdWVyeVNwZWMpO1xuICAgICAgICBjb25zdCB7IHJvd3MgfSA9IHF1ZXJ5KHRhYmxlcywgcXVlcnlTcGVjLCBzdGF0ZSk7XG5cbiAgICAgICAgY29uc3QgdGFibGUgPSB0YWJsZXNbdGFibGVOYW1lXTtcbiAgICAgICAgY29uc3QgY3VyclRhYmxlU3RhdGUgPSBzdGF0ZVt0YWJsZU5hbWVdO1xuXG4gICAgICAgIGlmIChhY3Rpb24gPT09IFVQREFURSkge1xuICAgICAgICAgICAgbmV4dFRhYmxlU3RhdGUgPSB0YWJsZS51cGRhdGUodHgsIGN1cnJUYWJsZVN0YXRlLCByb3dzLCBwYXlsb2FkKTtcbiAgICAgICAgICAgIC8vIHJldHVybiB1cGRhdGVkIHJvd3NcbiAgICAgICAgICAgIHJlc3VsdFBheWxvYWQgPSBxdWVyeSh0YWJsZXMsIHF1ZXJ5U3BlYywgc3RhdGUpLnJvd3M7XG4gICAgICAgIH0gZWxzZSBpZiAoYWN0aW9uID09PSBERUxFVEUpIHtcbiAgICAgICAgICAgIG5leHRUYWJsZVN0YXRlID0gdGFibGUuZGVsZXRlKHR4LCBjdXJyVGFibGVTdGF0ZSwgcm93cyk7XG4gICAgICAgICAgICAvLyByZXR1cm4gb3JpZ2luYWwgcm93cyB0aGF0IHdlIGp1c3QgZGVsZXRlZFxuICAgICAgICAgICAgcmVzdWx0UGF5bG9hZCA9IHJvd3M7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYERhdGFiYXNlIHJlY2VpdmVkIHVua25vd24gdXBkYXRlIHR5cGU6ICR7YWN0aW9ufWApO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgY29uc3QgbmV4dERCU3RhdGUgPSByZXBsYWNlVGFibGVTdGF0ZSh0YWJsZU5hbWUsIG5leHRUYWJsZVN0YXRlLCB0eCwgc3RhdGUpO1xuICAgIHJldHVybiB7XG4gICAgICAgIHN0YXR1czogU1VDQ0VTUyxcbiAgICAgICAgc3RhdGU6IG5leHREQlN0YXRlLFxuICAgICAgICBwYXlsb2FkOiByZXN1bHRQYXlsb2FkLFxuICAgIH07XG59XG5cbi8qKlxuICogQG1lbWJlcm9mIGRiXG4gKiBAcGFyYW0ge09iamVjdH0gc2NoZW1hU3BlY1xuICogQHJldHVybiBPYmplY3QgZGF0YWJhc2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZURhdGFiYXNlKHNjaGVtYVNwZWMpIHtcbiAgICBjb25zdCB7IHRhYmxlczogdGFibGVTcGVjcyB9ID0gc2NoZW1hU3BlYztcbiAgICBjb25zdCB0YWJsZXMgPSBPYmplY3QuZW50cmllcyh0YWJsZVNwZWNzKS5yZWR1Y2UoXG4gICAgICAgIChtYXAsIFt0YWJsZU5hbWUsIHRhYmxlU3BlY10pID0+ICh7XG4gICAgICAgICAgICAuLi5tYXAsXG4gICAgICAgICAgICBbdGFibGVOYW1lXTogbmV3IFRhYmxlKHRhYmxlU3BlYyksXG4gICAgICAgIH0pLFxuICAgICAgICB7fVxuICAgICk7XG5cbiAgICBjb25zdCBnZXRFbXB0eVN0YXRlID0gKCkgPT5cbiAgICAgICAgT2JqZWN0LmVudHJpZXModGFibGVzKS5yZWR1Y2UoXG4gICAgICAgICAgICAobWFwLCBbdGFibGVOYW1lLCB0YWJsZV0pID0+ICh7XG4gICAgICAgICAgICAgICAgLi4ubWFwLFxuICAgICAgICAgICAgICAgIFt0YWJsZU5hbWVdOiB0YWJsZS5nZXRFbXB0eVN0YXRlKCksXG4gICAgICAgICAgICB9KSxcbiAgICAgICAgICAgIEJBU0VfRU1QVFlfU1RBVEVcbiAgICAgICAgKTtcblxuICAgIHJldHVybiB7XG4gICAgICAgIGdldEVtcHR5U3RhdGUsXG4gICAgICAgIHF1ZXJ5OiBxdWVyeS5iaW5kKG51bGwsIHRhYmxlcyksXG4gICAgICAgIHVwZGF0ZTogdXBkYXRlLmJpbmQobnVsbCwgdGFibGVzKSxcbiAgICAgICAgLy8gVXNlZCB0byBpbnNwZWN0IHRoZSBzY2hlbWEuXG4gICAgICAgIGRlc2NyaWJlOiB0YWJsZU5hbWUgPT4gdGFibGVzW3RhYmxlTmFtZV0sXG4gICAgfTtcbn1cblxuZXhwb3J0IGRlZmF1bHQgY3JlYXRlRGF0YWJhc2U7XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./src/db/Database.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"createDatabase\\\", function() { return createDatabase; });\\n/* harmony import */ var immutable_ops__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! immutable-ops */ \\\"./node_modules/immutable-ops/es/index.js\\\");\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../constants */ \\\"./src/constants.js\\\");\\n/* harmony import */ var _Table__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Table */ \\\"./src/db/Table.js\\\");\\n\\n\\n\\nconst BASE_EMPTY_STATE = {};\\nObject.defineProperty(BASE_EMPTY_STATE, _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"STATE_FLAG\\\"], {\\n  enumerable: true,\\n  value: true\\n});\\n/** @private */\\n\\nfunction replaceTableState(tableName, newTableState, tx, state) {\\n  const {\\n    batchToken,\\n    withMutations\\n  } = tx;\\n\\n  if (withMutations) {\\n    state[tableName] = newTableState;\\n    return state;\\n  }\\n\\n  return immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.set(batchToken, tableName, newTableState, state);\\n}\\n/** @private */\\n\\n\\nfunction query(tables, querySpec, state) {\\n  const {\\n    table: tableName,\\n    clauses\\n  } = querySpec;\\n  const table = tables[tableName];\\n  const rows = table.query(state[tableName], clauses);\\n  return {\\n    rows\\n  };\\n}\\n/** @private */\\n\\n\\nfunction update(tables, updateSpec, tx, state) {\\n  const {\\n    action,\\n    payload\\n  } = updateSpec;\\n  let tableName;\\n  let nextTableState;\\n  let resultPayload;\\n\\n  if (action === _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"CREATE\\\"]) {\\n    ({\\n      table: tableName\\n    } = updateSpec);\\n    const table = tables[tableName];\\n    const currTableState = state[tableName];\\n    const result = table.insert(tx, currTableState, payload);\\n    nextTableState = result.state;\\n    resultPayload = result.created;\\n  } else {\\n    const {\\n      query: querySpec\\n    } = updateSpec;\\n    ({\\n      table: tableName\\n    } = querySpec);\\n    const {\\n      rows\\n    } = query(tables, querySpec, state);\\n    const table = tables[tableName];\\n    const currTableState = state[tableName];\\n\\n    if (action === _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"UPDATE\\\"]) {\\n      nextTableState = table.update(tx, currTableState, rows, payload); // return updated rows\\n\\n      resultPayload = query(tables, querySpec, state).rows;\\n    } else if (action === _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"DELETE\\\"]) {\\n      nextTableState = table.delete(tx, currTableState, rows); // return original rows that we just deleted\\n\\n      resultPayload = rows;\\n    } else {\\n      throw new Error(`Database received unknown update type: ${action}`);\\n    }\\n  }\\n\\n  const nextDBState = replaceTableState(tableName, nextTableState, tx, state);\\n  return {\\n    status: _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"SUCCESS\\\"],\\n    state: nextDBState,\\n    payload: resultPayload\\n  };\\n}\\n/**\\n * @memberof db\\n * @param {Object} schemaSpec\\n * @return Object database\\n */\\n\\n\\nfunction createDatabase(schemaSpec) {\\n  const {\\n    tables: tableSpecs\\n  } = schemaSpec;\\n  const tables = Object.entries(tableSpecs).reduce((map, [tableName, tableSpec]) => ({ ...map,\\n    [tableName]: new _Table__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"](tableSpec)\\n  }), {});\\n\\n  const getEmptyState = () => Object.entries(tables).reduce((map, [tableName, table]) => ({ ...map,\\n    [tableName]: table.getEmptyState()\\n  }), BASE_EMPTY_STATE);\\n\\n  return {\\n    getEmptyState,\\n    query: query.bind(null, tables),\\n    update: update.bind(null, tables),\\n    // Used to inspect the schema.\\n    describe: tableName => tables[tableName]\\n  };\\n}\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (createDatabase);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9kYi9EYXRhYmFzZS5qcz9lZjAxIl0sIm5hbWVzIjpbIkJBU0VfRU1QVFlfU1RBVEUiLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsIlNUQVRFX0ZMQUciLCJlbnVtZXJhYmxlIiwidmFsdWUiLCJyZXBsYWNlVGFibGVTdGF0ZSIsInRhYmxlTmFtZSIsIm5ld1RhYmxlU3RhdGUiLCJ0eCIsInN0YXRlIiwiYmF0Y2hUb2tlbiIsIndpdGhNdXRhdGlvbnMiLCJvcHMiLCJiYXRjaCIsInNldCIsInF1ZXJ5IiwidGFibGVzIiwicXVlcnlTcGVjIiwidGFibGUiLCJjbGF1c2VzIiwicm93cyIsInVwZGF0ZSIsInVwZGF0ZVNwZWMiLCJhY3Rpb24iLCJwYXlsb2FkIiwibmV4dFRhYmxlU3RhdGUiLCJyZXN1bHRQYXlsb2FkIiwiQ1JFQVRFIiwiY3VyclRhYmxlU3RhdGUiLCJyZXN1bHQiLCJpbnNlcnQiLCJjcmVhdGVkIiwiVVBEQVRFIiwiREVMRVRFIiwiZGVsZXRlIiwiRXJyb3IiLCJuZXh0REJTdGF0ZSIsInN0YXR1cyIsIlNVQ0NFU1MiLCJjcmVhdGVEYXRhYmFzZSIsInNjaGVtYVNwZWMiLCJ0YWJsZVNwZWNzIiwiZW50cmllcyIsInJlZHVjZSIsIm1hcCIsInRhYmxlU3BlYyIsIlRhYmxlIiwiZ2V0RW1wdHlTdGF0ZSIsImJpbmQiLCJkZXNjcmliZSJdLCJtYXBwaW5ncyI6IkFBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBRUE7QUFFQTtBQUVBLE1BQU1BLGdCQUFnQixHQUFHLEVBQXpCO0FBQ0FDLE1BQU0sQ0FBQ0MsY0FBUCxDQUFzQkYsZ0JBQXRCLEVBQXdDRyxxREFBeEMsRUFBb0Q7QUFDaERDLFlBQVUsRUFBRSxJQURvQztBQUVoREMsT0FBSyxFQUFFO0FBRnlDLENBQXBEO0FBS0E7O0FBQ0EsU0FBU0MsaUJBQVQsQ0FBMkJDLFNBQTNCLEVBQXNDQyxhQUF0QyxFQUFxREMsRUFBckQsRUFBeURD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QUVjaUMsNkVBQWYiLCJmaWxlIjoiLi9zcmMvZGIvRGF0YWJhc2UuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgb3BzIGZyb20gXCJpbW11dGFibGUtb3BzXCI7XG5cbmltcG9ydCB7IENSRUFURSwgVVBEQVRFLCBERUxFVEUsIFNVQ0NFU1MsIFNUQVRFX0ZMQUcgfSBmcm9tIFwiLi4vY29uc3RhbnRzXCI7XG5cbmltcG9ydCBUYWJsZSBmcm9tIFwiLi9UYWJsZVwiO1xuXG5jb25zdCBCQVNFX0VNUFRZX1NUQVRFID0ge307XG5PYmplY3QuZGVmaW5lUHJvcGVydHkoQkFTRV9FTVBUWV9TVEFURSwgU1RBVEVfRkxBRywge1xuICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgdmFsdWU6IHRydWUsXG59KTtcblxuLyoqIEBwcml2YXRlICovXG5mdW5jdGlvbiByZXBsYWNlVGFibGVTdGF0ZSh0YWJsZU5hbWUsIG5ld1RhYmxlU3RhdGUsIHR4LCBzdGF0ZSkge1xuICAgIGNvbnN0IHsgYmF0Y2hUb2tlbiwgd2l0aE11dGF0aW9ucyB9ID0gdHg7XG5cbiAgICBpZiAod2l0aE11dGF0aW9ucykge1xuICAgICAgICBzdGF0ZVt0YWJsZU5hbWVdID0gbmV3VGFibGVTdGF0ZTtcbiAgICAgICAgcmV0dXJuIHN0YXRlO1xuICAgIH1cblxuICAgIHJldHVybiBvcHMuYmF0Y2guc2V0KGJhdGNoVG9rZW4sIHRhYmxlTmFtZSwgbmV3VGFibGVTdGF0ZSwgc3RhdGUpO1xufVxuXG4vKiogQHByaXZhdGUgKi9cbmZ1bmN0aW9uIHF1ZXJ5KHRhYmxlcywgcXVlcnlTcGVjLCBzdGF0ZSkge1xuICAgIGNvbnN0IHsgdGFibGU6IHRhYmxlTmFtZSwgY2xhdXNlcyB9ID0gcXVlcnlTcGVjO1xuICAgIGNvbnN0IHRhYmxlID0gdGFibGVzW3RhYmxlTmFtZV07XG4gICAgY29uc3Qgcm93cyA9IHRhYmxlLnF1ZXJ5KHN0YXRlW3RhYmxlTmFtZV0sIGNsYXVzZXMpO1xuICAgIHJldHVybiB7XG4gICAgICAgIHJvd3MsXG4gICAgfTtcbn1cblxuLyoqIEBwcml2YXRlICovXG5mdW5jdGlvbiB1cGRhdGUodGFibGVzLCB1cGRhdGVTcGVjLCB0eCwgc3RhdGUpIHtcbiAgICBjb25zdCB7IGFjdGlvbiwgcGF5bG9hZCB9ID0gdXBkYXRlU3BlYztcblxuICAgIGxldCB0YWJsZU5hbWU7XG4gICAgbGV0IG5leHRUYWJsZVN0YXRlO1xuICAgIGxldCByZXN1bHRQYXlsb2FkO1xuXG4gICAgaWYgKGFjdGlvbiA9PT0gQ1JFQVRFKSB7XG4gICAgICAgICh7IHRhYmxlOiB0YWJsZU5hbWUgfSA9IHVwZGF0ZVNwZWMpO1xuICAgICAgICBjb25zdCB0YWJsZSA9IHRhYmxlc1t0YWJsZU5hbWVdO1xuICAgICAgICBjb25zdCBjdXJyVGFibGVTdGF0ZSA9IHN0YXRlW3RhYmxlTmFtZV07XG4gICAgICAgIGNvbnN0IHJlc3VsdCA9IHRhYmxlLmluc2VydCh0eCwgY3VyclRhYmxlU3RhdGUsIHBheWxvYWQpO1xuICAgICAgICBuZXh0VGFibGVTdGF0ZSA9IHJlc3VsdC5zdGF0ZTtcbiAgICAgICAgcmVzdWx0UGF5bG9hZCA9IHJlc3VsdC5jcmVhdGVkO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGNvbnN0IHsgcXVlcnk6IHF1ZXJ5U3BlYyB9ID0gdXBkYXRlU3BlYztcbiAgICAgICAgKHsgdGFibGU6IHRhYmxlTmFtZSB9ID0gcXVlcnlTcGVjKTtcbiAgICAgICAgY29uc3QgeyByb3dzIH0gPSBxdWVyeSh0YWJsZXMsIHF1ZXJ5U3BlYywgc3RhdGUpO1xuXG4gICAgICAgIGNvbnN0IHRhYmxlID0gdGFibGVzW3RhYmxlTmFtZV07XG4gICAgICAgIGNvbnN0IGN1cnJUYWJsZVN0YXRlID0gc3RhdGVbdGFibGVOYW1lXTtcblxuICAgICAgICBpZiAoYWN0aW9uID09PSBVUERBVEUpIHtcbiAgICAgICAgICAgIG5leHRUYWJsZVN0YXRlID0gdGFibGUudXBkYXRlKHR4LCBjdXJyVGFibGVTdGF0ZSwgcm93cywgcGF5bG9hZCk7XG4gICAgICAgICAgICAvLyByZXR1cm4gdXBkYXRlZCByb3dzXG4gICAgICAgICAgICByZXN1bHRQYXlsb2FkID0gcXVlcnkodGFibGVzLCBxdWVyeVNwZWMsIHN0YXRlKS5yb3dzO1xuICAgICAgICB9IGVsc2UgaWYgKGFjdGlvbiA9PT0gREVMRVRFKSB7XG4gICAgICAgICAgICBuZXh0VGFibGVTdGF0ZSA9IHRhYmxlLmRlbGV0ZSh0eCwgY3VyclRhYmxlU3RhdGUsIHJvd3MpO1xuICAgICAgICAgICAgLy8gcmV0dXJuIG9yaWdpbmFsIHJvd3MgdGhhdCB3ZSBqdXN0IGRlbGV0ZWRcbiAgICAgICAgICAgIHJlc3VsdFBheWxvYWQgPSByb3dzO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBEYXRhYmFzZSByZWNlaXZlZCB1bmtub3duIHVwZGF0ZSB0eXBlOiAke2FjdGlvbn1gKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IG5leHREQlN0YXRlID0gcmVwbGFjZVRhYmxlU3RhdGUodGFibGVOYW1lLCBuZXh0VGFibGVTdGF0ZSwgdHgsIHN0YXRlKTtcbiAgICByZXR1cm4ge1xuICAgICAgICBzdGF0dXM6IFNVQ0NFU1MsXG4gICAgICAgIHN0YXRlOiBuZXh0REJTdGF0ZSxcbiAgICAgICAgcGF5bG9hZDogcmVzdWx0UGF5bG9hZCxcbiAgICB9O1xufVxuXG4vKipcbiAqIEBtZW1iZXJvZiBkYlxuICogQHBhcmFtIHtPYmplY3R9IHNjaGVtYVNwZWNcbiAqIEByZXR1cm4gT2JqZWN0IGRhdGFiYXNlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVEYXRhYmFzZShzY2hlbWFTcGVjKSB7XG4gICAgY29uc3QgeyB0YWJsZXM6IHRhYmxlU3BlY3MgfSA9IHNjaGVtYVNwZWM7XG4gICAgY29uc3QgdGFibGVzID0gT2JqZWN0LmVudHJpZXModGFibGVTcGVjcykucmVkdWNlKFxuICAgICAgICAobWFwLCBbdGFibGVOYW1lLCB0YWJsZVNwZWNdKSA9PiAoe1xuICAgICAgICAgICAgLi4ubWFwLFxuICAgICAgICAgICAgW3RhYmxlTmFtZV06IG5ldyBUYWJsZSh0YWJsZVNwZWMpLFxuICAgICAgICB9KSxcbiAgICAgICAge31cbiAgICApO1xuXG4gICAgY29uc3QgZ2V0RW1wdHlTdGF0ZSA9ICgpID0+XG4gICAgICAgIE9iamVjdC5lbnRyaWVzKHRhYmxlcykucmVkdWNlKFxuICAgICAgICAgICAgKG1hcCwgW3RhYmxlTmFtZSwgdGFibGVdKSA9PiAoe1xuICAgICAgICAgICAgICAgIC4uLm1hcCxcbiAgICAgICAgICAgICAgICBbdGFibGVOYW1lXTogdGFibGUuZ2V0RW1wdHlTdGF0ZSgpLFxuICAgICAgICAgICAgfSksXG4gICAgICAgICAgICBCQVNFX0VNUFRZX1NUQVRFXG4gICAgICAgICk7XG5cbiAgICByZXR1cm4ge1xuICAgICAgICBnZXRFbXB0eVN0YXRlLFxuICAgICAgICBxdWVyeTogcXVlcnkuYmluZChudWxsLCB0YWJsZXMpLFxuICAgICAgICB1cGRhdGU6IHVwZGF0ZS5iaW5kKG51bGwsIHRhYmxlcyksXG4gICAgICAgIC8vIFVzZWQgdG8gaW5zcGVjdCB0aGUgc2NoZW1hLlxuICAgICAgICBkZXNjcmliZTogKHRhYmxlTmFtZSkgPT4gdGFibGVzW3RhYmxlTmFtZV0sXG4gICAgfTtcbn1cblxuZXhwb3J0IGRlZmF1bHQgY3JlYXRlRGF0YWJhc2U7XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./src/db/Database.js\\n\");\n \n /***/ }),\n \n@@ -4522,7 +4544,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"Table\\\", function() { return Table; });\\n/* harmony import */ var immutable_ops__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! immutable-ops */ \\\"./node_modules/immutable-ops/es/index.js\\\");\\n/* harmony import */ var lodash_filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash/filter */ \\\"./node_modules/lodash/filter.js\\\");\\n/* harmony import */ var lodash_filter__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash_filter__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var lodash_orderBy__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash/orderBy */ \\\"./node_modules/lodash/orderBy.js\\\");\\n/* harmony import */ var lodash_orderBy__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(lodash_orderBy__WEBPACK_IMPORTED_MODULE_2__);\\n/* harmony import */ var lodash_reject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! lodash/reject */ \\\"./node_modules/lodash/reject.js\\\");\\n/* harmony import */ var lodash_reject__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(lodash_reject__WEBPACK_IMPORTED_MODULE_3__);\\n/* harmony import */ var lodash_sortBy__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! lodash/sortBy */ \\\"./node_modules/lodash/sortBy.js\\\");\\n/* harmony import */ var lodash_sortBy__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(lodash_sortBy__WEBPACK_IMPORTED_MODULE_4__);\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../constants */ \\\"./src/constants.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n\\n\\n\\n\\nconst DEFAULT_TABLE_OPTIONS = {\\n  idAttribute: \\\"id\\\",\\n  arrName: \\\"items\\\",\\n  mapName: \\\"itemsById\\\",\\n  fields: {}\\n};\\n/**\\n * @private\\n * @param {*} _currMax - the current max id\\n * @param {*} userPassedId - the new id passed to the create action\\n *\\n * Both may be undefined. The current max id in the case that this is the first Model\\n * being created, and the new id if the id was not explicitly passed to the\\n * database.\\n *\\n * @return {Array} the new max id and the id to use to create the new row\\n *\\n * If the id's are strings, the id must be passed explicitly every time.\\n * In this case, the current max id will remain `NaN` due to `Math.max`, but that's fine.\\n */\\n\\nfunction idSequencer(_currMax, userPassedId) {\\n  let currMax = _currMax;\\n  let newMax;\\n  let newId;\\n\\n  if (currMax === undefined) {\\n    currMax = -1;\\n  }\\n\\n  if (userPassedId === undefined) {\\n    newMax = currMax + 1;\\n    newId = newMax;\\n  } else {\\n    newMax = Math.max(currMax + 1, userPassedId);\\n    newId = userPassedId;\\n  }\\n\\n  return [newMax, // new max id\\n  newId // id to use for row creation\\n  ];\\n}\\n/**\\n * Adapt order directions array to @{lodash.orderBy} API.\\n *\\n * @private\\n *\\n * @param {Array<Boolean|'asc'|'desc'>} orders? - an array of optional order query directions as provided to {@Link {QuerySet.orderBy}}\\n * @return {Array<'asc'|'desc'>|undefined} A normalized ordering array or undefined if none was provided.\\n */\\n\\n\\nfunction normalizeOrders(orders) {\\n  if (orders === undefined) {\\n    return undefined;\\n  }\\n\\n  const convert = order => {\\n    if ([\\\"desc\\\", false].includes(order)) {\\n      return \\\"desc\\\";\\n    }\\n\\n    return \\\"asc\\\";\\n  };\\n\\n  return Array.isArray(orders) ? orders.map(convert) : convert(orders);\\n}\\n/**\\n * Handles the underlying data structure for a {@link Model} class.\\n * @private\\n */\\n\\n\\nlet Table = /*#__PURE__*/function () {\\n  /**\\n   * Creates a new {@link Table} instance.\\n   * @param  {Object} userOpts - options to use.\\n   * @param  {string} [userOpts.idAttribute=id] - the id attribute of the entity.\\n   * @param  {string} [userOpts.arrName=items] - the state attribute where an array of\\n   *                                             entity id's are stored\\n   * @param  {string} [userOpts.mapName=itemsById] - the state attribute where the entity objects\\n   *                                                 are stored in a id to entity object\\n   *                                                 map.\\n   * @param  {string} [userOpts.fields={}] - mapping of field key to {@link Field} object\\n   */\\n  function Table(userOpts) {\\n    Object.assign(this, DEFAULT_TABLE_OPTIONS, userOpts);\\n  }\\n  /**\\n   * Returns a reference to the object at index `id`\\n   * in state `branch`.\\n   *\\n   * @param  {Object} branch - the state\\n   * @param  {Number} id - the id of the object to get\\n   * @return {Object|undefined} A reference to the raw object in the state or\\n   *                            `undefined` if not found.\\n   */\\n\\n\\n  var _proto = Table.prototype;\\n\\n  _proto.accessId = function accessId(branch, id) {\\n    return branch[this.mapName][id];\\n  };\\n\\n  _proto.accessIds = function accessIds(branch, ids) {\\n    const map = branch[this.mapName];\\n    return ids.map(id => map[id]);\\n  };\\n\\n  _proto.idExists = function idExists(branch, id) {\\n    return branch[this.mapName].hasOwnProperty(id);\\n  };\\n\\n  _proto.accessIdList = function accessIdList(branch) {\\n    return branch[this.arrName];\\n  };\\n\\n  _proto.accessList = function accessList(branch) {\\n    return this.accessIds(branch, this.accessIdList(branch));\\n  };\\n\\n  _proto.getMaxId = function getMaxId(branch) {\\n    return this.getMeta(branch, \\\"maxId\\\");\\n  };\\n\\n  _proto.setMaxId = function setMaxId(tx, branch, newMaxId) {\\n    return this.setMeta(tx, branch, \\\"maxId\\\", newMaxId);\\n  };\\n\\n  _proto.nextId = function nextId(id) {\\n    return id + 1;\\n  }\\n  /**\\n   * Returns the default state for the data structure.\\n   * @return {Object} The default state for this {@link ORM} instance's data structure\\n   */\\n  ;\\n\\n  _proto.getEmptyState = function getEmptyState() {\\n    const pkIndex = {\\n      [this.arrName]: [],\\n      [this.mapName]: {}\\n    };\\n    const attrIndexes = Object.keys(this.fields).filter(attr => attr !== this.idAttribute).filter(attr => this.fields[attr].index).reduce((indexes, attr) => ({ ...indexes,\\n      [attr]: {}\\n    }), {});\\n    return { ...pkIndex,\\n      indexes: attrIndexes,\\n      meta: {}\\n    };\\n  };\\n\\n  _proto.setMeta = function setMeta(tx, branch, key, value) {\\n    const {\\n      batchToken,\\n      withMutations\\n    } = tx;\\n\\n    if (withMutations) {\\n      const res = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.setIn([\\\"meta\\\", key], value, branch);\\n      return res;\\n    }\\n\\n    return immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.setIn(batchToken, [\\\"meta\\\", key], value, branch);\\n  };\\n\\n  _proto.getMeta = function getMeta(branch, key) {\\n    return branch.meta[key];\\n  };\\n\\n  _proto.query = function query(branch, clauses) {\\n    if (clauses.length === 0) {\\n      return this.accessList(branch);\\n    }\\n\\n    const {\\n      idAttribute\\n    } = this;\\n    const optimallyOrderedClauses = lodash_sortBy__WEBPACK_IMPORTED_MODULE_4___default()(clauses, clause => {\\n      if (Object(_utils__WEBPACK_IMPORTED_MODULE_6__[\\\"clauseFiltersByAttribute\\\"])(clause, idAttribute)) {\\n        return 1;\\n      }\\n\\n      if (Object(_utils__WEBPACK_IMPORTED_MODULE_6__[\\\"clauseReducesResultSetSize\\\"])(clause)) {\\n        return 2;\\n      }\\n\\n      return 3;\\n    });\\n\\n    const reducer = (rows, clause) => {\\n      const {\\n        type,\\n        payload\\n      } = clause;\\n\\n      if (!rows) {\\n        /**\\n         * First time this reducer is called during query.\\n         * This is where we apply query optimizations.\\n         */\\n        if (Object(_utils__WEBPACK_IMPORTED_MODULE_6__[\\\"clauseFiltersByAttribute\\\"])(clause, idAttribute)) {\\n          /**\\n           * Payload specified a primary key. Use PK index\\n           * to look up the single row identified by the PK.\\n           */\\n          const id = payload[idAttribute];\\n          const remainingPayload = Object.keys(payload).reduce((withoutPkAttr, filterAttr) => {\\n            if (filterAttr !== idAttribute) {\\n              withoutPkAttr[filterAttr] = payload[filterAttr];\\n            }\\n\\n            return withoutPkAttr;\\n          }, {});\\n          const ids = this.idExists(branch, id) ? [id] : [];\\n\\n          if (Object.keys(remainingPayload).length) {\\n            /**\\n             * Payload has additional, non-PK columns.\\n             * Filter accessed row by remaining payload (if one was found).\\n             */\\n            return reducer(this.accessIds(branch, ids), { ...clause,\\n              payload: remainingPayload\\n            });\\n          }\\n          /**\\n           * No need to filter these rows any further.\\n           * The primary key value satisfies this clause's conditions.\\n           */\\n\\n\\n          return this.accessIds(branch, ids);\\n        }\\n\\n        if (type === _constants__WEBPACK_IMPORTED_MODULE_5__[\\\"FILTER\\\"] && typeof payload === \\\"object\\\") {\\n          const indexes = Object.entries(branch.indexes);\\n          const accessedIndexes = [];\\n          const indexAttrs = [];\\n          indexes.forEach(([attr, index]) => {\\n            if (Object(_utils__WEBPACK_IMPORTED_MODULE_6__[\\\"clauseFiltersByAttribute\\\"])(clause, attr)) {\\n              /**\\n               * Payload specified an indexed attribute. Use index\\n               * to potentially decrease amount of accessed rows.\\n               */\\n              if (index.hasOwnProperty(payload[attr])) {\\n                accessedIndexes.push(index[payload[attr]]);\\n                indexAttrs.push(attr);\\n              }\\n            }\\n          });\\n          /**\\n           * Calculate set of unique PK values corresponding to each\\n           * foreign key's attribute value. Then retrieve all those rows.\\n           */\\n\\n          if (accessedIndexes.length) {\\n            const lastIndex = accessedIndexes.pop();\\n            const indexedIds = accessedIndexes.reduce((result, index) => {\\n              const indexSet = new Set(index);\\n              return result.filter(Set.prototype.has, indexSet);\\n            }, lastIndex);\\n            const remainingPayload = Object.keys(payload).reduce((withoutIndexAttrs, filterAttr) => {\\n              if (!indexAttrs.includes(filterAttr)) {\\n                withoutIndexAttrs[filterAttr] = payload[filterAttr];\\n              }\\n\\n              return withoutIndexAttrs;\\n            }, {});\\n\\n            if (Object.keys(remainingPayload).length) {\\n              /**\\n               * Payload has additional, non-indexed columns.\\n               * Filter indexed rows by remaining payload (if any were found).\\n               */\\n              return reducer(this.accessIds(branch, indexedIds), { ...clause,\\n                payload: remainingPayload\\n              });\\n            }\\n            /**\\n             * No need to filter these rows any further.\\n             * The used indexes satisfy this clause's conditions.\\n             */\\n\\n\\n            return this.accessIds(branch, indexedIds);\\n          }\\n        } // Give up optimization: Retrieve all rows (full table scan).\\n\\n\\n        return reducer(this.accessList(branch), clause);\\n      }\\n\\n      switch (type) {\\n        case _constants__WEBPACK_IMPORTED_MODULE_5__[\\\"FILTER\\\"]:\\n          {\\n            return lodash_filter__WEBPACK_IMPORTED_MODULE_1___default()(rows, payload);\\n          }\\n\\n        case _constants__WEBPACK_IMPORTED_MODULE_5__[\\\"EXCLUDE\\\"]:\\n          {\\n            return lodash_reject__WEBPACK_IMPORTED_MODULE_3___default()(rows, payload);\\n          }\\n\\n        case _constants__WEBPACK_IMPORTED_MODULE_5__[\\\"ORDER_BY\\\"]:\\n          {\\n            const [iteratees, orders] = payload;\\n            return lodash_orderBy__WEBPACK_IMPORTED_MODULE_2___default()(rows, iteratees, normalizeOrders(orders));\\n          }\\n\\n        default:\\n          return rows;\\n      }\\n    };\\n\\n    return optimallyOrderedClauses.reduce(reducer, undefined);\\n  }\\n  /**\\n   * Returns the data structure including a new object `entry`\\n   * @param  {Object} tx - transaction info\\n   * @param  {Object} branch - the data structure state\\n   * @param  {Object} entry - the object to insert\\n   * @return {Object} an object with two keys: `state` and `created`.\\n   *                  `state` is the new table state and `created` is the\\n   *                  row that was created.\\n   */\\n  ;\\n\\n  _proto.insert = function insert(tx, branch, entry) {\\n    const {\\n      batchToken,\\n      withMutations\\n    } = tx;\\n    const hasId = entry.hasOwnProperty(this.idAttribute);\\n    let workingState = branch; // This will not affect string id's.\\n\\n    const [newMaxId, id] = idSequencer(this.getMaxId(branch), entry[this.idAttribute]);\\n    workingState = this.setMaxId(tx, branch, newMaxId);\\n    const finalEntry = hasId ? entry : immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.set(batchToken, this.idAttribute, id, entry);\\n    const indexesToAppendTo = Object.keys(workingState.indexes).filter(fkAttr => entry.hasOwnProperty(fkAttr) && entry[fkAttr] !== null).map(fkAttr => [fkAttr, entry[fkAttr]]);\\n\\n    if (withMutations) {\\n      immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.push(id, workingState[this.arrName]);\\n      immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.set(id, finalEntry, workingState[this.mapName]); // add id to indexes\\n\\n      indexesToAppendTo.forEach(([attr, value]) => {\\n        const attrIndex = workingState.indexes[attr];\\n\\n        if (attrIndex.hasOwnProperty(value)) {\\n          immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.push(id, attrIndex[value]);\\n        } else {\\n          immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.set(value, [id], attrIndex);\\n        }\\n      });\\n      return {\\n        state: workingState,\\n        created: finalEntry\\n      };\\n    }\\n\\n    const nextIndexes = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, indexesToAppendTo.reduce((indexMap, [attr, value]) => {\\n      indexMap[attr] = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n        [value]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.push(batchToken, id, indexMap[attr][value] || [])\\n      }, indexMap[attr]);\\n      return indexMap;\\n    }, { ...workingState.indexes\\n    }), workingState.indexes);\\n    const nextState = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n      [this.arrName]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.push(batchToken, id, workingState[this.arrName]),\\n      [this.mapName]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n        [id]: finalEntry\\n      }, workingState[this.mapName]),\\n      indexes: nextIndexes\\n    }, workingState);\\n    return {\\n      state: nextState,\\n      created: finalEntry\\n    };\\n  }\\n  /**\\n   * Returns the data structure with objects where `rows`\\n   * are merged with `mergeObj`.\\n   *\\n   * @param  {Object} tx - transaction info\\n   * @param  {Object} branch - the data structure state\\n   * @param  {Object[]} rows - rows to update\\n   * @param  {Object} mergeObj - The object to merge with each row.\\n   * @return {Object}\\n   */\\n  ;\\n\\n  _proto.update = function update(tx, branch, rows, mergeObj) {\\n    const {\\n      batchToken,\\n      withMutations\\n    } = tx;\\n\\n    const mergeObjInto = row => {\\n      const merge = withMutations ? immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.merge : immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken);\\n      return merge(mergeObj, row);\\n    };\\n\\n    const set = withMutations ? immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.set : immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.set(batchToken);\\n    const indexedAttrs = Object.keys(branch.indexes).filter(attr => mergeObj.hasOwnProperty(attr));\\n    const indexIdsToAdd = [];\\n    const indexIdsToDelete = [];\\n    const nextMap = rows.reduce((map, row) => {\\n      const prevAttrValues = indexedAttrs.reduce((valueMap, attr) => ({ ...valueMap,\\n        [attr]: row[attr]\\n      }), {});\\n      const result = mergeObjInto(row);\\n      const nextAttrValues = indexedAttrs.reduce((valueMap, attr) => ({ ...valueMap,\\n        [attr]: result[attr]\\n      }), {});\\n      const id = result[this.idAttribute];\\n      const nextRow = set(id, result, map);\\n      indexedAttrs.forEach(attr => {\\n        const {\\n          [attr]: prevValue\\n        } = prevAttrValues;\\n        const {\\n          [attr]: nextValue\\n        } = nextAttrValues;\\n\\n        if (prevValue === nextValue) {\\n          // attribute has not changed, no need to update any index\\n          return;\\n        }\\n\\n        if (prevValue !== null && typeof prevValue !== \\\"undefined\\\") {\\n          // remove id from attribute's index for its old value\\n          indexIdsToDelete.push([attr, prevValue, id]);\\n        }\\n\\n        if (nextValue !== null) {\\n          // add id to attribute's index for its new value\\n          indexIdsToAdd.push([attr, nextValue, id]);\\n        }\\n      });\\n      return nextRow;\\n    }, branch[this.mapName]);\\n    let nextIndexes = branch.indexes;\\n\\n    if (withMutations) {\\n      indexIdsToDelete.forEach(([attr, value, id]) => {\\n        const arr = nextIndexes[attr][value];\\n        const idx = arr.indexOf(id);\\n        immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.splice(idx, 1, [], arr);\\n      });\\n      indexIdsToAdd.forEach(([attr, value, id]) => {\\n        immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.push(id, nextIndexes[attr][value]);\\n      });\\n    } else {\\n      if (indexIdsToAdd.length) {\\n        nextIndexes = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, indexIdsToAdd.reduce((indexMap, [attr, value, id]) => {\\n          indexMap[attr] = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n            [value]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.push(batchToken, id, indexMap[attr][value] || [])\\n          }, indexMap[attr]);\\n          return indexMap;\\n        }, { ...nextIndexes\\n        }), nextIndexes);\\n      }\\n\\n      if (indexIdsToDelete.length) {\\n        nextIndexes = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, indexIdsToDelete.reduce((indexMap, [attr, value, id]) => {\\n          indexMap[attr] = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n            [value]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.filter(batchToken, rowId => rowId !== id, indexMap[attr][value])\\n          }, indexMap[attr]);\\n          return indexMap;\\n        }, { ...nextIndexes\\n        }), nextIndexes);\\n      }\\n    }\\n\\n    return immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n      [this.mapName]: nextMap,\\n      indexes: nextIndexes\\n    }, branch);\\n  }\\n  /**\\n   * Returns the data structure without rows `rows`.\\n   * @param  {Object} tx - transaction info\\n   * @param  {Object} branch - the data structure state\\n   * @param  {Object[]} rows - rows to update\\n   * @return {Object} the data structure without ids in `idsToDelete`.\\n   */\\n  ;\\n\\n  _proto.delete = function _delete(tx, branch, rows) {\\n    const {\\n      batchToken,\\n      withMutations\\n    } = tx;\\n    const {\\n      arrName,\\n      mapName\\n    } = this;\\n    const arr = branch[arrName];\\n    const idsToDelete = rows.map(row => row[this.idAttribute]);\\n\\n    if (withMutations) {\\n      idsToDelete.forEach(id => {\\n        const idx = arr.indexOf(id);\\n        immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.splice(idx, 1, [], arr);\\n        immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.omit(id, branch[mapName]);\\n      }); // delete ids from all indexes\\n\\n      Object.values(branch.indexes).forEach(attrIndex => Object.values(attrIndex).forEach(valueIndex => idsToDelete.forEach(id => {\\n        const idx = valueIndex.indexOf(id);\\n\\n        if (idx !== -1) {\\n          immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.splice(idx, 1, [], valueIndex);\\n        }\\n      })));\\n      return branch;\\n    }\\n\\n    const nextIndexes = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, Object.entries(branch.indexes).reduce((indexMap, [attr, attrIndex]) => {\\n      indexMap[attr] = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, Object.entries(attrIndex).reduce((attrIndexMap, [value, valueIndex]) => {\\n        attrIndexMap[value] = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.filter(batchToken, id => !idsToDelete.includes(id), valueIndex);\\n        return attrIndexMap;\\n      }, { ...indexMap[attr]\\n      }), indexMap[attr]);\\n      return indexMap;\\n    }, { ...branch.indexes\\n    }), branch.indexes);\\n    return immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n      [arrName]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.filter(batchToken, id => !idsToDelete.includes(id), branch[arrName]),\\n      [mapName]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.omit(batchToken, idsToDelete, branch[mapName]),\\n      indexes: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, nextIndexes, branch.indexes)\\n    }, branch);\\n  };\\n\\n  return Table;\\n}();\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (Table);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9kYi9UYWJsZS5qcz83NDk3Il0sIm5hbWVzIjpbIkRFRkFVTFRfVEFCTEVfT1BUSU9OUyIsImlkQXR0cmlidXRlIiwiYXJyTmFtZSIsIm1hcE5hbWUiLCJmaWVsZHMiLCJpZFNlcXVlbmNlciIsIl9jdXJyTWF4IiwidXNlclBhc3NlZElkIiwiY3Vyck1heCIsIm5ld01heCIsIm5ld0lkIiwidW5kZWZpbmVkIiwiTWF0aCIsIm1heCIsIm5vcm1hbGl6ZU9yZGVycyIsIm9yZGVycyIsImNvbnZlcnQiLCJvcmRlciIsImluY2x1ZGVzIiwiQXJyYXkiLCJpc0FycmF5IiwibWFwIiwiVGFibGUiLCJ1c2VyT3B0cyIsIk9iamVjdCIsImFzc2lnbiIsImFjY2Vzc0lkIiwiYnJhbmNoIiwiaWQiLCJhY2Nlc3NJZHMiLCJpZHMiLCJpZEV4aXN0cyIsImhhc093blByb3BlcnR5IiwiYWNjZXNzSWRMaXN0IiwiYWNjZXNzTGlzdCIsImdldE1heElkIiwiZ2V0TWV0YSIsInNldE1heElkIiwidHgiLCJuZXdNYXhJZCIsInNldE1ldGEiLCJuZXh0SWQiLCJnZXRFbXB0eVN0YXRlIiwicGtJbmRleCIsImF0dHJJbmRleGVzIiwia2V5cyIsImZpbHRlciIsImF0dHIiLCJpbmRleCIsInJlZHVjZSIsImluZGV4ZXMiLCJtZXRhIiwia2V5IiwidmFsdWUiLCJiYXRjaFRva2VuIiwid2l0aE11dGF0aW9ucyIsInJlcyIsIm9wcyIsIm11dGFibGUiLCJzZXRJbiIsImJhdGNoIiwicXVlcnkiLCJjbGF1c2VzIiwibGVuZ3RoIiwib3B0aW1hbGx5T3JkZXJlZENsYXVzZXMiLCJzb3J0QnkiLCJjbGF1c2UiLCJjbGF1c2VGaWx0ZXJzQnlBdHRyaWJ1dGUiLCJjbGF1c2VSZWR1Y2VzUmVzdWx0U2V0U2l6ZSIsInJlZHVjZXIiLCJyb3dzIiwidHlwZSIsInBheWxvYWQiLCJyZW1haW5pbmdQYXlsb2FkIiwid2l0aG91dFBrQXR0ciIsImZpbHRlckF0dHIiLCJGSUxURVIiLCJlbnRyaWVzIiwiYWNjZXNzZWRJbmRleGVzIiwiaW5kZXhBdHRycyIsImZvckVhY2giLCJwdXNoIiwibGFzdEluZGV4IiwicG9wIiwiaW5kZXhlZElkcyIsInJlc3VsdCIsImluZGV4U2V0IiwiU2V0IiwicHJvdG90eXBlIiwiaGFzIiwid2l0aG91dEluZGV4QXR0cnMiLCJFWENMVURFIiwicmVqZWN0IiwiT1JERVJfQlkiLCJpdGVyYXRlZXMiLCJvcmRlckJ5IiwiaW5zZXJ0IiwiZW50cnkiLCJoYXNJZCIsIndvcmtpbmdTdGF0ZSIsImZpbmFsRW50cnkiLCJzZXQiLCJpbmRleGVzVG9BcHBlbmRUbyIsImZrQXR0ciIsImF0dHJJbmRleCIsInN0YXRlIiwiY3JlYXRlZCIsIm5leHRJbmRleGVzIiwibWVyZ2UiLCJpbmRleE1hcCIsIm5leHRTdGF0ZSIsInVwZGF0ZSIsIm1lcmdlT2JqIiwibWVyZ2VPYmpJbnRvIiwicm93IiwiaW5kZXhlZEF0dHJzIiwiaW5kZXhJZHNUb0FkZCIsImluZGV4SWRzVG9EZWxldGUiLCJuZXh0TWFwIiwicHJldkF0dHJWYWx1ZXMiLCJ2YWx1ZU1hcCIsIm5leHRBdHRyVmFsdWVzIiwibmV4dFJvdyIsInByZXZWYWx1ZSIsIm5leHRWYWx1ZSIsImFyciIsImlkeCIsImluZGV4T2YiLCJzcGxpY2UiLCJyb3dJZCIsImRlbGV0ZSIsImlkc1RvRGVsZXRlIiwib21pdCIsInZhbHVlcyIsInZhbHVlSW5kZXgiLCJhdHRySW5kZXhNYXAiXSwibWFwcGluZ3MiOiJBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUVBO0FBQ0E7QUFFQSxNQUFNQSxxQkFBcUIsR0FBRztBQUMxQkMsYUFBVyxFQUFFLElBRGE7QUFFMUJDLFNBQU8sRUFBRSxPQUZpQjtBQUcxQkMsU0FBTyxFQUFFLFdBSGlCO0FBSTFCQyxRQUFNLEVBQUU7QUFKa0IsQ0FB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LENBQUN4QixPQUFELENBSEMsQ0FOZjtBQVdJK0MsYUFBTyxFQUFFTyxxREFBRyxDQUFDRyxLQUFKLENBQVVnRCxLQUFWLENBQ0x0RCxVQURLLEVBRUxxRCxXQUZLLEVBR0xoRixNQUFNLENBQUN1QixPQUhGO0FBWGIsS0FGRyxFQW1CSHZCLE1BbkJHLENBQVA7QUFxQkgsR0F6aUJMOztBQUFBO0FBQUE7QUE0aUJlTCxvRUFBZiIsImZpbGUiOiIuL3NyYy9kYi9UYWJsZS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBvcHMgZnJvbSBcImltbXV0YWJsZS1vcHNcIjtcbmltcG9ydCBmaWx0ZXIgZnJvbSBcImxvZGFzaC9maWx0ZXJcIjtcbmltcG9ydCBvcmRlckJ5IGZyb20gXCJsb2Rhc2gvb3JkZXJCeVwiO1xuaW1wb3J0IHJlamVjdCBmcm9tIFwibG9kYXNoL3JlamVjdFwiO1xuaW1wb3J0IHNvcnRCeSBmcm9tIFwibG9kYXNoL3NvcnRCeVwiO1xuXG5pbXBvcnQgeyBFWENMVURFLCBGSUxURVIsIE9SREVSX0JZIH0gZnJvbSBcIi4uL2NvbnN0YW50c1wiO1xuaW1wb3J0IHsgY2xhdXNlRmlsdGVyc0J5QXR0cmlidXRlLCBjbGF1c2VSZWR1Y2VzUmVzdWx0U2V0U2l6ZSB9IGZyb20gXCIuLi91dGlsc1wiO1xuXG5jb25zdCBERUZBVUxUX1RBQkxFX09QVElPTlMgPSB7XG4gICAgaWRBdHRyaWJ1dGU6IFwiaWRcIixcbiAgICBhcnJOYW1lOiBcIml0ZW1zXCIsXG4gICAgbWFwTmFtZTogXCJpdGVtc0J5SWRcIixcbiAgICBmaWVsZHM6IHt9LFxufTtcblxuLyoqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHsqfSBfY3Vyck1heCAtIHRoZSBjdXJyZW50IG1heCBpZFxuICogQHBhcmFtIHsqfSB1c2VyUGFzc2VkSWQgLSB0aGUgbmV3IGlkIHBhc3NlZCB0byB0aGUgY3JlYXRlIGFjdGlvblxuICpcbiAqIEJvdGggbWF5IGJlIHVuZGVmaW5lZC4gVGhlIGN1cnJlbnQgbWF4IGlkIGluIHRoZSBjYXNlIHRoYXQgdGhpcyBpcyB0aGUgZmlyc3QgTW9kZWxcbiAqIGJlaW5nIGNyZWF0ZWQsIGFuZCB0aGUgbmV3IGlkIGlmIHRoZSBpZCB3YXMgbm90IGV4cGxpY2l0bHkgcGFzc2VkIHRvIHRoZVxuICogZGF0YWJhc2UuXG4gKlxuICogQHJldHVybiB7QXJyYXl9IHRoZSBuZXcgbWF4IGlkIGFuZCB0aGUgaWQgdG8gdXNlIHRvIGNyZWF0ZSB0aGUgbmV3IHJvd1xuICpcbiAqIElmIHRoZSBpZCdzIGFyZSBzdHJpbmdzLCB0aGUgaWQgbXVzdCBiZSBwYXNzZWQgZXhwbGljaXRseSBldmVyeSB0aW1lLlxuICogSW4gdGhpcyBjYXNlLCB0aGUgY3VycmVudCBtYXggaWQgd2lsbCByZW1haW4gYE5hTmAgZHVlIHRvIGBNYXRoLm1heGAsIGJ1dCB0aGF0J3MgZmluZS5cbiAqL1xuZnVuY3Rpb24gaWRTZXF1ZW5jZXIoX2N1cnJNYXgsIHVzZXJQYXNzZWRJZCkge1xuICAgIGxldCBjdXJyTWF4ID0gX2N1cnJNYXg7XG4gICAgbGV0IG5ld01heDtcbiAgICBsZXQgbmV3SWQ7XG5cbiAgICBpZiAoY3Vyck1heCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGN1cnJNYXggPSAtMTtcbiAgICB9XG5cbiAgICBpZiAodXNlclBhc3NlZElkID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgbmV3TWF4ID0gY3Vyck1heCArIDE7XG4gICAgICAgIG5ld0lkID0gbmV3TWF4O1xuICAgIH0gZWxzZSB7XG4gICAgICAgIG5ld01heCA9IE1hdGgubWF4KGN1cnJNYXggKyAxLCB1c2VyUGFzc2VkSWQpO1xuICAgICAgICBuZXdJZCA9IHVzZXJQYXNzZWRJZDtcbiAgICB9XG5cbiAgICByZXR1cm4gW1xuICAgICAgICBuZXdNYXgsIC8vIG5ldyBtYXggaWRcbiAgICAgICAgbmV3SWQsIC8vIGlkIHRvIHVzZSBmb3Igcm93IGNyZWF0aW9uXG4gICAgXTtcbn1cblxuLyoqXG4gKiBBZGFwdCBvcmRlciBkaXJlY3Rpb25zIGFycmF5IHRvIEB7bG9kYXNoLm9yZGVyQnl9IEFQSS5cbiAqXG4gKiBAcHJpdmF0ZVxuICpcbiAqIEBwYXJhbSB7QXJyYXk8Qm9vbGVhbnwnYXNjJ3wnZGVzYyc+fSBvcmRlcnM/IC0gYW4gYXJyYXkgb2Ygb3B0aW9uYWwgb3JkZXIgcXVlcnkgZGlyZWN0aW9ucyBhcyBwcm92aWRlZCB0byB7QExpbmsge1F1ZXJ5U2V0Lm9yZGVyQnl9fVxuICogQHJldHVybiB7QXJyYXk8J2FzYyd8J2Rlc2MnPnx1bmRlZmluZWR9IEEgbm9ybWFsaXplZCBvcmRlcmluZyBhcnJheSBvciB1bmRlZmluZWQgaWYgbm9uZSB3YXMgcHJvdmlkZWQuXG4gKi9cbmZ1bmN0aW9uIG5vcm1hbGl6ZU9yZGVycyhvcmRlcnMpIHtcbiAgICBpZiAob3JkZXJzID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gICAgY29uc3QgY29udmVydCA9IG9yZGVyID0+IHtcbiAgICAgICAgaWYgKFtcImRlc2NcIiwgZmFsc2VdLmluY2x1ZGVzKG9yZGVyKSkge1xuICAgICAgICAgICAgcmV0dXJuIFwiZGVzY1wiO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBcImFzY1wiO1xuICAgIH07XG4gICAgcmV0dXJuIEFycmF5LmlzQXJyYXkob3JkZXJzKSA/IG9yZGVycy5tYXAoY29udmVydCkgOiBjb252ZXJ0KG9yZGVycyk7XG59XG5cbi8qKlxuICogSGFuZGxlcyB0aGUgdW5kZXJseWluZyBkYXRhIHN0cnVjdHVyZSBmb3IgYSB7QGxpbmsgTW9kZWx9IGNsYXNzLlxuICogQHByaXZhdGVcbiAqL1xuZXhwb3J0IGNsYXNzIFRhYmxlIHtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IHtAbGluayBUYWJsZX0gaW5zdGFuY2UuXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSB1c2VyT3B0cyAtIG9wdGlvbnMgdG8gdXNlLlxuICAgICAqIEBwYXJhbSAge3N0cmluZ30gW3VzZXJPcHRzLmlkQXR0cmlidXRlPWlkXSAtIHRoZSBpZCBhdHRyaWJ1dGUgb2YgdGhlIGVudGl0eS5cbiAgICAgKiBAcGFyYW0gIHtzdHJpbmd9IFt1c2VyT3B0cy5hcnJOYW1lPWl0ZW1zXSAtIHRoZSBzdGF0ZSBhdHRyaWJ1dGUgd2hlcmUgYW4gYXJyYXkgb2ZcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVudGl0eSBpZCdzIGFyZSBzdG9yZWRcbiAgICAgKiBAcGFyYW0gIHtzdHJpbmd9IFt1c2VyT3B0cy5tYXBOYW1lPWl0ZW1zQnlJZF0gLSB0aGUgc3RhdGUgYXR0cmlidXRlIHdoZXJlIHRoZSBlbnRpdHkgb2JqZWN0c1xuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFyZSBzdG9yZWQgaW4gYSBpZCB0byBlbnRpdHkgb2JqZWN0XG4gICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFwLlxuICAgICAqIEBwYXJhbSAge3N0cmluZ30gW3VzZXJPcHRzLmZpZWxkcz17fV0gLSBtYXBwaW5nIG9mIGZpZWxkIGtleSB0byB7QGxpbmsgRmllbGR9IG9iamVjdFxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKHVzZXJPcHRzKSB7XG4gICAgICAgIE9iamVjdC5hc3NpZ24odGhpcywgREVGQVVMVF9UQUJMRV9PUFRJT05TLCB1c2VyT3B0cyk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhIHJlZmVyZW5jZSB0byB0aGUgb2JqZWN0IGF0IGluZGV4IGBpZGBcbiAgICAgKiBpbiBzdGF0ZSBgYnJhbmNoYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gYnJhbmNoIC0gdGhlIHN0YXRlXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSBpZCAtIHRoZSBpZCBvZiB0aGUgb2JqZWN0IHRvIGdldFxuICAgICAqIEByZXR1cm4ge09iamVjdHx1bmRlZmluZWR9IEEgcmVmZXJlbmNlIHRvIHRoZSByYXcgb2JqZWN0IGluIHRoZSBzdGF0ZSBvclxuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB1bmRlZmluZWRgIGlmIG5vdCBmb3VuZC5cbiAgICAgKi9cbiAgICBhY2Nlc3NJZChicmFuY2gsIGlkKSB7XG4gICAgICAgIHJldHVybiBicmFuY2hbdGhpcy5tYXBOYW1lXVtpZF07XG4gICAgfVxuXG4gICAgYWNjZXNzSWRzKGJyYW5jaCwgaWRzKSB7XG4gICAgICAgIGNvbnN0IG1hcCA9IGJyYW5jaFt0aGlzLm1hcE5hbWVdO1xuICAgICAgICByZXR1cm4gaWRzLm1hcChpZCA9PiBtYXBbaWRdKTtcbiAgICB9XG5cbiAgICBpZEV4aXN0cyhicmFuY2gsIGlkKSB7XG4gICAgICAgIHJldHVybiBicmFuY2hbdGhpcy5tYXBOYW1lXS5oYXNPd25Qcm9wZXJ0eShpZCk7XG4gICAgfVxuXG4gICAgYWNjZXNzSWRMaXN0KGJyYW5jaCkge1xuICAgICAgICByZXR1cm4gYnJhbmNoW3RoaXMuYXJyTmFtZV07XG4gICAgfVxuXG4gICAgYWNjZXNzTGlzdChicmFuY2gpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuYWNjZXNzSWRzKGJyYW5jaCwgdGhpcy5hY2Nlc3NJZExpc3QoYnJhbmNoKSk7XG4gICAgfVxuXG4gICAgZ2V0TWF4SWQoYnJhbmNoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldE1ldGEoYnJhbmNoLCBcIm1heElkXCIpO1xuICAgIH1cblxuICAgIHNldE1heElkKHR4LCBicmFuY2gsIG5ld01heElkKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnNldE1ldGEodHgsIGJyYW5jaCwgXCJtYXhJZFwiLCBuZXdNYXhJZCk7XG4gICAgfVxuXG4gICAgbmV4dElkKGlkKSB7XG4gICAgICAgIHJldHVybiBpZCArIDE7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgZGVmYXVsdCBzdGF0ZSBmb3IgdGhlIGRhdGEgc3RydWN0dXJlLlxuICAgICAqIEByZXR1cm4ge09iamVjdH0gVGhlIGRlZmF1bHQgc3RhdGUgZm9yIHRoaXMge0BsaW5rIE9STX0gaW5zdGFuY2UncyBkYXRhIHN0cnVjdHVyZVxuICAgICAqL1xuICAgIGdldEVtcHR5U3RhdGUoKSB7XG4gICAgICAgIGNvbnN0IHBrSW5kZXggPSB7XG4gICAgICAgICAgICBbdGhpcy5hcnJOYW1lXTogW10sXG4gICAgICAgICAgICBbdGhpcy5tYXBOYW1lXToge30sXG4gICAgICAgIH07XG4gICAgICAgIGNvbnN0IGF0dHJJbmRleGVzID0gT2JqZWN0LmtleXModGhpcy5maWVsZHMpXG4gICAgICAgICAgICAuZmlsdGVyKGF0dHIgPT4gYXR0ciAhPT0gdGhpcy5pZEF0dHJpYnV0ZSlcbiAgICAgICAgICAgIC5maWx0ZXIoYXR0ciA9PiB0aGlzLmZpZWxkc1thdHRyXS5pbmRleClcbiAgICAgICAgICAgIC5yZWR1Y2UoXG4gICAgICAgICAgICAgICAgKGluZGV4ZXMsIGF0dHIpID0+ICh7XG4gICAgICAgICAgICAgICAgICAgIC4uLmluZGV4ZXMsXG4gICAgICAgICAgICAgICAgICAgIFthdHRyXToge30sXG4gICAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICAgICAge31cbiAgICAgICAgICAgICk7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAuLi5wa0luZGV4LFxuICAgICAgICAgICAgaW5kZXhlczogYXR0ckluZGV4ZXMsXG4gICAgICAgICAgICBtZXRhOiB7fSxcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICBzZXRNZXRhKHR4LCBicmFuY2gsIGtleSwgdmFsdWUpIHtcbiAgICAgICAgY29uc3QgeyBiYXRjaFRva2VuLCB3aXRoTXV0YXRpb25zIH0gPSB0eDtcbiAgICAgICAgaWYgKHdpdGhNdXRhdGlvbnMpIHtcbiAgICAgICAgICAgIGNvbnN0IHJlcyA9IG9wcy5tdXRhYmxlLnNldEluKFtcIm1ldGFcIiwga2V5XSwgdmFsdWUsIGJyYW5jaCk7XG4gICAgICAgICAgICByZXR1cm4gcmVzO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG9wcy5iYXRjaC5zZXRJbihiYXRjaFRva2VuLCBbXCJtZXRhXCIsIGtleV0sIHZhbHVlLCBicmFuY2gpO1xuICAgIH1cblxuICAgIGdldE1ldGEoYnJhbmNoLCBrZXkpIHtcbiAgICAgICAgcmV0dXJuIGJyYW5jaC5tZXRhW2tleV07XG4gICAgfVxuXG4gICAgcXVlcnkoYnJhbmNoLCBjbGF1c2VzKSB7XG4gICAgICAgIGlmIChjbGF1c2VzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuYWNjZXNzTGlzdChicmFuY2gpO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgeyBpZEF0dHJpYnV0ZSB9ID0gdGhpcztcblxuICAgICAgICBjb25zdCBvcHRpbWFsbHlPcmRlcmVkQ2xhdXNlcyA9IHNvcnRCeShjbGF1c2VzLCBjbGF1c2UgPT4ge1xuICAgICAgICAgICAgaWYgKGNsYXVzZUZpbHRlcnNCeUF0dHJpYnV0ZShjbGF1c2UsIGlkQXR0cmlidXRlKSkge1xuICAgICAgICAgICAgICAgIHJldHVybiAxO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoY2xhdXNlUmVkdWNlc1Jlc3VsdFNldFNpemUoY2xhdXNlKSkge1xuICAgICAgICAgICAgICAgIHJldHVybiAyO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gMztcbiAgICAgICAgfSk7XG5cbiAgICAgICAgY29uc3QgcmVkdWNlciA9IChyb3dzLCBjbGF1c2UpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHsgdHlwZSwgcGF5bG9hZCB9ID0gY2xhdXNlO1xuICAgICAgICAgICAgaWYgKCFyb3dzKSB7XG4gICAgICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgICAgICogRmlyc3QgdGltZSB0aGlzIHJlZHVjZXIgaXMgY2FsbGVkIGR1cmluZyBxdWVyeS5cbiAgICAgICAgICAgICAgICAgKiBUaGlzIGlzIHdoZXJlIHdlIGFwcGx5IHF1ZXJ5IG9wdGltaXphdGlvbnMuXG4gICAgICAgICAgICAgICAgICovXG4gICAgICAgICAgICAgICAgaWYgKGNsYXVzZUZpbHRlcnNCeUF0dHJpYnV0ZShjbGF1c2UsIGlkQXR0cmlidXRlKSkge1xuICAgICAgICAgICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAgICAgICAgICogUGF5bG9hZCBzcGVjaWZpZWQgYSBwcmltYXJ5IGtleS4gVXNlIFBLIGluZGV4XG4gICAgICAgICAgICAgICAgICAgICAqIHRvIGxvb2sgdXAgdGhlIHNpbmdsZSByb3cgaWRlbnRpZmllZCBieSB0aGUgUEsuXG4gICAgICAgICAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBpZCA9IHBheWxvYWRbaWRBdHRyaWJ1dGVdO1xuICAgICAgICAgICAgICAgICAgICBjb25zdCByZW1haW5pbmdQYXlsb2FkID0gT2JqZWN0LmtleXMocGF5bG9hZCkucmVkdWNlKFxuICAgICAgICAgICAgICAgICAgICAgICAgKHdpdGhvdXRQa0F0dHIsIGZpbHRlckF0dHIpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoZmlsdGVyQXR0ciAhPT0gaWRBdHRyaWJ1dGUpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2l0aG91dFBrQXR0cltmaWx0ZXJBdHRyXSA9IHBheWxvYWRbZmlsdGVyQXR0cl07XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiB3aXRob3V0UGtBdHRyO1xuICAgICAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHt9XG4gICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGlkcyA9IHRoaXMuaWRFeGlzdHMoYnJhbmNoLCBpZCkgPyBbaWRdIDogW107XG4gICAgICAgICAgICAgICAgICAgIGlmIChPYmplY3Qua2V5cyhyZW1haW5pbmdQYXlsb2FkKS5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICAgICAgICAgICAgICogUGF5bG9hZCBoYXMgYWRkaXRpb25hbCwgbm9uLVBLIGNvbHVtbnMuXG4gICAgICAgICAgICAgICAgICAgICAgICAgKiBGaWx0ZXIgYWNjZXNzZWQgcm93IGJ5IHJlbWFpbmluZyBwYXlsb2FkIChpZiBvbmUgd2FzIGZvdW5kKS5cbiAgICAgICAgICAgICAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHJlZHVjZXIodGhpcy5hY2Nlc3NJZHMoYnJhbmNoLCBpZHMpLCB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLi4uY2xhdXNlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBheWxvYWQ6IHJlbWFpbmluZ1BheWxvYWQsXG4gICAgICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAgICAgICAgICogTm8gbmVlZCB0byBmaWx0ZXIgdGhlc2Ugcm93cyBhbnkgZnVydGhlci5cbiAgICAgICAgICAgICAgICAgICAgICogVGhlIHByaW1hcnkga2V5IHZhbHVlIHNhdGlzZmllcyB0aGlzIGNsYXVzZSdzIGNvbmRpdGlvbnMuXG4gICAgICAgICAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy5hY2Nlc3NJZHMoYnJhbmNoLCBpZHMpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBpZiAodHlwZSA9PT0gRklMVEVSICYmIHR5cGVvZiBwYXlsb2FkID09PSBcIm9iamVjdFwiKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGluZGV4ZXMgPSBPYmplY3QuZW50cmllcyhicmFuY2guaW5kZXhlcyk7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGFjY2Vzc2VkSW5kZXhlcyA9IFtdO1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBpbmRleEF0dHJzID0gW107XG4gICAgICAgICAgICAgICAgICAgIGluZGV4ZXMuZm9yRWFjaCgoW2F0dHIsIGluZGV4XSkgPT4ge1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGNsYXVzZUZpbHRlcnNCeUF0dHJpYnV0ZShjbGF1c2UsIGF0dHIpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICogUGF5bG9hZCBzcGVjaWZpZWQgYW4gaW5kZXhlZCBhdHRyaWJ1dGUuIFVzZSBpbmRleFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAqIHRvIHBvdGVudGlhbGx5IGRlY3JlYXNlIGFtb3VudCBvZiBhY2Nlc3NlZCByb3dzLlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChpbmRleC5oYXNPd25Qcm9wZXJ0eShwYXlsb2FkW2F0dHJdKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY2Nlc3NlZEluZGV4ZXMucHVzaChpbmRleFtwYXlsb2FkW2F0dHJdXSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4QXR0cnMucHVzaChhdHRyKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAgICAgICAgICogQ2FsY3VsYXRlIHNldCBvZiB1bmlxdWUgUEsgdmFsdWVzIGNvcnJlc3BvbmRpbmcgdG8gZWFjaFxuICAgICAgICAgICAgICAgICAgICAgKiBmb3JlaWduIGtleSdzIGF0dHJpYnV0ZSB2YWx1ZS4gVGhlbiByZXRyaWV2ZSBhbGwgdGhvc2Ugcm93cy5cbiAgICAgICAgICAgICAgICAgICAgICovXG4gICAgICAgICAgICAgICAgICAgIGlmIChhY2Nlc3NlZEluZGV4ZXMubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBsYXN0SW5kZXggPSBhY2Nlc3NlZEluZGV4ZXMucG9wKCk7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBpbmRleGVkSWRzID0gYWNjZXNzZWRJbmRleGVzLnJlZHVjZShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAocmVzdWx0LCBpbmRleCkgPT4ge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBpbmRleFNldCA9IG5ldyBTZXQoaW5kZXgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmVzdWx0LmZpbHRlcihcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNldC5wcm90b3R5cGUuaGFzLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXhTZXRcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RJbmRleFxuICAgICAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IHJlbWFpbmluZ1BheWxvYWQgPSBPYmplY3Qua2V5cyhwYXlsb2FkKS5yZWR1Y2UoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgKHdpdGhvdXRJbmRleEF0dHJzLCBmaWx0ZXJBdHRyKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICghaW5kZXhBdHRycy5pbmNsdWRlcyhmaWx0ZXJBdHRyKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2l0aG91dEluZGV4QXR0cnNbZmlsdGVyQXR0cl0gPVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBheWxvYWRbZmlsdGVyQXR0cl07XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHdpdGhvdXRJbmRleEF0dHJzO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAge31cbiAgICAgICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoT2JqZWN0LmtleXMocmVtYWluaW5nUGF5bG9hZCkubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICogUGF5bG9hZCBoYXMgYWRkaXRpb25hbCwgbm9uLWluZGV4ZWQgY29sdW1ucy5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKiBGaWx0ZXIgaW5kZXhlZCByb3dzIGJ5IHJlbWFpbmluZyBwYXlsb2FkIChpZiBhbnkgd2VyZSBmb3VuZCkuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICovXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHJlZHVjZXIodGhpcy5hY2Nlc3NJZHMoYnJhbmNoLCBpbmRleGVkSWRzKSwge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi5jbGF1c2UsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBheWxvYWQ6IHJlbWFpbmluZ1BheWxvYWQsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAgICAgICAgICAgICAqIE5vIG5lZWQgdG8gZmlsdGVyIHRoZXNlIHJvd3MgYW55IGZ1cnRoZXIuXG4gICAgICAgICAgICAgICAgICAgICAgICAgKiBUaGUgdXNlZCBpbmRleGVzIHNhdGlzZnkgdGhpcyBjbGF1c2UncyBjb25kaXRpb25zLlxuICAgICAgICAgICAgICAgICAgICAgICAgICovXG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy5hY2Nlc3NJZHMoYnJhbmNoLCBpbmRleGVkSWRzKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIC8vIEdpdmUgdXAgb3B0aW1pemF0aW9uOiBSZXRyaWV2ZSBhbGwgcm93cyAoZnVsbCB0YWJsZSBzY2FuKS5cbiAgICAgICAgICAgICAgICByZXR1cm4gcmVkdWNlcih0aGlzLmFjY2Vzc0xpc3QoYnJhbmNoKSwgY2xhdXNlKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgc3dpdGNoICh0eXBlKSB7XG4gICAgICAgICAgICAgICAgY2FzZSBGSUxURVI6IHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGZpbHRlcihyb3dzLCBwYXlsb2FkKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgY2FzZSBFWENMVURFOiB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiByZWplY3Qocm93cywgcGF5bG9hZCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGNhc2UgT1JERVJfQlk6IHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgW2l0ZXJhdGVlcywgb3JkZXJzXSA9IHBheWxvYWQ7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBvcmRlckJ5KHJvd3MsIGl0ZXJhdGVlcywgbm9ybWFsaXplT3JkZXJzKG9yZGVycykpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgICAgICAgICByZXR1cm4gcm93cztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfTtcblxuICAgICAgICByZXR1cm4gb3B0aW1hbGx5T3JkZXJlZENsYXVzZXMucmVkdWNlKHJlZHVjZXIsIHVuZGVmaW5lZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgZGF0YSBzdHJ1Y3R1cmUgaW5jbHVkaW5nIGEgbmV3IG9iamVjdCBgZW50cnlgXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSB0eCAtIHRyYW5zYWN0aW9uIGluZm9cbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGJyYW5jaCAtIHRoZSBkYXRhIHN0cnVjdHVyZSBzdGF0ZVxuICAgICAqIEBwYXJhbSAge09iamVjdH0gZW50cnkgLSB0aGUgb2JqZWN0IHRvIGluc2VydFxuICAgICAqIEByZXR1cm4ge09iamVjdH0gYW4gb2JqZWN0IHdpdGggdHdvIGtleXM6IGBzdGF0ZWAgYW5kIGBjcmVhdGVkYC5cbiAgICAgKiAgICAgICAgICAgICAgICAgIGBzdGF0ZWAgaXMgdGhlIG5ldyB0YWJsZSBzdGF0ZSBhbmQgYGNyZWF0ZWRgIGlzIHRoZVxuICAgICAqICAgICAgICAgICAgICAgICAgcm93IHRoYXQgd2FzIGNyZWF0ZWQuXG4gICAgICovXG4gICAgaW5zZXJ0KHR4LCBicmFuY2gsIGVudHJ5KSB7XG4gICAgICAgIGNvbnN0IHsgYmF0Y2hUb2tlbiwgd2l0aE11dGF0aW9ucyB9ID0gdHg7XG5cbiAgICAgICAgY29uc3QgaGFzSWQgPSBlbnRyeS5oYXNPd25Qcm9wZXJ0eSh0aGlzLmlkQXR0cmlidXRlKTtcblxuICAgICAgICBsZXQgd29ya2luZ1N0YXRlID0gYnJhbmNoO1xuXG4gICAgICAgIC8vIFRoaXMgd2lsbCBub3QgYWZmZWN0IHN0cmluZyBpZCdzLlxuICAgICAgICBjb25zdCBbbmV3TWF4SWQsIGlkXSA9IGlkU2VxdWVuY2VyKFxuICAgICAgICAgICAgdGhpcy5nZXRNYXhJZChicmFuY2gpLFxuICAgICAgICAgICAgZW50cnlbdGhpcy5pZEF0dHJpYnV0ZV1cbiAgICAgICAgKTtcbiAgICAgICAgd29ya2luZ1N0YXRlID0gdGhpcy5zZXRNYXhJZCh0eCwgYnJhbmNoLCBuZXdNYXhJZCk7XG5cbiAgICAgICAgY29uc3QgZmluYWxFbnRyeSA9IGhhc0lkXG4gICAgICAgICAgICA/IGVudHJ5XG4gICAgICAgICAgICA6IG9wcy5iYXRjaC5zZXQoYmF0Y2hUb2tlbiwgdGhpcy5pZEF0dHJpYnV0ZSwgaWQsIGVudHJ5KTtcblxuICAgICAgICBjb25zdCBpbmRleGVzVG9BcHBlbmRUbyA9IE9iamVjdC5rZXlzKHdvcmtpbmdTdGF0ZS5pbmRleGVzKVxuICAgICAgICAgICAgLmZpbHRlcihcbiAgICAgICAgICAgICAgICBma0F0dHIgPT4gZW50cnkuaGFzT3duUHJvcGVydHkoZmtBdHRyKSAmJiBlbnRyeVtma0F0dHJdICE9PSBudWxsXG4gICAgICAgICAgICApXG4gICAgICAgICAgICAubWFwKGZrQXR0ciA9PiBbZmtBdHRyLCBlbnRyeVtma0F0dHJdXSk7XG5cbiAgICAgICAgaWYgKHdpdGhNdXRhdGlvbnMpIHtcbiAgICAgICAgICAgIG9wcy5tdXRhYmxlLnB1c2goaWQsIHdvcmtpbmdTdGF0ZVt0aGlzLmFyck5hbWVdKTtcbiAgICAgICAgICAgIG9wcy5tdXRhYmxlLnNldChpZCwgZmluYWxFbnRyeSwgd29ya2luZ1N0YXRlW3RoaXMubWFwTmFtZV0pO1xuICAgICAgICAgICAgLy8gYWRkIGlkIHRvIGluZGV4ZXNcbiAgICAgICAgICAgIGluZGV4ZXNUb0FwcGVuZFRvLmZvckVhY2goKFthdHRyLCB2YWx1ZV0pID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBhdHRySW5kZXggPSB3b3JraW5nU3RhdGUuaW5kZXhlc1thdHRyXTtcbiAgICAgICAgICAgICAgICBpZiAoYXR0ckluZGV4Lmhhc093blByb3BlcnR5KHZhbHVlKSkge1xuICAgICAgICAgICAgICAgICAgICBvcHMubXV0YWJsZS5wdXNoKGlkLCBhdHRySW5kZXhbdmFsdWVdKTtcbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICBvcHMubXV0YWJsZS5zZXQodmFsdWUsIFtpZF0sIGF0dHJJbmRleCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIHN0YXRlOiB3b3JraW5nU3RhdGUsXG4gICAgICAgICAgICAgICAgY3JlYXRlZDogZmluYWxFbnRyeSxcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBuZXh0SW5kZXhlcyA9IG9wcy5iYXRjaC5tZXJnZShcbiAgICAgICAgICAgIGJhdGNoVG9rZW4sXG4gICAgICAgICAgICBpbmRleGVzVG9BcHBlbmRUby5yZWR1Y2UoXG4gICAgICAgICAgICAgICAgKGluZGV4TWFwLCBbYXR0ciwgdmFsdWVdKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGluZGV4TWFwW2F0dHJdID0gb3BzLmJhdGNoLm1lcmdlKFxuICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBbdmFsdWVdOiBvcHMuYmF0Y2gucHVzaChcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4TWFwW2F0dHJdW3ZhbHVlXSB8fCBbXVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXhNYXBbYXR0cl1cbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGluZGV4TWFwO1xuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgeyAuLi53b3JraW5nU3RhdGUuaW5kZXhlcyB9XG4gICAgICAgICAgICApLFxuICAgICAgICAgICAgd29ya2luZ1N0YXRlLmluZGV4ZXNcbiAgICAgICAgKTtcblxuICAgICAgICBjb25zdCBuZXh0U3RhdGUgPSBvcHMuYmF0Y2gubWVyZ2UoXG4gICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIFt0aGlzLmFyck5hbWVdOiBvcHMuYmF0Y2gucHVzaChcbiAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgaWQsXG4gICAgICAgICAgICAgICAgICAgIHdvcmtpbmdTdGF0ZVt0aGlzLmFyck5hbWVdXG4gICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgICBbdGhpcy5tYXBOYW1lXTogb3BzLmJhdGNoLm1lcmdlKFxuICAgICAgICAgICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICBbaWRdOiBmaW5hbEVudHJ5LFxuICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICB3b3JraW5nU3RhdGVbdGhpcy5tYXBOYW1lXVxuICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgaW5kZXhlczogbmV4dEluZGV4ZXMsXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgd29ya2luZ1N0YXRlXG4gICAgICAgICk7XG5cbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHN0YXRlOiBuZXh0U3RhdGUsXG4gICAgICAgICAgICBjcmVhdGVkOiBmaW5hbEVudHJ5LFxuICAgICAgICB9O1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGRhdGEgc3RydWN0dXJlIHdpdGggb2JqZWN0cyB3aGVyZSBgcm93c2BcbiAgICAgKiBhcmUgbWVyZ2VkIHdpdGggYG1lcmdlT2JqYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gdHggLSB0cmFuc2FjdGlvbiBpbmZvXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBicmFuY2ggLSB0aGUgZGF0YSBzdHJ1Y3R1cmUgc3RhdGVcbiAgICAgKiBAcGFyYW0gIHtPYmplY3RbXX0gcm93cyAtIHJvd3MgdG8gdXBkYXRlXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBtZXJnZU9iaiAtIFRoZSBvYmplY3QgdG8gbWVyZ2Ugd2l0aCBlYWNoIHJvdy5cbiAgICAgKiBAcmV0dXJuIHtPYmplY3R9XG4gICAgICovXG4gICAgdXBkYXRlKHR4LCBicmFuY2gsIHJvd3MsIG1lcmdlT2JqKSB7XG4gICAgICAgIGNvbnN0IHsgYmF0Y2hUb2tlbiwgd2l0aE11dGF0aW9ucyB9ID0gdHg7XG5cbiAgICAgICAgY29uc3QgbWVyZ2VPYmpJbnRvID0gcm93ID0+IHtcbiAgICAgICAgICAgIGNvbnN0IG1lcmdlID0gd2l0aE11dGF0aW9uc1xuICAgICAgICAgICAgICAgID8gb3BzLm11dGFibGUubWVyZ2VcbiAgICAgICAgICAgICAgICA6IG9wcy5iYXRjaC5tZXJnZShiYXRjaFRva2VuKTtcbiAgICAgICAgICAgIHJldHVybiBtZXJnZShtZXJnZU9iaiwgcm93KTtcbiAgICAgICAgfTtcblxuICAgICAgICBjb25zdCBzZXQgPSB3aXRoTXV0YXRpb25zID8gb3BzLm11dGFibGUuc2V0IDogb3BzLmJhdGNoLnNldChiYXRjaFRva2VuKTtcblxuICAgICAgICBjb25zdCBpbmRleGVkQXR0cnMgPSBPYmplY3Qua2V5cyhicmFuY2guaW5kZXhlcykuZmlsdGVyKGF0dHIgPT5cbiAgICAgICAgICAgIG1lcmdlT2JqLmhhc093blByb3BlcnR5KGF0dHIpXG4gICAgICAgICk7XG4gICAgICAgIGNvbnN0IGluZGV4SWRzVG9BZGQgPSBbXTtcbiAgICAgICAgY29uc3QgaW5kZXhJZHNUb0RlbGV0ZSA9IFtdO1xuXG4gICAgICAgIGNvbnN0IG5leHRNYXAgPSByb3dzLnJlZHVjZSgobWFwLCByb3cpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHByZXZBdHRyVmFsdWVzID0gaW5kZXhlZEF0dHJzLnJlZHVjZShcbiAgICAgICAgICAgICAgICAodmFsdWVNYXAsIGF0dHIpID0+ICh7XG4gICAgICAgICAgICAgICAgICAgIC4uLnZhbHVlTWFwLFxuICAgICAgICAgICAgICAgICAgICBbYXR0cl06IHJvd1thdHRyXSxcbiAgICAgICAgICAgICAgICB9KSxcbiAgICAgICAgICAgICAgICB7fVxuICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIGNvbnN0IHJlc3VsdCA9IG1lcmdlT2JqSW50byhyb3cpO1xuICAgICAgICAgICAgY29uc3QgbmV4dEF0dHJWYWx1ZXMgPSBpbmRleGVkQXR0cnMucmVkdWNlKFxuICAgICAgICAgICAgICAgICh2YWx1ZU1hcCwgYXR0cikgPT4gKHtcbiAgICAgICAgICAgICAgICAgICAgLi4udmFsdWVNYXAsXG4gICAgICAgICAgICAgICAgICAgIFthdHRyXTogcmVzdWx0W2F0dHJdLFxuICAgICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgICAgIHt9XG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgY29uc3QgaWQgPSByZXN1bHRbdGhpcy5pZEF0dHJpYnV0ZV07XG4gICAgICAgICAgICBjb25zdCBuZXh0Um93ID0gc2V0KGlkLCByZXN1bHQsIG1hcCk7XG4gICAgICAgICAgICBpbmRleGVkQXR0cnMuZm9yRWFjaChhdHRyID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCB7IFthdHRyXTogcHJldlZhbHVlIH0gPSBwcmV2QXR0clZhbHVlcztcbiAgICAgICAgICAgICAgICBjb25zdCB7IFthdHRyXTogbmV4dFZhbHVlIH0gPSBuZXh0QXR0clZhbHVlcztcbiAgICAgICAgICAgICAgICBpZiAocHJldlZhbHVlID09PSBuZXh0VmFsdWUpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gYXR0cmlidXRlIGhhcyBub3QgY2hhbmdlZCwgbm8gbmVlZCB0byB1cGRhdGUgYW55IGluZGV4XG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKHByZXZWYWx1ZSAhPT0gbnVsbCAmJiB0eXBlb2YgcHJldlZhbHVlICE9PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIHJlbW92ZSBpZCBmcm9tIGF0dHJpYnV0ZSdzIGluZGV4IGZvciBpdHMgb2xkIHZhbHVlXG4gICAgICAgICAgICAgICAgICAgIGluZGV4SWRzVG9EZWxldGUucHVzaChbYXR0ciwgcHJldlZhbHVlLCBpZF0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBpZiAobmV4dFZhbHVlICE9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIGFkZCBpZCB0byBhdHRyaWJ1dGUncyBpbmRleCBmb3IgaXRzIG5ldyB2YWx1ZVxuICAgICAgICAgICAgICAgICAgICBpbmRleElkc1RvQWRkLnB1c2goW2F0dHIsIG5leHRWYWx1ZSwgaWRdKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIHJldHVybiBuZXh0Um93O1xuICAgICAgICB9LCBicmFuY2hbdGhpcy5tYXBOYW1lXSk7XG5cbiAgICAgICAgbGV0IG5leHRJbmRleGVzID0gYnJhbmNoLmluZGV4ZXM7XG4gICAgICAgIGlmICh3aXRoTXV0YXRpb25zKSB7XG4gICAgICAgICAgICBpbmRleElkc1RvRGVsZXRlLmZvckVhY2goKFthdHRyLCB2YWx1ZSwgaWRdKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgYXJyID0gbmV4dEluZGV4ZXNbYXR0cl1bdmFsdWVdO1xuICAgICAgICAgICAgICAgIGNvbnN0IGlkeCA9IGFyci5pbmRleE9mKGlkKTtcbiAgICAgICAgICAgICAgICBvcHMubXV0YWJsZS5zcGxpY2UoaWR4LCAxLCBbXSwgYXJyKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgaW5kZXhJZHNUb0FkZC5mb3JFYWNoKChbYXR0ciwgdmFsdWUsIGlkXSkgPT4ge1xuICAgICAgICAgICAgICAgIG9wcy5tdXRhYmxlLnB1c2goaWQsIG5leHRJbmRleGVzW2F0dHJdW3ZhbHVlXSk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGlmIChpbmRleElkc1RvQWRkLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIG5leHRJbmRleGVzID0gb3BzLmJhdGNoLm1lcmdlKFxuICAgICAgICAgICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAgICAgICAgICBpbmRleElkc1RvQWRkLnJlZHVjZShcbiAgICAgICAgICAgICAgICAgICAgICAgIChpbmRleE1hcCwgW2F0dHIsIHZhbHVlLCBpZF0pID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleE1hcFthdHRyXSA9IG9wcy5iYXRjaC5tZXJnZShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgW3ZhbHVlXTogb3BzLmJhdGNoLnB1c2goXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleE1hcFthdHRyXVt2YWx1ZV0gfHwgW11cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4TWFwW2F0dHJdXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gaW5kZXhNYXA7XG4gICAgICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICAgICAgeyAuLi5uZXh0SW5kZXhlcyB9XG4gICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgIG5leHRJbmRleGVzXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmIChpbmRleElkc1RvRGVsZXRlLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIG5leHRJbmRleGVzID0gb3BzLmJhdGNoLm1lcmdlKFxuICAgICAgICAgICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAgICAgICAgICBpbmRleElkc1RvRGVsZXRlLnJlZHVjZShcbiAgICAgICAgICAgICAgICAgICAgICAgIChpbmRleE1hcCwgW2F0dHIsIHZhbHVlLCBpZF0pID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleE1hcFthdHRyXSA9IG9wcy5iYXRjaC5tZXJnZShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgW3ZhbHVlXTogb3BzLmJhdGNoLmZpbHRlcihcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd0lkID0+IHJvd0lkICE9PSBpZCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleE1hcFthdHRyXVt2YWx1ZV1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4TWFwW2F0dHJdXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gaW5kZXhNYXA7XG4gICAgICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICAgICAgeyAuLi5uZXh0SW5kZXhlcyB9XG4gICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgIG5leHRJbmRleGVzXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBvcHMuYmF0Y2gubWVyZ2UoXG4gICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIFt0aGlzLm1hcE5hbWVdOiBuZXh0TWFwLFxuICAgICAgICAgICAgICAgIGluZGV4ZXM6IG5leHRJbmRleGVzLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIGJyYW5jaFxuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGRhdGEgc3RydWN0dXJlIHdpdGhvdXQgcm93cyBgcm93c2AuXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSB0eCAtIHRyYW5zYWN0aW9uIGluZm9cbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGJyYW5jaCAtIHRoZSBkYXRhIHN0cnVjdHVyZSBzdGF0ZVxuICAgICAqIEBwYXJhbSAge09iamVjdFtdfSByb3dzIC0gcm93cyB0byB1cGRhdGVcbiAgICAgKiBAcmV0dXJuIHtPYmplY3R9IHRoZSBkYXRhIHN0cnVjdHVyZSB3aXRob3V0IGlkcyBpbiBgaWRzVG9EZWxldGVgLlxuICAgICAqL1xuICAgIGRlbGV0ZSh0eCwgYnJhbmNoLCByb3dzKSB7XG4gICAgICAgIGNvbnN0IHsgYmF0Y2hUb2tlbiwgd2l0aE11dGF0aW9ucyB9ID0gdHg7XG5cbiAgICAgICAgY29uc3QgeyBhcnJOYW1lLCBtYXBOYW1lIH0gPSB0aGlzO1xuICAgICAgICBjb25zdCBhcnIgPSBicmFuY2hbYXJyTmFtZV07XG5cbiAgICAgICAgY29uc3QgaWRzVG9EZWxldGUgPSByb3dzLm1hcChyb3cgPT4gcm93W3RoaXMuaWRBdHRyaWJ1dGVdKTtcbiAgICAgICAgaWYgKHdpdGhNdXRhdGlvbnMpIHtcbiAgICAgICAgICAgIGlkc1RvRGVsZXRlLmZvckVhY2goaWQgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGlkeCA9IGFyci5pbmRleE9mKGlkKTtcbiAgICAgICAgICAgICAgICBvcHMubXV0YWJsZS5zcGxpY2UoaWR4LCAxLCBbXSwgYXJyKTtcbiAgICAgICAgICAgICAgICBvcHMubXV0YWJsZS5vbWl0KGlkLCBicmFuY2hbbWFwTmFtZV0pO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAvLyBkZWxldGUgaWRzIGZyb20gYWxsIGluZGV4ZXNcbiAgICAgICAgICAgIE9iamVjdC52YWx1ZXMoYnJhbmNoLmluZGV4ZXMpLmZvckVhY2goYXR0ckluZGV4ID0+XG4gICAgICAgICAgICAgICAgT2JqZWN0LnZhbHVlcyhhdHRySW5kZXgpLmZvckVhY2godmFsdWVJbmRleCA9PlxuICAgICAgICAgICAgICAgICAgICBpZHNUb0RlbGV0ZS5mb3JFYWNoKGlkID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IGlkeCA9IHZhbHVlSW5kZXguaW5kZXhPZihpZCk7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoaWR4ICE9PSAtMSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9wcy5tdXRhYmxlLnNwbGljZShpZHgsIDEsIFtdLCB2YWx1ZUluZGV4KTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICApXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgcmV0dXJuIGJyYW5jaDtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IG5leHRJbmRleGVzID0gb3BzLmJhdGNoLm1lcmdlKFxuICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgIE9iamVjdC5lbnRyaWVzKGJyYW5jaC5pbmRleGVzKS5yZWR1Y2UoXG4gICAgICAgICAgICAgICAgKGluZGV4TWFwLCBbYXR0ciwgYXR0ckluZGV4XSkgPT4ge1xuICAgICAgICAgICAgICAgICAgICBpbmRleE1hcFthdHRyXSA9IG9wcy5iYXRjaC5tZXJnZShcbiAgICAgICAgICAgICAgICAgICAgICAgIGJhdGNoVG9rZW4sXG4gICAgICAgICAgICAgICAgICAgICAgICBPYmplY3QuZW50cmllcyhhdHRySW5kZXgpLnJlZHVjZShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAoYXR0ckluZGV4TWFwLCBbdmFsdWUsIHZhbHVlSW5kZXhdKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF0dHJJbmRleE1hcFt2YWx1ZV0gPSBvcHMuYmF0Y2guZmlsdGVyKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkID0+ICFpZHNUb0RlbGV0ZS5pbmNsdWRlcyhpZCksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZUluZGV4XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBhdHRySW5kZXhNYXA7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7IC4uLmluZGV4TWFwW2F0dHJdIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgICAgICBpbmRleE1hcFthdHRyXVxuICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gaW5kZXhNYXA7XG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICB7IC4uLmJyYW5jaC5pbmRleGVzIH1cbiAgICAgICAgICAgICksXG4gICAgICAgICAgICBicmFuY2guaW5kZXhlc1xuICAgICAgICApO1xuXG4gICAgICAgIHJldHVybiBvcHMuYmF0Y2gubWVyZ2UoXG4gICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIFthcnJOYW1lXTogb3BzLmJhdGNoLmZpbHRlcihcbiAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgaWQgPT4gIWlkc1RvRGVsZXRlLmluY2x1ZGVzKGlkKSxcbiAgICAgICAgICAgICAgICAgICAgYnJhbmNoW2Fyck5hbWVdXG4gICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgICBbbWFwTmFtZV06IG9wcy5iYXRjaC5vbWl0KFxuICAgICAgICAgICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAgICAgICAgICBpZHNUb0RlbGV0ZSxcbiAgICAgICAgICAgICAgICAgICAgYnJhbmNoW21hcE5hbWVdXG4gICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgICBpbmRleGVzOiBvcHMuYmF0Y2gubWVyZ2UoXG4gICAgICAgICAgICAgICAgICAgIGJhdGNoVG9rZW4sXG4gICAgICAgICAgICAgICAgICAgIG5leHRJbmRleGVzLFxuICAgICAgICAgICAgICAgICAgICBicmFuY2guaW5kZXhlc1xuICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgYnJhbmNoXG4gICAgICAgICk7XG4gICAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBUYWJsZTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/db/Table.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"Table\\\", function() { return Table; });\\n/* harmony import */ var immutable_ops__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! immutable-ops */ \\\"./node_modules/immutable-ops/es/index.js\\\");\\n/* harmony import */ var lodash_filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash/filter */ \\\"./node_modules/lodash/filter.js\\\");\\n/* harmony import */ var lodash_filter__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash_filter__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var lodash_orderBy__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash/orderBy */ \\\"./node_modules/lodash/orderBy.js\\\");\\n/* harmony import */ var lodash_orderBy__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(lodash_orderBy__WEBPACK_IMPORTED_MODULE_2__);\\n/* harmony import */ var lodash_reject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! lodash/reject */ \\\"./node_modules/lodash/reject.js\\\");\\n/* harmony import */ var lodash_reject__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(lodash_reject__WEBPACK_IMPORTED_MODULE_3__);\\n/* harmony import */ var lodash_sortBy__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! lodash/sortBy */ \\\"./node_modules/lodash/sortBy.js\\\");\\n/* harmony import */ var lodash_sortBy__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(lodash_sortBy__WEBPACK_IMPORTED_MODULE_4__);\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../constants */ \\\"./src/constants.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n\\n\\n\\n\\nconst DEFAULT_TABLE_OPTIONS = {\\n  idAttribute: \\\"id\\\",\\n  arrName: \\\"items\\\",\\n  mapName: \\\"itemsById\\\",\\n  fields: {}\\n};\\n/**\\n * @private\\n * @param {*} _currMax - the current max id\\n * @param {*} userPassedId - the new id passed to the create action\\n *\\n * Both may be undefined. The current max id in the case that this is the first Model\\n * being created, and the new id if the id was not explicitly passed to the\\n * database.\\n *\\n * @return {Array} the new max id and the id to use to create the new row\\n *\\n * If the id's are strings, the id must be passed explicitly every time.\\n * In this case, the current max id will remain `NaN` due to `Math.max`, but that's fine.\\n */\\n\\nfunction idSequencer(_currMax, userPassedId) {\\n  let currMax = _currMax;\\n  let newMax;\\n  let newId;\\n\\n  if (currMax === undefined) {\\n    currMax = -1;\\n  }\\n\\n  if (userPassedId === undefined) {\\n    newMax = currMax + 1;\\n    newId = newMax;\\n  } else {\\n    newMax = Math.max(currMax + 1, userPassedId);\\n    newId = userPassedId;\\n  }\\n\\n  return [newMax, // new max id\\n  newId // id to use for row creation\\n  ];\\n}\\n/**\\n * Adapt order directions array to @{lodash.orderBy} API.\\n *\\n * @private\\n *\\n * @param {Array<Boolean|'asc'|'desc'>} orders? - an array of optional order query directions as provided to {@Link {QuerySet.orderBy}}\\n * @return {Array<'asc'|'desc'>|undefined} A normalized ordering array or undefined if none was provided.\\n */\\n\\n\\nfunction normalizeOrders(orders) {\\n  if (orders === undefined) {\\n    return undefined;\\n  }\\n\\n  const convert = order => {\\n    if ([\\\"desc\\\", false].includes(order)) {\\n      return \\\"desc\\\";\\n    }\\n\\n    return \\\"asc\\\";\\n  };\\n\\n  return Array.isArray(orders) ? orders.map(convert) : convert(orders);\\n}\\n/**\\n * Handles the underlying data structure for a {@link Model} class.\\n * @private\\n */\\n\\n\\nlet Table = /*#__PURE__*/function () {\\n  /**\\n   * Creates a new {@link Table} instance.\\n   * @param  {Object} userOpts - options to use.\\n   * @param  {string} [userOpts.idAttribute=id] - the id attribute of the entity.\\n   * @param  {string} [userOpts.arrName=items] - the state attribute where an array of\\n   *                                             entity id's are stored\\n   * @param  {string} [userOpts.mapName=itemsById] - the state attribute where the entity objects\\n   *                                                 are stored in a id to entity object\\n   *                                                 map.\\n   * @param  {string} [userOpts.fields={}] - mapping of field key to {@link Field} object\\n   */\\n  function Table(userOpts) {\\n    Object.assign(this, DEFAULT_TABLE_OPTIONS, userOpts);\\n  }\\n  /**\\n   * Returns a reference to the object at index `id`\\n   * in state `branch`.\\n   *\\n   * @param  {Object} branch - the state\\n   * @param  {Number} id - the id of the object to get\\n   * @return {Object|undefined} A reference to the raw object in the state or\\n   *                            `undefined` if not found.\\n   */\\n\\n\\n  var _proto = Table.prototype;\\n\\n  _proto.accessId = function accessId(branch, id) {\\n    return branch[this.mapName][id];\\n  };\\n\\n  _proto.accessIds = function accessIds(branch, ids) {\\n    const map = branch[this.mapName];\\n    return ids.map(id => map[id]);\\n  };\\n\\n  _proto.idExists = function idExists(branch, id) {\\n    return branch[this.mapName].hasOwnProperty(id);\\n  };\\n\\n  _proto.accessIdList = function accessIdList(branch) {\\n    return branch[this.arrName];\\n  };\\n\\n  _proto.accessList = function accessList(branch) {\\n    return this.accessIds(branch, this.accessIdList(branch));\\n  };\\n\\n  _proto.getMaxId = function getMaxId(branch) {\\n    return this.getMeta(branch, \\\"maxId\\\");\\n  };\\n\\n  _proto.setMaxId = function setMaxId(tx, branch, newMaxId) {\\n    return this.setMeta(tx, branch, \\\"maxId\\\", newMaxId);\\n  };\\n\\n  _proto.nextId = function nextId(id) {\\n    return id + 1;\\n  }\\n  /**\\n   * Returns the default state for the data structure.\\n   * @return {Object} The default state for this {@link ORM} instance's data structure\\n   */\\n  ;\\n\\n  _proto.getEmptyState = function getEmptyState() {\\n    const pkIndex = {\\n      [this.arrName]: [],\\n      [this.mapName]: {}\\n    };\\n    const attrIndexes = Object.keys(this.fields).filter(attr => attr !== this.idAttribute).filter(attr => this.fields[attr].index).reduce((indexes, attr) => ({ ...indexes,\\n      [attr]: {}\\n    }), {});\\n    return { ...pkIndex,\\n      indexes: attrIndexes,\\n      meta: {}\\n    };\\n  };\\n\\n  _proto.setMeta = function setMeta(tx, branch, key, value) {\\n    const {\\n      batchToken,\\n      withMutations\\n    } = tx;\\n\\n    if (withMutations) {\\n      const res = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.setIn([\\\"meta\\\", key], value, branch);\\n      return res;\\n    }\\n\\n    return immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.setIn(batchToken, [\\\"meta\\\", key], value, branch);\\n  };\\n\\n  _proto.getMeta = function getMeta(branch, key) {\\n    return branch.meta[key];\\n  };\\n\\n  _proto.query = function query(branch, clauses) {\\n    if (clauses.length === 0) {\\n      return this.accessList(branch);\\n    }\\n\\n    const {\\n      idAttribute\\n    } = this;\\n    const optimallyOrderedClauses = lodash_sortBy__WEBPACK_IMPORTED_MODULE_4___default()(clauses, clause => {\\n      if (Object(_utils__WEBPACK_IMPORTED_MODULE_6__[\\\"clauseFiltersByAttribute\\\"])(clause, idAttribute)) {\\n        return 1;\\n      }\\n\\n      if (Object(_utils__WEBPACK_IMPORTED_MODULE_6__[\\\"clauseReducesResultSetSize\\\"])(clause)) {\\n        return 2;\\n      }\\n\\n      return 3;\\n    });\\n\\n    const reducer = (rows, clause) => {\\n      const {\\n        type,\\n        payload\\n      } = clause;\\n\\n      if (!rows) {\\n        /**\\n         * First time this reducer is called during query.\\n         * This is where we apply query optimizations.\\n         */\\n        if (Object(_utils__WEBPACK_IMPORTED_MODULE_6__[\\\"clauseFiltersByAttribute\\\"])(clause, idAttribute)) {\\n          /**\\n           * Payload specified a primary key. Use PK index\\n           * to look up the single row identified by the PK.\\n           */\\n          const id = payload[idAttribute];\\n          const remainingPayload = Object.keys(payload).reduce((withoutPkAttr, filterAttr) => {\\n            if (filterAttr !== idAttribute) {\\n              withoutPkAttr[filterAttr] = payload[filterAttr];\\n            }\\n\\n            return withoutPkAttr;\\n          }, {});\\n          const ids = this.idExists(branch, id) ? [id] : [];\\n\\n          if (Object.keys(remainingPayload).length) {\\n            /**\\n             * Payload has additional, non-PK columns.\\n             * Filter accessed row by remaining payload (if one was found).\\n             */\\n            return reducer(this.accessIds(branch, ids), { ...clause,\\n              payload: remainingPayload\\n            });\\n          }\\n          /**\\n           * No need to filter these rows any further.\\n           * The primary key value satisfies this clause's conditions.\\n           */\\n\\n\\n          return this.accessIds(branch, ids);\\n        }\\n\\n        if (type === _constants__WEBPACK_IMPORTED_MODULE_5__[\\\"FILTER\\\"] && typeof payload === \\\"object\\\") {\\n          const indexes = Object.entries(branch.indexes);\\n          const accessedIndexes = [];\\n          const indexAttrs = [];\\n          indexes.forEach(([attr, index]) => {\\n            if (Object(_utils__WEBPACK_IMPORTED_MODULE_6__[\\\"clauseFiltersByAttribute\\\"])(clause, attr)) {\\n              /**\\n               * Payload specified an indexed attribute. Use index\\n               * to potentially decrease amount of accessed rows.\\n               */\\n              if (index.hasOwnProperty(payload[attr])) {\\n                accessedIndexes.push(index[payload[attr]]);\\n                indexAttrs.push(attr);\\n              }\\n            }\\n          });\\n          /**\\n           * Calculate set of unique PK values corresponding to each\\n           * foreign key's attribute value. Then retrieve all those rows.\\n           */\\n\\n          if (accessedIndexes.length) {\\n            const lastIndex = accessedIndexes.pop();\\n            const indexedIds = accessedIndexes.reduce((result, index) => {\\n              const indexSet = new Set(index);\\n              return result.filter(Set.prototype.has, indexSet);\\n            }, lastIndex);\\n            const remainingPayload = Object.keys(payload).reduce((withoutIndexAttrs, filterAttr) => {\\n              if (!indexAttrs.includes(filterAttr)) {\\n                withoutIndexAttrs[filterAttr] = payload[filterAttr];\\n              }\\n\\n              return withoutIndexAttrs;\\n            }, {});\\n\\n            if (Object.keys(remainingPayload).length) {\\n              /**\\n               * Payload has additional, non-indexed columns.\\n               * Filter indexed rows by remaining payload (if any were found).\\n               */\\n              return reducer(this.accessIds(branch, indexedIds), { ...clause,\\n                payload: remainingPayload\\n              });\\n            }\\n            /**\\n             * No need to filter these rows any further.\\n             * The used indexes satisfy this clause's conditions.\\n             */\\n\\n\\n            return this.accessIds(branch, indexedIds);\\n          }\\n        } // Give up optimization: Retrieve all rows (full table scan).\\n\\n\\n        return reducer(this.accessList(branch), clause);\\n      }\\n\\n      switch (type) {\\n        case _constants__WEBPACK_IMPORTED_MODULE_5__[\\\"FILTER\\\"]:\\n          {\\n            return lodash_filter__WEBPACK_IMPORTED_MODULE_1___default()(rows, payload);\\n          }\\n\\n        case _constants__WEBPACK_IMPORTED_MODULE_5__[\\\"EXCLUDE\\\"]:\\n          {\\n            return lodash_reject__WEBPACK_IMPORTED_MODULE_3___default()(rows, payload);\\n          }\\n\\n        case _constants__WEBPACK_IMPORTED_MODULE_5__[\\\"ORDER_BY\\\"]:\\n          {\\n            const [iteratees, orders] = payload;\\n            return lodash_orderBy__WEBPACK_IMPORTED_MODULE_2___default()(rows, iteratees, normalizeOrders(orders));\\n          }\\n\\n        default:\\n          return rows;\\n      }\\n    };\\n\\n    return optimallyOrderedClauses.reduce(reducer, undefined);\\n  }\\n  /**\\n   * Returns the data structure including a new object `entry`\\n   * @param  {Object} tx - transaction info\\n   * @param  {Object} branch - the data structure state\\n   * @param  {Object} entry - the object to insert\\n   * @return {Object} an object with two keys: `state` and `created`.\\n   *                  `state` is the new table state and `created` is the\\n   *                  row that was created.\\n   */\\n  ;\\n\\n  _proto.insert = function insert(tx, branch, entry) {\\n    const {\\n      batchToken,\\n      withMutations\\n    } = tx;\\n    const hasId = entry.hasOwnProperty(this.idAttribute);\\n    let workingState = branch; // This will not affect string id's.\\n\\n    const [newMaxId, id] = idSequencer(this.getMaxId(branch), entry[this.idAttribute]);\\n    workingState = this.setMaxId(tx, branch, newMaxId);\\n    const finalEntry = hasId ? entry : immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.set(batchToken, this.idAttribute, id, entry);\\n    const indexesToAppendTo = Object.keys(workingState.indexes).filter(fkAttr => entry.hasOwnProperty(fkAttr) && entry[fkAttr] !== null).map(fkAttr => [fkAttr, entry[fkAttr]]);\\n\\n    if (withMutations) {\\n      immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.push(id, workingState[this.arrName]);\\n      immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.set(id, finalEntry, workingState[this.mapName]); // add id to indexes\\n\\n      indexesToAppendTo.forEach(([attr, value]) => {\\n        const attrIndex = workingState.indexes[attr];\\n\\n        if (attrIndex.hasOwnProperty(value)) {\\n          immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.push(id, attrIndex[value]);\\n        } else {\\n          immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.set(value, [id], attrIndex);\\n        }\\n      });\\n      return {\\n        state: workingState,\\n        created: finalEntry\\n      };\\n    }\\n\\n    const nextIndexes = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, indexesToAppendTo.reduce((indexMap, [attr, value]) => {\\n      indexMap[attr] = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n        [value]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.push(batchToken, id, indexMap[attr][value] || [])\\n      }, indexMap[attr]);\\n      return indexMap;\\n    }, { ...workingState.indexes\\n    }), workingState.indexes);\\n    const nextState = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n      [this.arrName]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.push(batchToken, id, workingState[this.arrName]),\\n      [this.mapName]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n        [id]: finalEntry\\n      }, workingState[this.mapName]),\\n      indexes: nextIndexes\\n    }, workingState);\\n    return {\\n      state: nextState,\\n      created: finalEntry\\n    };\\n  }\\n  /**\\n   * Returns the data structure with objects where `rows`\\n   * are merged with `mergeObj`.\\n   *\\n   * @param  {Object} tx - transaction info\\n   * @param  {Object} branch - the data structure state\\n   * @param  {Object[]} rows - rows to update\\n   * @param  {Object} mergeObj - The object to merge with each row.\\n   * @return {Object}\\n   */\\n  ;\\n\\n  _proto.update = function update(tx, branch, rows, mergeObj) {\\n    const {\\n      batchToken,\\n      withMutations\\n    } = tx;\\n\\n    const mergeObjInto = row => {\\n      const merge = withMutations ? immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.merge : immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken);\\n      return merge(mergeObj, row);\\n    };\\n\\n    const set = withMutations ? immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.set : immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.set(batchToken);\\n    const indexedAttrs = Object.keys(branch.indexes).filter(attr => mergeObj.hasOwnProperty(attr));\\n    const indexIdsToAdd = [];\\n    const indexIdsToDelete = [];\\n    const nextMap = rows.reduce((map, row) => {\\n      const prevAttrValues = indexedAttrs.reduce((valueMap, attr) => ({ ...valueMap,\\n        [attr]: row[attr]\\n      }), {});\\n      const result = mergeObjInto(row);\\n      const nextAttrValues = indexedAttrs.reduce((valueMap, attr) => ({ ...valueMap,\\n        [attr]: result[attr]\\n      }), {});\\n      const id = result[this.idAttribute];\\n      const nextRow = set(id, result, map);\\n      indexedAttrs.forEach(attr => {\\n        const {\\n          [attr]: prevValue\\n        } = prevAttrValues;\\n        const {\\n          [attr]: nextValue\\n        } = nextAttrValues;\\n\\n        if (prevValue === nextValue) {\\n          // attribute has not changed, no need to update any index\\n          return;\\n        }\\n\\n        if (prevValue !== null && typeof prevValue !== \\\"undefined\\\") {\\n          // remove id from attribute's index for its old value\\n          indexIdsToDelete.push([attr, prevValue, id]);\\n        }\\n\\n        if (nextValue !== null) {\\n          // add id to attribute's index for its new value\\n          indexIdsToAdd.push([attr, nextValue, id]);\\n        }\\n      });\\n      return nextRow;\\n    }, branch[this.mapName]);\\n    let nextIndexes = branch.indexes;\\n\\n    if (withMutations) {\\n      indexIdsToDelete.forEach(([attr, value, id]) => {\\n        const arr = nextIndexes[attr][value];\\n        const idx = arr.indexOf(id);\\n        immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.splice(idx, 1, [], arr);\\n      });\\n      indexIdsToAdd.forEach(([attr, value, id]) => {\\n        immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.push(id, nextIndexes[attr][value]);\\n      });\\n    } else {\\n      if (indexIdsToAdd.length) {\\n        nextIndexes = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, indexIdsToAdd.reduce((indexMap, [attr, value, id]) => {\\n          indexMap[attr] = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n            [value]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.push(batchToken, id, indexMap[attr][value] || [])\\n          }, indexMap[attr]);\\n          return indexMap;\\n        }, { ...nextIndexes\\n        }), nextIndexes);\\n      }\\n\\n      if (indexIdsToDelete.length) {\\n        nextIndexes = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, indexIdsToDelete.reduce((indexMap, [attr, value, id]) => {\\n          indexMap[attr] = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n            [value]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.filter(batchToken, rowId => rowId !== id, indexMap[attr][value])\\n          }, indexMap[attr]);\\n          return indexMap;\\n        }, { ...nextIndexes\\n        }), nextIndexes);\\n      }\\n    }\\n\\n    return immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n      [this.mapName]: nextMap,\\n      indexes: nextIndexes\\n    }, branch);\\n  }\\n  /**\\n   * Returns the data structure without rows `rows`.\\n   * @param  {Object} tx - transaction info\\n   * @param  {Object} branch - the data structure state\\n   * @param  {Object[]} rows - rows to update\\n   * @return {Object} the data structure without ids in `idsToDelete`.\\n   */\\n  ;\\n\\n  _proto.delete = function _delete(tx, branch, rows) {\\n    const {\\n      batchToken,\\n      withMutations\\n    } = tx;\\n    const {\\n      arrName,\\n      mapName\\n    } = this;\\n    const arr = branch[arrName];\\n    const idsToDelete = rows.map(row => row[this.idAttribute]);\\n\\n    if (withMutations) {\\n      idsToDelete.forEach(id => {\\n        const idx = arr.indexOf(id);\\n        immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.splice(idx, 1, [], arr);\\n        immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.omit(id, branch[mapName]);\\n      }); // delete ids from all indexes\\n\\n      Object.values(branch.indexes).forEach(attrIndex => Object.values(attrIndex).forEach(valueIndex => idsToDelete.forEach(id => {\\n        const idx = valueIndex.indexOf(id);\\n\\n        if (idx !== -1) {\\n          immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].mutable.splice(idx, 1, [], valueIndex);\\n        }\\n      })));\\n      return branch;\\n    }\\n\\n    const nextIndexes = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, Object.entries(branch.indexes).reduce((indexMap, [attr, attrIndex]) => {\\n      indexMap[attr] = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, Object.entries(attrIndex).reduce((attrIndexMap, [value, valueIndex]) => {\\n        attrIndexMap[value] = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.filter(batchToken, id => !idsToDelete.includes(id), valueIndex);\\n        return attrIndexMap;\\n      }, { ...indexMap[attr]\\n      }), indexMap[attr]);\\n      return indexMap;\\n    }, { ...branch.indexes\\n    }), branch.indexes);\\n    return immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, {\\n      [arrName]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.filter(batchToken, id => !idsToDelete.includes(id), branch[arrName]),\\n      [mapName]: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.omit(batchToken, idsToDelete, branch[mapName]),\\n      indexes: immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"].batch.merge(batchToken, nextIndexes, branch.indexes)\\n    }, branch);\\n  };\\n\\n  return Table;\\n}();\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (Table);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9kYi9UYWJsZS5qcz83NDk3Il0sIm5hbWVzIjpbIkRFRkFVTFRfVEFCTEVfT1BUSU9OUyIsImlkQXR0cmlidXRlIiwiYXJyTmFtZSIsIm1hcE5hbWUiLCJmaWVsZHMiLCJpZFNlcXVlbmNlciIsIl9jdXJyTWF4IiwidXNlclBhc3NlZElkIiwiY3Vyck1heCIsIm5ld01heCIsIm5ld0lkIiwidW5kZWZpbmVkIiwiTWF0aCIsIm1heCIsIm5vcm1hbGl6ZU9yZGVycyIsIm9yZGVycyIsImNvbnZlcnQiLCJvcmRlciIsImluY2x1ZGVzIiwiQXJyYXkiLCJpc0FycmF5IiwibWFwIiwiVGFibGUiLCJ1c2VyT3B0cyIsIk9iamVjdCIsImFzc2lnbiIsImFjY2Vzc0lkIiwiYnJhbmNoIiwiaWQiLCJhY2Nlc3NJZHMiLCJpZHMiLCJpZEV4aXN0cyIsImhhc093blByb3BlcnR5IiwiYWNjZXNzSWRMaXN0IiwiYWNjZXNzTGlzdCIsImdldE1heElkIiwiZ2V0TWV0YSIsInNldE1heElkIiwidHgiLCJuZXdNYXhJZCIsInNldE1ldGEiLCJuZXh0SWQiLCJnZXRFbXB0eVN0YXRlIiwicGtJbmRleCIsImF0dHJJbmRleGVzIiwia2V5cyIsImZpbHRlciIsImF0dHIiLCJpbmRleCIsInJlZHVjZSIsImluZGV4ZXMiLCJtZXRhIiwia2V5IiwidmFsdWUiLCJiYXRjaFRva2VuIiwid2l0aE11dGF0aW9ucyIsInJlcyIsIm9wcyIsIm11dGFibGUiLCJzZXRJbiIsImJhdGNoIiwicXVlcnkiLCJjbGF1c2VzIiwibGVuZ3RoIiwib3B0aW1hbGx5T3JkZXJlZENsYXVzZXMiLCJzb3J0QnkiLCJjbGF1c2UiLCJjbGF1c2VGaWx0ZXJzQnlBdHRyaWJ1dGUiLCJjbGF1c2VSZWR1Y2VzUmVzdWx0U2V0U2l6ZSIsInJlZHVjZXIiLCJyb3dzIiwidHlwZSIsInBheWxvYWQiLCJyZW1haW5pbmdQYXlsb2FkIiwid2l0aG91dFBrQXR0ciIsImZpbHRlckF0dHIiLCJGSUxURVIiLCJlbnRyaWVzIiwiYWNjZXNzZWRJbmRleGVzIiwiaW5kZXhBdHRycyIsImZvckVhY2giLCJwdXNoIiwibGFzdEluZGV4IiwicG9wIiwiaW5kZXhlZElkcyIsInJlc3VsdCIsImluZGV4U2V0IiwiU2V0IiwicHJvdG90eXBlIiwiaGFzIiwid2l0aG91dEluZGV4QXR0cnMiLCJFWENMVURFIiwicmVqZWN0IiwiT1JERVJfQlkiLCJpdGVyYXRlZXMiLCJvcmRlckJ5IiwiaW5zZXJ0IiwiZW50cnkiLCJoYXNJZCIsIndvcmtpbmdTdGF0ZSIsImZpbmFsRW50cnkiLCJzZXQiLCJpbmRleGVzVG9BcHBlbmRUbyIsImZrQXR0ciIsImF0dHJJbmRleCIsInN0YXRlIiwiY3JlYXRlZCIsIm5leHRJbmRleGVzIiwibWVyZ2UiLCJpbmRleE1hcCIsIm5leHRTdGF0ZSIsInVwZGF0ZSIsIm1lcmdlT2JqIiwibWVyZ2VPYmpJbnRvIiwicm93IiwiaW5kZXhlZEF0dHJzIiwiaW5kZXhJZHNUb0FkZCIsImluZGV4SWRzVG9EZWxldGUiLCJuZXh0TWFwIiwicHJldkF0dHJWYWx1ZXMiLCJ2YWx1ZU1hcCIsIm5leHRBdHRyVmFsdWVzIiwibmV4dFJvdyIsInByZXZWYWx1ZSIsIm5leHRWYWx1ZSIsImFyciIsImlkeCIsImluZGV4T2YiLCJzcGxpY2UiLCJyb3dJZCIsImRlbGV0ZSIsImlkc1RvRGVsZXRlIiwib21pdCIsInZhbHVlcyIsInZhbHVlSW5kZXgiLCJhdHRySW5kZXhNYXAiXSwibWFwcGluZ3MiOiJBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUVBO0FBQ0E7QUFFQSxNQUFNQSxxQkFBcUIsR0FBRztBQUMxQkMsYUFBVyxFQUFFLElBRGE7QUFFMUJDLFNBQU8sRUFBRSxPQUZpQjtBQUcxQkMsU0FBTyxFQUFFLFdBSGlCO0FBSTFCQyxRQUFNLEVBQUU7QUFKa0IsQ0FB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TCxFQW1CSSxFQUFFLEdBQUdsRixNQUFNLENBQUN1QjtBQUFaLEtBbkJKLENBRmdCLEVBdUJoQnZCLE1BQU0sQ0FBQ3VCLE9BdkJTLENBQXBCO0FBMEJBLFdBQU9PLHFEQUFHLENBQUNHLEtBQUosQ0FBVWdELEtBQVYsQ0FDSHRELFVBREcsRUFFSDtBQUNJLE9BQUNwRCxPQUFELEdBQVd1RCxxREFBRyxDQUFDRyxLQUFKLENBQVVkLE1BQVYsQ0FDUFEsVUFETyxFQUVOMUIsRUFBRCxJQUFRLENBQUN1RyxXQUFXLENBQUNqSCxRQUFaLENBQXFCVSxFQUFyQixDQUZGLEVBR1BELE1BQU0sQ0FBQ3pCLE9BQUQsQ0FIQyxDQURmO0FBTUksT0FBQ0MsT0FBRCxHQUFXc0QscURBQUcsQ0FBQ0csS0FBSixDQUFVd0UsSUFBVixDQUNQOUUsVUFETyxFQUVQNkUsV0FGTyxFQUdQeEcsTUFBTSxDQUFDeEIsT0FBRCxDQUhDLENBTmY7QUFXSStDLGFBQU8sRUFBRU8scURBQUcsQ0FBQ0csS0FBSixDQUFVZ0QsS0FBVixDQUNMdEQsVUFESyxFQUVMcUQsV0FGSyxFQUdMaEYsTUFBTSxDQUFDdUIsT0FIRjtBQVhiLEtBRkcsRUFtQkh2QixNQW5CRyxDQUFQO0FBcUJILEdBMWlCTDs7QUFBQTtBQUFBO0FBNmlCZUwsb0VBQWYiLCJmaWxlIjoiLi9zcmMvZGIvVGFibGUuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgb3BzIGZyb20gXCJpbW11dGFibGUtb3BzXCI7XG5pbXBvcnQgZmlsdGVyIGZyb20gXCJsb2Rhc2gvZmlsdGVyXCI7XG5pbXBvcnQgb3JkZXJCeSBmcm9tIFwibG9kYXNoL29yZGVyQnlcIjtcbmltcG9ydCByZWplY3QgZnJvbSBcImxvZGFzaC9yZWplY3RcIjtcbmltcG9ydCBzb3J0QnkgZnJvbSBcImxvZGFzaC9zb3J0QnlcIjtcblxuaW1wb3J0IHsgRVhDTFVERSwgRklMVEVSLCBPUkRFUl9CWSB9IGZyb20gXCIuLi9jb25zdGFudHNcIjtcbmltcG9ydCB7IGNsYXVzZUZpbHRlcnNCeUF0dHJpYnV0ZSwgY2xhdXNlUmVkdWNlc1Jlc3VsdFNldFNpemUgfSBmcm9tIFwiLi4vdXRpbHNcIjtcblxuY29uc3QgREVGQVVMVF9UQUJMRV9PUFRJT05TID0ge1xuICAgIGlkQXR0cmlidXRlOiBcImlkXCIsXG4gICAgYXJyTmFtZTogXCJpdGVtc1wiLFxuICAgIG1hcE5hbWU6IFwiaXRlbXNCeUlkXCIsXG4gICAgZmllbGRzOiB7fSxcbn07XG5cbi8qKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7Kn0gX2N1cnJNYXggLSB0aGUgY3VycmVudCBtYXggaWRcbiAqIEBwYXJhbSB7Kn0gdXNlclBhc3NlZElkIC0gdGhlIG5ldyBpZCBwYXNzZWQgdG8gdGhlIGNyZWF0ZSBhY3Rpb25cbiAqXG4gKiBCb3RoIG1heSBiZSB1bmRlZmluZWQuIFRoZSBjdXJyZW50IG1heCBpZCBpbiB0aGUgY2FzZSB0aGF0IHRoaXMgaXMgdGhlIGZpcnN0IE1vZGVsXG4gKiBiZWluZyBjcmVhdGVkLCBhbmQgdGhlIG5ldyBpZCBpZiB0aGUgaWQgd2FzIG5vdCBleHBsaWNpdGx5IHBhc3NlZCB0byB0aGVcbiAqIGRhdGFiYXNlLlxuICpcbiAqIEByZXR1cm4ge0FycmF5fSB0aGUgbmV3IG1heCBpZCBhbmQgdGhlIGlkIHRvIHVzZSB0byBjcmVhdGUgdGhlIG5ldyByb3dcbiAqXG4gKiBJZiB0aGUgaWQncyBhcmUgc3RyaW5ncywgdGhlIGlkIG11c3QgYmUgcGFzc2VkIGV4cGxpY2l0bHkgZXZlcnkgdGltZS5cbiAqIEluIHRoaXMgY2FzZSwgdGhlIGN1cnJlbnQgbWF4IGlkIHdpbGwgcmVtYWluIGBOYU5gIGR1ZSB0byBgTWF0aC5tYXhgLCBidXQgdGhhdCdzIGZpbmUuXG4gKi9cbmZ1bmN0aW9uIGlkU2VxdWVuY2VyKF9jdXJyTWF4LCB1c2VyUGFzc2VkSWQpIHtcbiAgICBsZXQgY3Vyck1heCA9IF9jdXJyTWF4O1xuICAgIGxldCBuZXdNYXg7XG4gICAgbGV0IG5ld0lkO1xuXG4gICAgaWYgKGN1cnJNYXggPT09IHVuZGVmaW5lZCkge1xuICAgICAgICBjdXJyTWF4ID0gLTE7XG4gICAgfVxuXG4gICAgaWYgKHVzZXJQYXNzZWRJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIG5ld01heCA9IGN1cnJNYXggKyAxO1xuICAgICAgICBuZXdJZCA9IG5ld01heDtcbiAgICB9IGVsc2Uge1xuICAgICAgICBuZXdNYXggPSBNYXRoLm1heChjdXJyTWF4ICsgMSwgdXNlclBhc3NlZElkKTtcbiAgICAgICAgbmV3SWQgPSB1c2VyUGFzc2VkSWQ7XG4gICAgfVxuXG4gICAgcmV0dXJuIFtcbiAgICAgICAgbmV3TWF4LCAvLyBuZXcgbWF4IGlkXG4gICAgICAgIG5ld0lkLCAvLyBpZCB0byB1c2UgZm9yIHJvdyBjcmVhdGlvblxuICAgIF07XG59XG5cbi8qKlxuICogQWRhcHQgb3JkZXIgZGlyZWN0aW9ucyBhcnJheSB0byBAe2xvZGFzaC5vcmRlckJ5fSBBUEkuXG4gKlxuICogQHByaXZhdGVcbiAqXG4gKiBAcGFyYW0ge0FycmF5PEJvb2xlYW58J2FzYyd8J2Rlc2MnPn0gb3JkZXJzPyAtIGFuIGFycmF5IG9mIG9wdGlvbmFsIG9yZGVyIHF1ZXJ5IGRpcmVjdGlvbnMgYXMgcHJvdmlkZWQgdG8ge0BMaW5rIHtRdWVyeVNldC5vcmRlckJ5fX1cbiAqIEByZXR1cm4ge0FycmF5PCdhc2MnfCdkZXNjJz58dW5kZWZpbmVkfSBBIG5vcm1hbGl6ZWQgb3JkZXJpbmcgYXJyYXkgb3IgdW5kZWZpbmVkIGlmIG5vbmUgd2FzIHByb3ZpZGVkLlxuICovXG5mdW5jdGlvbiBub3JtYWxpemVPcmRlcnMob3JkZXJzKSB7XG4gICAgaWYgKG9yZGVycyA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfVxuICAgIGNvbnN0IGNvbnZlcnQgPSAob3JkZXIpID0+IHtcbiAgICAgICAgaWYgKFtcImRlc2NcIiwgZmFsc2VdLmluY2x1ZGVzKG9yZGVyKSkge1xuICAgICAgICAgICAgcmV0dXJuIFwiZGVzY1wiO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBcImFzY1wiO1xuICAgIH07XG4gICAgcmV0dXJuIEFycmF5LmlzQXJyYXkob3JkZXJzKSA/IG9yZGVycy5tYXAoY29udmVydCkgOiBjb252ZXJ0KG9yZGVycyk7XG59XG5cbi8qKlxuICogSGFuZGxlcyB0aGUgdW5kZXJseWluZyBkYXRhIHN0cnVjdHVyZSBmb3IgYSB7QGxpbmsgTW9kZWx9IGNsYXNzLlxuICogQHByaXZhdGVcbiAqL1xuZXhwb3J0IGNsYXNzIFRhYmxlIHtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGEgbmV3IHtAbGluayBUYWJsZX0gaW5zdGFuY2UuXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSB1c2VyT3B0cyAtIG9wdGlvbnMgdG8gdXNlLlxuICAgICAqIEBwYXJhbSAge3N0cmluZ30gW3VzZXJPcHRzLmlkQXR0cmlidXRlPWlkXSAtIHRoZSBpZCBhdHRyaWJ1dGUgb2YgdGhlIGVudGl0eS5cbiAgICAgKiBAcGFyYW0gIHtzdHJpbmd9IFt1c2VyT3B0cy5hcnJOYW1lPWl0ZW1zXSAtIHRoZSBzdGF0ZSBhdHRyaWJ1dGUgd2hlcmUgYW4gYXJyYXkgb2ZcbiAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVudGl0eSBpZCdzIGFyZSBzdG9yZWRcbiAgICAgKiBAcGFyYW0gIHtzdHJpbmd9IFt1c2VyT3B0cy5tYXBOYW1lPWl0ZW1zQnlJZF0gLSB0aGUgc3RhdGUgYXR0cmlidXRlIHdoZXJlIHRoZSBlbnRpdHkgb2JqZWN0c1xuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFyZSBzdG9yZWQgaW4gYSBpZCB0byBlbnRpdHkgb2JqZWN0XG4gICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFwLlxuICAgICAqIEBwYXJhbSAge3N0cmluZ30gW3VzZXJPcHRzLmZpZWxkcz17fV0gLSBtYXBwaW5nIG9mIGZpZWxkIGtleSB0byB7QGxpbmsgRmllbGR9IG9iamVjdFxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKHVzZXJPcHRzKSB7XG4gICAgICAgIE9iamVjdC5hc3NpZ24odGhpcywgREVGQVVMVF9UQUJMRV9PUFRJT05TLCB1c2VyT3B0cyk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhIHJlZmVyZW5jZSB0byB0aGUgb2JqZWN0IGF0IGluZGV4IGBpZGBcbiAgICAgKiBpbiBzdGF0ZSBgYnJhbmNoYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gYnJhbmNoIC0gdGhlIHN0YXRlXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSBpZCAtIHRoZSBpZCBvZiB0aGUgb2JqZWN0IHRvIGdldFxuICAgICAqIEByZXR1cm4ge09iamVjdHx1bmRlZmluZWR9IEEgcmVmZXJlbmNlIHRvIHRoZSByYXcgb2JqZWN0IGluIHRoZSBzdGF0ZSBvclxuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB1bmRlZmluZWRgIGlmIG5vdCBmb3VuZC5cbiAgICAgKi9cbiAgICBhY2Nlc3NJZChicmFuY2gsIGlkKSB7XG4gICAgICAgIHJldHVybiBicmFuY2hbdGhpcy5tYXBOYW1lXVtpZF07XG4gICAgfVxuXG4gICAgYWNjZXNzSWRzKGJyYW5jaCwgaWRzKSB7XG4gICAgICAgIGNvbnN0IG1hcCA9IGJyYW5jaFt0aGlzLm1hcE5hbWVdO1xuICAgICAgICByZXR1cm4gaWRzLm1hcCgoaWQpID0+IG1hcFtpZF0pO1xuICAgIH1cblxuICAgIGlkRXhpc3RzKGJyYW5jaCwgaWQpIHtcbiAgICAgICAgcmV0dXJuIGJyYW5jaFt0aGlzLm1hcE5hbWVdLmhhc093blByb3BlcnR5KGlkKTtcbiAgICB9XG5cbiAgICBhY2Nlc3NJZExpc3QoYnJhbmNoKSB7XG4gICAgICAgIHJldHVybiBicmFuY2hbdGhpcy5hcnJOYW1lXTtcbiAgICB9XG5cbiAgICBhY2Nlc3NMaXN0KGJyYW5jaCkge1xuICAgICAgICByZXR1cm4gdGhpcy5hY2Nlc3NJZHMoYnJhbmNoLCB0aGlzLmFjY2Vzc0lkTGlzdChicmFuY2gpKTtcbiAgICB9XG5cbiAgICBnZXRNYXhJZChicmFuY2gpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0TWV0YShicmFuY2gsIFwibWF4SWRcIik7XG4gICAgfVxuXG4gICAgc2V0TWF4SWQodHgsIGJyYW5jaCwgbmV3TWF4SWQpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuc2V0TWV0YSh0eCwgYnJhbmNoLCBcIm1heElkXCIsIG5ld01heElkKTtcbiAgICB9XG5cbiAgICBuZXh0SWQoaWQpIHtcbiAgICAgICAgcmV0dXJuIGlkICsgMTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBkZWZhdWx0IHN0YXRlIGZvciB0aGUgZGF0YSBzdHJ1Y3R1cmUuXG4gICAgICogQHJldHVybiB7T2JqZWN0fSBUaGUgZGVmYXVsdCBzdGF0ZSBmb3IgdGhpcyB7QGxpbmsgT1JNfSBpbnN0YW5jZSdzIGRhdGEgc3RydWN0dXJlXG4gICAgICovXG4gICAgZ2V0RW1wdHlTdGF0ZSgpIHtcbiAgICAgICAgY29uc3QgcGtJbmRleCA9IHtcbiAgICAgICAgICAgIFt0aGlzLmFyck5hbWVdOiBbXSxcbiAgICAgICAgICAgIFt0aGlzLm1hcE5hbWVdOiB7fSxcbiAgICAgICAgfTtcbiAgICAgICAgY29uc3QgYXR0ckluZGV4ZXMgPSBPYmplY3Qua2V5cyh0aGlzLmZpZWxkcylcbiAgICAgICAgICAgIC5maWx0ZXIoKGF0dHIpID0+IGF0dHIgIT09IHRoaXMuaWRBdHRyaWJ1dGUpXG4gICAgICAgICAgICAuZmlsdGVyKChhdHRyKSA9PiB0aGlzLmZpZWxkc1thdHRyXS5pbmRleClcbiAgICAgICAgICAgIC5yZWR1Y2UoXG4gICAgICAgICAgICAgICAgKGluZGV4ZXMsIGF0dHIpID0+ICh7XG4gICAgICAgICAgICAgICAgICAgIC4uLmluZGV4ZXMsXG4gICAgICAgICAgICAgICAgICAgIFthdHRyXToge30sXG4gICAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICAgICAge31cbiAgICAgICAgICAgICk7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAuLi5wa0luZGV4LFxuICAgICAgICAgICAgaW5kZXhlczogYXR0ckluZGV4ZXMsXG4gICAgICAgICAgICBtZXRhOiB7fSxcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICBzZXRNZXRhKHR4LCBicmFuY2gsIGtleSwgdmFsdWUpIHtcbiAgICAgICAgY29uc3QgeyBiYXRjaFRva2VuLCB3aXRoTXV0YXRpb25zIH0gPSB0eDtcbiAgICAgICAgaWYgKHdpdGhNdXRhdGlvbnMpIHtcbiAgICAgICAgICAgIGNvbnN0IHJlcyA9IG9wcy5tdXRhYmxlLnNldEluKFtcIm1ldGFcIiwga2V5XSwgdmFsdWUsIGJyYW5jaCk7XG4gICAgICAgICAgICByZXR1cm4gcmVzO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG9wcy5iYXRjaC5zZXRJbihiYXRjaFRva2VuLCBbXCJtZXRhXCIsIGtleV0sIHZhbHVlLCBicmFuY2gpO1xuICAgIH1cblxuICAgIGdldE1ldGEoYnJhbmNoLCBrZXkpIHtcbiAgICAgICAgcmV0dXJuIGJyYW5jaC5tZXRhW2tleV07XG4gICAgfVxuXG4gICAgcXVlcnkoYnJhbmNoLCBjbGF1c2VzKSB7XG4gICAgICAgIGlmIChjbGF1c2VzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuYWNjZXNzTGlzdChicmFuY2gpO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgeyBpZEF0dHJpYnV0ZSB9ID0gdGhpcztcblxuICAgICAgICBjb25zdCBvcHRpbWFsbHlPcmRlcmVkQ2xhdXNlcyA9IHNvcnRCeShjbGF1c2VzLCAoY2xhdXNlKSA9PiB7XG4gICAgICAgICAgICBpZiAoY2xhdXNlRmlsdGVyc0J5QXR0cmlidXRlKGNsYXVzZSwgaWRBdHRyaWJ1dGUpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIDE7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChjbGF1c2VSZWR1Y2VzUmVzdWx0U2V0U2l6ZShjbGF1c2UpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIDI7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiAzO1xuICAgICAgICB9KTtcblxuICAgICAgICBjb25zdCByZWR1Y2VyID0gKHJvd3MsIGNsYXVzZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgeyB0eXBlLCBwYXlsb2FkIH0gPSBjbGF1c2U7XG4gICAgICAgICAgICBpZiAoIXJvd3MpIHtcbiAgICAgICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAgICAgKiBGaXJzdCB0aW1lIHRoaXMgcmVkdWNlciBpcyBjYWxsZWQgZHVyaW5nIHF1ZXJ5LlxuICAgICAgICAgICAgICAgICAqIFRoaXMgaXMgd2hlcmUgd2UgYXBwbHkgcXVlcnkgb3B0aW1pemF0aW9ucy5cbiAgICAgICAgICAgICAgICAgKi9cbiAgICAgICAgICAgICAgICBpZiAoY2xhdXNlRmlsdGVyc0J5QXR0cmlidXRlKGNsYXVzZSwgaWRBdHRyaWJ1dGUpKSB7XG4gICAgICAgICAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICAgICAgICAgKiBQYXlsb2FkIHNwZWNpZmllZCBhIHByaW1hcnkga2V5LiBVc2UgUEsgaW5kZXhcbiAgICAgICAgICAgICAgICAgICAgICogdG8gbG9vayB1cCB0aGUgc2luZ2xlIHJvdyBpZGVudGlmaWVkIGJ5IHRoZSBQSy5cbiAgICAgICAgICAgICAgICAgICAgICovXG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGlkID0gcGF5bG9hZFtpZEF0dHJpYnV0ZV07XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHJlbWFpbmluZ1BheWxvYWQgPSBPYmplY3Qua2V5cyhwYXlsb2FkKS5yZWR1Y2UoXG4gICAgICAgICAgICAgICAgICAgICAgICAod2l0aG91dFBrQXR0ciwgZmlsdGVyQXR0cikgPT4ge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChmaWx0ZXJBdHRyICE9PSBpZEF0dHJpYnV0ZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aXRob3V0UGtBdHRyW2ZpbHRlckF0dHJdID0gcGF5bG9hZFtmaWx0ZXJBdHRyXTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHdpdGhvdXRQa0F0dHI7XG4gICAgICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICAgICAge31cbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgaWRzID0gdGhpcy5pZEV4aXN0cyhicmFuY2gsIGlkKSA/IFtpZF0gOiBbXTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKE9iamVjdC5rZXlzKHJlbWFpbmluZ1BheWxvYWQpLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgICAgICAgICAgICAgKiBQYXlsb2FkIGhhcyBhZGRpdGlvbmFsLCBub24tUEsgY29sdW1ucy5cbiAgICAgICAgICAgICAgICAgICAgICAgICAqIEZpbHRlciBhY2Nlc3NlZCByb3cgYnkgcmVtYWluaW5nIHBheWxvYWQgKGlmIG9uZSB3YXMgZm91bmQpLlxuICAgICAgICAgICAgICAgICAgICAgICAgICovXG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmVkdWNlcih0aGlzLmFjY2Vzc0lkcyhicmFuY2gsIGlkcyksIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi5jbGF1c2UsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF5bG9hZDogcmVtYWluaW5nUGF5bG9hZCxcbiAgICAgICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICAgICAgICAgKiBObyBuZWVkIHRvIGZpbHRlciB0aGVzZSByb3dzIGFueSBmdXJ0aGVyLlxuICAgICAgICAgICAgICAgICAgICAgKiBUaGUgcHJpbWFyeSBrZXkgdmFsdWUgc2F0aXNmaWVzIHRoaXMgY2xhdXNlJ3MgY29uZGl0aW9ucy5cbiAgICAgICAgICAgICAgICAgICAgICovXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLmFjY2Vzc0lkcyhicmFuY2gsIGlkcyk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmICh0eXBlID09PSBGSUxURVIgJiYgdHlwZW9mIHBheWxvYWQgPT09IFwib2JqZWN0XCIpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgaW5kZXhlcyA9IE9iamVjdC5lbnRyaWVzKGJyYW5jaC5pbmRleGVzKTtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgYWNjZXNzZWRJbmRleGVzID0gW107XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGluZGV4QXR0cnMgPSBbXTtcbiAgICAgICAgICAgICAgICAgICAgaW5kZXhlcy5mb3JFYWNoKChbYXR0ciwgaW5kZXhdKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoY2xhdXNlRmlsdGVyc0J5QXR0cmlidXRlKGNsYXVzZSwgYXR0cikpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKiBQYXlsb2FkIHNwZWNpZmllZCBhbiBpbmRleGVkIGF0dHJpYnV0ZS4gVXNlIGluZGV4XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICogdG8gcG90ZW50aWFsbHkgZGVjcmVhc2UgYW1vdW50IG9mIGFjY2Vzc2VkIHJvd3MuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICovXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGluZGV4Lmhhc093blByb3BlcnR5KHBheWxvYWRbYXR0cl0pKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjY2Vzc2VkSW5kZXhlcy5wdXNoKGluZGV4W3BheWxvYWRbYXR0cl1dKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXhBdHRycy5wdXNoKGF0dHIpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICAgICAgICAgKiBDYWxjdWxhdGUgc2V0IG9mIHVuaXF1ZSBQSyB2YWx1ZXMgY29ycmVzcG9uZGluZyB0byBlYWNoXG4gICAgICAgICAgICAgICAgICAgICAqIGZvcmVpZ24ga2V5J3MgYXR0cmlidXRlIHZhbHVlLiBUaGVuIHJldHJpZXZlIGFsbCB0aG9zZSByb3dzLlxuICAgICAgICAgICAgICAgICAgICAgKi9cbiAgICAgICAgICAgICAgICAgICAgaWYgKGFjY2Vzc2VkSW5kZXhlcy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IGxhc3RJbmRleCA9IGFjY2Vzc2VkSW5kZXhlcy5wb3AoKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IGluZGV4ZWRJZHMgPSBhY2Nlc3NlZEluZGV4ZXMucmVkdWNlKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIChyZXN1bHQsIGluZGV4KSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IGluZGV4U2V0ID0gbmV3IFNldChpbmRleCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiByZXN1bHQuZmlsdGVyKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU2V0LnByb3RvdHlwZS5oYXMsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleFNldFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFzdEluZGV4XG4gICAgICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgcmVtYWluaW5nUGF5bG9hZCA9IE9iamVjdC5rZXlzKHBheWxvYWQpLnJlZHVjZShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAod2l0aG91dEluZGV4QXR0cnMsIGZpbHRlckF0dHIpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFpbmRleEF0dHJzLmluY2x1ZGVzKGZpbHRlckF0dHIpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aXRob3V0SW5kZXhBdHRyc1tmaWx0ZXJBdHRyXSA9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF5bG9hZFtmaWx0ZXJBdHRyXTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gd2l0aG91dEluZGV4QXR0cnM7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7fVxuICAgICAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChPYmplY3Qua2V5cyhyZW1haW5pbmdQYXlsb2FkKS5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKiBQYXlsb2FkIGhhcyBhZGRpdGlvbmFsLCBub24taW5kZXhlZCBjb2x1bW5zLlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAqIEZpbHRlciBpbmRleGVkIHJvd3MgYnkgcmVtYWluaW5nIHBheWxvYWQgKGlmIGFueSB3ZXJlIGZvdW5kKS5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKi9cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmVkdWNlcih0aGlzLmFjY2Vzc0lkcyhicmFuY2gsIGluZGV4ZWRJZHMpLCB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4uLmNsYXVzZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF5bG9hZDogcmVtYWluaW5nUGF5bG9hZCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICAgICAgICAgICAgICogTm8gbmVlZCB0byBmaWx0ZXIgdGhlc2Ugcm93cyBhbnkgZnVydGhlci5cbiAgICAgICAgICAgICAgICAgICAgICAgICAqIFRoZSB1c2VkIGluZGV4ZXMgc2F0aXNmeSB0aGlzIGNsYXVzZSdzIGNvbmRpdGlvbnMuXG4gICAgICAgICAgICAgICAgICAgICAgICAgKi9cbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLmFjY2Vzc0lkcyhicmFuY2gsIGluZGV4ZWRJZHMpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgLy8gR2l2ZSB1cCBvcHRpbWl6YXRpb246IFJldHJpZXZlIGFsbCByb3dzIChmdWxsIHRhYmxlIHNjYW4pLlxuICAgICAgICAgICAgICAgIHJldHVybiByZWR1Y2VyKHRoaXMuYWNjZXNzTGlzdChicmFuY2gpLCBjbGF1c2UpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBzd2l0Y2ggKHR5cGUpIHtcbiAgICAgICAgICAgICAgICBjYXNlIEZJTFRFUjoge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gZmlsdGVyKHJvd3MsIHBheWxvYWQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBjYXNlIEVYQ0xVREU6IHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHJlamVjdChyb3dzLCBwYXlsb2FkKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgY2FzZSBPUkRFUl9CWToge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBbaXRlcmF0ZWVzLCBvcmRlcnNdID0gcGF5bG9hZDtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG9yZGVyQnkocm93cywgaXRlcmF0ZWVzLCBub3JtYWxpemVPcmRlcnMob3JkZXJzKSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiByb3dzO1xuICAgICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICAgIHJldHVybiBvcHRpbWFsbHlPcmRlcmVkQ2xhdXNlcy5yZWR1Y2UocmVkdWNlciwgdW5kZWZpbmVkKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBkYXRhIHN0cnVjdHVyZSBpbmNsdWRpbmcgYSBuZXcgb2JqZWN0IGBlbnRyeWBcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IHR4IC0gdHJhbnNhY3Rpb24gaW5mb1xuICAgICAqIEBwYXJhbSAge09iamVjdH0gYnJhbmNoIC0gdGhlIGRhdGEgc3RydWN0dXJlIHN0YXRlXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBlbnRyeSAtIHRoZSBvYmplY3QgdG8gaW5zZXJ0XG4gICAgICogQHJldHVybiB7T2JqZWN0fSBhbiBvYmplY3Qgd2l0aCB0d28ga2V5czogYHN0YXRlYCBhbmQgYGNyZWF0ZWRgLlxuICAgICAqICAgICAgICAgICAgICAgICAgYHN0YXRlYCBpcyB0aGUgbmV3IHRhYmxlIHN0YXRlIGFuZCBgY3JlYXRlZGAgaXMgdGhlXG4gICAgICogICAgICAgICAgICAgICAgICByb3cgdGhhdCB3YXMgY3JlYXRlZC5cbiAgICAgKi9cbiAgICBpbnNlcnQodHgsIGJyYW5jaCwgZW50cnkpIHtcbiAgICAgICAgY29uc3QgeyBiYXRjaFRva2VuLCB3aXRoTXV0YXRpb25zIH0gPSB0eDtcblxuICAgICAgICBjb25zdCBoYXNJZCA9IGVudHJ5Lmhhc093blByb3BlcnR5KHRoaXMuaWRBdHRyaWJ1dGUpO1xuXG4gICAgICAgIGxldCB3b3JraW5nU3RhdGUgPSBicmFuY2g7XG5cbiAgICAgICAgLy8gVGhpcyB3aWxsIG5vdCBhZmZlY3Qgc3RyaW5nIGlkJ3MuXG4gICAgICAgIGNvbnN0IFtuZXdNYXhJZCwgaWRdID0gaWRTZXF1ZW5jZXIoXG4gICAgICAgICAgICB0aGlzLmdldE1heElkKGJyYW5jaCksXG4gICAgICAgICAgICBlbnRyeVt0aGlzLmlkQXR0cmlidXRlXVxuICAgICAgICApO1xuICAgICAgICB3b3JraW5nU3RhdGUgPSB0aGlzLnNldE1heElkKHR4LCBicmFuY2gsIG5ld01heElkKTtcblxuICAgICAgICBjb25zdCBmaW5hbEVudHJ5ID0gaGFzSWRcbiAgICAgICAgICAgID8gZW50cnlcbiAgICAgICAgICAgIDogb3BzLmJhdGNoLnNldChiYXRjaFRva2VuLCB0aGlzLmlkQXR0cmlidXRlLCBpZCwgZW50cnkpO1xuXG4gICAgICAgIGNvbnN0IGluZGV4ZXNUb0FwcGVuZFRvID0gT2JqZWN0LmtleXMod29ya2luZ1N0YXRlLmluZGV4ZXMpXG4gICAgICAgICAgICAuZmlsdGVyKFxuICAgICAgICAgICAgICAgIChma0F0dHIpID0+XG4gICAgICAgICAgICAgICAgICAgIGVudHJ5Lmhhc093blByb3BlcnR5KGZrQXR0cikgJiYgZW50cnlbZmtBdHRyXSAhPT0gbnVsbFxuICAgICAgICAgICAgKVxuICAgICAgICAgICAgLm1hcCgoZmtBdHRyKSA9PiBbZmtBdHRyLCBlbnRyeVtma0F0dHJdXSk7XG5cbiAgICAgICAgaWYgKHdpdGhNdXRhdGlvbnMpIHtcbiAgICAgICAgICAgIG9wcy5tdXRhYmxlLnB1c2goaWQsIHdvcmtpbmdTdGF0ZVt0aGlzLmFyck5hbWVdKTtcbiAgICAgICAgICAgIG9wcy5tdXRhYmxlLnNldChpZCwgZmluYWxFbnRyeSwgd29ya2luZ1N0YXRlW3RoaXMubWFwTmFtZV0pO1xuICAgICAgICAgICAgLy8gYWRkIGlkIHRvIGluZGV4ZXNcbiAgICAgICAgICAgIGluZGV4ZXNUb0FwcGVuZFRvLmZvckVhY2goKFthdHRyLCB2YWx1ZV0pID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBhdHRySW5kZXggPSB3b3JraW5nU3RhdGUuaW5kZXhlc1thdHRyXTtcbiAgICAgICAgICAgICAgICBpZiAoYXR0ckluZGV4Lmhhc093blByb3BlcnR5KHZhbHVlKSkge1xuICAgICAgICAgICAgICAgICAgICBvcHMubXV0YWJsZS5wdXNoKGlkLCBhdHRySW5kZXhbdmFsdWVdKTtcbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICBvcHMubXV0YWJsZS5zZXQodmFsdWUsIFtpZF0sIGF0dHJJbmRleCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIHN0YXRlOiB3b3JraW5nU3RhdGUsXG4gICAgICAgICAgICAgICAgY3JlYXRlZDogZmluYWxFbnRyeSxcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBuZXh0SW5kZXhlcyA9IG9wcy5iYXRjaC5tZXJnZShcbiAgICAgICAgICAgIGJhdGNoVG9rZW4sXG4gICAgICAgICAgICBpbmRleGVzVG9BcHBlbmRUby5yZWR1Y2UoXG4gICAgICAgICAgICAgICAgKGluZGV4TWFwLCBbYXR0ciwgdmFsdWVdKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGluZGV4TWFwW2F0dHJdID0gb3BzLmJhdGNoLm1lcmdlKFxuICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBbdmFsdWVdOiBvcHMuYmF0Y2gucHVzaChcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4TWFwW2F0dHJdW3ZhbHVlXSB8fCBbXVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXhNYXBbYXR0cl1cbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGluZGV4TWFwO1xuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgeyAuLi53b3JraW5nU3RhdGUuaW5kZXhlcyB9XG4gICAgICAgICAgICApLFxuICAgICAgICAgICAgd29ya2luZ1N0YXRlLmluZGV4ZXNcbiAgICAgICAgKTtcblxuICAgICAgICBjb25zdCBuZXh0U3RhdGUgPSBvcHMuYmF0Y2gubWVyZ2UoXG4gICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIFt0aGlzLmFyck5hbWVdOiBvcHMuYmF0Y2gucHVzaChcbiAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgaWQsXG4gICAgICAgICAgICAgICAgICAgIHdvcmtpbmdTdGF0ZVt0aGlzLmFyck5hbWVdXG4gICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgICBbdGhpcy5tYXBOYW1lXTogb3BzLmJhdGNoLm1lcmdlKFxuICAgICAgICAgICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICBbaWRdOiBmaW5hbEVudHJ5LFxuICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICB3b3JraW5nU3RhdGVbdGhpcy5tYXBOYW1lXVxuICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgaW5kZXhlczogbmV4dEluZGV4ZXMsXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgd29ya2luZ1N0YXRlXG4gICAgICAgICk7XG5cbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHN0YXRlOiBuZXh0U3RhdGUsXG4gICAgICAgICAgICBjcmVhdGVkOiBmaW5hbEVudHJ5LFxuICAgICAgICB9O1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGRhdGEgc3RydWN0dXJlIHdpdGggb2JqZWN0cyB3aGVyZSBgcm93c2BcbiAgICAgKiBhcmUgbWVyZ2VkIHdpdGggYG1lcmdlT2JqYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gdHggLSB0cmFuc2FjdGlvbiBpbmZvXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBicmFuY2ggLSB0aGUgZGF0YSBzdHJ1Y3R1cmUgc3RhdGVcbiAgICAgKiBAcGFyYW0gIHtPYmplY3RbXX0gcm93cyAtIHJvd3MgdG8gdXBkYXRlXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBtZXJnZU9iaiAtIFRoZSBvYmplY3QgdG8gbWVyZ2Ugd2l0aCBlYWNoIHJvdy5cbiAgICAgKiBAcmV0dXJuIHtPYmplY3R9XG4gICAgICovXG4gICAgdXBkYXRlKHR4LCBicmFuY2gsIHJvd3MsIG1lcmdlT2JqKSB7XG4gICAgICAgIGNvbnN0IHsgYmF0Y2hUb2tlbiwgd2l0aE11dGF0aW9ucyB9ID0gdHg7XG5cbiAgICAgICAgY29uc3QgbWVyZ2VPYmpJbnRvID0gKHJvdykgPT4ge1xuICAgICAgICAgICAgY29uc3QgbWVyZ2UgPSB3aXRoTXV0YXRpb25zXG4gICAgICAgICAgICAgICAgPyBvcHMubXV0YWJsZS5tZXJnZVxuICAgICAgICAgICAgICAgIDogb3BzLmJhdGNoLm1lcmdlKGJhdGNoVG9rZW4pO1xuICAgICAgICAgICAgcmV0dXJuIG1lcmdlKG1lcmdlT2JqLCByb3cpO1xuICAgICAgICB9O1xuXG4gICAgICAgIGNvbnN0IHNldCA9IHdpdGhNdXRhdGlvbnMgPyBvcHMubXV0YWJsZS5zZXQgOiBvcHMuYmF0Y2guc2V0KGJhdGNoVG9rZW4pO1xuXG4gICAgICAgIGNvbnN0IGluZGV4ZWRBdHRycyA9IE9iamVjdC5rZXlzKGJyYW5jaC5pbmRleGVzKS5maWx0ZXIoKGF0dHIpID0+XG4gICAgICAgICAgICBtZXJnZU9iai5oYXNPd25Qcm9wZXJ0eShhdHRyKVxuICAgICAgICApO1xuICAgICAgICBjb25zdCBpbmRleElkc1RvQWRkID0gW107XG4gICAgICAgIGNvbnN0IGluZGV4SWRzVG9EZWxldGUgPSBbXTtcblxuICAgICAgICBjb25zdCBuZXh0TWFwID0gcm93cy5yZWR1Y2UoKG1hcCwgcm93KSA9PiB7XG4gICAgICAgICAgICBjb25zdCBwcmV2QXR0clZhbHVlcyA9IGluZGV4ZWRBdHRycy5yZWR1Y2UoXG4gICAgICAgICAgICAgICAgKHZhbHVlTWFwLCBhdHRyKSA9PiAoe1xuICAgICAgICAgICAgICAgICAgICAuLi52YWx1ZU1hcCxcbiAgICAgICAgICAgICAgICAgICAgW2F0dHJdOiByb3dbYXR0cl0sXG4gICAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICAgICAge31cbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICBjb25zdCByZXN1bHQgPSBtZXJnZU9iakludG8ocm93KTtcbiAgICAgICAgICAgIGNvbnN0IG5leHRBdHRyVmFsdWVzID0gaW5kZXhlZEF0dHJzLnJlZHVjZShcbiAgICAgICAgICAgICAgICAodmFsdWVNYXAsIGF0dHIpID0+ICh7XG4gICAgICAgICAgICAgICAgICAgIC4uLnZhbHVlTWFwLFxuICAgICAgICAgICAgICAgICAgICBbYXR0cl06IHJlc3VsdFthdHRyXSxcbiAgICAgICAgICAgICAgICB9KSxcbiAgICAgICAgICAgICAgICB7fVxuICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIGNvbnN0IGlkID0gcmVzdWx0W3RoaXMuaWRBdHRyaWJ1dGVdO1xuICAgICAgICAgICAgY29uc3QgbmV4dFJvdyA9IHNldChpZCwgcmVzdWx0LCBtYXApO1xuICAgICAgICAgICAgaW5kZXhlZEF0dHJzLmZvckVhY2goKGF0dHIpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCB7IFthdHRyXTogcHJldlZhbHVlIH0gPSBwcmV2QXR0clZhbHVlcztcbiAgICAgICAgICAgICAgICBjb25zdCB7IFthdHRyXTogbmV4dFZhbHVlIH0gPSBuZXh0QXR0clZhbHVlcztcbiAgICAgICAgICAgICAgICBpZiAocHJldlZhbHVlID09PSBuZXh0VmFsdWUpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gYXR0cmlidXRlIGhhcyBub3QgY2hhbmdlZCwgbm8gbmVlZCB0byB1cGRhdGUgYW55IGluZGV4XG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKHByZXZWYWx1ZSAhPT0gbnVsbCAmJiB0eXBlb2YgcHJldlZhbHVlICE9PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIHJlbW92ZSBpZCBmcm9tIGF0dHJpYnV0ZSdzIGluZGV4IGZvciBpdHMgb2xkIHZhbHVlXG4gICAgICAgICAgICAgICAgICAgIGluZGV4SWRzVG9EZWxldGUucHVzaChbYXR0ciwgcHJldlZhbHVlLCBpZF0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBpZiAobmV4dFZhbHVlICE9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIGFkZCBpZCB0byBhdHRyaWJ1dGUncyBpbmRleCBmb3IgaXRzIG5ldyB2YWx1ZVxuICAgICAgICAgICAgICAgICAgICBpbmRleElkc1RvQWRkLnB1c2goW2F0dHIsIG5leHRWYWx1ZSwgaWRdKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIHJldHVybiBuZXh0Um93O1xuICAgICAgICB9LCBicmFuY2hbdGhpcy5tYXBOYW1lXSk7XG5cbiAgICAgICAgbGV0IG5leHRJbmRleGVzID0gYnJhbmNoLmluZGV4ZXM7XG4gICAgICAgIGlmICh3aXRoTXV0YXRpb25zKSB7XG4gICAgICAgICAgICBpbmRleElkc1RvRGVsZXRlLmZvckVhY2goKFthdHRyLCB2YWx1ZSwgaWRdKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgYXJyID0gbmV4dEluZGV4ZXNbYXR0cl1bdmFsdWVdO1xuICAgICAgICAgICAgICAgIGNvbnN0IGlkeCA9IGFyci5pbmRleE9mKGlkKTtcbiAgICAgICAgICAgICAgICBvcHMubXV0YWJsZS5zcGxpY2UoaWR4LCAxLCBbXSwgYXJyKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgaW5kZXhJZHNUb0FkZC5mb3JFYWNoKChbYXR0ciwgdmFsdWUsIGlkXSkgPT4ge1xuICAgICAgICAgICAgICAgIG9wcy5tdXRhYmxlLnB1c2goaWQsIG5leHRJbmRleGVzW2F0dHJdW3ZhbHVlXSk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGlmIChpbmRleElkc1RvQWRkLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIG5leHRJbmRleGVzID0gb3BzLmJhdGNoLm1lcmdlKFxuICAgICAgICAgICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAgICAgICAgICBpbmRleElkc1RvQWRkLnJlZHVjZShcbiAgICAgICAgICAgICAgICAgICAgICAgIChpbmRleE1hcCwgW2F0dHIsIHZhbHVlLCBpZF0pID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleE1hcFthdHRyXSA9IG9wcy5iYXRjaC5tZXJnZShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgW3ZhbHVlXTogb3BzLmJhdGNoLnB1c2goXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleE1hcFthdHRyXVt2YWx1ZV0gfHwgW11cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4TWFwW2F0dHJdXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gaW5kZXhNYXA7XG4gICAgICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICAgICAgeyAuLi5uZXh0SW5kZXhlcyB9XG4gICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgIG5leHRJbmRleGVzXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmIChpbmRleElkc1RvRGVsZXRlLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIG5leHRJbmRleGVzID0gb3BzLmJhdGNoLm1lcmdlKFxuICAgICAgICAgICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAgICAgICAgICBpbmRleElkc1RvRGVsZXRlLnJlZHVjZShcbiAgICAgICAgICAgICAgICAgICAgICAgIChpbmRleE1hcCwgW2F0dHIsIHZhbHVlLCBpZF0pID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleE1hcFthdHRyXSA9IG9wcy5iYXRjaC5tZXJnZShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgW3ZhbHVlXTogb3BzLmJhdGNoLmZpbHRlcihcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChyb3dJZCkgPT4gcm93SWQgIT09IGlkLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4TWFwW2F0dHJdW3ZhbHVlXVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXhNYXBbYXR0cl1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBpbmRleE1hcDtcbiAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICB7IC4uLm5leHRJbmRleGVzIH1cbiAgICAgICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgICAgICAgbmV4dEluZGV4ZXNcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG9wcy5iYXRjaC5tZXJnZShcbiAgICAgICAgICAgIGJhdGNoVG9rZW4sXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgW3RoaXMubWFwTmFtZV06IG5leHRNYXAsXG4gICAgICAgICAgICAgICAgaW5kZXhlczogbmV4dEluZGV4ZXMsXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgYnJhbmNoXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgZGF0YSBzdHJ1Y3R1cmUgd2l0aG91dCByb3dzIGByb3dzYC5cbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IHR4IC0gdHJhbnNhY3Rpb24gaW5mb1xuICAgICAqIEBwYXJhbSAge09iamVjdH0gYnJhbmNoIC0gdGhlIGRhdGEgc3RydWN0dXJlIHN0YXRlXG4gICAgICogQHBhcmFtICB7T2JqZWN0W119IHJvd3MgLSByb3dzIHRvIHVwZGF0ZVxuICAgICAqIEByZXR1cm4ge09iamVjdH0gdGhlIGRhdGEgc3RydWN0dXJlIHdpdGhvdXQgaWRzIGluIGBpZHNUb0RlbGV0ZWAuXG4gICAgICovXG4gICAgZGVsZXRlKHR4LCBicmFuY2gsIHJvd3MpIHtcbiAgICAgICAgY29uc3QgeyBiYXRjaFRva2VuLCB3aXRoTXV0YXRpb25zIH0gPSB0eDtcblxuICAgICAgICBjb25zdCB7IGFyck5hbWUsIG1hcE5hbWUgfSA9IHRoaXM7XG4gICAgICAgIGNvbnN0IGFyciA9IGJyYW5jaFthcnJOYW1lXTtcblxuICAgICAgICBjb25zdCBpZHNUb0RlbGV0ZSA9IHJvd3MubWFwKChyb3cpID0+IHJvd1t0aGlzLmlkQXR0cmlidXRlXSk7XG4gICAgICAgIGlmICh3aXRoTXV0YXRpb25zKSB7XG4gICAgICAgICAgICBpZHNUb0RlbGV0ZS5mb3JFYWNoKChpZCkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGlkeCA9IGFyci5pbmRleE9mKGlkKTtcbiAgICAgICAgICAgICAgICBvcHMubXV0YWJsZS5zcGxpY2UoaWR4LCAxLCBbXSwgYXJyKTtcbiAgICAgICAgICAgICAgICBvcHMubXV0YWJsZS5vbWl0KGlkLCBicmFuY2hbbWFwTmFtZV0pO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAvLyBkZWxldGUgaWRzIGZyb20gYWxsIGluZGV4ZXNcbiAgICAgICAgICAgIE9iamVjdC52YWx1ZXMoYnJhbmNoLmluZGV4ZXMpLmZvckVhY2goKGF0dHJJbmRleCkgPT5cbiAgICAgICAgICAgICAgICBPYmplY3QudmFsdWVzKGF0dHJJbmRleCkuZm9yRWFjaCgodmFsdWVJbmRleCkgPT5cbiAgICAgICAgICAgICAgICAgICAgaWRzVG9EZWxldGUuZm9yRWFjaCgoaWQpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IGlkeCA9IHZhbHVlSW5kZXguaW5kZXhPZihpZCk7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoaWR4ICE9PSAtMSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9wcy5tdXRhYmxlLnNwbGljZShpZHgsIDEsIFtdLCB2YWx1ZUluZGV4KTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICApXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgcmV0dXJuIGJyYW5jaDtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IG5leHRJbmRleGVzID0gb3BzLmJhdGNoLm1lcmdlKFxuICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgIE9iamVjdC5lbnRyaWVzKGJyYW5jaC5pbmRleGVzKS5yZWR1Y2UoXG4gICAgICAgICAgICAgICAgKGluZGV4TWFwLCBbYXR0ciwgYXR0ckluZGV4XSkgPT4ge1xuICAgICAgICAgICAgICAgICAgICBpbmRleE1hcFthdHRyXSA9IG9wcy5iYXRjaC5tZXJnZShcbiAgICAgICAgICAgICAgICAgICAgICAgIGJhdGNoVG9rZW4sXG4gICAgICAgICAgICAgICAgICAgICAgICBPYmplY3QuZW50cmllcyhhdHRySW5kZXgpLnJlZHVjZShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAoYXR0ckluZGV4TWFwLCBbdmFsdWUsIHZhbHVlSW5kZXhdKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF0dHJJbmRleE1hcFt2YWx1ZV0gPSBvcHMuYmF0Y2guZmlsdGVyKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChpZCkgPT4gIWlkc1RvRGVsZXRlLmluY2x1ZGVzKGlkKSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlSW5kZXhcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGF0dHJJbmRleE1hcDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHsgLi4uaW5kZXhNYXBbYXR0cl0gfVxuICAgICAgICAgICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4TWFwW2F0dHJdXG4gICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBpbmRleE1hcDtcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIHsgLi4uYnJhbmNoLmluZGV4ZXMgfVxuICAgICAgICAgICAgKSxcbiAgICAgICAgICAgIGJyYW5jaC5pbmRleGVzXG4gICAgICAgICk7XG5cbiAgICAgICAgcmV0dXJuIG9wcy5iYXRjaC5tZXJnZShcbiAgICAgICAgICAgIGJhdGNoVG9rZW4sXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgW2Fyck5hbWVdOiBvcHMuYmF0Y2guZmlsdGVyKFxuICAgICAgICAgICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAgICAgICAgICAoaWQpID0+ICFpZHNUb0RlbGV0ZS5pbmNsdWRlcyhpZCksXG4gICAgICAgICAgICAgICAgICAgIGJyYW5jaFthcnJOYW1lXVxuICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgW21hcE5hbWVdOiBvcHMuYmF0Y2gub21pdChcbiAgICAgICAgICAgICAgICAgICAgYmF0Y2hUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgaWRzVG9EZWxldGUsXG4gICAgICAgICAgICAgICAgICAgIGJyYW5jaFttYXBOYW1lXVxuICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgaW5kZXhlczogb3BzLmJhdGNoLm1lcmdlKFxuICAgICAgICAgICAgICAgICAgICBiYXRjaFRva2VuLFxuICAgICAgICAgICAgICAgICAgICBuZXh0SW5kZXhlcyxcbiAgICAgICAgICAgICAgICAgICAgYnJhbmNoLmluZGV4ZXNcbiAgICAgICAgICAgICAgICApLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIGJyYW5jaFxuICAgICAgICApO1xuICAgIH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgVGFibGU7XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./src/db/Table.js\\n\");\n \n /***/ }),\n \n@@ -4534,7 +4556,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _Database__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Database */ \\\"./src/db/Database.js\\\");\\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \\\"createDatabase\\\", function() { return _Database__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"]; });\\n\\n/* harmony import */ var _Table__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Table */ \\\"./src/db/Table.js\\\");\\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \\\"Table\\\", function() { return _Table__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"]; });\\n\\n/**\\n * @module db\\n * @desc Internal implementation of data storage, fetching and optimizations.\\n * @private\\n */\\n\\n\\n\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (_Database__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"]);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9kYi9pbmRleC5qcz84MTQ5Il0sIm5hbWVzIjpbImNyZWF0ZURhdGFiYXNlIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBOzs7OztBQU1BO0FBQ0E7QUFFQTtBQUVlQSxnSEFBZiIsImZpbGUiOiIuL3NyYy9kYi9pbmRleC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQG1vZHVsZSBkYlxuICogQGRlc2MgSW50ZXJuYWwgaW1wbGVtZW50YXRpb24gb2YgZGF0YSBzdG9yYWdlLCBmZXRjaGluZyBhbmQgb3B0aW1pemF0aW9ucy5cbiAqIEBwcml2YXRlXG4gKi9cblxuaW1wb3J0IGNyZWF0ZURhdGFiYXNlIGZyb20gXCIuL0RhdGFiYXNlXCI7XG5pbXBvcnQgVGFibGUgZnJvbSBcIi4vVGFibGVcIjtcblxuZXhwb3J0IHsgY3JlYXRlRGF0YWJhc2UsIFRhYmxlIH07XG5cbmV4cG9ydCBkZWZhdWx0IGNyZWF0ZURhdGFiYXNlO1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/db/index.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _Database__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Database */ \\\"./src/db/Database.js\\\");\\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \\\"createDatabase\\\", function() { return _Database__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"]; });\\n\\n/* harmony import */ var _Table__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Table */ \\\"./src/db/Table.js\\\");\\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \\\"Table\\\", function() { return _Table__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"]; });\\n\\n/**\\n * @module db\\n * @desc Internal implementation of data storage, fetching and optimizations.\\n * @private\\n */\\n\\n\\n\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (_Database__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"]);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9kYi9pbmRleC5qcz84MTQ5Il0sIm5hbWVzIjpbImNyZWF0ZURhdGFiYXNlIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFFQTtBQUNBO0FBRUE7QUFFZUEsZ0hBQWYiLCJmaWxlIjoiLi9zcmMvZGIvaW5kZXguanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEBtb2R1bGUgZGJcbiAqIEBkZXNjIEludGVybmFsIGltcGxlbWVudGF0aW9uIG9mIGRhdGEgc3RvcmFnZSwgZmV0Y2hpbmcgYW5kIG9wdGltaXphdGlvbnMuXG4gKiBAcHJpdmF0ZVxuICovXG5cbmltcG9ydCBjcmVhdGVEYXRhYmFzZSBmcm9tIFwiLi9EYXRhYmFzZVwiO1xuaW1wb3J0IFRhYmxlIGZyb20gXCIuL1RhYmxlXCI7XG5cbmV4cG9ydCB7IGNyZWF0ZURhdGFiYXNlLCBUYWJsZSB9O1xuXG5leHBvcnQgZGVmYXVsdCBjcmVhdGVEYXRhYmFzZTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/db/index.js\\n\");\n \n /***/ }),\n \n@@ -4546,7 +4568,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony import */ var _Dat\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"attrDescriptor\\\", function() { return attrDescriptor; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"forwardsManyToOneDescriptor\\\", function() { return forwardsManyToOneDescriptor; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"forwardsOneToOneDescriptor\\\", function() { return forwardsOneToOneDescriptor; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"backwardsOneToOneDescriptor\\\", function() { return backwardsOneToOneDescriptor; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"backwardsManyToOneDescriptor\\\", function() { return backwardsManyToOneDescriptor; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"manyToManyDescriptor\\\", function() { return manyToManyDescriptor; });\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ \\\"./src/utils.js\\\");\\n\\n/**\\n * The functions in this file return custom JS property descriptors\\n * that are supposed to be assigned to Model fields.\\n *\\n * Some include the logic to look up models using foreign keys and\\n * to add or remove relationships between models.\\n *\\n * @module descriptors\\n * @private\\n */\\n\\n/**\\n * Defines a basic non-key attribute.\\n * @param  {string} fieldName - the name of the field the descriptor will be assigned to.\\n */\\n\\nfunction attrDescriptor(fieldName) {\\n  return {\\n    get() {\\n      return this._fields[fieldName];\\n    },\\n\\n    set(value) {\\n      return this.set(fieldName, value);\\n    },\\n\\n    enumerable: true,\\n    configurable: true\\n  };\\n}\\n/**\\n * Forwards direction of a Foreign Key: returns one object.\\n * Also works as {@link .forwardsOneToOneDescriptor|forwardsOneToOneDescriptor}.\\n *\\n * For `book.author` referencing an `Author` model instance,\\n * `fieldName` would be `'author'` and `declaredToModelName` would be `'Author'`.\\n * @param  {string} fieldName - the name of the field the descriptor will be assigned to.\\n * @param  {string} declaredToModelName - the name of the model that the field references.\\n */\\n\\n\\nfunction forwardsManyToOneDescriptor(fieldName, declaredToModelName) {\\n  return {\\n    get() {\\n      const {\\n        session: {\\n          [declaredToModelName]: DeclaredToModel\\n        }\\n      } = this.getClass();\\n      const {\\n        [fieldName]: toId\\n      } = this._fields;\\n      return DeclaredToModel.withId(toId);\\n    },\\n\\n    set(value) {\\n      this.update({\\n        [fieldName]: Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\\\"normalizeEntity\\\"])(value)\\n      });\\n    }\\n\\n  };\\n}\\n/**\\n * Dereferencing foreign keys in {@link module:fields.oneToOne|oneToOne}\\n * relationships works the same way as in many-to-one relationships:\\n * just look up the related model.\\n *\\n * For example, a human face tends to have a single nose.\\n * So if we want to resolve `face.nose`, we need to\\n * look up the `Nose` that has the primary key that `face` references.\\n *\\n * @see {@link module:descriptors~forwardsManyToOneDescriptor|forwardsManyToOneDescriptor}\\n */\\n\\n\\nfunction forwardsOneToOneDescriptor(...args) {\\n  return forwardsManyToOneDescriptor(...args);\\n}\\n/**\\n * Here we resolve 1-to-1 relationships starting at the model on which the\\n * field was not installed. This means we need to find the instance of the\\n * other model whose {@link module:fields.oneToOne|oneToOne} FK field contains the current model's primary key.\\n *\\n * @param  {string} declaredFieldName - the name of the field referencing the current model.\\n * @param  {string} declaredFromModelName - the name of the other model.\\n */\\n\\n\\nfunction backwardsOneToOneDescriptor(declaredFieldName, declaredFromModelName) {\\n  return {\\n    get() {\\n      const {\\n        session: {\\n          [declaredFromModelName]: DeclaredFromModel\\n        }\\n      } = this.getClass();\\n      return DeclaredFromModel.get({\\n        [declaredFieldName]: this.getId()\\n      });\\n    },\\n\\n    set() {\\n      throw new Error(\\\"Can't mutate a reverse one-to-one relation.\\\");\\n    }\\n\\n  };\\n}\\n/**\\n * The backwards direction of a n-to-1 relationship (i.e. 1-to-n),\\n * meaning this will return an a collection (`QuerySet`) of model instances.\\n *\\n * An example would be `author.books` referencing all instances of\\n * the `Book` model that reference the author using `fk()`.\\n */\\n\\n\\nfunction backwardsManyToOneDescriptor(declaredFieldName, declaredFromModelName) {\\n  return {\\n    get() {\\n      const {\\n        session: {\\n          [declaredFromModelName]: DeclaredFromModel\\n        }\\n      } = this.getClass();\\n      return DeclaredFromModel.filter({\\n        [declaredFieldName]: this.getId()\\n      });\\n    },\\n\\n    set() {\\n      throw new Error(\\\"Can't mutate a reverse many-to-one relation.\\\");\\n    }\\n\\n  };\\n}\\n/**\\n * This descriptor is assigned to both sides of a many-to-many relationship.\\n * To indicate the backwards direction pass `true` for `reverse`.\\n */\\n\\n\\nfunction manyToManyDescriptor(declaredFromModelName, declaredToModelName, throughModelName, throughFields, reverse) {\\n  return {\\n    get() {\\n      const {\\n        session: {\\n          [declaredFromModelName]: DeclaredFromModel,\\n          [declaredToModelName]: DeclaredToModel,\\n          [throughModelName]: ThroughModel\\n        }\\n      } = this.getClass();\\n      const ThisModel = reverse ? DeclaredToModel : DeclaredFromModel;\\n      const OtherModel = reverse ? DeclaredFromModel : DeclaredToModel;\\n      const thisReferencingField = reverse ? throughFields.to : throughFields.from;\\n      const otherReferencingField = reverse ? throughFields.from : throughFields.to;\\n      const thisId = this.getId();\\n      const throughQs = ThroughModel.filter({\\n        [thisReferencingField]: thisId\\n      });\\n      /**\\n       * all IDs of instances of the other model that are\\n       * referenced by any instance of the current model\\n       */\\n\\n      const referencedOtherIds = new Set(throughQs.toRefArray().map(obj => obj[otherReferencingField]));\\n      /**\\n       * selects all instances of other model that are referenced\\n       * by any instance of the current model\\n       */\\n\\n      const qs = OtherModel.filter(otherModelInstance => referencedOtherIds.has(otherModelInstance[OtherModel.idAttribute]));\\n      /**\\n       * Allows adding OtherModel instances to be referenced by the current instance.\\n       *\\n       * E.g. Book.first().authors.add(1, 2) would add the authors with IDs 1 and 2\\n       * to the first book's list of referenced authors.\\n       *\\n       * @return undefined\\n       */\\n\\n      qs.add = function add(...entities) {\\n        const idsToAdd = new Set(entities.map(_utils__WEBPACK_IMPORTED_MODULE_0__[\\\"normalizeEntity\\\"]));\\n        const existingQs = throughQs.filter(through => idsToAdd.has(through[otherReferencingField]));\\n\\n        if (existingQs.exists()) {\\n          const existingIds = existingQs.toRefArray().map(through => through[otherReferencingField]);\\n          throw new Error(`Tried to add already existing ${OtherModel.modelName} id(s) ${existingIds} to the ${ThisModel.modelName} instance with id ${thisId}`);\\n        }\\n\\n        idsToAdd.forEach(id => {\\n          ThroughModel.create({\\n            [otherReferencingField]: id,\\n            [thisReferencingField]: thisId\\n          });\\n        });\\n      };\\n      /**\\n       * Removes references to all OtherModel instances from the current model.\\n       *\\n       * E.g. Book.first().authors.clear() would cause the first book's list\\n       * of referenced authors to become empty.\\n       *\\n       * @return undefined\\n       */\\n\\n\\n      qs.clear = function clear() {\\n        throughQs.delete();\\n      };\\n      /**\\n       * Removes references to all passed OtherModel instances from the current model.\\n       *\\n       * E.g. Book.first().authors.remove(1, 2) would cause the authors with\\n       * IDs 1 and 2 to no longer be referenced by the first book.\\n       *\\n       * @return undefined\\n       */\\n\\n\\n      qs.remove = function remove(...entities) {\\n        const idsToRemove = new Set(entities.map(_utils__WEBPACK_IMPORTED_MODULE_0__[\\\"normalizeEntity\\\"]));\\n        const entitiesToDelete = throughQs.filter(through => idsToRemove.has(through[otherReferencingField]));\\n\\n        if (entitiesToDelete.count() !== idsToRemove.size) {\\n          // Tried deleting non-existing entities.\\n          const entitiesToDeleteIds = entitiesToDelete.toRefArray().map(through => through[otherReferencingField]);\\n          const unexistingIds = [...idsToRemove].filter(id => !entitiesToDeleteIds.includes(id));\\n          throw new Error(`Tried to delete non-existing ${OtherModel.modelName} id(s) ${unexistingIds} from the ${ThisModel.modelName} instance with id ${thisId}`);\\n        }\\n\\n        entitiesToDelete.delete();\\n      };\\n\\n      return qs;\\n    },\\n\\n    set() {\\n      throw new Error(\\\"Tried setting a M2M field. Please use the related QuerySet methods add, remove and clear.\\\");\\n    }\\n\\n  };\\n}\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9kZXNjcmlwdG9ycy5qcz8zZDUxIl0sIm5hbWVzIjpbImF0dHJEZXNjcmlwdG9yIiwiZmllbGROYW1lIiwiZ2V0IiwiX2ZpZWxkcyIsInNldCIsInZhbHVlIiwiZW51bWVyYWJsZSIsImNvbmZpZ3VyYWJsZSIsImZvcndhcmRzTWFueVRvT25lRGVzY3JpcHRvciIsImRlY2xhcmVkVG9Nb2RlbE5hbWUiLCJzZXNzaW9uIiwiRGVjbGFyZWRUb01vZGVsIiwiZ2V0Q2xhc3MiLCJ0b0lkIiwid2l0aElkIiwidXBkYXRlIiwibm9ybWFsaXplRW50aXR5IiwiZm9yd2FyZHNPbmVUb09uZURlc2NyaXB0b3IiLCJhcmdzIiwiYmFja3dhcmRzT25lVG9PbmVEZXNjcmlwdG9yIiwiZGVjbGFyZWRGaWVsZE5hbWUiLCJkZWNsYXJlZEZyb21Nb2RlbE5hbWUiLCJEZWNsYXJlZEZyb21Nb2RlbCIsImdldElkIiwiRXJyb3IiLCJiYWNrd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yIiwiZmlsdGVyIiwibWFueVRvTWFueURlc2NyaXB0b3IiLCJ0aHJvdWdoTW9kZWxOYW1lIiwidGhyb3VnaEZpZWxkcyIsInJldmVyc2UiLCJUaHJvdWdoTW9kZWwiLCJUaGlzTW9kZWwiLCJPdGhlck1vZGVsIiwidGhpc1JlZmVyZW5jaW5nRmllbGQiLCJ0byIsImZyb20iLCJvdGhlclJlZmVyZW5jaW5nRmllbGQiLCJ0aGlzSWQiLCJ0aHJvdWdoUXMiLCJyZWZlcmVuY2VkT3RoZXJJZHMiLCJTZXQiLCJ0b1JlZkFycmF5IiwibWFwIiwib2JqIiwicXMiLCJvdGhlck1vZGVsSW5zdGFuY2UiLCJoYXMiLCJpZEF0dHJpYnV0ZSIsImFkZCIsImVudGl0aWVzIiwiaWRzVG9BZGQiLCJleGlzdGluZ1FzIiwidGhyb3VnaCIsImV4aXN0cyIsImV4aXN0aW5nSWRzIiwibW9kZWxOYW1lIiwiZm9yRWFjaCIsImlkIiwiY3JlYXRlIiwiY2xlYXIiLCJkZWxldGUiLCJyZW1vdmUiLCJpZHNUb1JlbW92ZSIsImVudGl0aWVzVG9EZWxldGUiLCJjb3VudCIsInNpemUiLCJlbnRpdGllc1RvRGVsZXRlSWRzIiwidW5leGlzdGluZ0lkcyIsImluY2x1ZGVzIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFFQTs7Ozs7Ozs7Ozs7QUFXQTs7Ozs7QUFJQSxTQUFTQSxjQUFULENBQXdCQyxTQUF4QixFQUFtQztBQUMvQixTQUFPO0FBQ0hDLE9BQUcsR0FBRztBQUNGLGFBQU8sS0FBS0MsT0FBTCxDQUFhRixTQUFiLENBQVA7QUFDSCxLQUhFOztBQUtIRyxPQUFHLENBQUNDLEtBQUQsRUFBUTtBQUNQLGFBQU8sS0FBS0QsR0FBTCxDQUFTSCxTQUFULEVBQW9CSSxLQUFwQixDQUFQO0FBQ0gsS0FQRTs7QUFTSEMsY0FBVSxFQUFFLElBVFQ7QUFVSEMsZ0JBQVksRUFBRTtBQVZYLEdBQVA7QUFZSDtBQUVEOzs7Ozs7Ozs7OztBQVNBLFNBQVNDLDJCQUFULENBQXFDUCxTQUFyQyxFQUFnRFEsbUJBQWhELEVBQXFFO0FBQ2pFLFNBQU87QUFDSFAsT0FBRyxHQUFHO0FBQ0YsWUFBTTtBQUNGUSxlQUFPLEVBQUU7QUFBRSxXQUFDRCxtQkFBRCxHQUF1QkU7QUFBekI7QUFEUCxVQUVGLEtBQUtDLFFBQUwsRUFGSjtBQUdBLFlBQU07QUFBRSxTQUFDWCxTQUFELEdBQWFZO0FBQWYsVUFBd0IsS0FBS1YsT0FBbkM7QUFFQSxhQUFPUSxlQUFlLENBQUNHLE1BQWhCLENBQXVCRCxJQUF2QixDQUFQO0FBQ0gsS0FSRTs7QUFTSFQsT0FBRyxDQUFDQyxLQUFELEVBQVE7QUFDUCxXQUFLVSxNQUFMLENBQVk7QUFDUixTQUFDZCxTQUFELEdBQWFlLDhEQUFlLENBQUNYLEtBQUQ7QUFEcEIsT0FBWjtBQUdIOztBQWJFLEdBQVA7QUFlSDtBQUVEOzs7Ozs7Ozs7Ozs7O0FBV0EsU0FBU1ksMEJBQVQsQ0FBb0MsR0FBR0MsSUFBdkMsRUFB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QUQsQ0FBdkIsQ0FEcUIsQ0FBekI7O0FBSUEsWUFBSTJCLGdCQUFnQixDQUFDQyxLQUFqQixPQUE2QkYsV0FBVyxDQUFDRyxJQUE3QyxFQUFtRDtBQUMvQztBQUNBLGdCQUFNQyxtQkFBbUIsR0FBR0gsZ0JBQWdCLENBQ3ZDdEIsVUFEdUIsR0FFdkJDLEdBRnVCLENBRW5CVSxPQUFPLElBQUlBLE9BQU8sQ0FBQ2hCLHFCQUFELENBRkMsQ0FBNUI7QUFJQSxnQkFBTStCLGFBQWEsR0FBRyxDQUFDLEdBQUdMLFdBQUosRUFBaUJyQyxNQUFqQixDQUNsQmdDLEVBQUUsSUFBSSxDQUFDUyxtQkFBbUIsQ0FBQ0UsUUFBcEIsQ0FBNkJYLEVBQTdCLENBRFcsQ0FBdEI7QUFJQSxnQkFBTSxJQUFJbEMsS0FBSixDQUNELGdDQUErQlMsVUFBVSxDQUFDdUIsU0FBVSxVQUFTWSxhQUFjLGFBQVlwQyxTQUFTLENBQUN3QixTQUFVLHFCQUFvQmxCLE1BQU8sRUFEckksQ0FBTjtBQUdIOztBQUVEMEIsd0JBQWdCLENBQUNILE1BQWpCO0FBQ0gsT0F2QkQ7O0FBeUJBLGFBQU9oQixFQUFQO0FBQ0gsS0EzSEU7O0FBNkhIekMsT0FBRyxHQUFHO0FBQ0YsWUFBTSxJQUFJb0IsS0FBSixDQUNGLDJGQURFLENBQU47QUFHSDs7QUFqSUUsR0FBUDtBQW1JSCIsImZpbGUiOiIuL3NyYy9kZXNjcmlwdG9ycy5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IG5vcm1hbGl6ZUVudGl0eSB9IGZyb20gXCIuL3V0aWxzXCI7XG5cbi8qKlxuICogVGhlIGZ1bmN0aW9ucyBpbiB0aGlzIGZpbGUgcmV0dXJuIGN1c3RvbSBKUyBwcm9wZXJ0eSBkZXNjcmlwdG9yc1xuICogdGhhdCBhcmUgc3VwcG9zZWQgdG8gYmUgYXNzaWduZWQgdG8gTW9kZWwgZmllbGRzLlxuICpcbiAqIFNvbWUgaW5jbHVkZSB0aGUgbG9naWMgdG8gbG9vayB1cCBtb2RlbHMgdXNpbmcgZm9yZWlnbiBrZXlzIGFuZFxuICogdG8gYWRkIG9yIHJlbW92ZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gbW9kZWxzLlxuICpcbiAqIEBtb2R1bGUgZGVzY3JpcHRvcnNcbiAqIEBwcml2YXRlXG4gKi9cblxuLyoqXG4gKiBEZWZpbmVzIGEgYmFzaWMgbm9uLWtleSBhdHRyaWJ1dGUuXG4gKiBAcGFyYW0gIHtzdHJpbmd9IGZpZWxkTmFtZSAtIHRoZSBuYW1lIG9mIHRoZSBmaWVsZCB0aGUgZGVzY3JpcHRvciB3aWxsIGJlIGFzc2lnbmVkIHRvLlxuICovXG5mdW5jdGlvbiBhdHRyRGVzY3JpcHRvcihmaWVsZE5hbWUpIHtcbiAgICByZXR1cm4ge1xuICAgICAgICBnZXQoKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5fZmllbGRzW2ZpZWxkTmFtZV07XG4gICAgICAgIH0sXG5cbiAgICAgICAgc2V0KHZhbHVlKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5zZXQoZmllbGROYW1lLCB2YWx1ZSk7XG4gICAgICAgIH0sXG5cbiAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgIH07XG59XG5cbi8qKlxuICogRm9yd2FyZHMgZGlyZWN0aW9uIG9mIGEgRm9yZWlnbiBLZXk6IHJldHVybnMgb25lIG9iamVjdC5cbiAqIEFsc28gd29ya3MgYXMge0BsaW5rIC5mb3J3YXJkc09uZVRvT25lRGVzY3JpcHRvcnxmb3J3YXJkc09uZVRvT25lRGVzY3JpcHRvcn0uXG4gKlxuICogRm9yIGBib29rLmF1dGhvcmAgcmVmZXJlbmNpbmcgYW4gYEF1dGhvcmAgbW9kZWwgaW5zdGFuY2UsXG4gKiBgZmllbGROYW1lYCB3b3VsZCBiZSBgJ2F1dGhvcidgIGFuZCBgZGVjbGFyZWRUb01vZGVsTmFtZWAgd291bGQgYmUgYCdBdXRob3InYC5cbiAqIEBwYXJhbSAge3N0cmluZ30gZmllbGROYW1lIC0gdGhlIG5hbWUgb2YgdGhlIGZpZWxkIHRoZSBkZXNjcmlwdG9yIHdpbGwgYmUgYXNzaWduZWQgdG8uXG4gKiBAcGFyYW0gIHtzdHJpbmd9IGRlY2xhcmVkVG9Nb2RlbE5hbWUgLSB0aGUgbmFtZSBvZiB0aGUgbW9kZWwgdGhhdCB0aGUgZmllbGQgcmVmZXJlbmNlcy5cbiAqL1xuZnVuY3Rpb24gZm9yd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yKGZpZWxkTmFtZSwgZGVjbGFyZWRUb01vZGVsTmFtZSkge1xuICAgIHJldHVybiB7XG4gICAgICAgIGdldCgpIHtcbiAgICAgICAgICAgIGNvbnN0IHtcbiAgICAgICAgICAgICAgICBzZXNzaW9uOiB7IFtkZWNsYXJlZFRvTW9kZWxOYW1lXTogRGVjbGFyZWRUb01vZGVsIH0sXG4gICAgICAgICAgICB9ID0gdGhpcy5nZXRDbGFzcygpO1xuICAgICAgICAgICAgY29uc3QgeyBbZmllbGROYW1lXTogdG9JZCB9ID0gdGhpcy5fZmllbGRzO1xuXG4gICAgICAgICAgICByZXR1cm4gRGVjbGFyZWRUb01vZGVsLndpdGhJZCh0b0lkKTtcbiAgICAgICAgfSxcbiAgICAgICAgc2V0KHZhbHVlKSB7XG4gICAgICAgICAgICB0aGlzLnVwZGF0ZSh7XG4gICAgICAgICAgICAgICAgW2ZpZWxkTmFtZV06IG5vcm1hbGl6ZUVudGl0eSh2YWx1ZSksXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSxcbiAgICB9O1xufVxuXG4vKipcbiAqIERlcmVmZXJlbmNpbmcgZm9yZWlnbiBrZXlzIGluIHtAbGluayBtb2R1bGU6ZmllbGRzLm9uZVRvT25lfG9uZVRvT25lfVxuICogcmVsYXRpb25zaGlwcyB3b3JrcyB0aGUgc2FtZSB3YXkgYXMgaW4gbWFueS10by1vbmUgcmVsYXRpb25zaGlwczpcbiAqIGp1c3QgbG9vayB1cCB0aGUgcmVsYXRlZCBtb2RlbC5cbiAqXG4gKiBGb3IgZXhhbXBsZSwgYSBodW1hbiBmYWNlIHRlbmRzIHRvIGhhdmUgYSBzaW5nbGUgbm9zZS5cbiAqIFNvIGlmIHdlIHdhbnQgdG8gcmVzb2x2ZSBgZmFjZS5ub3NlYCwgd2UgbmVlZCB0b1xuICogbG9vayB1cCB0aGUgYE5vc2VgIHRoYXQgaGFzIHRoZSBwcmltYXJ5IGtleSB0aGF0IGBmYWNlYCByZWZlcmVuY2VzLlxuICpcbiAqIEBzZWUge0BsaW5rIG1vZHVsZTpkZXNjcmlwdG9yc35mb3J3YXJkc01hbnlUb09uZURlc2NyaXB0b3J8Zm9yd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yfVxuICovXG5mdW5jdGlvbiBmb3J3YXJkc09uZVRvT25lRGVzY3JpcHRvciguLi5hcmdzKSB7XG4gICAgcmV0dXJuIGZvcndhcmRzTWFueVRvT25lRGVzY3JpcHRvciguLi5hcmdzKTtcbn1cblxuLyoqXG4gKiBIZXJlIHdlIHJlc29sdmUgMS10by0xIHJlbGF0aW9uc2hpcHMgc3RhcnRpbmcgYXQgdGhlIG1vZGVsIG9uIHdoaWNoIHRoZVxuICogZmllbGQgd2FzIG5vdCBpbnN0YWxsZWQuIFRoaXMgbWVhbnMgd2UgbmVlZCB0byBmaW5kIHRoZSBpbnN0YW5jZSBvZiB0aGVcbiAqIG90aGVyIG1vZGVsIHdob3NlIHtAbGluayBtb2R1bGU6ZmllbGRzLm9uZVRvT25lfG9uZVRvT25lfSBGSyBmaWVsZCBjb250YWlucyB0aGUgY3VycmVudCBtb2RlbCdzIHByaW1hcnkga2V5LlxuICpcbiAqIEBwYXJhbSAge3N0cmluZ30gZGVjbGFyZWRGaWVsZE5hbWUgLSB0aGUgbmFtZSBvZiB0aGUgZmllbGQgcmVmZXJlbmNpbmcgdGhlIGN1cnJlbnQgbW9kZWwuXG4gKiBAcGFyYW0gIHtzdHJpbmd9IGRlY2xhcmVkRnJvbU1vZGVsTmFtZSAtIHRoZSBuYW1lIG9mIHRoZSBvdGhlciBtb2RlbC5cbiAqL1xuZnVuY3Rpb24gYmFja3dhcmRzT25lVG9PbmVEZXNjcmlwdG9yKGRlY2xhcmVkRmllbGROYW1lLCBkZWNsYXJlZEZyb21Nb2RlbE5hbWUpIHtcbiAgICByZXR1cm4ge1xuICAgICAgICBnZXQoKSB7XG4gICAgICAgICAgICBjb25zdCB7XG4gICAgICAgICAgICAgICAgc2Vzc2lvbjogeyBbZGVjbGFyZWRGcm9tTW9kZWxOYW1lXTogRGVjbGFyZWRGcm9tTW9kZWwgfSxcbiAgICAgICAgICAgIH0gPSB0aGlzLmdldENsYXNzKCk7XG5cbiAgICAgICAgICAgIHJldHVybiBEZWNsYXJlZEZyb21Nb2RlbC5nZXQoe1xuICAgICAgICAgICAgICAgIFtkZWNsYXJlZEZpZWxkTmFtZV06IHRoaXMuZ2V0SWQoKSxcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9LFxuICAgICAgICBzZXQoKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJDYW4ndCBtdXRhdGUgYSByZXZlcnNlIG9uZS10by1vbmUgcmVsYXRpb24uXCIpO1xuICAgICAgICB9LFxuICAgIH07XG59XG5cbi8qKlxuICogVGhlIGJhY2t3YXJkcyBkaXJlY3Rpb24gb2YgYSBuLXRvLTEgcmVsYXRpb25zaGlwIChpLmUuIDEtdG8tbiksXG4gKiBtZWFuaW5nIHRoaXMgd2lsbCByZXR1cm4gYW4gYSBjb2xsZWN0aW9uIChgUXVlcnlTZXRgKSBvZiBtb2RlbCBpbnN0YW5jZXMuXG4gKlxuICogQW4gZXhhbXBsZSB3b3VsZCBiZSBgYXV0aG9yLmJvb2tzYCByZWZlcmVuY2luZyBhbGwgaW5zdGFuY2VzIG9mXG4gKiB0aGUgYEJvb2tgIG1vZGVsIHRoYXQgcmVmZXJlbmNlIHRoZSBhdXRob3IgdXNpbmcgYGZrKClgLlxuICovXG5mdW5jdGlvbiBiYWNrd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yKFxuICAgIGRlY2xhcmVkRmllbGROYW1lLFxuICAgIGRlY2xhcmVkRnJvbU1vZGVsTmFtZVxuKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgZ2V0KCkge1xuICAgICAgICAgICAgY29uc3Qge1xuICAgICAgICAgICAgICAgIHNlc3Npb246IHsgW2RlY2xhcmVkRnJvbU1vZGVsTmFtZV06IERlY2xhcmVkRnJvbU1vZGVsIH0sXG4gICAgICAgICAgICB9ID0gdGhpcy5nZXRDbGFzcygpO1xuXG4gICAgICAgICAgICByZXR1cm4gRGVjbGFyZWRGcm9tTW9kZWwuZmlsdGVyKHtcbiAgICAgICAgICAgICAgICBbZGVjbGFyZWRGaWVsZE5hbWVdOiB0aGlzLmdldElkKCksXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSxcbiAgICAgICAgc2V0KCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiQ2FuJ3QgbXV0YXRlIGEgcmV2ZXJzZSBtYW55LXRvLW9uZSByZWxhdGlvbi5cIik7XG4gICAgICAgIH0sXG4gICAgfTtcbn1cblxuLyoqXG4gKiBUaGlzIGRlc2NyaXB0b3IgaXMgYXNzaWduZWQgdG8gYm90aCBzaWRlcyBvZiBhIG1hbnktdG8tbWFueSByZWxhdGlvbnNoaXAuXG4gKiBUbyBpbmRpY2F0ZSB0aGUgYmFja3dhcmRzIGRpcmVjdGlvbiBwYXNzIGB0cnVlYCBmb3IgYHJldmVyc2VgLlxuICovXG5mdW5jdGlvbiBtYW55VG9NYW55RGVzY3JpcHRvcihcbiAgICBkZWNsYXJlZEZyb21Nb2RlbE5hbWUsXG4gICAgZGVjbGFyZWRUb01vZGVsTmFtZSxcbiAgICB0aHJvdWdoTW9kZWxOYW1lLFxuICAgIHRocm91Z2hGaWVsZHMsXG4gICAgcmV2ZXJzZVxuKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgZ2V0KCkge1xuICAgICAgICAgICAgY29uc3Qge1xuICAgICAgICAgICAgICAgIHNlc3Npb246IHtcbiAgICAgICAgICAgICAgICAgICAgW2RlY2xhcmVkRnJvbU1vZGVsTmFtZV06IERlY2xhcmVkRnJvbU1vZGVsLFxuICAgICAgICAgICAgICAgICAgICBbZGVjbGFyZWRUb01vZGVsTmFtZV06IERlY2xhcmVkVG9Nb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgW3Rocm91Z2hNb2RlbE5hbWVdOiBUaHJvdWdoTW9kZWwsXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIH0gPSB0aGlzLmdldENsYXNzKCk7XG5cbiAgICAgICAgICAgIGNvbnN0IFRoaXNNb2RlbCA9IHJldmVyc2UgPyBEZWNsYXJlZFRvTW9kZWwgOiBEZWNsYXJlZEZyb21Nb2RlbDtcbiAgICAgICAgICAgIGNvbnN0IE90aGVyTW9kZWwgPSByZXZlcnNlID8gRGVjbGFyZWRGcm9tTW9kZWwgOiBEZWNsYXJlZFRvTW9kZWw7XG5cbiAgICAgICAgICAgIGNvbnN0IHRoaXNSZWZlcmVuY2luZ0ZpZWxkID0gcmV2ZXJzZVxuICAgICAgICAgICAgICAgID8gdGhyb3VnaEZpZWxkcy50b1xuICAgICAgICAgICAgICAgIDogdGhyb3VnaEZpZWxkcy5mcm9tO1xuICAgICAgICAgICAgY29uc3Qgb3RoZXJSZWZlcmVuY2luZ0ZpZWxkID0gcmV2ZXJzZVxuICAgICAgICAgICAgICAgID8gdGhyb3VnaEZpZWxkcy5mcm9tXG4gICAgICAgICAgICAgICAgOiB0aHJvdWdoRmllbGRzLnRvO1xuXG4gICAgICAgICAgICBjb25zdCB0aGlzSWQgPSB0aGlzLmdldElkKCk7XG5cbiAgICAgICAgICAgIGNvbnN0IHRocm91Z2hRcyA9IFRocm91Z2hNb2RlbC5maWx0ZXIoe1xuICAgICAgICAgICAgICAgIFt0aGlzUmVmZXJlbmNpbmdGaWVsZF06IHRoaXNJZCxcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIGFsbCBJRHMgb2YgaW5zdGFuY2VzIG9mIHRoZSBvdGhlciBtb2RlbCB0aGF0IGFyZVxuICAgICAgICAgICAgICogcmVmZXJlbmNlZCBieSBhbnkgaW5zdGFuY2Ugb2YgdGhlIGN1cnJlbnQgbW9kZWxcbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgY29uc3QgcmVmZXJlbmNlZE90aGVySWRzID0gbmV3IFNldChcbiAgICAgICAgICAgICAgICB0aHJvdWdoUXMudG9SZWZBcnJheSgpLm1hcChvYmogPT4gb2JqW290aGVyUmVmZXJlbmNpbmdGaWVsZF0pXG4gICAgICAgICAgICApO1xuXG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIHNlbGVjdHMgYWxsIGluc3RhbmNlcyBvZiBvdGhlciBtb2RlbCB0aGF0IGFyZSByZWZlcmVuY2VkXG4gICAgICAgICAgICAgKiBieSBhbnkgaW5zdGFuY2Ugb2YgdGhlIGN1cnJlbnQgbW9kZWxcbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgY29uc3QgcXMgPSBPdGhlck1vZGVsLmZpbHRlcihvdGhlck1vZGVsSW5zdGFuY2UgPT5cbiAgICAgICAgICAgICAgICByZWZlcmVuY2VkT3RoZXJJZHMuaGFzKFxuICAgICAgICAgICAgICAgICAgICBvdGhlck1vZGVsSW5zdGFuY2VbT3RoZXJNb2RlbC5pZEF0dHJpYnV0ZV1cbiAgICAgICAgICAgICAgICApXG4gICAgICAgICAgICApO1xuXG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIEFsbG93cyBhZGRpbmcgT3RoZXJNb2RlbCBpbnN0YW5jZXMgdG8gYmUgcmVmZXJlbmNlZCBieSB0aGUgY3VycmVudCBpbnN0YW5jZS5cbiAgICAgICAgICAgICAqXG4gICAgICAgICAgICAgKiBFLmcuIEJvb2suZmlyc3QoKS5hdXRob3JzLmFkZCgxLCAyKSB3b3VsZCBhZGQgdGhlIGF1dGhvcnMgd2l0aCBJRHMgMSBhbmQgMlxuICAgICAgICAgICAgICogdG8gdGhlIGZpcnN0IGJvb2sncyBsaXN0IG9mIHJlZmVyZW5jZWQgYXV0aG9ycy5cbiAgICAgICAgICAgICAqXG4gICAgICAgICAgICAgKiBAcmV0dXJuIHVuZGVmaW5lZFxuICAgICAgICAgICAgICovXG4gICAgICAgICAgICBxcy5hZGQgPSBmdW5jdGlvbiBhZGQoLi4uZW50aXRpZXMpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBpZHNUb0FkZCA9IG5ldyBTZXQoZW50aXRpZXMubWFwKG5vcm1hbGl6ZUVudGl0eSkpO1xuXG4gICAgICAgICAgICAgICAgY29uc3QgZXhpc3RpbmdRcyA9IHRocm91Z2hRcy5maWx0ZXIodGhyb3VnaCA9PlxuICAgICAgICAgICAgICAgICAgICBpZHNUb0FkZC5oYXModGhyb3VnaFtvdGhlclJlZmVyZW5jaW5nRmllbGRdKVxuICAgICAgICAgICAgICAgICk7XG5cbiAgICAgICAgICAgICAgICBpZiAoZXhpc3RpbmdRcy5leGlzdHMoKSkge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBleGlzdGluZ0lkcyA9IGV4aXN0aW5nUXNcbiAgICAgICAgICAgICAgICAgICAgICAgIC50b1JlZkFycmF5KClcbiAgICAgICAgICAgICAgICAgICAgICAgIC5tYXAodGhyb3VnaCA9PiB0aHJvdWdoW290aGVyUmVmZXJlbmNpbmdGaWVsZF0pO1xuXG4gICAgICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICAgICAgICAgIGBUcmllZCB0byBhZGQgYWxyZWFkeSBleGlzdGluZyAke090aGVyTW9kZWwubW9kZWxOYW1lfSBpZChzKSAke2V4aXN0aW5nSWRzfSB0byB0aGUgJHtUaGlzTW9kZWwubW9kZWxOYW1lfSBpbnN0YW5jZSB3aXRoIGlkICR7dGhpc0lkfWBcbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpZHNUb0FkZC5mb3JFYWNoKGlkID0+IHtcbiAgICAgICAgICAgICAgICAgICAgVGhyb3VnaE1vZGVsLmNyZWF0ZSh7XG4gICAgICAgICAgICAgICAgICAgICAgICBbb3RoZXJSZWZlcmVuY2luZ0ZpZWxkXTogaWQsXG4gICAgICAgICAgICAgICAgICAgICAgICBbdGhpc1JlZmVyZW5jaW5nRmllbGRdOiB0aGlzSWQsXG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgKiBSZW1vdmVzIHJlZmVyZW5jZXMgdG8gYWxsIE90aGVyTW9kZWwgaW5zdGFuY2VzIGZyb20gdGhlIGN1cnJlbnQgbW9kZWwuXG4gICAgICAgICAgICAgKlxuICAgICAgICAgICAgICogRS5nLiBCb29rLmZpcnN0KCkuYXV0aG9ycy5jbGVhcigpIHdvdWxkIGNhdXNlIHRoZSBmaXJzdCBib29rJ3MgbGlzdFxuICAgICAgICAgICAgICogb2YgcmVmZXJlbmNlZCBhdXRob3JzIHRvIGJlY29tZSBlbXB0eS5cbiAgICAgICAgICAgICAqXG4gICAgICAgICAgICAgKiBAcmV0dXJuIHVuZGVmaW5lZFxuICAgICAgICAgICAgICovXG4gICAgICAgICAgICBxcy5jbGVhciA9IGZ1bmN0aW9uIGNsZWFyKCkge1xuICAgICAgICAgICAgICAgIHRocm91Z2hRcy5kZWxldGUoKTtcbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICogUmVtb3ZlcyByZWZlcmVuY2VzIHRvIGFsbCBwYXNzZWQgT3RoZXJNb2RlbCBpbnN0YW5jZXMgZnJvbSB0aGUgY3VycmVudCBtb2RlbC5cbiAgICAgICAgICAgICAqXG4gICAgICAgICAgICAgKiBFLmcuIEJvb2suZmlyc3QoKS5hdXRob3JzLnJlbW92ZSgxLCAyKSB3b3VsZCBjYXVzZSB0aGUgYXV0aG9ycyB3aXRoXG4gICAgICAgICAgICAgKiBJRHMgMSBhbmQgMiB0byBubyBsb25nZXIgYmUgcmVmZXJlbmNlZCBieSB0aGUgZmlyc3QgYm9vay5cbiAgICAgICAgICAgICAqXG4gICAgICAgICAgICAgKiBAcmV0dXJuIHVuZGVmaW5lZFxuICAgICAgICAgICAgICovXG4gICAgICAgICAgICBxcy5yZW1vdmUgPSBmdW5jdGlvbiByZW1vdmUoLi4uZW50aXRpZXMpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBpZHNUb1JlbW92ZSA9IG5ldyBTZXQoZW50aXRpZXMubWFwKG5vcm1hbGl6ZUVudGl0eSkpO1xuXG4gICAgICAgICAgICAgICAgY29uc3QgZW50aXRpZXNUb0RlbGV0ZSA9IHRocm91Z2hRcy5maWx0ZXIodGhyb3VnaCA9PlxuICAgICAgICAgICAgICAgICAgICBpZHNUb1JlbW92ZS5oYXModGhyb3VnaFtvdGhlclJlZmVyZW5jaW5nRmllbGRdKVxuICAgICAgICAgICAgICAgICk7XG5cbiAgICAgICAgICAgICAgICBpZiAoZW50aXRpZXNUb0RlbGV0ZS5jb3VudCgpICE9PSBpZHNUb1JlbW92ZS5zaXplKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIFRyaWVkIGRlbGV0aW5nIG5vbi1leGlzdGluZyBlbnRpdGllcy5cbiAgICAgICAgICAgICAgICAgICAgY29uc3QgZW50aXRpZXNUb0RlbGV0ZUlkcyA9IGVudGl0aWVzVG9EZWxldGVcbiAgICAgICAgICAgICAgICAgICAgICAgIC50b1JlZkFycmF5KClcbiAgICAgICAgICAgICAgICAgICAgICAgIC5tYXAodGhyb3VnaCA9PiB0aHJvdWdoW290aGVyUmVmZXJlbmNpbmdGaWVsZF0pO1xuXG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHVuZXhpc3RpbmdJZHMgPSBbLi4uaWRzVG9SZW1vdmVdLmZpbHRlcihcbiAgICAgICAgICAgICAgICAgICAgICAgIGlkID0+ICFlbnRpdGllc1RvRGVsZXRlSWRzLmluY2x1ZGVzKGlkKVxuICAgICAgICAgICAgICAgICAgICApO1xuXG4gICAgICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICAgICAgICAgIGBUcmllZCB0byBkZWxldGUgbm9uLWV4aXN0aW5nICR7T3RoZXJNb2RlbC5tb2RlbE5hbWV9IGlkKHMpICR7dW5leGlzdGluZ0lkc30gZnJvbSB0aGUgJHtUaGlzTW9kZWwubW9kZWxOYW1lfSBpbnN0YW5jZSB3aXRoIGlkICR7dGhpc0lkfWBcbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBlbnRpdGllc1RvRGVsZXRlLmRlbGV0ZSgpO1xuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgcmV0dXJuIHFzO1xuICAgICAgICB9LFxuXG4gICAgICAgIHNldCgpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBcIlRyaWVkIHNldHRpbmcgYSBNMk0gZmllbGQuIFBsZWFzZSB1c2UgdGhlIHJlbGF0ZWQgUXVlcnlTZXQgbWV0aG9kcyBhZGQsIHJlbW92ZSBhbmQgY2xlYXIuXCJcbiAgICAgICAgICAgICk7XG4gICAgICAgIH0sXG4gICAgfTtcbn1cblxuZXhwb3J0IHtcbiAgICBhdHRyRGVzY3JpcHRvcixcbiAgICBmb3J3YXJkc01hbnlUb09uZURlc2NyaXB0b3IsXG4gICAgZm9yd2FyZHNPbmVUb09uZURlc2NyaXB0b3IsXG4gICAgYmFja3dhcmRzT25lVG9PbmVEZXNjcmlwdG9yLFxuICAgIGJhY2t3YXJkc01hbnlUb09uZURlc2NyaXB0b3IsXG4gICAgbWFueVRvTWFueURlc2NyaXB0b3IsXG59O1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/descriptors.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"attrDescriptor\\\", function() { return attrDescriptor; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"forwardsManyToOneDescriptor\\\", function() { return forwardsManyToOneDescriptor; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"forwardsOneToOneDescriptor\\\", function() { return forwardsOneToOneDescriptor; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"backwardsOneToOneDescriptor\\\", function() { return backwardsOneToOneDescriptor; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"backwardsManyToOneDescriptor\\\", function() { return backwardsManyToOneDescriptor; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"manyToManyDescriptor\\\", function() { return manyToManyDescriptor; });\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ \\\"./src/utils.js\\\");\\n\\n/**\\n * The functions in this file return custom JS property descriptors\\n * that are supposed to be assigned to Model fields.\\n *\\n * Some include the logic to look up models using foreign keys and\\n * to add or remove relationships between models.\\n *\\n * @module descriptors\\n * @private\\n */\\n\\n/**\\n * Defines a basic non-key attribute.\\n * @param  {string} fieldName - the name of the field the descriptor will be assigned to.\\n */\\n\\nfunction attrDescriptor(fieldName) {\\n  return {\\n    get() {\\n      return this._fields[fieldName];\\n    },\\n\\n    set(value) {\\n      return this.set(fieldName, value);\\n    },\\n\\n    enumerable: true,\\n    configurable: true\\n  };\\n}\\n/**\\n * Forwards direction of a Foreign Key: returns one object.\\n * Also works as {@link .forwardsOneToOneDescriptor|forwardsOneToOneDescriptor}.\\n *\\n * For `book.author` referencing an `Author` model instance,\\n * `fieldName` would be `'author'` and `declaredToModelName` would be `'Author'`.\\n * @param  {string} fieldName - the name of the field the descriptor will be assigned to.\\n * @param  {string} declaredToModelName - the name of the model that the field references.\\n */\\n\\n\\nfunction forwardsManyToOneDescriptor(fieldName, declaredToModelName) {\\n  return {\\n    get() {\\n      const {\\n        session: {\\n          [declaredToModelName]: DeclaredToModel\\n        }\\n      } = this.getClass();\\n      const {\\n        [fieldName]: toId\\n      } = this._fields;\\n      return DeclaredToModel.withId(toId);\\n    },\\n\\n    set(value) {\\n      this.update({\\n        [fieldName]: Object(_utils__WEBPACK_IMPORTED_MODULE_0__[\\\"normalizeEntity\\\"])(value)\\n      });\\n    }\\n\\n  };\\n}\\n/**\\n * Dereferencing foreign keys in {@link module:fields.oneToOne|oneToOne}\\n * relationships works the same way as in many-to-one relationships:\\n * just look up the related model.\\n *\\n * For example, a human face tends to have a single nose.\\n * So if we want to resolve `face.nose`, we need to\\n * look up the `Nose` that has the primary key that `face` references.\\n *\\n * @see {@link module:descriptors~forwardsManyToOneDescriptor|forwardsManyToOneDescriptor}\\n */\\n\\n\\nfunction forwardsOneToOneDescriptor(...args) {\\n  return forwardsManyToOneDescriptor(...args);\\n}\\n/**\\n * Here we resolve 1-to-1 relationships starting at the model on which the\\n * field was not installed. This means we need to find the instance of the\\n * other model whose {@link module:fields.oneToOne|oneToOne} FK field contains the current model's primary key.\\n *\\n * @param  {string} declaredFieldName - the name of the field referencing the current model.\\n * @param  {string} declaredFromModelName - the name of the other model.\\n */\\n\\n\\nfunction backwardsOneToOneDescriptor(declaredFieldName, declaredFromModelName) {\\n  return {\\n    get() {\\n      const {\\n        session: {\\n          [declaredFromModelName]: DeclaredFromModel\\n        }\\n      } = this.getClass();\\n      return DeclaredFromModel.get({\\n        [declaredFieldName]: this.getId()\\n      });\\n    },\\n\\n    set() {\\n      throw new Error(\\\"Can't mutate a reverse one-to-one relation.\\\");\\n    }\\n\\n  };\\n}\\n/**\\n * The backwards direction of a n-to-1 relationship (i.e. 1-to-n),\\n * meaning this will return an a collection (`QuerySet`) of model instances.\\n *\\n * An example would be `author.books` referencing all instances of\\n * the `Book` model that reference the author using `fk()`.\\n */\\n\\n\\nfunction backwardsManyToOneDescriptor(declaredFieldName, declaredFromModelName) {\\n  return {\\n    get() {\\n      const {\\n        session: {\\n          [declaredFromModelName]: DeclaredFromModel\\n        }\\n      } = this.getClass();\\n      return DeclaredFromModel.filter({\\n        [declaredFieldName]: this.getId()\\n      });\\n    },\\n\\n    set() {\\n      throw new Error(\\\"Can't mutate a reverse many-to-one relation.\\\");\\n    }\\n\\n  };\\n}\\n/**\\n * This descriptor is assigned to both sides of a many-to-many relationship.\\n * To indicate the backwards direction pass `true` for `reverse`.\\n */\\n\\n\\nfunction manyToManyDescriptor(declaredFromModelName, declaredToModelName, throughModelName, throughFields, reverse) {\\n  return {\\n    get() {\\n      const {\\n        session: {\\n          [declaredFromModelName]: DeclaredFromModel,\\n          [declaredToModelName]: DeclaredToModel,\\n          [throughModelName]: ThroughModel\\n        }\\n      } = this.getClass();\\n      const ThisModel = reverse ? DeclaredToModel : DeclaredFromModel;\\n      const OtherModel = reverse ? DeclaredFromModel : DeclaredToModel;\\n      const thisReferencingField = reverse ? throughFields.to : throughFields.from;\\n      const otherReferencingField = reverse ? throughFields.from : throughFields.to;\\n      const thisId = this.getId();\\n      const throughQs = ThroughModel.filter({\\n        [thisReferencingField]: thisId\\n      });\\n      /**\\n       * all IDs of instances of the other model that are\\n       * referenced by any instance of the current model\\n       */\\n\\n      const referencedOtherIds = new Set(throughQs.toRefArray().map(obj => obj[otherReferencingField]));\\n      /**\\n       * selects all instances of other model that are referenced\\n       * by any instance of the current model\\n       */\\n\\n      const qs = OtherModel.filter(otherModelInstance => referencedOtherIds.has(otherModelInstance[OtherModel.idAttribute]));\\n      /**\\n       * Allows adding OtherModel instances to be referenced by the current instance.\\n       *\\n       * E.g. Book.first().authors.add(1, 2) would add the authors with IDs 1 and 2\\n       * to the first book's list of referenced authors.\\n       *\\n       * @return undefined\\n       */\\n\\n      qs.add = function add(...entities) {\\n        const idsToAdd = new Set(entities.map(_utils__WEBPACK_IMPORTED_MODULE_0__[\\\"normalizeEntity\\\"]));\\n        const existingQs = throughQs.filter(through => idsToAdd.has(through[otherReferencingField]));\\n\\n        if (existingQs.exists()) {\\n          const existingIds = existingQs.toRefArray().map(through => through[otherReferencingField]);\\n          throw new Error(`Tried to add already existing ${OtherModel.modelName} id(s) ${existingIds} to the ${ThisModel.modelName} instance with id ${thisId}`);\\n        }\\n\\n        idsToAdd.forEach(id => {\\n          ThroughModel.create({\\n            [otherReferencingField]: id,\\n            [thisReferencingField]: thisId\\n          });\\n        });\\n      };\\n      /**\\n       * Removes references to all OtherModel instances from the current model.\\n       *\\n       * E.g. Book.first().authors.clear() would cause the first book's list\\n       * of referenced authors to become empty.\\n       *\\n       * @return undefined\\n       */\\n\\n\\n      qs.clear = function clear() {\\n        throughQs.delete();\\n      };\\n      /**\\n       * Removes references to all passed OtherModel instances from the current model.\\n       *\\n       * E.g. Book.first().authors.remove(1, 2) would cause the authors with\\n       * IDs 1 and 2 to no longer be referenced by the first book.\\n       *\\n       * @return undefined\\n       */\\n\\n\\n      qs.remove = function remove(...entities) {\\n        const idsToRemove = new Set(entities.map(_utils__WEBPACK_IMPORTED_MODULE_0__[\\\"normalizeEntity\\\"]));\\n        const entitiesToDelete = throughQs.filter(through => idsToRemove.has(through[otherReferencingField]));\\n\\n        if (entitiesToDelete.count() !== idsToRemove.size) {\\n          // Tried deleting non-existing entities.\\n          const entitiesToDeleteIds = entitiesToDelete.toRefArray().map(through => through[otherReferencingField]);\\n          const unexistingIds = [...idsToRemove].filter(id => !entitiesToDeleteIds.includes(id));\\n          throw new Error(`Tried to delete non-existing ${OtherModel.modelName} id(s) ${unexistingIds} from the ${ThisModel.modelName} instance with id ${thisId}`);\\n        }\\n\\n        entitiesToDelete.delete();\\n      };\\n\\n      return qs;\\n    },\\n\\n    set() {\\n      throw new Error(\\\"Tried setting a M2M field. Please use the related QuerySet methods add, remove and clear.\\\");\\n    }\\n\\n  };\\n}\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9kZXNjcmlwdG9ycy5qcz8zZDUxIl0sIm5hbWVzIjpbImF0dHJEZXNjcmlwdG9yIiwiZmllbGROYW1lIiwiZ2V0IiwiX2ZpZWxkcyIsInNldCIsInZhbHVlIiwiZW51bWVyYWJsZSIsImNvbmZpZ3VyYWJsZSIsImZvcndhcmRzTWFueVRvT25lRGVzY3JpcHRvciIsImRlY2xhcmVkVG9Nb2RlbE5hbWUiLCJzZXNzaW9uIiwiRGVjbGFyZWRUb01vZGVsIiwiZ2V0Q2xhc3MiLCJ0b0lkIiwid2l0aElkIiwidXBkYXRlIiwibm9ybWFsaXplRW50aXR5IiwiZm9yd2FyZHNPbmVUb09uZURlc2NyaXB0b3IiLCJhcmdzIiwiYmFja3dhcmRzT25lVG9PbmVEZXNjcmlwdG9yIiwiZGVjbGFyZWRGaWVsZE5hbWUiLCJkZWNsYXJlZEZyb21Nb2RlbE5hbWUiLCJEZWNsYXJlZEZyb21Nb2RlbCIsImdldElkIiwiRXJyb3IiLCJiYWNrd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yIiwiZmlsdGVyIiwibWFueVRvTWFueURlc2NyaXB0b3IiLCJ0aHJvdWdoTW9kZWxOYW1lIiwidGhyb3VnaEZpZWxkcyIsInJldmVyc2UiLCJUaHJvdWdoTW9kZWwiLCJUaGlzTW9kZWwiLCJPdGhlck1vZGVsIiwidGhpc1JlZmVyZW5jaW5nRmllbGQiLCJ0byIsImZyb20iLCJvdGhlclJlZmVyZW5jaW5nRmllbGQiLCJ0aGlzSWQiLCJ0aHJvdWdoUXMiLCJyZWZlcmVuY2VkT3RoZXJJZHMiLCJTZXQiLCJ0b1JlZkFycmF5IiwibWFwIiwib2JqIiwicXMiLCJvdGhlck1vZGVsSW5zdGFuY2UiLCJoYXMiLCJpZEF0dHJpYnV0ZSIsImFkZCIsImVudGl0aWVzIiwiaWRzVG9BZGQiLCJleGlzdGluZ1FzIiwidGhyb3VnaCIsImV4aXN0cyIsImV4aXN0aW5nSWRzIiwibW9kZWxOYW1lIiwiZm9yRWFjaCIsImlkIiwiY3JlYXRlIiwiY2xlYXIiLCJkZWxldGUiLCJyZW1vdmUiLCJpZHNUb1JlbW92ZSIsImVudGl0aWVzVG9EZWxldGUiLCJjb3VudCIsInNpemUiLCJlbnRpdGllc1RvRGVsZXRlSWRzIiwidW5leGlzdGluZ0lkcyIsImluY2x1ZGVzIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFDQSxTQUFTQSxjQUFULENBQXdCQyxTQUF4QixFQUFtQztBQUMvQixTQUFPO0FBQ0hDLE9BQUcsR0FBRztBQUNGLGFBQU8sS0FBS0MsT0FBTCxDQUFhRixTQUFiLENBQVA7QUFDSCxLQUhFOztBQUtIRyxPQUFHLENBQUNDLEtBQUQsRUFBUTtBQUNQLGFBQU8sS0FBS0QsR0FBTCxDQUFTSCxTQUFULEVBQW9CSSxLQUFwQixDQUFQO0FBQ0gsS0FQRTs7QUFTSEMsY0FBVSxFQUFFLElBVFQ7QUFVSEMsZ0JBQVksRUFBRTtBQVZYLEdBQVA7QUFZSDtBQUVEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7O0FBQ0EsU0FBU0MsMkJBQVQsQ0FBcUNQLFNBQXJDLEVBQWdEUSxtQkFBaEQsRUFBcUU7QUFDakUsU0FBTztBQUNIUCxPQUFHLEdBQUc7QUFDRixZQUFNO0FBQ0ZRLGVBQU8sRUFBRTtBQUFFLFdBQUNELG1CQUFELEdBQXVCRTtBQUF6QjtBQURQLFVBRUYsS0FBS0MsUUFBTCxFQUZKO0FBR0EsWUFBTTtBQUFFLFNBQUNYLFNBQUQsR0FBYVk7QUFBZixVQUF3QixLQUFLVixPQUFuQztBQUVBLGFBQU9RLGVBQWUsQ0FBQ0csTUFBaEIsQ0FBdUJELElBQXZCLENBQVA7QUFDSCxLQVJFOztBQVNIVCxPQUFHLENBQUNDLEtBQUQsRUFBUTtBQUNQLFdBQUtVLE1BQUwsQ0FBWTtBQUNSLFNBQUNkLFNBQUQsR0FBYWUsOERBQWUsQ0FBQ1gsS0FBRDtBQURwQixPQUFaO0FBR0g7O0FBYkUsR0FBUDtBQWVIO0FBRUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7O0FBQ0EsU0FBU1ksMEJBQVQsQ0FBb0MsR0FBR0MsSUFBdkMsRUFBNkM7QUFDekMsU0FBT1YsMkJBQTJCLENBQUMsR0FBR1UsSUFBSixDQUFsQztBQUNIO0FBRUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7O0FBQ0EsU0FBU0MsMkJBQVQsQ0FBcUNDLGlCQUFyQyxFQUF3REMscUJBQXhELEVBQStFO0FBQzNFLFNBQU87QUFDSG5CLE9BQUcsR0FBRztBQUNGLFlBQU07QUFDRlEsZUFBTyxFQUFFO0FBQUUsV0FBQ1cscUJBQUQsR0FBeUJDO0FBQTNCO0FBRFAsVUFFRixLQUFLVixRQUFMLEVBRko7QUFJQSxhQUFPVSxpQkFBaUIsQ0FBQ3BCLEdBQWxCLENBQXNCO0FBQ3pCLFNBQUNrQixpQkFBRCxHQUFxQixLQUFLRyxLQUFMO0FBREksT0FBdEIsQ0FBUDtBQUdILEtBVEU7O0FBVUhuQixPQUFHLEdBQUc7QUFDRixZQUFNLElBQUlvQixLQUFKLENBQVUsNkNBQVYsQ0FBTjtBQUNIOztBQVpFLEdBQVA7QUFjSDtBQUVEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7QUFDQSxTQUFTQyw0QkFBVCxDQUNJTCxpQkFESixFQUVJQyxxQkFGSixFQUdFO0FBQ0UsU0FBTztBQUNIbkIsT0FBRyxHQUFHO0FBQ0YsWUFBTTtBQUNGUSxlQUFPLEVBQUU7QUFBRSxXQUFDVyxxQkFBRCxHQUF5QkM7QUFBM0I7QUFEUCxVQUVGLEtBQUtWLFFBQUwsRUFGSjtBQUlBLGFBQU9VLGlCQUFpQixDQUFDSSxNQUFsQixDQUF5QjtBQUM1QixTQUFDTixpQkFBRCxHQUFxQixLQUFLRyxLQUFMO0FBRE8sT0FBekIsQ0FBUDtBQUdILEtBVEU7O0FBVUhuQixPQUFHLEdBQUc7QUFDRixZQUFNLElBQUlvQixLQUFKLENBQVUsOENBQVYsQ0FBTjtBQUNIOztBQVpFLEdBQVA7QUFjSDtBQUVEO0FBQ0E7QUFDQTtBQUNBOzs7QUFDQSxTQUFTRyxvQkFBVCxDQUNJTixxQkFESixFQUVJWixtQkFGSixFQUdJbUIsZ0JBSEosRUFJSUMsYUFKSixFQUtJQyxPQUxKLEVBTUU7QUFDRSxTQUFPO0FBQ0g1QixPQUFHLEdBQUc7QUFDRixZQUFNO0FBQ0ZRLGVBQU8sRUFBRTtBQUNMLFdBQUNXLHFCQUFELEdBQXlCQyxpQkFEcEI7QUFFTCxXQUFDYixtQkFBRCxHQUF1QkUsZUFGbEI7QUFHTCxXQUFDaUIsZ0JBQUQsR0FBb0JHO0FBSGY7QUFEUCxVQU1GLEtBQUtuQixRQUFMLEVBTko7QUFRQSxZQUFNb0IsU0FBUyxHQUFHRixPQUFPLEdBQUduQixlQUFILEdBQXFCVyxpQkFBOUM7QUFDQSxZQUFNVyxVQUFVLEdBQUdILE9BQU8sR0FBR1IsaUJBQUgsR0FBdUJYLGVBQWpEO0FBRUEsWUFBTXVCLG9CQUFvQixHQUFHSixPQUFPLEdBQzlCRCxhQUFhLENBQUNNLEVBRGdCLEdBRTlCTixhQUFhLENBQUNPLElBRnBCO0FBR0EsWUFBTUMscUJBQXFCLEdBQUdQLE9BQU8sR0FDL0JELGFBQWEsQ0FBQ08sSUFEaUIsR0FFL0JQLGFBQWEsQ0FBQ00sRUFGcEI7QUFJQSxZQUFNRyxNQUFNLEdBQUcsS0FBS2YsS0FBTCxFQUFmO0FBRUEsWUFBTWdCLFNBQVMsR0FBR1IsWUFBWSxDQUFDTCxNQUFiLENBQW9CO0FBQ2xDLFNBQUNRLG9CQUFELEdBQXdCSTtBQURVLE9BQXBCLENBQWxCO0FBSUE7QUFDWjtBQUNBO0FBQ0E7O0FBQ1ksWUFBTUUsa0JBQWtCLEdBQUcsSUFBSUMsR0FBSixDQUN2QkYsU0FBUyxDQUFDRyxVQUFWLEdBQXVCQyxHQUF2QixDQUE0QkMsR0FBRCxJQUFTQSxHQUFHLENBQUNQLHFCQUFELENBQXZDLENBRHVCLENBQTNCO0FBSUE7QUFDWjtBQUNBO0FBQ0E7O0FBQ1ksWUFBTVEsRUFBRSxHQUFHWixVQUFVLENBQUNQLE1BQVgsQ0FBbUJvQixrQkFBRCxJQUN6Qk4sa0JBQWtCLENBQUNPLEdBQW5CLENBQ0lELGtCQUFrQixDQUFDYixVQUFVLENBQUNlLFdBQVosQ0FEdEIsQ0FETyxDQUFYO0FBTUE7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFDWUgsUUFBRSxDQUFDSSxHQUFILEdBQVMsU0FBU0EsR0FBVCxDQUFhLEdBQUdDLFFBQWhCLEVBQTBCO0FBQy9CLGNBQU1DLFFBQVEsR0FBRyxJQUFJVixHQUFKLENBQVFTLFFBQVEsQ0FBQ1AsR0FBVCxDQUFhM0Isc0RBQWIsQ0FBUixDQUFqQjtBQUVBLGNBQU1vQyxVQUFVLEdBQUdiLFNBQVMsQ0FBQ2IsTUFBVixDQUFrQjJCLE9BQUQsSUFDaENGLFFBQVEsQ0FBQ0osR0FBVCxDQUFhTSxPQUFPLENBQUNoQixxQkFBRCxDQUFwQixDQURlLENBQW5COztBQUlBLFlBQUllLFVBQVUsQ0FBQ0UsTUFBWCxFQUFKLEVBQXlCO0FBQ3JCLGdCQUFNQyxXQUFXLEdBQUdILFVBQVUsQ0FDekJWLFVBRGUsR0FFZkMsR0FGZSxDQUVWVSxPQUFELElBQWFBLE9BQU8sQ0FBQ2hCLHFCQUFELENBRlQsQ0FBcEI7QUFJQSxnQkFBTSxJQUFJYixLQUFKLENBQ0QsaUNBQWdDUyxVQUFVLENBQUN1QixTQUFVLFVBQVNELFdBQVksV0FBVXZCLFNBQVMsQ0FBQ3dCLFNBQVUscUJBQW9CbEIsTUFBTyxFQURsSSxDQUFOO0FBR0g7O0FBRURhLGdCQUFRLENBQUNNLE9BQVQsQ0FBa0JDLEVBQUQsSUFBUTtBQUNyQjNCLHNCQUFZLENBQUM0QixNQUFiLENBQW9CO0FBQ2hCLGFBQUN0QixxQkFBRCxHQUF5QnFCLEVBRFQ7QUFFaEIsYUFBQ3hCLG9CQUFELEdBQXdCSTtBQUZSLFdBQXBCO0FBSUgsU0FMRDtBQU1ILE9BdkJEO0FBeUJBO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQUNZTyxRQUFFLENBQUNlLEtBQUgsR0FBVyxTQUFTQSxLQUFULEdBQWlCO0FBQ3hCckIsaUJBQVMsQ0FBQ3NCLE1BQVY7QUFDSCxPQUZEO0FBSUE7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7O0FBQ1loQixRQUFFLENBQUNpQixNQUFILEdBQVksU0FBU0EsTUFBVCxDQUFnQixHQUFHWixRQUFuQixFQUE2QjtBQUNyQyxjQUFNYSxXQUFXLEdBQUcsSUFBSXRCLEdBQUosQ0FBUVMsUUFBUSxDQUFDUCxHQUFULENBQWEzQixzREFBYixDQUFSLENBQXBCO0FBRUEsY0FBTWdELGdCQUFnQixHQUFHekIsU0FBUyxDQUFDYixNQUFWLENBQWtCMkIsT0FBRCxJQUN0Q1UsV0FBVyxDQUFDaEIsR0FBWixDQUFnQk0sT0FBTyxDQUFDaEIscUJBQUQsQ0FBdkIsQ0FEcUIsQ0FBekI7O0FBSUEsWUFBSTJCLGdCQUFnQixDQUFDQyxLQUFqQixPQUE2QkYsV0FBVyxDQUFDRyxJQUE3QyxFQUFtRDtBQUMvQztBQUNBLGdCQUFNQyxtQkFBbUIsR0FBR0gsZ0JBQWdCLENBQ3ZDdEIsVUFEdUIsR0FFdkJDLEdBRnVCLENBRWxCVSxPQUFELElBQWFBLE9BQU8sQ0FBQ2hCLHFCQUFELENBRkQsQ0FBNUI7QUFJQSxnQkFBTStCLGFBQWEsR0FBRyxDQUFDLEdBQUdMLFdBQUosRUFBaUJyQyxNQUFqQixDQUNqQmdDLEVBQUQsSUFBUSxDQUFDUyxtQkFBbUIsQ0FBQ0UsUUFBcEIsQ0FBNkJYLEVBQTdCLENBRFMsQ0FBdEI7QUFJQSxnQkFBTSxJQUFJbEMsS0FBSixDQUNELGdDQUErQlMsVUFBVSxDQUFDdUIsU0FBVSxVQUFTWSxhQUFjLGFBQVlwQyxTQUFTLENBQUN3QixTQUFVLHFCQUFvQmxCLE1BQU8sRUFEckksQ0FBTjtBQUdIOztBQUVEMEIsd0JBQWdCLENBQUNILE1BQWpCO0FBQ0gsT0F2QkQ7O0FBeUJBLGFBQU9oQixFQUFQO0FBQ0gsS0EzSEU7O0FBNkhIekMsT0FBRyxHQUFHO0FBQ0YsWUFBTSxJQUFJb0IsS0FBSixDQUNGLDJGQURFLENBQU47QUFHSDs7QUFqSUUsR0FBUDtBQW1JSCIsImZpbGUiOiIuL3NyYy9kZXNjcmlwdG9ycy5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IG5vcm1hbGl6ZUVudGl0eSB9IGZyb20gXCIuL3V0aWxzXCI7XG5cbi8qKlxuICogVGhlIGZ1bmN0aW9ucyBpbiB0aGlzIGZpbGUgcmV0dXJuIGN1c3RvbSBKUyBwcm9wZXJ0eSBkZXNjcmlwdG9yc1xuICogdGhhdCBhcmUgc3VwcG9zZWQgdG8gYmUgYXNzaWduZWQgdG8gTW9kZWwgZmllbGRzLlxuICpcbiAqIFNvbWUgaW5jbHVkZSB0aGUgbG9naWMgdG8gbG9vayB1cCBtb2RlbHMgdXNpbmcgZm9yZWlnbiBrZXlzIGFuZFxuICogdG8gYWRkIG9yIHJlbW92ZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gbW9kZWxzLlxuICpcbiAqIEBtb2R1bGUgZGVzY3JpcHRvcnNcbiAqIEBwcml2YXRlXG4gKi9cblxuLyoqXG4gKiBEZWZpbmVzIGEgYmFzaWMgbm9uLWtleSBhdHRyaWJ1dGUuXG4gKiBAcGFyYW0gIHtzdHJpbmd9IGZpZWxkTmFtZSAtIHRoZSBuYW1lIG9mIHRoZSBmaWVsZCB0aGUgZGVzY3JpcHRvciB3aWxsIGJlIGFzc2lnbmVkIHRvLlxuICovXG5mdW5jdGlvbiBhdHRyRGVzY3JpcHRvcihmaWVsZE5hbWUpIHtcbiAgICByZXR1cm4ge1xuICAgICAgICBnZXQoKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5fZmllbGRzW2ZpZWxkTmFtZV07XG4gICAgICAgIH0sXG5cbiAgICAgICAgc2V0KHZhbHVlKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5zZXQoZmllbGROYW1lLCB2YWx1ZSk7XG4gICAgICAgIH0sXG5cbiAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgIH07XG59XG5cbi8qKlxuICogRm9yd2FyZHMgZGlyZWN0aW9uIG9mIGEgRm9yZWlnbiBLZXk6IHJldHVybnMgb25lIG9iamVjdC5cbiAqIEFsc28gd29ya3MgYXMge0BsaW5rIC5mb3J3YXJkc09uZVRvT25lRGVzY3JpcHRvcnxmb3J3YXJkc09uZVRvT25lRGVzY3JpcHRvcn0uXG4gKlxuICogRm9yIGBib29rLmF1dGhvcmAgcmVmZXJlbmNpbmcgYW4gYEF1dGhvcmAgbW9kZWwgaW5zdGFuY2UsXG4gKiBgZmllbGROYW1lYCB3b3VsZCBiZSBgJ2F1dGhvcidgIGFuZCBgZGVjbGFyZWRUb01vZGVsTmFtZWAgd291bGQgYmUgYCdBdXRob3InYC5cbiAqIEBwYXJhbSAge3N0cmluZ30gZmllbGROYW1lIC0gdGhlIG5hbWUgb2YgdGhlIGZpZWxkIHRoZSBkZXNjcmlwdG9yIHdpbGwgYmUgYXNzaWduZWQgdG8uXG4gKiBAcGFyYW0gIHtzdHJpbmd9IGRlY2xhcmVkVG9Nb2RlbE5hbWUgLSB0aGUgbmFtZSBvZiB0aGUgbW9kZWwgdGhhdCB0aGUgZmllbGQgcmVmZXJlbmNlcy5cbiAqL1xuZnVuY3Rpb24gZm9yd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yKGZpZWxkTmFtZSwgZGVjbGFyZWRUb01vZGVsTmFtZSkge1xuICAgIHJldHVybiB7XG4gICAgICAgIGdldCgpIHtcbiAgICAgICAgICAgIGNvbnN0IHtcbiAgICAgICAgICAgICAgICBzZXNzaW9uOiB7IFtkZWNsYXJlZFRvTW9kZWxOYW1lXTogRGVjbGFyZWRUb01vZGVsIH0sXG4gICAgICAgICAgICB9ID0gdGhpcy5nZXRDbGFzcygpO1xuICAgICAgICAgICAgY29uc3QgeyBbZmllbGROYW1lXTogdG9JZCB9ID0gdGhpcy5fZmllbGRzO1xuXG4gICAgICAgICAgICByZXR1cm4gRGVjbGFyZWRUb01vZGVsLndpdGhJZCh0b0lkKTtcbiAgICAgICAgfSxcbiAgICAgICAgc2V0KHZhbHVlKSB7XG4gICAgICAgICAgICB0aGlzLnVwZGF0ZSh7XG4gICAgICAgICAgICAgICAgW2ZpZWxkTmFtZV06IG5vcm1hbGl6ZUVudGl0eSh2YWx1ZSksXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSxcbiAgICB9O1xufVxuXG4vKipcbiAqIERlcmVmZXJlbmNpbmcgZm9yZWlnbiBrZXlzIGluIHtAbGluayBtb2R1bGU6ZmllbGRzLm9uZVRvT25lfG9uZVRvT25lfVxuICogcmVsYXRpb25zaGlwcyB3b3JrcyB0aGUgc2FtZSB3YXkgYXMgaW4gbWFueS10by1vbmUgcmVsYXRpb25zaGlwczpcbiAqIGp1c3QgbG9vayB1cCB0aGUgcmVsYXRlZCBtb2RlbC5cbiAqXG4gKiBGb3IgZXhhbXBsZSwgYSBodW1hbiBmYWNlIHRlbmRzIHRvIGhhdmUgYSBzaW5nbGUgbm9zZS5cbiAqIFNvIGlmIHdlIHdhbnQgdG8gcmVzb2x2ZSBgZmFjZS5ub3NlYCwgd2UgbmVlZCB0b1xuICogbG9vayB1cCB0aGUgYE5vc2VgIHRoYXQgaGFzIHRoZSBwcmltYXJ5IGtleSB0aGF0IGBmYWNlYCByZWZlcmVuY2VzLlxuICpcbiAqIEBzZWUge0BsaW5rIG1vZHVsZTpkZXNjcmlwdG9yc35mb3J3YXJkc01hbnlUb09uZURlc2NyaXB0b3J8Zm9yd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yfVxuICovXG5mdW5jdGlvbiBmb3J3YXJkc09uZVRvT25lRGVzY3JpcHRvciguLi5hcmdzKSB7XG4gICAgcmV0dXJuIGZvcndhcmRzTWFueVRvT25lRGVzY3JpcHRvciguLi5hcmdzKTtcbn1cblxuLyoqXG4gKiBIZXJlIHdlIHJlc29sdmUgMS10by0xIHJlbGF0aW9uc2hpcHMgc3RhcnRpbmcgYXQgdGhlIG1vZGVsIG9uIHdoaWNoIHRoZVxuICogZmllbGQgd2FzIG5vdCBpbnN0YWxsZWQuIFRoaXMgbWVhbnMgd2UgbmVlZCB0byBmaW5kIHRoZSBpbnN0YW5jZSBvZiB0aGVcbiAqIG90aGVyIG1vZGVsIHdob3NlIHtAbGluayBtb2R1bGU6ZmllbGRzLm9uZVRvT25lfG9uZVRvT25lfSBGSyBmaWVsZCBjb250YWlucyB0aGUgY3VycmVudCBtb2RlbCdzIHByaW1hcnkga2V5LlxuICpcbiAqIEBwYXJhbSAge3N0cmluZ30gZGVjbGFyZWRGaWVsZE5hbWUgLSB0aGUgbmFtZSBvZiB0aGUgZmllbGQgcmVmZXJlbmNpbmcgdGhlIGN1cnJlbnQgbW9kZWwuXG4gKiBAcGFyYW0gIHtzdHJpbmd9IGRlY2xhcmVkRnJvbU1vZGVsTmFtZSAtIHRoZSBuYW1lIG9mIHRoZSBvdGhlciBtb2RlbC5cbiAqL1xuZnVuY3Rpb24gYmFja3dhcmRzT25lVG9PbmVEZXNjcmlwdG9yKGRlY2xhcmVkRmllbGROYW1lLCBkZWNsYXJlZEZyb21Nb2RlbE5hbWUpIHtcbiAgICByZXR1cm4ge1xuICAgICAgICBnZXQoKSB7XG4gICAgICAgICAgICBjb25zdCB7XG4gICAgICAgICAgICAgICAgc2Vzc2lvbjogeyBbZGVjbGFyZWRGcm9tTW9kZWxOYW1lXTogRGVjbGFyZWRGcm9tTW9kZWwgfSxcbiAgICAgICAgICAgIH0gPSB0aGlzLmdldENsYXNzKCk7XG5cbiAgICAgICAgICAgIHJldHVybiBEZWNsYXJlZEZyb21Nb2RlbC5nZXQoe1xuICAgICAgICAgICAgICAgIFtkZWNsYXJlZEZpZWxkTmFtZV06IHRoaXMuZ2V0SWQoKSxcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9LFxuICAgICAgICBzZXQoKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJDYW4ndCBtdXRhdGUgYSByZXZlcnNlIG9uZS10by1vbmUgcmVsYXRpb24uXCIpO1xuICAgICAgICB9LFxuICAgIH07XG59XG5cbi8qKlxuICogVGhlIGJhY2t3YXJkcyBkaXJlY3Rpb24gb2YgYSBuLXRvLTEgcmVsYXRpb25zaGlwIChpLmUuIDEtdG8tbiksXG4gKiBtZWFuaW5nIHRoaXMgd2lsbCByZXR1cm4gYW4gYSBjb2xsZWN0aW9uIChgUXVlcnlTZXRgKSBvZiBtb2RlbCBpbnN0YW5jZXMuXG4gKlxuICogQW4gZXhhbXBsZSB3b3VsZCBiZSBgYXV0aG9yLmJvb2tzYCByZWZlcmVuY2luZyBhbGwgaW5zdGFuY2VzIG9mXG4gKiB0aGUgYEJvb2tgIG1vZGVsIHRoYXQgcmVmZXJlbmNlIHRoZSBhdXRob3IgdXNpbmcgYGZrKClgLlxuICovXG5mdW5jdGlvbiBiYWNrd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yKFxuICAgIGRlY2xhcmVkRmllbGROYW1lLFxuICAgIGRlY2xhcmVkRnJvbU1vZGVsTmFtZVxuKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgZ2V0KCkge1xuICAgICAgICAgICAgY29uc3Qge1xuICAgICAgICAgICAgICAgIHNlc3Npb246IHsgW2RlY2xhcmVkRnJvbU1vZGVsTmFtZV06IERlY2xhcmVkRnJvbU1vZGVsIH0sXG4gICAgICAgICAgICB9ID0gdGhpcy5nZXRDbGFzcygpO1xuXG4gICAgICAgICAgICByZXR1cm4gRGVjbGFyZWRGcm9tTW9kZWwuZmlsdGVyKHtcbiAgICAgICAgICAgICAgICBbZGVjbGFyZWRGaWVsZE5hbWVdOiB0aGlzLmdldElkKCksXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSxcbiAgICAgICAgc2V0KCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiQ2FuJ3QgbXV0YXRlIGEgcmV2ZXJzZSBtYW55LXRvLW9uZSByZWxhdGlvbi5cIik7XG4gICAgICAgIH0sXG4gICAgfTtcbn1cblxuLyoqXG4gKiBUaGlzIGRlc2NyaXB0b3IgaXMgYXNzaWduZWQgdG8gYm90aCBzaWRlcyBvZiBhIG1hbnktdG8tbWFueSByZWxhdGlvbnNoaXAuXG4gKiBUbyBpbmRpY2F0ZSB0aGUgYmFja3dhcmRzIGRpcmVjdGlvbiBwYXNzIGB0cnVlYCBmb3IgYHJldmVyc2VgLlxuICovXG5mdW5jdGlvbiBtYW55VG9NYW55RGVzY3JpcHRvcihcbiAgICBkZWNsYXJlZEZyb21Nb2RlbE5hbWUsXG4gICAgZGVjbGFyZWRUb01vZGVsTmFtZSxcbiAgICB0aHJvdWdoTW9kZWxOYW1lLFxuICAgIHRocm91Z2hGaWVsZHMsXG4gICAgcmV2ZXJzZVxuKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgZ2V0KCkge1xuICAgICAgICAgICAgY29uc3Qge1xuICAgICAgICAgICAgICAgIHNlc3Npb246IHtcbiAgICAgICAgICAgICAgICAgICAgW2RlY2xhcmVkRnJvbU1vZGVsTmFtZV06IERlY2xhcmVkRnJvbU1vZGVsLFxuICAgICAgICAgICAgICAgICAgICBbZGVjbGFyZWRUb01vZGVsTmFtZV06IERlY2xhcmVkVG9Nb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgW3Rocm91Z2hNb2RlbE5hbWVdOiBUaHJvdWdoTW9kZWwsXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIH0gPSB0aGlzLmdldENsYXNzKCk7XG5cbiAgICAgICAgICAgIGNvbnN0IFRoaXNNb2RlbCA9IHJldmVyc2UgPyBEZWNsYXJlZFRvTW9kZWwgOiBEZWNsYXJlZEZyb21Nb2RlbDtcbiAgICAgICAgICAgIGNvbnN0IE90aGVyTW9kZWwgPSByZXZlcnNlID8gRGVjbGFyZWRGcm9tTW9kZWwgOiBEZWNsYXJlZFRvTW9kZWw7XG5cbiAgICAgICAgICAgIGNvbnN0IHRoaXNSZWZlcmVuY2luZ0ZpZWxkID0gcmV2ZXJzZVxuICAgICAgICAgICAgICAgID8gdGhyb3VnaEZpZWxkcy50b1xuICAgICAgICAgICAgICAgIDogdGhyb3VnaEZpZWxkcy5mcm9tO1xuICAgICAgICAgICAgY29uc3Qgb3RoZXJSZWZlcmVuY2luZ0ZpZWxkID0gcmV2ZXJzZVxuICAgICAgICAgICAgICAgID8gdGhyb3VnaEZpZWxkcy5mcm9tXG4gICAgICAgICAgICAgICAgOiB0aHJvdWdoRmllbGRzLnRvO1xuXG4gICAgICAgICAgICBjb25zdCB0aGlzSWQgPSB0aGlzLmdldElkKCk7XG5cbiAgICAgICAgICAgIGNvbnN0IHRocm91Z2hRcyA9IFRocm91Z2hNb2RlbC5maWx0ZXIoe1xuICAgICAgICAgICAgICAgIFt0aGlzUmVmZXJlbmNpbmdGaWVsZF06IHRoaXNJZCxcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIGFsbCBJRHMgb2YgaW5zdGFuY2VzIG9mIHRoZSBvdGhlciBtb2RlbCB0aGF0IGFyZVxuICAgICAgICAgICAgICogcmVmZXJlbmNlZCBieSBhbnkgaW5zdGFuY2Ugb2YgdGhlIGN1cnJlbnQgbW9kZWxcbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgY29uc3QgcmVmZXJlbmNlZE90aGVySWRzID0gbmV3IFNldChcbiAgICAgICAgICAgICAgICB0aHJvdWdoUXMudG9SZWZBcnJheSgpLm1hcCgob2JqKSA9PiBvYmpbb3RoZXJSZWZlcmVuY2luZ0ZpZWxkXSlcbiAgICAgICAgICAgICk7XG5cbiAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICogc2VsZWN0cyBhbGwgaW5zdGFuY2VzIG9mIG90aGVyIG1vZGVsIHRoYXQgYXJlIHJlZmVyZW5jZWRcbiAgICAgICAgICAgICAqIGJ5IGFueSBpbnN0YW5jZSBvZiB0aGUgY3VycmVudCBtb2RlbFxuICAgICAgICAgICAgICovXG4gICAgICAgICAgICBjb25zdCBxcyA9IE90aGVyTW9kZWwuZmlsdGVyKChvdGhlck1vZGVsSW5zdGFuY2UpID0+XG4gICAgICAgICAgICAgICAgcmVmZXJlbmNlZE90aGVySWRzLmhhcyhcbiAgICAgICAgICAgICAgICAgICAgb3RoZXJNb2RlbEluc3RhbmNlW090aGVyTW9kZWwuaWRBdHRyaWJ1dGVdXG4gICAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgKTtcblxuICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgKiBBbGxvd3MgYWRkaW5nIE90aGVyTW9kZWwgaW5zdGFuY2VzIHRvIGJlIHJlZmVyZW5jZWQgYnkgdGhlIGN1cnJlbnQgaW5zdGFuY2UuXG4gICAgICAgICAgICAgKlxuICAgICAgICAgICAgICogRS5nLiBCb29rLmZpcnN0KCkuYXV0aG9ycy5hZGQoMSwgMikgd291bGQgYWRkIHRoZSBhdXRob3JzIHdpdGggSURzIDEgYW5kIDJcbiAgICAgICAgICAgICAqIHRvIHRoZSBmaXJzdCBib29rJ3MgbGlzdCBvZiByZWZlcmVuY2VkIGF1dGhvcnMuXG4gICAgICAgICAgICAgKlxuICAgICAgICAgICAgICogQHJldHVybiB1bmRlZmluZWRcbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgcXMuYWRkID0gZnVuY3Rpb24gYWRkKC4uLmVudGl0aWVzKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgaWRzVG9BZGQgPSBuZXcgU2V0KGVudGl0aWVzLm1hcChub3JtYWxpemVFbnRpdHkpKTtcblxuICAgICAgICAgICAgICAgIGNvbnN0IGV4aXN0aW5nUXMgPSB0aHJvdWdoUXMuZmlsdGVyKCh0aHJvdWdoKSA9PlxuICAgICAgICAgICAgICAgICAgICBpZHNUb0FkZC5oYXModGhyb3VnaFtvdGhlclJlZmVyZW5jaW5nRmllbGRdKVxuICAgICAgICAgICAgICAgICk7XG5cbiAgICAgICAgICAgICAgICBpZiAoZXhpc3RpbmdRcy5leGlzdHMoKSkge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBleGlzdGluZ0lkcyA9IGV4aXN0aW5nUXNcbiAgICAgICAgICAgICAgICAgICAgICAgIC50b1JlZkFycmF5KClcbiAgICAgICAgICAgICAgICAgICAgICAgIC5tYXAoKHRocm91Z2gpID0+IHRocm91Z2hbb3RoZXJSZWZlcmVuY2luZ0ZpZWxkXSk7XG5cbiAgICAgICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgICAgICAgICAgYFRyaWVkIHRvIGFkZCBhbHJlYWR5IGV4aXN0aW5nICR7T3RoZXJNb2RlbC5tb2RlbE5hbWV9IGlkKHMpICR7ZXhpc3RpbmdJZHN9IHRvIHRoZSAke1RoaXNNb2RlbC5tb2RlbE5hbWV9IGluc3RhbmNlIHdpdGggaWQgJHt0aGlzSWR9YFxuICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGlkc1RvQWRkLmZvckVhY2goKGlkKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIFRocm91Z2hNb2RlbC5jcmVhdGUoe1xuICAgICAgICAgICAgICAgICAgICAgICAgW290aGVyUmVmZXJlbmNpbmdGaWVsZF06IGlkLFxuICAgICAgICAgICAgICAgICAgICAgICAgW3RoaXNSZWZlcmVuY2luZ0ZpZWxkXTogdGhpc0lkLFxuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICogUmVtb3ZlcyByZWZlcmVuY2VzIHRvIGFsbCBPdGhlck1vZGVsIGluc3RhbmNlcyBmcm9tIHRoZSBjdXJyZW50IG1vZGVsLlxuICAgICAgICAgICAgICpcbiAgICAgICAgICAgICAqIEUuZy4gQm9vay5maXJzdCgpLmF1dGhvcnMuY2xlYXIoKSB3b3VsZCBjYXVzZSB0aGUgZmlyc3QgYm9vaydzIGxpc3RcbiAgICAgICAgICAgICAqIG9mIHJlZmVyZW5jZWQgYXV0aG9ycyB0byBiZWNvbWUgZW1wdHkuXG4gICAgICAgICAgICAgKlxuICAgICAgICAgICAgICogQHJldHVybiB1bmRlZmluZWRcbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgcXMuY2xlYXIgPSBmdW5jdGlvbiBjbGVhcigpIHtcbiAgICAgICAgICAgICAgICB0aHJvdWdoUXMuZGVsZXRlKCk7XG4gICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIFJlbW92ZXMgcmVmZXJlbmNlcyB0byBhbGwgcGFzc2VkIE90aGVyTW9kZWwgaW5zdGFuY2VzIGZyb20gdGhlIGN1cnJlbnQgbW9kZWwuXG4gICAgICAgICAgICAgKlxuICAgICAgICAgICAgICogRS5nLiBCb29rLmZpcnN0KCkuYXV0aG9ycy5yZW1vdmUoMSwgMikgd291bGQgY2F1c2UgdGhlIGF1dGhvcnMgd2l0aFxuICAgICAgICAgICAgICogSURzIDEgYW5kIDIgdG8gbm8gbG9uZ2VyIGJlIHJlZmVyZW5jZWQgYnkgdGhlIGZpcnN0IGJvb2suXG4gICAgICAgICAgICAgKlxuICAgICAgICAgICAgICogQHJldHVybiB1bmRlZmluZWRcbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgcXMucmVtb3ZlID0gZnVuY3Rpb24gcmVtb3ZlKC4uLmVudGl0aWVzKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgaWRzVG9SZW1vdmUgPSBuZXcgU2V0KGVudGl0aWVzLm1hcChub3JtYWxpemVFbnRpdHkpKTtcblxuICAgICAgICAgICAgICAgIGNvbnN0IGVudGl0aWVzVG9EZWxldGUgPSB0aHJvdWdoUXMuZmlsdGVyKCh0aHJvdWdoKSA9PlxuICAgICAgICAgICAgICAgICAgICBpZHNUb1JlbW92ZS5oYXModGhyb3VnaFtvdGhlclJlZmVyZW5jaW5nRmllbGRdKVxuICAgICAgICAgICAgICAgICk7XG5cbiAgICAgICAgICAgICAgICBpZiAoZW50aXRpZXNUb0RlbGV0ZS5jb3VudCgpICE9PSBpZHNUb1JlbW92ZS5zaXplKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIFRyaWVkIGRlbGV0aW5nIG5vbi1leGlzdGluZyBlbnRpdGllcy5cbiAgICAgICAgICAgICAgICAgICAgY29uc3QgZW50aXRpZXNUb0RlbGV0ZUlkcyA9IGVudGl0aWVzVG9EZWxldGVcbiAgICAgICAgICAgICAgICAgICAgICAgIC50b1JlZkFycmF5KClcbiAgICAgICAgICAgICAgICAgICAgICAgIC5tYXAoKHRocm91Z2gpID0+IHRocm91Z2hbb3RoZXJSZWZlcmVuY2luZ0ZpZWxkXSk7XG5cbiAgICAgICAgICAgICAgICAgICAgY29uc3QgdW5leGlzdGluZ0lkcyA9IFsuLi5pZHNUb1JlbW92ZV0uZmlsdGVyKFxuICAgICAgICAgICAgICAgICAgICAgICAgKGlkKSA9PiAhZW50aXRpZXNUb0RlbGV0ZUlkcy5pbmNsdWRlcyhpZClcbiAgICAgICAgICAgICAgICAgICAgKTtcblxuICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgICAgICAgICBgVHJpZWQgdG8gZGVsZXRlIG5vbi1leGlzdGluZyAke090aGVyTW9kZWwubW9kZWxOYW1lfSBpZChzKSAke3VuZXhpc3RpbmdJZHN9IGZyb20gdGhlICR7VGhpc01vZGVsLm1vZGVsTmFtZX0gaW5zdGFuY2Ugd2l0aCBpZCAke3RoaXNJZH1gXG4gICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgZW50aXRpZXNUb0RlbGV0ZS5kZWxldGUoKTtcbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIHJldHVybiBxcztcbiAgICAgICAgfSxcblxuICAgICAgICBzZXQoKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgXCJUcmllZCBzZXR0aW5nIGEgTTJNIGZpZWxkLiBQbGVhc2UgdXNlIHRoZSByZWxhdGVkIFF1ZXJ5U2V0IG1ldGhvZHMgYWRkLCByZW1vdmUgYW5kIGNsZWFyLlwiXG4gICAgICAgICAgICApO1xuICAgICAgICB9LFxuICAgIH07XG59XG5cbmV4cG9ydCB7XG4gICAgYXR0ckRlc2NyaXB0b3IsXG4gICAgZm9yd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yLFxuICAgIGZvcndhcmRzT25lVG9PbmVEZXNjcmlwdG9yLFxuICAgIGJhY2t3YXJkc09uZVRvT25lRGVzY3JpcHRvcixcbiAgICBiYWNrd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yLFxuICAgIG1hbnlUb01hbnlEZXNjcmlwdG9yLFxufTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/descriptors.js\\n\");\n \n /***/ }),\n \n@@ -4558,7 +4580,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"Attribute\\\", function() { return Attribute; });\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _Field__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Field */ \\\"./src/fields/Field.js\\\");\\n/* harmony import */ var _descriptors__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../descriptors */ \\\"./src/descriptors.js\\\");\\n\\n\\n\\n/**\\n * @memberof module:fields\\n */\\n\\nlet Attribute = /*#__PURE__*/function (_Field) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default()(Attribute, _Field);\\n\\n  function Attribute(opts) {\\n    var _this;\\n\\n    _this = _Field.call(this) || this;\\n    _this.opts = opts || {};\\n\\n    if (_this.opts.hasOwnProperty(\\\"getDefault\\\")) {\\n      _this.getDefault = _this.opts.getDefault;\\n    }\\n\\n    return _this;\\n  }\\n\\n  var _proto = Attribute.prototype;\\n\\n  _proto.createForwardsDescriptor = function createForwardsDescriptor(fieldName, model) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_2__[\\\"attrDescriptor\\\"])(fieldName);\\n  };\\n\\n  return Attribute;\\n}(_Field__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (Attribute);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvQXR0cmlidXRlLmpzPzJkNDMiXSwibmFtZXMiOlsiQXR0cmlidXRlIiwib3B0cyIsImhhc093blByb3BlcnR5IiwiZ2V0RGVmYXVsdCIsImNyZWF0ZUZvcndhcmRzRGVzY3JpcHRvciIsImZpZWxkTmFtZSIsIm1vZGVsIiwiYXR0ckRlc2NyaXB0b3IiLCJGaWVsZCJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFBO0FBRUE7QUFFQTs7OztBQUdPLElBQU1BLFNBQWI7QUFBQTs7QUFDSSxxQkFBWUMsSUFBWixFQUFrQjtBQUFBOztBQUNkO0FBQ0EsVUFBS0EsSUFBTCxHQUFZQSxJQUFJLElBQUksRUFBcEI7O0FBRUEsUUFBSSxNQUFLQSxJQUFMLENBQVVDLGNBQVYsQ0FBeUIsWUFBekIsQ0FBSixFQUE0QztBQUN4QyxZQUFLQyxVQUFMLEdBQWtCLE1BQUtGLElBQUwsQ0FBVUUsVUFBNUI7QUFDSDs7QUFOYTtBQU9qQjs7QUFSTDs7QUFBQSxTQVVJQyx3QkFWSixHQVVJLGtDQUF5QkMsU0FBekIsRUFBb0NDLEtBQXBDLEVBQTJDO0FBQ3ZDLFdBQU9DLG1FQUFjLENBQUNGLFNBQUQsQ0FBckI7QUFDSCxHQVpMOztBQUFBO0FBQUEsRUFBK0JHLDhDQUEvQjtBQWVlUix3RUFBZiIsImZpbGUiOiIuL3NyYy9maWVsZHMvQXR0cmlidXRlLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IEZpZWxkIGZyb20gXCIuL0ZpZWxkXCI7XG5cbmltcG9ydCB7IGF0dHJEZXNjcmlwdG9yIH0gZnJvbSBcIi4uL2Rlc2NyaXB0b3JzXCI7XG5cbi8qKlxuICogQG1lbWJlcm9mIG1vZHVsZTpmaWVsZHNcbiAqL1xuZXhwb3J0IGNsYXNzIEF0dHJpYnV0ZSBleHRlbmRzIEZpZWxkIHtcbiAgICBjb25zdHJ1Y3RvcihvcHRzKSB7XG4gICAgICAgIHN1cGVyKCk7XG4gICAgICAgIHRoaXMub3B0cyA9IG9wdHMgfHwge307XG5cbiAgICAgICAgaWYgKHRoaXMub3B0cy5oYXNPd25Qcm9wZXJ0eShcImdldERlZmF1bHRcIikpIHtcbiAgICAgICAgICAgIHRoaXMuZ2V0RGVmYXVsdCA9IHRoaXMub3B0cy5nZXREZWZhdWx0O1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgY3JlYXRlRm9yd2FyZHNEZXNjcmlwdG9yKGZpZWxkTmFtZSwgbW9kZWwpIHtcbiAgICAgICAgcmV0dXJuIGF0dHJEZXNjcmlwdG9yKGZpZWxkTmFtZSk7XG4gICAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBBdHRyaWJ1dGU7XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./src/fields/Attribute.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"Attribute\\\", function() { return Attribute; });\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _Field__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Field */ \\\"./src/fields/Field.js\\\");\\n/* harmony import */ var _descriptors__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../descriptors */ \\\"./src/descriptors.js\\\");\\n\\n\\n\\n/**\\n * @memberof module:fields\\n */\\n\\nlet Attribute = /*#__PURE__*/function (_Field) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default()(Attribute, _Field);\\n\\n  function Attribute(opts) {\\n    var _this;\\n\\n    _this = _Field.call(this) || this;\\n    _this.opts = opts || {};\\n\\n    if (_this.opts.hasOwnProperty(\\\"getDefault\\\")) {\\n      _this.getDefault = _this.opts.getDefault;\\n    }\\n\\n    return _this;\\n  }\\n\\n  var _proto = Attribute.prototype;\\n\\n  _proto.createForwardsDescriptor = function createForwardsDescriptor(fieldName, model) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_2__[\\\"attrDescriptor\\\"])(fieldName);\\n  };\\n\\n  return Attribute;\\n}(_Field__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (Attribute);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvQXR0cmlidXRlLmpzPzJkNDMiXSwibmFtZXMiOlsiQXR0cmlidXRlIiwib3B0cyIsImhhc093blByb3BlcnR5IiwiZ2V0RGVmYXVsdCIsImNyZWF0ZUZvcndhcmRzRGVzY3JpcHRvciIsImZpZWxkTmFtZSIsIm1vZGVsIiwiYXR0ckRlc2NyaXB0b3IiLCJGaWVsZCJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFBO0FBRUE7QUFFQTtBQUNBO0FBQ0E7O0FBQ08sSUFBTUEsU0FBYjtBQUFBOztBQUNJLHFCQUFZQyxJQUFaLEVBQWtCO0FBQUE7O0FBQ2Q7QUFDQSxVQUFLQSxJQUFMLEdBQVlBLElBQUksSUFBSSxFQUFwQjs7QUFFQSxRQUFJLE1BQUtBLElBQUwsQ0FBVUMsY0FBVixDQUF5QixZQUF6QixDQUFKLEVBQTRDO0FBQ3hDLFlBQUtDLFVBQUwsR0FBa0IsTUFBS0YsSUFBTCxDQUFVRSxVQUE1QjtBQUNIOztBQU5hO0FBT2pCOztBQVJMOztBQUFBLFNBVUlDLHdCQVZKLEdBVUksa0NBQXlCQyxTQUF6QixFQUFvQ0MsS0FBcEMsRUFBMkM7QUFDdkMsV0FBT0MsbUVBQWMsQ0FBQ0YsU0FBRCxDQUFyQjtBQUNILEdBWkw7O0FBQUE7QUFBQSxFQUErQkcsOENBQS9CO0FBZWVSLHdFQUFmIiwiZmlsZSI6Ii4vc3JjL2ZpZWxkcy9BdHRyaWJ1dGUuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgRmllbGQgZnJvbSBcIi4vRmllbGRcIjtcblxuaW1wb3J0IHsgYXR0ckRlc2NyaXB0b3IgfSBmcm9tIFwiLi4vZGVzY3JpcHRvcnNcIjtcblxuLyoqXG4gKiBAbWVtYmVyb2YgbW9kdWxlOmZpZWxkc1xuICovXG5leHBvcnQgY2xhc3MgQXR0cmlidXRlIGV4dGVuZHMgRmllbGQge1xuICAgIGNvbnN0cnVjdG9yKG9wdHMpIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy5vcHRzID0gb3B0cyB8fCB7fTtcblxuICAgICAgICBpZiAodGhpcy5vcHRzLmhhc093blByb3BlcnR5KFwiZ2V0RGVmYXVsdFwiKSkge1xuICAgICAgICAgICAgdGhpcy5nZXREZWZhdWx0ID0gdGhpcy5vcHRzLmdldERlZmF1bHQ7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBjcmVhdGVGb3J3YXJkc0Rlc2NyaXB0b3IoZmllbGROYW1lLCBtb2RlbCkge1xuICAgICAgICByZXR1cm4gYXR0ckRlc2NyaXB0b3IoZmllbGROYW1lKTtcbiAgICB9XG59XG5cbmV4cG9ydCBkZWZhdWx0IEF0dHJpYnV0ZTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/fields/Attribute.js\\n\");\n \n /***/ }),\n \n@@ -4570,7 +4592,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"DefaultFieldInstaller\\\", function() { return DefaultFieldInstaller; });\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _FieldInstallerTemplate__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./FieldInstallerTemplate */ \\\"./src/fields/FieldInstallerTemplate.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n/**\\n * Default implementation for the template method in FieldInstallerTemplate.\\n * @private\\n * @memberof module:fields\\n */\\n\\nlet DefaultFieldInstaller = /*#__PURE__*/function (_FieldInstallerTempla) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default()(DefaultFieldInstaller, _FieldInstallerTempla);\\n\\n  function DefaultFieldInstaller() {\\n    return _FieldInstallerTempla.apply(this, arguments) || this;\\n  }\\n\\n  var _proto = DefaultFieldInstaller.prototype;\\n\\n  _proto.installForwardsDescriptor = function installForwardsDescriptor() {\\n    Object.defineProperty(this.model.prototype, this.fieldName, this.field.createForwardsDescriptor(this.fieldName, this.model, this.toModel, this.throughModel));\\n  };\\n\\n  _proto.installForwardsVirtualField = function installForwardsVirtualField() {\\n    this.model.virtualFields[this.fieldName] = this.field.createForwardsVirtualField(this.fieldName, this.model, this.toModel, this.throughModel);\\n  };\\n\\n  _proto.installBackwardsDescriptor = function installBackwardsDescriptor() {\\n    const backwardsDescriptor = Object.getOwnPropertyDescriptor(this.toModel.prototype, this.backwardsFieldName);\\n\\n    if (backwardsDescriptor) {\\n      throw new Error(Object(_utils__WEBPACK_IMPORTED_MODULE_2__[\\\"reverseFieldErrorMessage\\\"])(this.model.modelName, this.fieldName, this.toModel.modelName, this.backwardsFieldName));\\n    } // install backwards descriptor\\n\\n\\n    Object.defineProperty(this.toModel.prototype, this.backwardsFieldName, this.field.createBackwardsDescriptor(this.fieldName, this.model, this.toModel, this.throughModel));\\n  };\\n\\n  _proto.installBackwardsVirtualField = function installBackwardsVirtualField() {\\n    this.toModel.virtualFields[this.backwardsFieldName] = this.field.createBackwardsVirtualField(this.fieldName, this.model, this.toModel, this.throughModel);\\n  };\\n\\n  return DefaultFieldInstaller;\\n}(_FieldInstallerTemplate__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (DefaultFieldInstaller);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvRGVmYXVsdEZpZWxkSW5zdGFsbGVyLmpzPzQxOTYiXSwibmFtZXMiOlsiRGVmYXVsdEZpZWxkSW5zdGFsbGVyIiwiaW5zdGFsbEZvcndhcmRzRGVzY3JpcHRvciIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwibW9kZWwiLCJwcm90b3R5cGUiLCJmaWVsZE5hbWUiLCJmaWVsZCIsImNyZWF0ZUZvcndhcmRzRGVzY3JpcHRvciIsInRvTW9kZWwiLCJ0aHJvdWdoTW9kZWwiLCJpbnN0YWxsRm9yd2FyZHNWaXJ0dWFsRmllbGQiLCJ2aXJ0dWFsRmllbGRzIiwiY3JlYXRlRm9yd2FyZHNWaXJ0dWFsRmllbGQiLCJpbnN0YWxsQmFja3dhcmRzRGVzY3JpcHRvciIsImJhY2t3YXJkc0Rlc2NyaXB0b3IiLCJnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IiLCJiYWNrd2FyZHNGaWVsZE5hbWUiLCJFcnJvciIsInJldmVyc2VGaWVsZEVycm9yTWVzc2FnZSIsIm1vZGVsTmFtZSIsImNyZWF0ZUJhY2t3YXJkc0Rlc2NyaXB0b3IiLCJpbnN0YWxsQmFja3dhcmRzVmlydHVhbEZpZWxkIiwiY3JlYXRlQmFja3dhcmRzVmlydHVhbEZpZWxkIiwiRmllbGRJbnN0YWxsZXJUZW1wbGF0ZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFBO0FBRUE7QUFFQTs7Ozs7O0FBS08sSUFBTUEscUJBQWI7QUFBQTs7QUFBQTtBQUFBO0FBQUE7O0FBQUE7O0FBQUEsU0FDSUMseUJBREosR0FDSSxxQ0FBNEI7QUFDeEJDLFVBQU0sQ0FBQ0MsY0FBUCxDQUNJLEtBQUtDLEtBQUwsQ0FBV0MsU0FEZixFQUVJLEtBQUtDLFNBRlQsRUFHSSxLQUFLQyxLQUFMLENBQVdDLHdCQUFYLENBQ0ksS0FBS0YsU0FEVCxFQUVJLEtBQUtGLEtBRlQsRUFHSSxLQUFLSyxPQUhULEVBSUksS0FBS0MsWUFKVCxDQUhKO0FBVUgsR0FaTDs7QUFBQSxTQWNJQywyQkFkSixHQWNJLHVDQUE4QjtBQUMxQixTQUFLUCxLQUFMLENBQVdRLGFBQVgsQ0FDSSxLQUFLTixTQURULElBRUksS0FBS0MsS0FBTCxDQUFXTSwwQkFBWCxDQUNBLEtBQUtQLFNBREwsRUFFQSxLQUFLRixLQUZMLEVBR0EsS0FBS0ssT0FITCxFQUlBLEtBQUtDLFlBSkwsQ0FGSjtBQVFILEdBdkJMOztBQUFBLFNBeUJJSSwwQkF6QkosR0F5Qkksc0NBQTZCO0FBQ3pCLFVBQU1DLG1CQUFtQixHQUFHYixNQUFNLENBQUNjLHdCQUFQLENBQ3hCLEtBQUtQLE9BQUwsQ0FBYUosU0FEVyxFQUV4QixLQUFLWSxrQkFGbUIsQ0FBNUI7O0FBSUEsUUFBSUYsbUJBQUosRUFBeUI7QUFDckIsWUFBTSxJQUFJRyxLQUFKLENBQ0ZDLHVFQUF3QixDQUNwQixLQUFLZixLQUFMLENBQVdnQixTQURTLEVBRXBCLEtBQUtkLFNBRmUsRUFHcEIsS0FBS0csT0FBTCxDQUFhVyxTQUhPLEVBSXBCLEtBQUtILGtCQUplLENBRHRCLENBQU47QUFRSCxLQWR3QixDQWdCekI7OztBQUNBZixVQUFNLENBQUNDLGNBQVAsQ0FDSSxLQUFLTSxPQUFMLENBQWFKLFNBRGpCLEVBRUksS0FBS1ksa0JBRlQsRUFHSSxLQUFLVixLQUFMLENBQVdjLHlCQUFYLENBQ0ksS0FBS2YsU0FEVCxFQUVJLEtBQUtGLEtBRlQsRUFHSSxLQUFLSyxPQUhULEVBSUksS0FBS0MsWUFKVCxDQUhKO0FBVUgsR0FwREw7O0FBQUEsU0FzRElZLDRCQXRESixHQXNESSx3Q0FBK0I7QUFDM0IsU0FBS2IsT0FBTCxDQUFhRyxhQUFiLENBQ0ksS0FBS0ssa0JBRFQsSUFFSSxLQUFLVixLQUFMLENBQVdnQiwyQkFBWCxDQUNBLEtBQUtqQixTQURMLEVBRUEsS0FBS0YsS0FGTCxFQUdBLEtBQUtLLE9BSEwsRUFJQSxLQUFLQyxZQUpMLENBRko7QUFRSCxHQS9ETDs7QUFBQTtBQUFBLEVBQTJDYywrREFBM0M7QUFrRWV4QixvRkFBZiIsImZpbGUiOiIuL3NyYy9maWVsZHMvRGVmYXVsdEZpZWxkSW5zdGFsbGVyLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IEZpZWxkSW5zdGFsbGVyVGVtcGxhdGUgZnJvbSBcIi4vRmllbGRJbnN0YWxsZXJUZW1wbGF0ZVwiO1xuXG5pbXBvcnQgeyByZXZlcnNlRmllbGRFcnJvck1lc3NhZ2UgfSBmcm9tIFwiLi4vdXRpbHNcIjtcblxuLyoqXG4gKiBEZWZhdWx0IGltcGxlbWVudGF0aW9uIGZvciB0aGUgdGVtcGxhdGUgbWV0aG9kIGluIEZpZWxkSW5zdGFsbGVyVGVtcGxhdGUuXG4gKiBAcHJpdmF0ZVxuICogQG1lbWJlcm9mIG1vZHVsZTpmaWVsZHNcbiAqL1xuZXhwb3J0IGNsYXNzIERlZmF1bHRGaWVsZEluc3RhbGxlciBleHRlbmRzIEZpZWxkSW5zdGFsbGVyVGVtcGxhdGUge1xuICAgIGluc3RhbGxGb3J3YXJkc0Rlc2NyaXB0b3IoKSB7XG4gICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShcbiAgICAgICAgICAgIHRoaXMubW9kZWwucHJvdG90eXBlLFxuICAgICAgICAgICAgdGhpcy5maWVsZE5hbWUsXG4gICAgICAgICAgICB0aGlzLmZpZWxkLmNyZWF0ZUZvcndhcmRzRGVzY3JpcHRvcihcbiAgICAgICAgICAgICAgICB0aGlzLmZpZWxkTmFtZSxcbiAgICAgICAgICAgICAgICB0aGlzLm1vZGVsLFxuICAgICAgICAgICAgICAgIHRoaXMudG9Nb2RlbCxcbiAgICAgICAgICAgICAgICB0aGlzLnRocm91Z2hNb2RlbFxuICAgICAgICAgICAgKVxuICAgICAgICApO1xuICAgIH1cblxuICAgIGluc3RhbGxGb3J3YXJkc1ZpcnR1YWxGaWVsZCgpIHtcbiAgICAgICAgdGhpcy5tb2RlbC52aXJ0dWFsRmllbGRzW1xuICAgICAgICAgICAgdGhpcy5maWVsZE5hbWVcbiAgICAgICAgXSA9IHRoaXMuZmllbGQuY3JlYXRlRm9yd2FyZHNWaXJ0dWFsRmllbGQoXG4gICAgICAgICAgICB0aGlzLmZpZWxkTmFtZSxcbiAgICAgICAgICAgIHRoaXMubW9kZWwsXG4gICAgICAgICAgICB0aGlzLnRvTW9kZWwsXG4gICAgICAgICAgICB0aGlzLnRocm91Z2hNb2RlbFxuICAgICAgICApO1xuICAgIH1cblxuICAgIGluc3RhbGxCYWNrd2FyZHNEZXNjcmlwdG9yKCkge1xuICAgICAgICBjb25zdCBiYWNrd2FyZHNEZXNjcmlwdG9yID0gT2JqZWN0LmdldE93blByb3BlcnR5RGVzY3JpcHRvcihcbiAgICAgICAgICAgIHRoaXMudG9Nb2RlbC5wcm90b3R5cGUsXG4gICAgICAgICAgICB0aGlzLmJhY2t3YXJkc0ZpZWxkTmFtZVxuICAgICAgICApO1xuICAgICAgICBpZiAoYmFja3dhcmRzRGVzY3JpcHRvcikge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIHJldmVyc2VGaWVsZEVycm9yTWVzc2FnZShcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5tb2RlbC5tb2RlbE5hbWUsXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuZmllbGROYW1lLFxuICAgICAgICAgICAgICAgICAgICB0aGlzLnRvTW9kZWwubW9kZWxOYW1lLFxuICAgICAgICAgICAgICAgICAgICB0aGlzLmJhY2t3YXJkc0ZpZWxkTmFtZVxuICAgICAgICAgICAgICAgIClcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBpbnN0YWxsIGJhY2t3YXJkcyBkZXNjcmlwdG9yXG4gICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShcbiAgICAgICAgICAgIHRoaXMudG9Nb2RlbC5wcm90b3R5cGUsXG4gICAgICAgICAgICB0aGlzLmJhY2t3YXJkc0ZpZWxkTmFtZSxcbiAgICAgICAgICAgIHRoaXMuZmllbGQuY3JlYXRlQmFja3dhcmRzRGVzY3JpcHRvcihcbiAgICAgICAgICAgICAgICB0aGlzLmZpZWxkTmFtZSxcbiAgICAgICAgICAgICAgICB0aGlzLm1vZGVsLFxuICAgICAgICAgICAgICAgIHRoaXMudG9Nb2RlbCxcbiAgICAgICAgICAgICAgICB0aGlzLnRocm91Z2hNb2RlbFxuICAgICAgICAgICAgKVxuICAgICAgICApO1xuICAgIH1cblxuICAgIGluc3RhbGxCYWNrd2FyZHNWaXJ0dWFsRmllbGQoKSB7XG4gICAgICAgIHRoaXMudG9Nb2RlbC52aXJ0dWFsRmllbGRzW1xuICAgICAgICAgICAgdGhpcy5iYWNrd2FyZHNGaWVsZE5hbWVcbiAgICAgICAgXSA9IHRoaXMuZmllbGQuY3JlYXRlQmFja3dhcmRzVmlydHVhbEZpZWxkKFxuICAgICAgICAgICAgdGhpcy5maWVsZE5hbWUsXG4gICAgICAgICAgICB0aGlzLm1vZGVsLFxuICAgICAgICAgICAgdGhpcy50b01vZGVsLFxuICAgICAgICAgICAgdGhpcy50aHJvdWdoTW9kZWxcbiAgICAgICAgKTtcbiAgICB9XG59XG5cbmV4cG9ydCBkZWZhdWx0IERlZmF1bHRGaWVsZEluc3RhbGxlcjtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/fields/DefaultFieldInstaller.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"DefaultFieldInstaller\\\", function() { return DefaultFieldInstaller; });\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _FieldInstallerTemplate__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./FieldInstallerTemplate */ \\\"./src/fields/FieldInstallerTemplate.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n/**\\n * Default implementation for the template method in FieldInstallerTemplate.\\n * @private\\n * @memberof module:fields\\n */\\n\\nlet DefaultFieldInstaller = /*#__PURE__*/function (_FieldInstallerTempla) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default()(DefaultFieldInstaller, _FieldInstallerTempla);\\n\\n  function DefaultFieldInstaller() {\\n    return _FieldInstallerTempla.apply(this, arguments) || this;\\n  }\\n\\n  var _proto = DefaultFieldInstaller.prototype;\\n\\n  _proto.installForwardsDescriptor = function installForwardsDescriptor() {\\n    Object.defineProperty(this.model.prototype, this.fieldName, this.field.createForwardsDescriptor(this.fieldName, this.model, this.toModel, this.throughModel));\\n  };\\n\\n  _proto.installForwardsVirtualField = function installForwardsVirtualField() {\\n    this.model.virtualFields[this.fieldName] = this.field.createForwardsVirtualField(this.fieldName, this.model, this.toModel, this.throughModel);\\n  };\\n\\n  _proto.installBackwardsDescriptor = function installBackwardsDescriptor() {\\n    const backwardsDescriptor = Object.getOwnPropertyDescriptor(this.toModel.prototype, this.backwardsFieldName);\\n\\n    if (backwardsDescriptor) {\\n      throw new Error(Object(_utils__WEBPACK_IMPORTED_MODULE_2__[\\\"reverseFieldErrorMessage\\\"])(this.model.modelName, this.fieldName, this.toModel.modelName, this.backwardsFieldName));\\n    } // install backwards descriptor\\n\\n\\n    Object.defineProperty(this.toModel.prototype, this.backwardsFieldName, this.field.createBackwardsDescriptor(this.fieldName, this.model, this.toModel, this.throughModel));\\n  };\\n\\n  _proto.installBackwardsVirtualField = function installBackwardsVirtualField() {\\n    this.toModel.virtualFields[this.backwardsFieldName] = this.field.createBackwardsVirtualField(this.fieldName, this.model, this.toModel, this.throughModel);\\n  };\\n\\n  return DefaultFieldInstaller;\\n}(_FieldInstallerTemplate__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (DefaultFieldInstaller);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvRGVmYXVsdEZpZWxkSW5zdGFsbGVyLmpzPzQxOTYiXSwibmFtZXMiOlsiRGVmYXVsdEZpZWxkSW5zdGFsbGVyIiwiaW5zdGFsbEZvcndhcmRzRGVzY3JpcHRvciIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwibW9kZWwiLCJwcm90b3R5cGUiLCJmaWVsZE5hbWUiLCJmaWVsZCIsImNyZWF0ZUZvcndhcmRzRGVzY3JpcHRvciIsInRvTW9kZWwiLCJ0aHJvdWdoTW9kZWwiLCJpbnN0YWxsRm9yd2FyZHNWaXJ0dWFsRmllbGQiLCJ2aXJ0dWFsRmllbGRzIiwiY3JlYXRlRm9yd2FyZHNWaXJ0dWFsRmllbGQiLCJpbnN0YWxsQmFja3dhcmRzRGVzY3JpcHRvciIsImJhY2t3YXJkc0Rlc2NyaXB0b3IiLCJnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IiLCJiYWNrd2FyZHNGaWVsZE5hbWUiLCJFcnJvciIsInJldmVyc2VGaWVsZEVycm9yTWVzc2FnZSIsIm1vZGVsTmFtZSIsImNyZWF0ZUJhY2t3YXJkc0Rlc2NyaXB0b3IiLCJpbnN0YWxsQmFja3dhcmRzVmlydHVhbEZpZWxkIiwiY3JlYXRlQmFja3dhcmRzVmlydHVhbEZpZWxkIiwiRmllbGRJbnN0YWxsZXJUZW1wbGF0ZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFBO0FBRUE7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUNPLElBQU1BLHFCQUFiO0FBQUE7O0FBQUE7QUFBQTtBQUFBOztBQUFBOztBQUFBLFNBQ0lDLHlCQURKLEdBQ0kscUNBQTRCO0FBQ3hCQyxVQUFNLENBQUNDLGNBQVAsQ0FDSSxLQUFLQyxLQUFMLENBQVdDLFNBRGYsRUFFSSxLQUFLQyxTQUZULEVBR0ksS0FBS0MsS0FBTCxDQUFXQyx3QkFBWCxDQUNJLEtBQUtGLFNBRFQsRUFFSSxLQUFLRixLQUZULEVBR0ksS0FBS0ssT0FIVCxFQUlJLEtBQUtDLFlBSlQsQ0FISjtBQVVILEdBWkw7O0FBQUEsU0FjSUMsMkJBZEosR0FjSSx1Q0FBOEI7QUFDMUIsU0FBS1AsS0FBTCxDQUFXUSxhQUFYLENBQ0ksS0FBS04sU0FEVCxJQUVJLEtBQUtDLEtBQUwsQ0FBV00sMEJBQVgsQ0FDQSxLQUFLUCxTQURMLEVBRUEsS0FBS0YsS0FGTCxFQUdBLEtBQUtLLE9BSEwsRUFJQSxLQUFLQyxZQUpMLENBRko7QUFRSCxHQXZCTDs7QUFBQSxTQXlCSUksMEJBekJKLEdBeUJJLHNDQUE2QjtBQUN6QixVQUFNQyxtQkFBbUIsR0FBR2IsTUFBTSxDQUFDYyx3QkFBUCxDQUN4QixLQUFLUCxPQUFMLENBQWFKLFNBRFcsRUFFeEIsS0FBS1ksa0JBRm1CLENBQTVCOztBQUlBLFFBQUlGLG1CQUFKLEVBQXlCO0FBQ3JCLFlBQU0sSUFBSUcsS0FBSixDQUNGQyx1RUFBd0IsQ0FDcEIsS0FBS2YsS0FBTCxDQUFXZ0IsU0FEUyxFQUVwQixLQUFLZCxTQUZlLEVBR3BCLEtBQUtHLE9BQUwsQ0FBYVcsU0FITyxFQUlwQixLQUFLSCxrQkFKZSxDQUR0QixDQUFOO0FBUUgsS0Fkd0IsQ0FnQnpCOzs7QUFDQWYsVUFBTSxDQUFDQyxjQUFQLENBQ0ksS0FBS00sT0FBTCxDQUFhSixTQURqQixFQUVJLEtBQUtZLGtCQUZULEVBR0ksS0FBS1YsS0FBTCxDQUFXYyx5QkFBWCxDQUNJLEtBQUtmLFNBRFQsRUFFSSxLQUFLRixLQUZULEVBR0ksS0FBS0ssT0FIVCxFQUlJLEtBQUtDLFlBSlQsQ0FISjtBQVVILEdBcERMOztBQUFBLFNBc0RJWSw0QkF0REosR0FzREksd0NBQStCO0FBQzNCLFNBQUtiLE9BQUwsQ0FBYUcsYUFBYixDQUNJLEtBQUtLLGtCQURULElBRUksS0FBS1YsS0FBTCxDQUFXZ0IsMkJBQVgsQ0FDQSxLQUFLakIsU0FETCxFQUVBLEtBQUtGLEtBRkwsRUFHQSxLQUFLSyxPQUhMLEVBSUEsS0FBS0MsWUFKTCxDQUZKO0FBUUgsR0EvREw7O0FBQUE7QUFBQSxFQUEyQ2MsK0RBQTNDO0FBa0VleEIsb0ZBQWYiLCJmaWxlIjoiLi9zcmMvZmllbGRzL0RlZmF1bHRGaWVsZEluc3RhbGxlci5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBGaWVsZEluc3RhbGxlclRlbXBsYXRlIGZyb20gXCIuL0ZpZWxkSW5zdGFsbGVyVGVtcGxhdGVcIjtcblxuaW1wb3J0IHsgcmV2ZXJzZUZpZWxkRXJyb3JNZXNzYWdlIH0gZnJvbSBcIi4uL3V0aWxzXCI7XG5cbi8qKlxuICogRGVmYXVsdCBpbXBsZW1lbnRhdGlvbiBmb3IgdGhlIHRlbXBsYXRlIG1ldGhvZCBpbiBGaWVsZEluc3RhbGxlclRlbXBsYXRlLlxuICogQHByaXZhdGVcbiAqIEBtZW1iZXJvZiBtb2R1bGU6ZmllbGRzXG4gKi9cbmV4cG9ydCBjbGFzcyBEZWZhdWx0RmllbGRJbnN0YWxsZXIgZXh0ZW5kcyBGaWVsZEluc3RhbGxlclRlbXBsYXRlIHtcbiAgICBpbnN0YWxsRm9yd2FyZHNEZXNjcmlwdG9yKCkge1xuICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoXG4gICAgICAgICAgICB0aGlzLm1vZGVsLnByb3RvdHlwZSxcbiAgICAgICAgICAgIHRoaXMuZmllbGROYW1lLFxuICAgICAgICAgICAgdGhpcy5maWVsZC5jcmVhdGVGb3J3YXJkc0Rlc2NyaXB0b3IoXG4gICAgICAgICAgICAgICAgdGhpcy5maWVsZE5hbWUsXG4gICAgICAgICAgICAgICAgdGhpcy5tb2RlbCxcbiAgICAgICAgICAgICAgICB0aGlzLnRvTW9kZWwsXG4gICAgICAgICAgICAgICAgdGhpcy50aHJvdWdoTW9kZWxcbiAgICAgICAgICAgIClcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBpbnN0YWxsRm9yd2FyZHNWaXJ0dWFsRmllbGQoKSB7XG4gICAgICAgIHRoaXMubW9kZWwudmlydHVhbEZpZWxkc1tcbiAgICAgICAgICAgIHRoaXMuZmllbGROYW1lXG4gICAgICAgIF0gPSB0aGlzLmZpZWxkLmNyZWF0ZUZvcndhcmRzVmlydHVhbEZpZWxkKFxuICAgICAgICAgICAgdGhpcy5maWVsZE5hbWUsXG4gICAgICAgICAgICB0aGlzLm1vZGVsLFxuICAgICAgICAgICAgdGhpcy50b01vZGVsLFxuICAgICAgICAgICAgdGhpcy50aHJvdWdoTW9kZWxcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBpbnN0YWxsQmFja3dhcmRzRGVzY3JpcHRvcigpIHtcbiAgICAgICAgY29uc3QgYmFja3dhcmRzRGVzY3JpcHRvciA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IoXG4gICAgICAgICAgICB0aGlzLnRvTW9kZWwucHJvdG90eXBlLFxuICAgICAgICAgICAgdGhpcy5iYWNrd2FyZHNGaWVsZE5hbWVcbiAgICAgICAgKTtcbiAgICAgICAgaWYgKGJhY2t3YXJkc0Rlc2NyaXB0b3IpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICByZXZlcnNlRmllbGRFcnJvck1lc3NhZ2UoXG4gICAgICAgICAgICAgICAgICAgIHRoaXMubW9kZWwubW9kZWxOYW1lLFxuICAgICAgICAgICAgICAgICAgICB0aGlzLmZpZWxkTmFtZSxcbiAgICAgICAgICAgICAgICAgICAgdGhpcy50b01vZGVsLm1vZGVsTmFtZSxcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5iYWNrd2FyZHNGaWVsZE5hbWVcbiAgICAgICAgICAgICAgICApXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gaW5zdGFsbCBiYWNrd2FyZHMgZGVzY3JpcHRvclxuICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoXG4gICAgICAgICAgICB0aGlzLnRvTW9kZWwucHJvdG90eXBlLFxuICAgICAgICAgICAgdGhpcy5iYWNrd2FyZHNGaWVsZE5hbWUsXG4gICAgICAgICAgICB0aGlzLmZpZWxkLmNyZWF0ZUJhY2t3YXJkc0Rlc2NyaXB0b3IoXG4gICAgICAgICAgICAgICAgdGhpcy5maWVsZE5hbWUsXG4gICAgICAgICAgICAgICAgdGhpcy5tb2RlbCxcbiAgICAgICAgICAgICAgICB0aGlzLnRvTW9kZWwsXG4gICAgICAgICAgICAgICAgdGhpcy50aHJvdWdoTW9kZWxcbiAgICAgICAgICAgIClcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBpbnN0YWxsQmFja3dhcmRzVmlydHVhbEZpZWxkKCkge1xuICAgICAgICB0aGlzLnRvTW9kZWwudmlydHVhbEZpZWxkc1tcbiAgICAgICAgICAgIHRoaXMuYmFja3dhcmRzRmllbGROYW1lXG4gICAgICAgIF0gPSB0aGlzLmZpZWxkLmNyZWF0ZUJhY2t3YXJkc1ZpcnR1YWxGaWVsZChcbiAgICAgICAgICAgIHRoaXMuZmllbGROYW1lLFxuICAgICAgICAgICAgdGhpcy5tb2RlbCxcbiAgICAgICAgICAgIHRoaXMudG9Nb2RlbCxcbiAgICAgICAgICAgIHRoaXMudGhyb3VnaE1vZGVsXG4gICAgICAgICk7XG4gICAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBEZWZhdWx0RmllbGRJbnN0YWxsZXI7XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./src/fields/DefaultFieldInstaller.js\\n\");\n \n /***/ }),\n \n@@ -4582,7 +4604,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"Field\\\", function() { return Field; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _DefaultFieldInstaller__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./DefaultFieldInstaller */ \\\"./src/fields/DefaultFieldInstaller.js\\\");\\n\\n\\n/**\\n * @private\\n * @memberof module:fields\\n */\\n\\nlet Field = /*#__PURE__*/function () {\\n  function Field() {}\\n\\n  var _proto = Field.prototype;\\n\\n  _proto.getClass = function getClass() {\\n    return this.constructor;\\n  };\\n\\n  _proto.references = function references(model) {\\n    return false;\\n  };\\n\\n  _proto.getThroughModelName = function getThroughModelName(fieldName, model) {\\n    return null;\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(Field, [{\\n    key: \\\"installerClass\\\",\\n    get: function () {\\n      return _DefaultFieldInstaller__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"];\\n    }\\n  }, {\\n    key: \\\"installsForwardsVirtualField\\\",\\n    get: function () {\\n      return false;\\n    }\\n  }, {\\n    key: \\\"installsBackwardsDescriptor\\\",\\n    get: function () {\\n      return false;\\n    }\\n  }, {\\n    key: \\\"installsBackwardsVirtualField\\\",\\n    get: function () {\\n      return false;\\n    }\\n  }, {\\n    key: \\\"index\\\",\\n    get: function () {\\n      return false;\\n    }\\n  }]);\\n\\n  return Field;\\n}();\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (Field);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvRmllbGQuanM/OTMwNSJdLCJuYW1lcyI6WyJGaWVsZCIsImdldENsYXNzIiwiY29uc3RydWN0b3IiLCJyZWZlcmVuY2VzIiwibW9kZWwiLCJnZXRUaHJvdWdoTW9kZWxOYW1lIiwiZmllbGROYW1lIiwiRGVmYXVsdEZpZWxkSW5zdGFsbGVyIl0sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQTtBQUVBOzs7OztBQUlPLElBQU1BLEtBQWI7QUFBQTs7QUFBQTs7QUFBQSxTQUtJQyxRQUxKLEdBS0ksb0JBQVc7QUFDUCxXQUFPLEtBQUtDLFdBQVo7QUFDSCxHQVBMOztBQUFBLFNBU0lDLFVBVEosR0FTSSxvQkFBV0MsS0FBWCxFQUFrQjtBQUNkLFdBQU8sS0FBUDtBQUNILEdBWEw7O0FBQUEsU0FhSUMsbUJBYkosR0FhSSw2QkFBb0JDLFNBQXBCLEVBQStCRixLQUEvQixFQUFzQztBQUNsQyxXQUFPLElBQVA7QUFDSCxHQWZMOztBQUFBO0FBQUE7QUFBQSxxQkFDeUI7QUFDakIsYUFBT0csOERBQVA7QUFDSDtBQUhMO0FBQUE7QUFBQSxxQkFpQnVDO0FBQy9CLGFBQU8sS0FBUDtBQUNIO0FBbkJMO0FBQUE7QUFBQSxxQkFxQnNDO0FBQzlCLGFBQU8sS0FBUDtBQUNIO0FBdkJMO0FBQUE7QUFBQSxxQkF5QndDO0FBQ2hDLGFBQU8sS0FBUDtBQUNIO0FBM0JMO0FBQUE7QUFBQSxxQkE2QmdCO0FBQ1IsYUFBTyxLQUFQO0FBQ0g7QUEvQkw7O0FBQUE7QUFBQTtBQWtDZVAsb0VBQWYiLCJmaWxlIjoiLi9zcmMvZmllbGRzL0ZpZWxkLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IERlZmF1bHRGaWVsZEluc3RhbGxlciBmcm9tIFwiLi9EZWZhdWx0RmllbGRJbnN0YWxsZXJcIjtcblxuLyoqXG4gKiBAcHJpdmF0ZVxuICogQG1lbWJlcm9mIG1vZHVsZTpmaWVsZHNcbiAqL1xuZXhwb3J0IGNsYXNzIEZpZWxkIHtcbiAgICBnZXQgaW5zdGFsbGVyQ2xhc3MoKSB7XG4gICAgICAgIHJldHVybiBEZWZhdWx0RmllbGRJbnN0YWxsZXI7XG4gICAgfVxuXG4gICAgZ2V0Q2xhc3MoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmNvbnN0cnVjdG9yO1xuICAgIH1cblxuICAgIHJlZmVyZW5jZXMobW9kZWwpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIGdldFRocm91Z2hNb2RlbE5hbWUoZmllbGROYW1lLCBtb2RlbCkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG5cbiAgICBnZXQgaW5zdGFsbHNGb3J3YXJkc1ZpcnR1YWxGaWVsZCgpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIGdldCBpbnN0YWxsc0JhY2t3YXJkc0Rlc2NyaXB0b3IoKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICBnZXQgaW5zdGFsbHNCYWNrd2FyZHNWaXJ0dWFsRmllbGQoKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICBnZXQgaW5kZXgoKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG59XG5cbmV4cG9ydCBkZWZhdWx0IEZpZWxkO1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/fields/Field.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"Field\\\", function() { return Field; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _DefaultFieldInstaller__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./DefaultFieldInstaller */ \\\"./src/fields/DefaultFieldInstaller.js\\\");\\n\\n\\n/**\\n * @private\\n * @memberof module:fields\\n */\\n\\nlet Field = /*#__PURE__*/function () {\\n  function Field() {}\\n\\n  var _proto = Field.prototype;\\n\\n  _proto.getClass = function getClass() {\\n    return this.constructor;\\n  };\\n\\n  _proto.references = function references(model) {\\n    return false;\\n  };\\n\\n  _proto.getThroughModelName = function getThroughModelName(fieldName, model) {\\n    return null;\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(Field, [{\\n    key: \\\"installerClass\\\",\\n    get: function () {\\n      return _DefaultFieldInstaller__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"];\\n    }\\n  }, {\\n    key: \\\"installsForwardsVirtualField\\\",\\n    get: function () {\\n      return false;\\n    }\\n  }, {\\n    key: \\\"installsBackwardsDescriptor\\\",\\n    get: function () {\\n      return false;\\n    }\\n  }, {\\n    key: \\\"installsBackwardsVirtualField\\\",\\n    get: function () {\\n      return false;\\n    }\\n  }, {\\n    key: \\\"index\\\",\\n    get: function () {\\n      return false;\\n    }\\n  }]);\\n\\n  return Field;\\n}();\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (Field);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvRmllbGQuanM/OTMwNSJdLCJuYW1lcyI6WyJGaWVsZCIsImdldENsYXNzIiwiY29uc3RydWN0b3IiLCJyZWZlcmVuY2VzIiwibW9kZWwiLCJnZXRUaHJvdWdoTW9kZWxOYW1lIiwiZmllbGROYW1lIiwiRGVmYXVsdEZpZWxkSW5zdGFsbGVyIl0sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQTtBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUNPLElBQU1BLEtBQWI7QUFBQTs7QUFBQTs7QUFBQSxTQUtJQyxRQUxKLEdBS0ksb0JBQVc7QUFDUCxXQUFPLEtBQUtDLFdBQVo7QUFDSCxHQVBMOztBQUFBLFNBU0lDLFVBVEosR0FTSSxvQkFBV0MsS0FBWCxFQUFrQjtBQUNkLFdBQU8sS0FBUDtBQUNILEdBWEw7O0FBQUEsU0FhSUMsbUJBYkosR0FhSSw2QkFBb0JDLFNBQXBCLEVBQStCRixLQUEvQixFQUFzQztBQUNsQyxXQUFPLElBQVA7QUFDSCxHQWZMOztBQUFBO0FBQUE7QUFBQSxTQUNJLFlBQXFCO0FBQ2pCLGFBQU9HLDhEQUFQO0FBQ0g7QUFITDtBQUFBO0FBQUEsU0FpQkksWUFBbUM7QUFDL0IsYUFBTyxLQUFQO0FBQ0g7QUFuQkw7QUFBQTtBQUFBLFNBcUJJLFlBQWtDO0FBQzlCLGFBQU8sS0FBUDtBQUNIO0FBdkJMO0FBQUE7QUFBQSxTQXlCSSxZQUFvQztBQUNoQyxhQUFPLEtBQVA7QUFDSDtBQTNCTDtBQUFBO0FBQUEsU0E2QkksWUFBWTtBQUNSLGFBQU8sS0FBUDtBQUNIO0FBL0JMOztBQUFBO0FBQUE7QUFrQ2VQLG9FQUFmIiwiZmlsZSI6Ii4vc3JjL2ZpZWxkcy9GaWVsZC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBEZWZhdWx0RmllbGRJbnN0YWxsZXIgZnJvbSBcIi4vRGVmYXVsdEZpZWxkSW5zdGFsbGVyXCI7XG5cbi8qKlxuICogQHByaXZhdGVcbiAqIEBtZW1iZXJvZiBtb2R1bGU6ZmllbGRzXG4gKi9cbmV4cG9ydCBjbGFzcyBGaWVsZCB7XG4gICAgZ2V0IGluc3RhbGxlckNsYXNzKCkge1xuICAgICAgICByZXR1cm4gRGVmYXVsdEZpZWxkSW5zdGFsbGVyO1xuICAgIH1cblxuICAgIGdldENsYXNzKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5jb25zdHJ1Y3RvcjtcbiAgICB9XG5cbiAgICByZWZlcmVuY2VzKG1vZGVsKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICBnZXRUaHJvdWdoTW9kZWxOYW1lKGZpZWxkTmFtZSwgbW9kZWwpIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuXG4gICAgZ2V0IGluc3RhbGxzRm9yd2FyZHNWaXJ0dWFsRmllbGQoKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICBnZXQgaW5zdGFsbHNCYWNrd2FyZHNEZXNjcmlwdG9yKCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgZ2V0IGluc3RhbGxzQmFja3dhcmRzVmlydHVhbEZpZWxkKCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgZ2V0IGluZGV4KCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBGaWVsZDtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/fields/Field.js\\n\");\n \n /***/ }),\n \n@@ -4594,7 +4616,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"FieldInstallerTemplate\\\", function() { return FieldInstallerTemplate; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n\\n\\n/**\\n * Defines algorithm for installing a field onto a model and related models.\\n * Conforms to the template method behavioral design pattern.\\n * @private\\n * @memberof module:fields\\n */\\nlet FieldInstallerTemplate = /*#__PURE__*/function () {\\n  function FieldInstallerTemplate(opts) {\\n    this.field = opts.field;\\n    this.fieldName = opts.fieldName;\\n    this.model = opts.model;\\n    this.orm = opts.orm;\\n    /**\\n     * the field itself has no knowledge of the model\\n     * it is being installed upon; we need to inform it\\n     * that it is a self-referencing field for the field\\n     * to be able to make better informed decisions\\n     */\\n\\n    if (this.field.references(this.model)) {\\n      this.field.toModelName = \\\"this\\\";\\n    }\\n  }\\n\\n  var _proto = FieldInstallerTemplate.prototype;\\n\\n  _proto.run = function run() {\\n    this.installForwardsDescriptor();\\n\\n    if (this.field.installsForwardsVirtualField) {\\n      this.installForwardsVirtualField();\\n    }\\n    /**\\n     * Install a backwards field on a model as a consequence\\n     * of having installed the forwards field on another model.\\n     */\\n\\n\\n    if (this.field.installsBackwardsDescriptor) {\\n      this.installBackwardsDescriptor();\\n    }\\n\\n    if (this.field.installsBackwardsVirtualField) {\\n      this.installBackwardsVirtualField();\\n    }\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(FieldInstallerTemplate, [{\\n    key: \\\"toModel\\\",\\n    get: function () {\\n      if (typeof this._toModel === \\\"undefined\\\") {\\n        const {\\n          toModelName\\n        } = this.field;\\n\\n        if (!toModelName) {\\n          this._toModel = null;\\n        } else if (toModelName === \\\"this\\\") {\\n          this._toModel = this.model;\\n        } else {\\n          this._toModel = this.orm.get(toModelName);\\n        }\\n      }\\n\\n      return this._toModel;\\n    }\\n  }, {\\n    key: \\\"throughModel\\\",\\n    get: function () {\\n      if (typeof this._throughModel === \\\"undefined\\\") {\\n        const throughModelName = this.field.getThroughModelName(this.fieldName, this.model);\\n\\n        if (!throughModelName) {\\n          this._throughModel = null;\\n        } else {\\n          this._throughModel = this.orm.get(throughModelName);\\n        }\\n      }\\n\\n      return this._throughModel;\\n    }\\n  }, {\\n    key: \\\"backwardsFieldName\\\",\\n    get: function () {\\n      return this.field.getBackwardsFieldName(this.model);\\n    }\\n  }]);\\n\\n  return FieldInstallerTemplate;\\n}();\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (FieldInstallerTemplate);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvRmllbGRJbnN0YWxsZXJUZW1wbGF0ZS5qcz9jZGFiIl0sIm5hbWVzIjpbIkZpZWxkSW5zdGFsbGVyVGVtcGxhdGUiLCJvcHRzIiwiZmllbGQiLCJmaWVsZE5hbWUiLCJtb2RlbCIsIm9ybSIsInJlZmVyZW5jZXMiLCJ0b01vZGVsTmFtZSIsInJ1biIsImluc3RhbGxGb3J3YXJkc0Rlc2NyaXB0b3IiLCJpbnN0YWxsc0ZvcndhcmRzVmlydHVhbEZpZWxkIiwiaW5zdGFsbEZvcndhcmRzVmlydHVhbEZpZWxkIiwiaW5zdGFsbHNCYWNrd2FyZHNEZXNjcmlwdG9yIiwiaW5zdGFsbEJhY2t3YXJkc0Rlc2NyaXB0b3IiLCJpbnN0YWxsc0JhY2t3YXJkc1ZpcnR1YWxGaWVsZCIsImluc3RhbGxCYWNrd2FyZHNWaXJ0dWFsRmllbGQiLCJfdG9Nb2RlbCIsImdldCIsIl90aHJvdWdoTW9kZWwiLCJ0aHJvdWdoTW9kZWxOYW1lIiwiZ2V0VGhyb3VnaE1vZGVsTmFtZSIsImdldEJhY2t3YXJkc0ZpZWxkTmFtZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUE7Ozs7OztBQU1PLElBQU1BLHNCQUFiO0FBQ0ksa0NBQVlDLElBQVosRUFBa0I7QUFDZCxTQUFLQyxLQUFMLEdBQWFELElBQUksQ0FBQ0MsS0FBbEI7QUFDQSxTQUFLQyxTQUFMLEdBQWlCRixJQUFJLENBQUNFLFNBQXRCO0FBQ0EsU0FBS0MsS0FBTCxHQUFhSCxJQUFJLENBQUNHLEtBQWxCO0FBQ0EsU0FBS0MsR0FBTCxHQUFXSixJQUFJLENBQUNJLEdBQWhCO0FBQ0E7Ozs7Ozs7QUFNQSxRQUFJLEtBQUtILEtBQUwsQ0FBV0ksVUFBWCxDQUFzQixLQUFLRixLQUEzQixDQUFKLEVBQXVDO0FBQ25DLFdBQUtGLEtBQUwsQ0FBV0ssV0FBWCxHQUF5QixNQUF6QjtBQUNIO0FBQ0o7O0FBZkw7O0FBQUEsU0FrRElDLEdBbERKLEdBa0RJLGVBQU07QUFDRixTQUFLQyx5QkFBTDs7QUFDQSxRQUFJLEtBQUtQLEtBQUwsQ0FBV1EsNEJBQWYsRUFBNkM7QUFDekMsV0FBS0MsMkJBQUw7QUFDSDtBQUNEOzs7Ozs7QUFJQSxRQUFJLEtBQUtULEtBQUwsQ0FBV1UsMkJBQWYsRUFBNEM7QUFDeEMsV0FBS0MsMEJBQUw7QUFDSDs7QUFDRCxRQUFJLEtBQUtYLEtBQUwsQ0FBV1ksNkJBQWYsRUFBOEM7QUFDMUMsV0FBS0MsNEJBQUw7QUFDSDtBQUNKLEdBakVMOztBQUFBO0FBQUE7QUFBQSxxQkFpQmtCO0FBQ1YsVUFBSSxPQUFPLEtBQUtDLFFBQVosS0FBeUIsV0FBN0IsRUFBMEM7QUFDdEMsY0FBTTtBQUFFVDtBQUFGLFlBQWtCLEtBQUtMLEtBQTdCOztBQUNBLFlBQUksQ0FBQ0ssV0FBTCxFQUFrQjtBQUNkLGVBQUtTLFFBQUwsR0FBZ0IsSUFBaEI7QUFDSCxTQUZELE1BRU8sSUFBSVQsV0FBVyxLQUFLLE1BQXBCLEVBQTRCO0FBQy9CLGVBQUtTLFFBQUwsR0FBZ0IsS0FBS1osS0FBckI7QUFDSCxTQUZNLE1BRUE7QUFDSCxlQUFLWSxRQUFMLEdBQWdCLEtBQUtYLEdBQUwsQ0FBU1ksR0FBVCxDQUFhVixXQUFiLENBQWhCO0FBQ0g7QUFDSjs7QUFDRCxhQUFPLEtBQUtTLFFBQVo7QUFDSDtBQTdCTDtBQUFBO0FBQUEscUJBK0J1QjtBQUNmLFVBQUksT0FBTyxLQUFLRSxhQUFaLEtBQThCLFdBQWxDLEVBQStDO0FBQzNDLGNBQU1DLGdCQUFnQixHQUFHLEtBQUtqQixLQUFMLENBQVdrQixtQkFBWCxDQUNyQixLQUFLakIsU0FEZ0IsRUFFckIsS0FBS0MsS0FGZ0IsQ0FBekI7O0FBSUEsWUFBSSxDQUFDZSxnQkFBTCxFQUF1QjtBQUNuQixlQUFLRCxhQUFMLEdBQXFCLElBQXJCO0FBQ0gsU0FGRCxNQUVPO0FBQ0gsZUFBS0EsYUFBTCxHQUFxQixLQUFLYixHQUFMLENBQVNZLEdBQVQsQ0FBYUUsZ0JBQWIsQ0FBckI7QUFDSDtBQUNKOztBQUNELGFBQU8sS0FBS0QsYUFBWjtBQUNIO0FBNUNMO0FBQUE7QUFBQSxxQkE4QzZCO0FBQ3JCLGFBQU8sS0FBS2hCLEtBQUwsQ0FBV21CLHFCQUFYLENBQWlDLEtBQUtqQixLQUF0QyxDQUFQO0FBQ0g7QUFoREw7O0FBQUE7QUFBQTtBQW9FZUoscUZBQWYiLCJmaWxlIjoiLi9zcmMvZmllbGRzL0ZpZWxkSW5zdGFsbGVyVGVtcGxhdGUuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIERlZmluZXMgYWxnb3JpdGhtIGZvciBpbnN0YWxsaW5nIGEgZmllbGQgb250byBhIG1vZGVsIGFuZCByZWxhdGVkIG1vZGVscy5cbiAqIENvbmZvcm1zIHRvIHRoZSB0ZW1wbGF0ZSBtZXRob2QgYmVoYXZpb3JhbCBkZXNpZ24gcGF0dGVybi5cbiAqIEBwcml2YXRlXG4gKiBAbWVtYmVyb2YgbW9kdWxlOmZpZWxkc1xuICovXG5leHBvcnQgY2xhc3MgRmllbGRJbnN0YWxsZXJUZW1wbGF0ZSB7XG4gICAgY29uc3RydWN0b3Iob3B0cykge1xuICAgICAgICB0aGlzLmZpZWxkID0gb3B0cy5maWVsZDtcbiAgICAgICAgdGhpcy5maWVsZE5hbWUgPSBvcHRzLmZpZWxkTmFtZTtcbiAgICAgICAgdGhpcy5tb2RlbCA9IG9wdHMubW9kZWw7XG4gICAgICAgIHRoaXMub3JtID0gb3B0cy5vcm07XG4gICAgICAgIC8qKlxuICAgICAgICAgKiB0aGUgZmllbGQgaXRzZWxmIGhhcyBubyBrbm93bGVkZ2Ugb2YgdGhlIG1vZGVsXG4gICAgICAgICAqIGl0IGlzIGJlaW5nIGluc3RhbGxlZCB1cG9uOyB3ZSBuZWVkIHRvIGluZm9ybSBpdFxuICAgICAgICAgKiB0aGF0IGl0IGlzIGEgc2VsZi1yZWZlcmVuY2luZyBmaWVsZCBmb3IgdGhlIGZpZWxkXG4gICAgICAgICAqIHRvIGJlIGFibGUgdG8gbWFrZSBiZXR0ZXIgaW5mb3JtZWQgZGVjaXNpb25zXG4gICAgICAgICAqL1xuICAgICAgICBpZiAodGhpcy5maWVsZC5yZWZlcmVuY2VzKHRoaXMubW9kZWwpKSB7XG4gICAgICAgICAgICB0aGlzLmZpZWxkLnRvTW9kZWxOYW1lID0gXCJ0aGlzXCI7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBnZXQgdG9Nb2RlbCgpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLl90b01vZGVsID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICBjb25zdCB7IHRvTW9kZWxOYW1lIH0gPSB0aGlzLmZpZWxkO1xuICAgICAgICAgICAgaWYgKCF0b01vZGVsTmFtZSkge1xuICAgICAgICAgICAgICAgIHRoaXMuX3RvTW9kZWwgPSBudWxsO1xuICAgICAgICAgICAgfSBlbHNlIGlmICh0b01vZGVsTmFtZSA9PT0gXCJ0aGlzXCIpIHtcbiAgICAgICAgICAgICAgICB0aGlzLl90b01vZGVsID0gdGhpcy5tb2RlbDtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgdGhpcy5fdG9Nb2RlbCA9IHRoaXMub3JtLmdldCh0b01vZGVsTmFtZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuX3RvTW9kZWw7XG4gICAgfVxuXG4gICAgZ2V0IHRocm91Z2hNb2RlbCgpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLl90aHJvdWdoTW9kZWwgPT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgIGNvbnN0IHRocm91Z2hNb2RlbE5hbWUgPSB0aGlzLmZpZWxkLmdldFRocm91Z2hNb2RlbE5hbWUoXG4gICAgICAgICAgICAgICAgdGhpcy5maWVsZE5hbWUsXG4gICAgICAgICAgICAgICAgdGhpcy5tb2RlbFxuICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIGlmICghdGhyb3VnaE1vZGVsTmFtZSkge1xuICAgICAgICAgICAgICAgIHRoaXMuX3Rocm91Z2hNb2RlbCA9IG51bGw7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHRoaXMuX3Rocm91Z2hNb2RlbCA9IHRoaXMub3JtLmdldCh0aHJvdWdoTW9kZWxOYW1lKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5fdGhyb3VnaE1vZGVsO1xuICAgIH1cblxuICAgIGdldCBiYWNrd2FyZHNGaWVsZE5hbWUoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmZpZWxkLmdldEJhY2t3YXJkc0ZpZWxkTmFtZSh0aGlzLm1vZGVsKTtcbiAgICB9XG5cbiAgICBydW4oKSB7XG4gICAgICAgIHRoaXMuaW5zdGFsbEZvcndhcmRzRGVzY3JpcHRvcigpO1xuICAgICAgICBpZiAodGhpcy5maWVsZC5pbnN0YWxsc0ZvcndhcmRzVmlydHVhbEZpZWxkKSB7XG4gICAgICAgICAgICB0aGlzLmluc3RhbGxGb3J3YXJkc1ZpcnR1YWxGaWVsZCgpO1xuICAgICAgICB9XG4gICAgICAgIC8qKlxuICAgICAgICAgKiBJbnN0YWxsIGEgYmFja3dhcmRzIGZpZWxkIG9uIGEgbW9kZWwgYXMgYSBjb25zZXF1ZW5jZVxuICAgICAgICAgKiBvZiBoYXZpbmcgaW5zdGFsbGVkIHRoZSBmb3J3YXJkcyBmaWVsZCBvbiBhbm90aGVyIG1vZGVsLlxuICAgICAgICAgKi9cbiAgICAgICAgaWYgKHRoaXMuZmllbGQuaW5zdGFsbHNCYWNrd2FyZHNEZXNjcmlwdG9yKSB7XG4gICAgICAgICAgICB0aGlzLmluc3RhbGxCYWNrd2FyZHNEZXNjcmlwdG9yKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuZmllbGQuaW5zdGFsbHNCYWNrd2FyZHNWaXJ0dWFsRmllbGQpIHtcbiAgICAgICAgICAgIHRoaXMuaW5zdGFsbEJhY2t3YXJkc1ZpcnR1YWxGaWVsZCgpO1xuICAgICAgICB9XG4gICAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBGaWVsZEluc3RhbGxlclRlbXBsYXRlO1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/fields/FieldInstallerTemplate.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"FieldInstallerTemplate\\\", function() { return FieldInstallerTemplate; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n\\n\\n/**\\n * Defines algorithm for installing a field onto a model and related models.\\n * Conforms to the template method behavioral design pattern.\\n * @private\\n * @memberof module:fields\\n */\\nlet FieldInstallerTemplate = /*#__PURE__*/function () {\\n  function FieldInstallerTemplate(opts) {\\n    this.field = opts.field;\\n    this.fieldName = opts.fieldName;\\n    this.model = opts.model;\\n    this.orm = opts.orm;\\n    /**\\n     * the field itself has no knowledge of the model\\n     * it is being installed upon; we need to inform it\\n     * that it is a self-referencing field for the field\\n     * to be able to make better informed decisions\\n     */\\n\\n    if (this.field.references(this.model)) {\\n      this.field.toModelName = \\\"this\\\";\\n    }\\n  }\\n\\n  var _proto = FieldInstallerTemplate.prototype;\\n\\n  _proto.run = function run() {\\n    this.installForwardsDescriptor();\\n\\n    if (this.field.installsForwardsVirtualField) {\\n      this.installForwardsVirtualField();\\n    }\\n    /**\\n     * Install a backwards field on a model as a consequence\\n     * of having installed the forwards field on another model.\\n     */\\n\\n\\n    if (this.field.installsBackwardsDescriptor) {\\n      this.installBackwardsDescriptor();\\n    }\\n\\n    if (this.field.installsBackwardsVirtualField) {\\n      this.installBackwardsVirtualField();\\n    }\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(FieldInstallerTemplate, [{\\n    key: \\\"toModel\\\",\\n    get: function () {\\n      if (typeof this._toModel === \\\"undefined\\\") {\\n        const {\\n          toModelName\\n        } = this.field;\\n\\n        if (!toModelName) {\\n          this._toModel = null;\\n        } else if (toModelName === \\\"this\\\") {\\n          this._toModel = this.model;\\n        } else {\\n          this._toModel = this.orm.get(toModelName);\\n        }\\n      }\\n\\n      return this._toModel;\\n    }\\n  }, {\\n    key: \\\"throughModel\\\",\\n    get: function () {\\n      if (typeof this._throughModel === \\\"undefined\\\") {\\n        const throughModelName = this.field.getThroughModelName(this.fieldName, this.model);\\n\\n        if (!throughModelName) {\\n          this._throughModel = null;\\n        } else {\\n          this._throughModel = this.orm.get(throughModelName);\\n        }\\n      }\\n\\n      return this._throughModel;\\n    }\\n  }, {\\n    key: \\\"backwardsFieldName\\\",\\n    get: function () {\\n      return this.field.getBackwardsFieldName(this.model);\\n    }\\n  }]);\\n\\n  return FieldInstallerTemplate;\\n}();\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (FieldInstallerTemplate);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvRmllbGRJbnN0YWxsZXJUZW1wbGF0ZS5qcz9jZGFiIl0sIm5hbWVzIjpbIkZpZWxkSW5zdGFsbGVyVGVtcGxhdGUiLCJvcHRzIiwiZmllbGQiLCJmaWVsZE5hbWUiLCJtb2RlbCIsIm9ybSIsInJlZmVyZW5jZXMiLCJ0b01vZGVsTmFtZSIsInJ1biIsImluc3RhbGxGb3J3YXJkc0Rlc2NyaXB0b3IiLCJpbnN0YWxsc0ZvcndhcmRzVmlydHVhbEZpZWxkIiwiaW5zdGFsbEZvcndhcmRzVmlydHVhbEZpZWxkIiwiaW5zdGFsbHNCYWNrd2FyZHNEZXNjcmlwdG9yIiwiaW5zdGFsbEJhY2t3YXJkc0Rlc2NyaXB0b3IiLCJpbnN0YWxsc0JhY2t3YXJkc1ZpcnR1YWxGaWVsZCIsImluc3RhbGxCYWNrd2FyZHNWaXJ0dWFsRmllbGQiLCJfdG9Nb2RlbCIsImdldCIsIl90aHJvdWdoTW9kZWwiLCJ0aHJvdWdoTW9kZWxOYW1lIiwiZ2V0VGhyb3VnaE1vZGVsTmFtZSIsImdldEJhY2t3YXJkc0ZpZWxkTmFtZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ08sSUFBTUEsc0JBQWI7QUFDSSxrQ0FBWUMsSUFBWixFQUFrQjtBQUNkLFNBQUtDLEtBQUwsR0FBYUQsSUFBSSxDQUFDQyxLQUFsQjtBQUNBLFNBQUtDLFNBQUwsR0FBaUJGLElBQUksQ0FBQ0UsU0FBdEI7QUFDQSxTQUFLQyxLQUFMLEdBQWFILElBQUksQ0FBQ0csS0FBbEI7QUFDQSxTQUFLQyxHQUFMLEdBQVdKLElBQUksQ0FBQ0ksR0FBaEI7QUFDQTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBQ1EsUUFBSSxLQUFLSCxLQUFMLENBQVdJLFVBQVgsQ0FBc0IsS0FBS0YsS0FBM0IsQ0FBSixFQUF1QztBQUNuQyxXQUFLRixLQUFMLENBQVdLLFdBQVgsR0FBeUIsTUFBekI7QUFDSDtBQUNKOztBQWZMOztBQUFBLFNBa0RJQyxHQWxESixHQWtESSxlQUFNO0FBQ0YsU0FBS0MseUJBQUw7O0FBQ0EsUUFBSSxLQUFLUCxLQUFMLENBQVdRLDRCQUFmLEVBQTZDO0FBQ3pDLFdBQUtDLDJCQUFMO0FBQ0g7QUFDRDtBQUNSO0FBQ0E7QUFDQTs7O0FBQ1EsUUFBSSxLQUFLVCxLQUFMLENBQVdVLDJCQUFmLEVBQTRDO0FBQ3hDLFdBQUtDLDBCQUFMO0FBQ0g7O0FBQ0QsUUFBSSxLQUFLWCxLQUFMLENBQVdZLDZCQUFmLEVBQThDO0FBQzFDLFdBQUtDLDRCQUFMO0FBQ0g7QUFDSixHQWpFTDs7QUFBQTtBQUFBO0FBQUEsU0FpQkksWUFBYztBQUNWLFVBQUksT0FBTyxLQUFLQyxRQUFaLEtBQXlCLFdBQTdCLEVBQTBDO0FBQ3RDLGNBQU07QUFBRVQ7QUFBRixZQUFrQixLQUFLTCxLQUE3Qjs7QUFDQSxZQUFJLENBQUNLLFdBQUwsRUFBa0I7QUFDZCxlQUFLUyxRQUFMLEdBQWdCLElBQWhCO0FBQ0gsU0FGRCxNQUVPLElBQUlULFdBQVcsS0FBSyxNQUFwQixFQUE0QjtBQUMvQixlQUFLUyxRQUFMLEdBQWdCLEtBQUtaLEtBQXJCO0FBQ0gsU0FGTSxNQUVBO0FBQ0gsZUFBS1ksUUFBTCxHQUFnQixLQUFLWCxHQUFMLENBQVNZLEdBQVQsQ0FBYVYsV0FBYixDQUFoQjtBQUNIO0FBQ0o7O0FBQ0QsYUFBTyxLQUFLUyxRQUFaO0FBQ0g7QUE3Qkw7QUFBQTtBQUFBLFNBK0JJLFlBQW1CO0FBQ2YsVUFBSSxPQUFPLEtBQUtFLGFBQVosS0FBOEIsV0FBbEMsRUFBK0M7QUFDM0MsY0FBTUMsZ0JBQWdCLEdBQUcsS0FBS2pCLEtBQUwsQ0FBV2tCLG1CQUFYLENBQ3JCLEtBQUtqQixTQURnQixFQUVyQixLQUFLQyxLQUZnQixDQUF6Qjs7QUFJQSxZQUFJLENBQUNlLGdCQUFMLEVBQXVCO0FBQ25CLGVBQUtELGFBQUwsR0FBcUIsSUFBckI7QUFDSCxTQUZELE1BRU87QUFDSCxlQUFLQSxhQUFMLEdBQXFCLEtBQUtiLEdBQUwsQ0FBU1ksR0FBVCxDQUFhRSxnQkFBYixDQUFyQjtBQUNIO0FBQ0o7O0FBQ0QsYUFBTyxLQUFLRCxhQUFaO0FBQ0g7QUE1Q0w7QUFBQTtBQUFBLFNBOENJLFlBQXlCO0FBQ3JCLGFBQU8sS0FBS2hCLEtBQUwsQ0FBV21CLHFCQUFYLENBQWlDLEtBQUtqQixLQUF0QyxDQUFQO0FBQ0g7QUFoREw7O0FBQUE7QUFBQTtBQW9FZUoscUZBQWYiLCJmaWxlIjoiLi9zcmMvZmllbGRzL0ZpZWxkSW5zdGFsbGVyVGVtcGxhdGUuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIERlZmluZXMgYWxnb3JpdGhtIGZvciBpbnN0YWxsaW5nIGEgZmllbGQgb250byBhIG1vZGVsIGFuZCByZWxhdGVkIG1vZGVscy5cbiAqIENvbmZvcm1zIHRvIHRoZSB0ZW1wbGF0ZSBtZXRob2QgYmVoYXZpb3JhbCBkZXNpZ24gcGF0dGVybi5cbiAqIEBwcml2YXRlXG4gKiBAbWVtYmVyb2YgbW9kdWxlOmZpZWxkc1xuICovXG5leHBvcnQgY2xhc3MgRmllbGRJbnN0YWxsZXJUZW1wbGF0ZSB7XG4gICAgY29uc3RydWN0b3Iob3B0cykge1xuICAgICAgICB0aGlzLmZpZWxkID0gb3B0cy5maWVsZDtcbiAgICAgICAgdGhpcy5maWVsZE5hbWUgPSBvcHRzLmZpZWxkTmFtZTtcbiAgICAgICAgdGhpcy5tb2RlbCA9IG9wdHMubW9kZWw7XG4gICAgICAgIHRoaXMub3JtID0gb3B0cy5vcm07XG4gICAgICAgIC8qKlxuICAgICAgICAgKiB0aGUgZmllbGQgaXRzZWxmIGhhcyBubyBrbm93bGVkZ2Ugb2YgdGhlIG1vZGVsXG4gICAgICAgICAqIGl0IGlzIGJlaW5nIGluc3RhbGxlZCB1cG9uOyB3ZSBuZWVkIHRvIGluZm9ybSBpdFxuICAgICAgICAgKiB0aGF0IGl0IGlzIGEgc2VsZi1yZWZlcmVuY2luZyBmaWVsZCBmb3IgdGhlIGZpZWxkXG4gICAgICAgICAqIHRvIGJlIGFibGUgdG8gbWFrZSBiZXR0ZXIgaW5mb3JtZWQgZGVjaXNpb25zXG4gICAgICAgICAqL1xuICAgICAgICBpZiAodGhpcy5maWVsZC5yZWZlcmVuY2VzKHRoaXMubW9kZWwpKSB7XG4gICAgICAgICAgICB0aGlzLmZpZWxkLnRvTW9kZWxOYW1lID0gXCJ0aGlzXCI7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBnZXQgdG9Nb2RlbCgpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLl90b01vZGVsID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICBjb25zdCB7IHRvTW9kZWxOYW1lIH0gPSB0aGlzLmZpZWxkO1xuICAgICAgICAgICAgaWYgKCF0b01vZGVsTmFtZSkge1xuICAgICAgICAgICAgICAgIHRoaXMuX3RvTW9kZWwgPSBudWxsO1xuICAgICAgICAgICAgfSBlbHNlIGlmICh0b01vZGVsTmFtZSA9PT0gXCJ0aGlzXCIpIHtcbiAgICAgICAgICAgICAgICB0aGlzLl90b01vZGVsID0gdGhpcy5tb2RlbDtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgdGhpcy5fdG9Nb2RlbCA9IHRoaXMub3JtLmdldCh0b01vZGVsTmFtZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuX3RvTW9kZWw7XG4gICAgfVxuXG4gICAgZ2V0IHRocm91Z2hNb2RlbCgpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLl90aHJvdWdoTW9kZWwgPT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgIGNvbnN0IHRocm91Z2hNb2RlbE5hbWUgPSB0aGlzLmZpZWxkLmdldFRocm91Z2hNb2RlbE5hbWUoXG4gICAgICAgICAgICAgICAgdGhpcy5maWVsZE5hbWUsXG4gICAgICAgICAgICAgICAgdGhpcy5tb2RlbFxuICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIGlmICghdGhyb3VnaE1vZGVsTmFtZSkge1xuICAgICAgICAgICAgICAgIHRoaXMuX3Rocm91Z2hNb2RlbCA9IG51bGw7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHRoaXMuX3Rocm91Z2hNb2RlbCA9IHRoaXMub3JtLmdldCh0aHJvdWdoTW9kZWxOYW1lKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5fdGhyb3VnaE1vZGVsO1xuICAgIH1cblxuICAgIGdldCBiYWNrd2FyZHNGaWVsZE5hbWUoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmZpZWxkLmdldEJhY2t3YXJkc0ZpZWxkTmFtZSh0aGlzLm1vZGVsKTtcbiAgICB9XG5cbiAgICBydW4oKSB7XG4gICAgICAgIHRoaXMuaW5zdGFsbEZvcndhcmRzRGVzY3JpcHRvcigpO1xuICAgICAgICBpZiAodGhpcy5maWVsZC5pbnN0YWxsc0ZvcndhcmRzVmlydHVhbEZpZWxkKSB7XG4gICAgICAgICAgICB0aGlzLmluc3RhbGxGb3J3YXJkc1ZpcnR1YWxGaWVsZCgpO1xuICAgICAgICB9XG4gICAgICAgIC8qKlxuICAgICAgICAgKiBJbnN0YWxsIGEgYmFja3dhcmRzIGZpZWxkIG9uIGEgbW9kZWwgYXMgYSBjb25zZXF1ZW5jZVxuICAgICAgICAgKiBvZiBoYXZpbmcgaW5zdGFsbGVkIHRoZSBmb3J3YXJkcyBmaWVsZCBvbiBhbm90aGVyIG1vZGVsLlxuICAgICAgICAgKi9cbiAgICAgICAgaWYgKHRoaXMuZmllbGQuaW5zdGFsbHNCYWNrd2FyZHNEZXNjcmlwdG9yKSB7XG4gICAgICAgICAgICB0aGlzLmluc3RhbGxCYWNrd2FyZHNEZXNjcmlwdG9yKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuZmllbGQuaW5zdGFsbHNCYWNrd2FyZHNWaXJ0dWFsRmllbGQpIHtcbiAgICAgICAgICAgIHRoaXMuaW5zdGFsbEJhY2t3YXJkc1ZpcnR1YWxGaWVsZCgpO1xuICAgICAgICB9XG4gICAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBGaWVsZEluc3RhbGxlclRlbXBsYXRlO1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/fields/FieldInstallerTemplate.js\\n\");\n \n /***/ }),\n \n@@ -4606,7 +4628,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"ForeignKey\\\", function() { return ForeignKey; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _RelationalField__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./RelationalField */ \\\"./src/fields/RelationalField.js\\\");\\n/* harmony import */ var _descriptors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../descriptors */ \\\"./src/descriptors.js\\\");\\n\\n\\n\\n\\n/**\\n * @memberof module:fields\\n */\\n\\nlet ForeignKey = /*#__PURE__*/function (_RelationalField) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(ForeignKey, _RelationalField);\\n\\n  function ForeignKey() {\\n    return _RelationalField.apply(this, arguments) || this;\\n  }\\n\\n  var _proto = ForeignKey.prototype;\\n\\n  _proto.createForwardsDescriptor = function createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_3__[\\\"forwardsManyToOneDescriptor\\\"])(fieldName, toModel.modelName);\\n  };\\n\\n  _proto.createBackwardsDescriptor = function createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_3__[\\\"backwardsManyToOneDescriptor\\\"])(fieldName, model.modelName);\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(ForeignKey, [{\\n    key: \\\"index\\\",\\n    get: function () {\\n      return true;\\n    }\\n  }]);\\n\\n  return ForeignKey;\\n}(_RelationalField__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (ForeignKey);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvRm9yZWlnbktleS5qcz9lMjVlIl0sIm5hbWVzIjpbIkZvcmVpZ25LZXkiLCJjcmVhdGVGb3J3YXJkc0Rlc2NyaXB0b3IiLCJmaWVsZE5hbWUiLCJtb2RlbCIsInRvTW9kZWwiLCJ0aHJvdWdoTW9kZWwiLCJmb3J3YXJkc01hbnlUb09uZURlc2NyaXB0b3IiLCJtb2RlbE5hbWUiLCJjcmVhdGVCYWNrd2FyZHNEZXNjcmlwdG9yIiwiYmFja3dhcmRzTWFueVRvT25lRGVzY3JpcHRvciIsIlJlbGF0aW9uYWxGaWVsZCJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7OztBQUFBO0FBRUE7QUFLQTs7OztBQUdPLElBQU1BLFVBQWI7QUFBQTs7QUFBQTtBQUFBO0FBQUE7O0FBQUE7O0FBQUEsU0FDSUMsd0JBREosR0FDSSxrQ0FBeUJDLFNBQXpCLEVBQW9DQyxLQUFwQyxFQUEyQ0MsT0FBM0MsRUFBb0RDLFlBQXBELEVBQWtFO0FBQzlELFdBQU9DLGdGQUEyQixDQUFDSixTQUFELEVBQVlFLE9BQU8sQ0FBQ0csU0FBcEIsQ0FBbEM7QUFDSCxHQUhMOztBQUFBLFNBS0lDLHlCQUxKLEdBS0ksbUNBQTBCTixTQUExQixFQUFxQ0MsS0FBckMsRUFBNENDLE9BQTVDLEVBQXFEQyxZQUFyRCxFQUFtRTtBQUMvRCxXQUFPSSxpRkFBNEIsQ0FBQ1AsU0FBRCxFQUFZQyxLQUFLLENBQUNJLFNBQWxCLENBQW5DO0FBQ0gsR0FQTDs7QUFBQTtBQUFBO0FBQUEscUJBU2dCO0FBQ1IsYUFBTyxJQUFQO0FBQ0g7QUFYTDs7QUFBQTtBQUFBLEVBQWdDRyx3REFBaEM7QUFjZVYseUVBQWYiLCJmaWxlIjoiLi9zcmMvZmllbGRzL0ZvcmVpZ25LZXkuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgUmVsYXRpb25hbEZpZWxkIGZyb20gXCIuL1JlbGF0aW9uYWxGaWVsZFwiO1xuXG5pbXBvcnQge1xuICAgIGZvcndhcmRzTWFueVRvT25lRGVzY3JpcHRvcixcbiAgICBiYWNrd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yLFxufSBmcm9tIFwiLi4vZGVzY3JpcHRvcnNcIjtcblxuLyoqXG4gKiBAbWVtYmVyb2YgbW9kdWxlOmZpZWxkc1xuICovXG5leHBvcnQgY2xhc3MgRm9yZWlnbktleSBleHRlbmRzIFJlbGF0aW9uYWxGaWVsZCB7XG4gICAgY3JlYXRlRm9yd2FyZHNEZXNjcmlwdG9yKGZpZWxkTmFtZSwgbW9kZWwsIHRvTW9kZWwsIHRocm91Z2hNb2RlbCkge1xuICAgICAgICByZXR1cm4gZm9yd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yKGZpZWxkTmFtZSwgdG9Nb2RlbC5tb2RlbE5hbWUpO1xuICAgIH1cblxuICAgIGNyZWF0ZUJhY2t3YXJkc0Rlc2NyaXB0b3IoZmllbGROYW1lLCBtb2RlbCwgdG9Nb2RlbCwgdGhyb3VnaE1vZGVsKSB7XG4gICAgICAgIHJldHVybiBiYWNrd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yKGZpZWxkTmFtZSwgbW9kZWwubW9kZWxOYW1lKTtcbiAgICB9XG5cbiAgICBnZXQgaW5kZXgoKSB7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgRm9yZWlnbktleTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/fields/ForeignKey.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"ForeignKey\\\", function() { return ForeignKey; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _RelationalField__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./RelationalField */ \\\"./src/fields/RelationalField.js\\\");\\n/* harmony import */ var _descriptors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../descriptors */ \\\"./src/descriptors.js\\\");\\n\\n\\n\\n\\n/**\\n * @memberof module:fields\\n */\\n\\nlet ForeignKey = /*#__PURE__*/function (_RelationalField) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(ForeignKey, _RelationalField);\\n\\n  function ForeignKey() {\\n    return _RelationalField.apply(this, arguments) || this;\\n  }\\n\\n  var _proto = ForeignKey.prototype;\\n\\n  _proto.createForwardsDescriptor = function createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_3__[\\\"forwardsManyToOneDescriptor\\\"])(fieldName, toModel.modelName);\\n  };\\n\\n  _proto.createBackwardsDescriptor = function createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_3__[\\\"backwardsManyToOneDescriptor\\\"])(fieldName, model.modelName);\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(ForeignKey, [{\\n    key: \\\"index\\\",\\n    get: function () {\\n      return true;\\n    }\\n  }]);\\n\\n  return ForeignKey;\\n}(_RelationalField__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (ForeignKey);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvRm9yZWlnbktleS5qcz9lMjVlIl0sIm5hbWVzIjpbIkZvcmVpZ25LZXkiLCJjcmVhdGVGb3J3YXJkc0Rlc2NyaXB0b3IiLCJmaWVsZE5hbWUiLCJtb2RlbCIsInRvTW9kZWwiLCJ0aHJvdWdoTW9kZWwiLCJmb3J3YXJkc01hbnlUb09uZURlc2NyaXB0b3IiLCJtb2RlbE5hbWUiLCJjcmVhdGVCYWNrd2FyZHNEZXNjcmlwdG9yIiwiYmFja3dhcmRzTWFueVRvT25lRGVzY3JpcHRvciIsIlJlbGF0aW9uYWxGaWVsZCJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7OztBQUFBO0FBRUE7QUFLQTtBQUNBO0FBQ0E7O0FBQ08sSUFBTUEsVUFBYjtBQUFBOztBQUFBO0FBQUE7QUFBQTs7QUFBQTs7QUFBQSxTQUNJQyx3QkFESixHQUNJLGtDQUF5QkMsU0FBekIsRUFBb0NDLEtBQXBDLEVBQTJDQyxPQUEzQyxFQUFvREMsWUFBcEQsRUFBa0U7QUFDOUQsV0FBT0MsZ0ZBQTJCLENBQUNKLFNBQUQsRUFBWUUsT0FBTyxDQUFDRyxTQUFwQixDQUFsQztBQUNILEdBSEw7O0FBQUEsU0FLSUMseUJBTEosR0FLSSxtQ0FBMEJOLFNBQTFCLEVBQXFDQyxLQUFyQyxFQUE0Q0MsT0FBNUMsRUFBcURDLFlBQXJELEVBQW1FO0FBQy9ELFdBQU9JLGlGQUE0QixDQUFDUCxTQUFELEVBQVlDLEtBQUssQ0FBQ0ksU0FBbEIsQ0FBbkM7QUFDSCxHQVBMOztBQUFBO0FBQUE7QUFBQSxTQVNJLFlBQVk7QUFDUixhQUFPLElBQVA7QUFDSDtBQVhMOztBQUFBO0FBQUEsRUFBZ0NHLHdEQUFoQztBQWNlVix5RUFBZiIsImZpbGUiOiIuL3NyYy9maWVsZHMvRm9yZWlnbktleS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBSZWxhdGlvbmFsRmllbGQgZnJvbSBcIi4vUmVsYXRpb25hbEZpZWxkXCI7XG5cbmltcG9ydCB7XG4gICAgZm9yd2FyZHNNYW55VG9PbmVEZXNjcmlwdG9yLFxuICAgIGJhY2t3YXJkc01hbnlUb09uZURlc2NyaXB0b3IsXG59IGZyb20gXCIuLi9kZXNjcmlwdG9yc1wiO1xuXG4vKipcbiAqIEBtZW1iZXJvZiBtb2R1bGU6ZmllbGRzXG4gKi9cbmV4cG9ydCBjbGFzcyBGb3JlaWduS2V5IGV4dGVuZHMgUmVsYXRpb25hbEZpZWxkIHtcbiAgICBjcmVhdGVGb3J3YXJkc0Rlc2NyaXB0b3IoZmllbGROYW1lLCBtb2RlbCwgdG9Nb2RlbCwgdGhyb3VnaE1vZGVsKSB7XG4gICAgICAgIHJldHVybiBmb3J3YXJkc01hbnlUb09uZURlc2NyaXB0b3IoZmllbGROYW1lLCB0b01vZGVsLm1vZGVsTmFtZSk7XG4gICAgfVxuXG4gICAgY3JlYXRlQmFja3dhcmRzRGVzY3JpcHRvcihmaWVsZE5hbWUsIG1vZGVsLCB0b01vZGVsLCB0aHJvdWdoTW9kZWwpIHtcbiAgICAgICAgcmV0dXJuIGJhY2t3YXJkc01hbnlUb09uZURlc2NyaXB0b3IoZmllbGROYW1lLCBtb2RlbC5tb2RlbE5hbWUpO1xuICAgIH1cblxuICAgIGdldCBpbmRleCgpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBGb3JlaWduS2V5O1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/fields/ForeignKey.js\\n\");\n \n /***/ }),\n \n@@ -4618,7 +4640,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"ManyToMany\\\", function() { return ManyToMany; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _RelationalField__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./RelationalField */ \\\"./src/fields/RelationalField.js\\\");\\n/* harmony import */ var _descriptors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../descriptors */ \\\"./src/descriptors.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n\\n\\n/**\\n * @memberof module:fields\\n */\\n\\nlet ManyToMany = /*#__PURE__*/function (_RelationalField) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(ManyToMany, _RelationalField);\\n\\n  function ManyToMany() {\\n    return _RelationalField.apply(this, arguments) || this;\\n  }\\n\\n  var _proto = ManyToMany.prototype;\\n\\n  _proto.getDefault = function getDefault() {\\n    return [];\\n  };\\n\\n  _proto.getThroughModelName = function getThroughModelName(fieldName, model) {\\n    return this.through || Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"m2mName\\\"])(model.modelName, fieldName);\\n  };\\n\\n  _proto.createForwardsDescriptor = function createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_3__[\\\"manyToManyDescriptor\\\"])(model.modelName, toModel.modelName, throughModel.modelName, this.getThroughFields(fieldName, model, toModel, throughModel), false);\\n  };\\n\\n  _proto.createBackwardsDescriptor = function createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_3__[\\\"manyToManyDescriptor\\\"])(model.modelName, toModel.modelName, throughModel.modelName, this.getThroughFields(fieldName, model, toModel, throughModel), true);\\n  };\\n\\n  _proto.createBackwardsVirtualField = function createBackwardsVirtualField(fieldName, model, toModel, throughModel) {\\n    const ThisField = this.getClass();\\n    return new ThisField({\\n      to: model.modelName,\\n      relatedName: fieldName,\\n      through: throughModel.modelName,\\n      throughFields: this.getThroughFields(fieldName, model, toModel, throughModel)\\n    });\\n  };\\n\\n  _proto.createForwardsVirtualField = function createForwardsVirtualField(fieldName, model, toModel, throughModel) {\\n    const ThisField = this.getClass();\\n    return new ThisField({\\n      to: toModel.modelName,\\n      relatedName: fieldName,\\n      through: this.through,\\n      throughFields: this.getThroughFields(fieldName, model, toModel, throughModel),\\n      as: this.as\\n    });\\n  };\\n\\n  _proto.getThroughFields = function getThroughFields(fieldName, model, toModel, throughModel) {\\n    if (this.throughFields) {\\n      const [fieldAName, fieldBName] = this.throughFields;\\n      const fieldA = throughModel.fields[fieldAName];\\n      return {\\n        to: fieldA.references(toModel) ? fieldAName : fieldBName,\\n        from: fieldA.references(toModel) ? fieldBName : fieldAName\\n      };\\n    }\\n\\n    if (model.modelName === toModel.modelName) {\\n      /**\\n       * we have no way of determining the relationship's\\n       * direction here, so we need to assume that the user\\n       * did not use a custom through model\\n       * see ORM#registerManyToManyModelsFor\\n       */\\n      return {\\n        to: Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"m2mToFieldName\\\"])(toModel.modelName),\\n        from: Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"m2mFromFieldName\\\"])(model.modelName)\\n      };\\n    }\\n    /**\\n     * determine which field references which model\\n     * and infer the directions from that\\n     */\\n\\n\\n    const throughModelFieldReferencing = otherModel => Object.keys(throughModel.fields).find(someFieldName => throughModel.fields[someFieldName].references(otherModel));\\n\\n    return {\\n      to: throughModelFieldReferencing(toModel),\\n      from: throughModelFieldReferencing(model)\\n    };\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(ManyToMany, [{\\n    key: \\\"installsForwardsVirtualField\\\",\\n    get: function () {\\n      return true;\\n    }\\n  }]);\\n\\n  return ManyToMany;\\n}(_RelationalField__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (ManyToMany);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvTWFueVRvTWFueS5qcz9mMmNkIl0sIm5hbWVzIjpbIk1hbnlUb01hbnkiLCJnZXREZWZhdWx0IiwiZ2V0VGhyb3VnaE1vZGVsTmFtZSIsImZpZWxkTmFtZSIsIm1vZGVsIiwidGhyb3VnaCIsIm0ybU5hbWUiLCJtb2RlbE5hbWUiLCJjcmVhdGVGb3J3YXJkc0Rlc2NyaXB0b3IiLCJ0b01vZGVsIiwidGhyb3VnaE1vZGVsIiwibWFueVRvTWFueURlc2NyaXB0b3IiLCJnZXRUaHJvdWdoRmllbGRzIiwiY3JlYXRlQmFja3dhcmRzRGVzY3JpcHRvciIsImNyZWF0ZUJhY2t3YXJkc1ZpcnR1YWxGaWVsZCIsIlRoaXNGaWVsZCIsImdldENsYXNzIiwidG8iLCJyZWxhdGVkTmFtZSIsInRocm91Z2hGaWVsZHMiLCJjcmVhdGVGb3J3YXJkc1ZpcnR1YWxGaWVsZCIsImFzIiwiZmllbGRBTmFtZSIsImZpZWxkQk5hbWUiLCJmaWVsZEEiLCJmaWVsZHMiLCJyZWZlcmVuY2VzIiwiZnJvbSIsIm0ybVRvRmllbGROYW1lIiwibTJtRnJvbUZpZWxkTmFtZSIsInRocm91Z2hNb2RlbEZpZWxkUmVmZXJlbmNpbmciLCJvdGhlck1vZGVsIiwiT2JqZWN0Iiwia2V5cyIsImZpbmQiLCJzb21lRmllbGROYW1lIiwiUmVsYXRpb25hbEZpZWxkIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7OztBQUFBO0FBRUE7QUFFQTtBQUVBOzs7O0FBR08sSUFBTUEsVUFBYjtBQUFBOztBQUFBO0FBQUE7QUFBQTs7QUFBQTs7QUFBQSxTQUNJQyxVQURKLEdBQ0ksc0JBQWE7QUFDVCxXQUFPLEVBQVA7QUFDSCxHQUhMOztBQUFBLFNBS0lDLG1CQUxKLEdBS0ksNkJBQW9CQyxTQUFwQixFQUErQkMsS0FBL0IsRUFBc0M7QUFDbEMsV0FBTyxLQUFLQyxPQUFMLElBQWdCQyxzREFBTyxDQUFDRixLQUFLLENBQUNHLFNBQVAsRUFBa0JKLFNBQWxC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b0IsWUFBSSxFQUFFRSwrREFBZ0IsQ0FBQ3pCLEtBQUssQ0FBQ0csU0FBUDtBQUZuQixPQUFQO0FBSUg7QUFFRDs7Ozs7O0FBSUEsVUFBTXVCLDRCQUE0QixHQUFHQyxVQUFVLElBQzNDQyxNQUFNLENBQUNDLElBQVAsQ0FBWXZCLFlBQVksQ0FBQ2UsTUFBekIsRUFBaUNTLElBQWpDLENBQXNDQyxhQUFhLElBQy9DekIsWUFBWSxDQUFDZSxNQUFiLENBQW9CVSxhQUFwQixFQUFtQ1QsVUFBbkMsQ0FBOENLLFVBQTlDLENBREosQ0FESjs7QUFLQSxXQUFPO0FBQ0hkLFFBQUUsRUFBRWEsNEJBQTRCLENBQUNyQixPQUFELENBRDdCO0FBRUhrQixVQUFJLEVBQUVHLDRCQUE0QixDQUFDMUIsS0FBRDtBQUYvQixLQUFQO0FBSUgsR0FwR0w7O0FBQUE7QUFBQTtBQUFBLHFCQTREdUM7QUFDL0IsYUFBTyxJQUFQO0FBQ0g7QUE5REw7O0FBQUE7QUFBQSxFQUFnQ2dDLHdEQUFoQztBQXVHZXBDLHlFQUFmIiwiZmlsZSI6Ii4vc3JjL2ZpZWxkcy9NYW55VG9NYW55LmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFJlbGF0aW9uYWxGaWVsZCBmcm9tIFwiLi9SZWxhdGlvbmFsRmllbGRcIjtcblxuaW1wb3J0IHsgbWFueVRvTWFueURlc2NyaXB0b3IgfSBmcm9tIFwiLi4vZGVzY3JpcHRvcnNcIjtcblxuaW1wb3J0IHsgbTJtTmFtZSwgbTJtVG9GaWVsZE5hbWUsIG0ybUZyb21GaWVsZE5hbWUgfSBmcm9tIFwiLi4vdXRpbHNcIjtcblxuLyoqXG4gKiBAbWVtYmVyb2YgbW9kdWxlOmZpZWxkc1xuICovXG5leHBvcnQgY2xhc3MgTWFueVRvTWFueSBleHRlbmRzIFJlbGF0aW9uYWxGaWVsZCB7XG4gICAgZ2V0RGVmYXVsdCgpIHtcbiAgICAgICAgcmV0dXJuIFtdO1xuICAgIH1cblxuICAgIGdldFRocm91Z2hNb2RlbE5hbWUoZmllbGROYW1lLCBtb2RlbCkge1xuICAgICAgICByZXR1cm4gdGhpcy50aHJvdWdoIHx8IG0ybU5hbWUobW9kZWwubW9kZWxOYW1lLCBmaWVsZE5hbWUpO1xuICAgIH1cblxuICAgIGNyZWF0ZUZvcndhcmRzRGVzY3JpcHRvcihmaWVsZE5hbWUsIG1vZGVsLCB0b01vZGVsLCB0aHJvdWdoTW9kZWwpIHtcbiAgICAgICAgcmV0dXJuIG1hbnlUb01hbnlEZXNjcmlwdG9yKFxuICAgICAgICAgICAgbW9kZWwubW9kZWxOYW1lLFxuICAgICAgICAgICAgdG9Nb2RlbC5tb2RlbE5hbWUsXG4gICAgICAgICAgICB0aHJvdWdoTW9kZWwubW9kZWxOYW1lLFxuICAgICAgICAgICAgdGhpcy5nZXRUaHJvdWdoRmllbGRzKGZpZWxkTmFtZSwgbW9kZWwsIHRvTW9kZWwsIHRocm91Z2hNb2RlbCksXG4gICAgICAgICAgICBmYWxzZVxuICAgICAgICApO1xuICAgIH1cblxuICAgIGNyZWF0ZUJhY2t3YXJkc0Rlc2NyaXB0b3IoZmllbGROYW1lLCBtb2RlbCwgdG9Nb2RlbCwgdGhyb3VnaE1vZGVsKSB7XG4gICAgICAgIHJldHVybiBtYW55VG9NYW55RGVzY3JpcHRvcihcbiAgICAgICAgICAgIG1vZGVsLm1vZGVsTmFtZSxcbiAgICAgICAgICAgIHRvTW9kZWwubW9kZWxOYW1lLFxuICAgICAgICAgICAgdGhyb3VnaE1vZGVsLm1vZGVsTmFtZSxcbiAgICAgICAgICAgIHRoaXMuZ2V0VGhyb3VnaEZpZWxkcyhmaWVsZE5hbWUsIG1vZGVsLCB0b01vZGVsLCB0aHJvdWdoTW9kZWwpLFxuICAgICAgICAgICAgdHJ1ZVxuICAgICAgICApO1xuICAgIH1cblxuICAgIGNyZWF0ZUJhY2t3YXJkc1ZpcnR1YWxGaWVsZChmaWVsZE5hbWUsIG1vZGVsLCB0b01vZGVsLCB0aHJvdWdoTW9kZWwpIHtcbiAgICAgICAgY29uc3QgVGhpc0ZpZWxkID0gdGhpcy5nZXRDbGFzcygpO1xuICAgICAgICByZXR1cm4gbmV3IFRoaXNGaWVsZCh7XG4gICAgICAgICAgICB0bzogbW9kZWwubW9kZWxOYW1lLFxuICAgICAgICAgICAgcmVsYXRlZE5hbWU6IGZpZWxkTmFtZSxcbiAgICAgICAgICAgIHRocm91Z2g6IHRocm91Z2hNb2RlbC5tb2RlbE5hbWUsXG4gICAgICAgICAgICB0aHJvdWdoRmllbGRzOiB0aGlzLmdldFRocm91Z2hGaWVsZHMoXG4gICAgICAgICAgICAgICAgZmllbGROYW1lLFxuICAgICAgICAgICAgICAgIG1vZGVsLFxuICAgICAgICAgICAgICAgIHRvTW9kZWwsXG4gICAgICAgICAgICAgICAgdGhyb3VnaE1vZGVsXG4gICAgICAgICAgICApLFxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBjcmVhdGVGb3J3YXJkc1ZpcnR1YWxGaWVsZChmaWVsZE5hbWUsIG1vZGVsLCB0b01vZGVsLCB0aHJvdWdoTW9kZWwpIHtcbiAgICAgICAgY29uc3QgVGhpc0ZpZWxkID0gdGhpcy5nZXRDbGFzcygpO1xuICAgICAgICByZXR1cm4gbmV3IFRoaXNGaWVsZCh7XG4gICAgICAgICAgICB0bzogdG9Nb2RlbC5tb2RlbE5hbWUsXG4gICAgICAgICAgICByZWxhdGVkTmFtZTogZmllbGROYW1lLFxuICAgICAgICAgICAgdGhyb3VnaDogdGhpcy50aHJvdWdoLFxuICAgICAgICAgICAgdGhyb3VnaEZpZWxkczogdGhpcy5nZXRUaHJvdWdoRmllbGRzKFxuICAgICAgICAgICAgICAgIGZpZWxkTmFtZSxcbiAgICAgICAgICAgICAgICBtb2RlbCxcbiAgICAgICAgICAgICAgICB0b01vZGVsLFxuICAgICAgICAgICAgICAgIHRocm91Z2hNb2RlbFxuICAgICAgICAgICAgKSxcbiAgICAgICAgICAgIGFzOiB0aGlzLmFzLFxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBnZXQgaW5zdGFsbHNGb3J3YXJkc1ZpcnR1YWxGaWVsZCgpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgZ2V0VGhyb3VnaEZpZWxkcyhmaWVsZE5hbWUsIG1vZGVsLCB0b01vZGVsLCB0aHJvdWdoTW9kZWwpIHtcbiAgICAgICAgaWYgKHRoaXMudGhyb3VnaEZpZWxkcykge1xuICAgICAgICAgICAgY29uc3QgW2ZpZWxkQU5hbWUsIGZpZWxkQk5hbWVdID0gdGhpcy50aHJvdWdoRmllbGRzO1xuICAgICAgICAgICAgY29uc3QgZmllbGRBID0gdGhyb3VnaE1vZGVsLmZpZWxkc1tmaWVsZEFOYW1lXTtcbiAgICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAgICAgdG86IGZpZWxkQS5yZWZlcmVuY2VzKHRvTW9kZWwpID8gZmllbGRBTmFtZSA6IGZpZWxkQk5hbWUsXG4gICAgICAgICAgICAgICAgZnJvbTogZmllbGRBLnJlZmVyZW5jZXModG9Nb2RlbCkgPyBmaWVsZEJOYW1lIDogZmllbGRBTmFtZSxcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cblxuICAgICAgICBpZiAobW9kZWwubW9kZWxOYW1lID09PSB0b01vZGVsLm1vZGVsTmFtZSkge1xuICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgKiB3ZSBoYXZlIG5vIHdheSBvZiBkZXRlcm1pbmluZyB0aGUgcmVsYXRpb25zaGlwJ3NcbiAgICAgICAgICAgICAqIGRpcmVjdGlvbiBoZXJlLCBzbyB3ZSBuZWVkIHRvIGFzc3VtZSB0aGF0IHRoZSB1c2VyXG4gICAgICAgICAgICAgKiBkaWQgbm90IHVzZSBhIGN1c3RvbSB0aHJvdWdoIG1vZGVsXG4gICAgICAgICAgICAgKiBzZWUgT1JNI3JlZ2lzdGVyTWFueVRvTWFueU1vZGVsc0ZvclxuICAgICAgICAgICAgICovXG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIHRvOiBtMm1Ub0ZpZWxkTmFtZSh0b01vZGVsLm1vZGVsTmFtZSksXG4gICAgICAgICAgICAgICAgZnJvbTogbTJtRnJvbUZpZWxkTmFtZShtb2RlbC5tb2RlbE5hbWUpLFxuICAgICAgICAgICAgfTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBkZXRlcm1pbmUgd2hpY2ggZmllbGQgcmVmZXJlbmNlcyB3aGljaCBtb2RlbFxuICAgICAgICAgKiBhbmQgaW5mZXIgdGhlIGRpcmVjdGlvbnMgZnJvbSB0aGF0XG4gICAgICAgICAqL1xuICAgICAgICBjb25zdCB0aHJvdWdoTW9kZWxGaWVsZFJlZmVyZW5jaW5nID0gb3RoZXJNb2RlbCA9PlxuICAgICAgICAgICAgT2JqZWN0LmtleXModGhyb3VnaE1vZGVsLmZpZWxkcykuZmluZChzb21lRmllbGROYW1lID0+XG4gICAgICAgICAgICAgICAgdGhyb3VnaE1vZGVsLmZpZWxkc1tzb21lRmllbGROYW1lXS5yZWZlcmVuY2VzKG90aGVyTW9kZWwpXG4gICAgICAgICAgICApO1xuXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICB0bzogdGhyb3VnaE1vZGVsRmllbGRSZWZlcmVuY2luZyh0b01vZGVsKSxcbiAgICAgICAgICAgIGZyb206IHRocm91Z2hNb2RlbEZpZWxkUmVmZXJlbmNpbmcobW9kZWwpLFxuICAgICAgICB9O1xuICAgIH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgTWFueVRvTWFueTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/fields/ManyToMany.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"ManyToMany\\\", function() { return ManyToMany; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _RelationalField__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./RelationalField */ \\\"./src/fields/RelationalField.js\\\");\\n/* harmony import */ var _descriptors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../descriptors */ \\\"./src/descriptors.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n\\n\\n/**\\n * @memberof module:fields\\n */\\n\\nlet ManyToMany = /*#__PURE__*/function (_RelationalField) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(ManyToMany, _RelationalField);\\n\\n  function ManyToMany() {\\n    return _RelationalField.apply(this, arguments) || this;\\n  }\\n\\n  var _proto = ManyToMany.prototype;\\n\\n  _proto.getDefault = function getDefault() {\\n    return [];\\n  };\\n\\n  _proto.getThroughModelName = function getThroughModelName(fieldName, model) {\\n    return this.through || Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"m2mName\\\"])(model.modelName, fieldName);\\n  };\\n\\n  _proto.createForwardsDescriptor = function createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_3__[\\\"manyToManyDescriptor\\\"])(model.modelName, toModel.modelName, throughModel.modelName, this.getThroughFields(fieldName, model, toModel, throughModel), false);\\n  };\\n\\n  _proto.createBackwardsDescriptor = function createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_3__[\\\"manyToManyDescriptor\\\"])(model.modelName, toModel.modelName, throughModel.modelName, this.getThroughFields(fieldName, model, toModel, throughModel), true);\\n  };\\n\\n  _proto.createBackwardsVirtualField = function createBackwardsVirtualField(fieldName, model, toModel, throughModel) {\\n    const ThisField = this.getClass();\\n    return new ThisField({\\n      to: model.modelName,\\n      relatedName: fieldName,\\n      through: throughModel.modelName,\\n      throughFields: this.getThroughFields(fieldName, model, toModel, throughModel)\\n    });\\n  };\\n\\n  _proto.createForwardsVirtualField = function createForwardsVirtualField(fieldName, model, toModel, throughModel) {\\n    const ThisField = this.getClass();\\n    return new ThisField({\\n      to: toModel.modelName,\\n      relatedName: fieldName,\\n      through: this.through,\\n      throughFields: this.getThroughFields(fieldName, model, toModel, throughModel),\\n      as: this.as\\n    });\\n  };\\n\\n  _proto.getThroughFields = function getThroughFields(fieldName, model, toModel, throughModel) {\\n    if (this.throughFields) {\\n      const [fieldAName, fieldBName] = this.throughFields;\\n      const fieldA = throughModel.fields[fieldAName];\\n      return {\\n        to: fieldA.references(toModel) ? fieldAName : fieldBName,\\n        from: fieldA.references(toModel) ? fieldBName : fieldAName\\n      };\\n    }\\n\\n    if (model.modelName === toModel.modelName) {\\n      /**\\n       * we have no way of determining the relationship's\\n       * direction here, so we need to assume that the user\\n       * did not use a custom through model\\n       * see ORM#registerManyToManyModelsFor\\n       */\\n      return {\\n        to: Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"m2mToFieldName\\\"])(toModel.modelName),\\n        from: Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"m2mFromFieldName\\\"])(model.modelName)\\n      };\\n    }\\n    /**\\n     * determine which field references which model\\n     * and infer the directions from that\\n     */\\n\\n\\n    const throughModelFieldReferencing = otherModel => Object.keys(throughModel.fields).find(someFieldName => throughModel.fields[someFieldName].references(otherModel));\\n\\n    return {\\n      to: throughModelFieldReferencing(toModel),\\n      from: throughModelFieldReferencing(model)\\n    };\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(ManyToMany, [{\\n    key: \\\"installsForwardsVirtualField\\\",\\n    get: function () {\\n      return true;\\n    }\\n  }]);\\n\\n  return ManyToMany;\\n}(_RelationalField__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (ManyToMany);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvTWFueVRvTWFueS5qcz9mMmNkIl0sIm5hbWVzIjpbIk1hbnlUb01hbnkiLCJnZXREZWZhdWx0IiwiZ2V0VGhyb3VnaE1vZGVsTmFtZSIsImZpZWxkTmFtZSIsIm1vZGVsIiwidGhyb3VnaCIsIm0ybU5hbWUiLCJtb2RlbE5hbWUiLCJjcmVhdGVGb3J3YXJkc0Rlc2NyaXB0b3IiLCJ0b01vZGVsIiwidGhyb3VnaE1vZGVsIiwibWFueVRvTWFueURlc2NyaXB0b3IiLCJnZXRUaHJvdWdoRmllbGRzIiwiY3JlYXRlQmFja3dhcmRzRGVzY3JpcHRvciIsImNyZWF0ZUJhY2t3YXJkc1ZpcnR1YWxGaWVsZCIsIlRoaXNGaWVsZCIsImdldENsYXNzIiwidG8iLCJyZWxhdGVkTmFtZSIsInRocm91Z2hGaWVsZHMiLCJjcmVhdGVGb3J3YXJkc1ZpcnR1YWxGaWVsZCIsImFzIiwiZmllbGRBTmFtZSIsImZpZWxkQk5hbWUiLCJmaWVsZEEiLCJmaWVsZHMiLCJyZWZlcmVuY2VzIiwiZnJvbSIsIm0ybVRvRmllbGROYW1lIiwibTJtRnJvbUZpZWxkTmFtZSIsInRocm91Z2hNb2RlbEZpZWxkUmVmZXJlbmNpbmciLCJvdGhlck1vZGVsIiwiT2JqZWN0Iiwia2V5cyIsImZpbmQiLCJzb21lRmllbGROYW1lIiwiUmVsYXRpb25hbEZpZWxkIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7OztBQUFBO0FBRUE7QUFFQTtBQUVBO0FBQ0E7QUFDQTs7QUFDTyxJQUFNQSxVQUFiO0FBQUE7O0FBQUE7QUFBQTtBQUFBOztBQUFBOztBQUFBLFNBQ0lDLFVBREosR0FDSSxzQkFBYTtBQUNULFdBQU8sRUFBUDtBQUNILEdBSEw7O0FBQUEsU0FLSUMsbUJBTEosR0FLSSw2QkFBb0JDLFNBQXBCLEVBQStCQyxLQUEvQixFQUFzQztBQUNsQyxXQUFPLEtBQUtDLE9BQUwsSUFBZ0JDLHNEQUFPLENBQUNGLEtBQUssQ0FBQ0csU0FBUCxFQUFr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bkIsT0FBTyxDQUFDRixTQUFULENBRGY7QUFFSG9CLFlBQUksRUFBRUUsK0RBQWdCLENBQUN6QixLQUFLLENBQUNHLFNBQVA7QUFGbkIsT0FBUDtBQUlIO0FBRUQ7QUFDUjtBQUNBO0FBQ0E7OztBQUNRLFVBQU11Qiw0QkFBNEIsR0FBSUMsVUFBRCxJQUNqQ0MsTUFBTSxDQUFDQyxJQUFQLENBQVl2QixZQUFZLENBQUNlLE1BQXpCLEVBQWlDUyxJQUFqQyxDQUF1Q0MsYUFBRCxJQUNsQ3pCLFlBQVksQ0FBQ2UsTUFBYixDQUFvQlUsYUFBcEIsRUFBbUNULFVBQW5DLENBQThDSyxVQUE5QyxDQURKLENBREo7O0FBS0EsV0FBTztBQUNIZCxRQUFFLEVBQUVhLDRCQUE0QixDQUFDckIsT0FBRCxDQUQ3QjtBQUVIa0IsVUFBSSxFQUFFRyw0QkFBNEIsQ0FBQzFCLEtBQUQ7QUFGL0IsS0FBUDtBQUlILEdBcEdMOztBQUFBO0FBQUE7QUFBQSxTQTRESSxZQUFtQztBQUMvQixhQUFPLElBQVA7QUFDSDtBQTlETDs7QUFBQTtBQUFBLEVBQWdDZ0Msd0RBQWhDO0FBdUdlcEMseUVBQWYiLCJmaWxlIjoiLi9zcmMvZmllbGRzL01hbnlUb01hbnkuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgUmVsYXRpb25hbEZpZWxkIGZyb20gXCIuL1JlbGF0aW9uYWxGaWVsZFwiO1xuXG5pbXBvcnQgeyBtYW55VG9NYW55RGVzY3JpcHRvciB9IGZyb20gXCIuLi9kZXNjcmlwdG9yc1wiO1xuXG5pbXBvcnQgeyBtMm1OYW1lLCBtMm1Ub0ZpZWxkTmFtZSwgbTJtRnJvbUZpZWxkTmFtZSB9IGZyb20gXCIuLi91dGlsc1wiO1xuXG4vKipcbiAqIEBtZW1iZXJvZiBtb2R1bGU6ZmllbGRzXG4gKi9cbmV4cG9ydCBjbGFzcyBNYW55VG9NYW55IGV4dGVuZHMgUmVsYXRpb25hbEZpZWxkIHtcbiAgICBnZXREZWZhdWx0KCkge1xuICAgICAgICByZXR1cm4gW107XG4gICAgfVxuXG4gICAgZ2V0VGhyb3VnaE1vZGVsTmFtZShmaWVsZE5hbWUsIG1vZGVsKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnRocm91Z2ggfHwgbTJtTmFtZShtb2RlbC5tb2RlbE5hbWUsIGZpZWxkTmFtZSk7XG4gICAgfVxuXG4gICAgY3JlYXRlRm9yd2FyZHNEZXNjcmlwdG9yKGZpZWxkTmFtZSwgbW9kZWwsIHRvTW9kZWwsIHRocm91Z2hNb2RlbCkge1xuICAgICAgICByZXR1cm4gbWFueVRvTWFueURlc2NyaXB0b3IoXG4gICAgICAgICAgICBtb2RlbC5tb2RlbE5hbWUsXG4gICAgICAgICAgICB0b01vZGVsLm1vZGVsTmFtZSxcbiAgICAgICAgICAgIHRocm91Z2hNb2RlbC5tb2RlbE5hbWUsXG4gICAgICAgICAgICB0aGlzLmdldFRocm91Z2hGaWVsZHMoZmllbGROYW1lLCBtb2RlbCwgdG9Nb2RlbCwgdGhyb3VnaE1vZGVsKSxcbiAgICAgICAgICAgIGZhbHNlXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgY3JlYXRlQmFja3dhcmRzRGVzY3JpcHRvcihmaWVsZE5hbWUsIG1vZGVsLCB0b01vZGVsLCB0aHJvdWdoTW9kZWwpIHtcbiAgICAgICAgcmV0dXJuIG1hbnlUb01hbnlEZXNjcmlwdG9yKFxuICAgICAgICAgICAgbW9kZWwubW9kZWxOYW1lLFxuICAgICAgICAgICAgdG9Nb2RlbC5tb2RlbE5hbWUsXG4gICAgICAgICAgICB0aHJvdWdoTW9kZWwubW9kZWxOYW1lLFxuICAgICAgICAgICAgdGhpcy5nZXRUaHJvdWdoRmllbGRzKGZpZWxkTmFtZSwgbW9kZWwsIHRvTW9kZWwsIHRocm91Z2hNb2RlbCksXG4gICAgICAgICAgICB0cnVlXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgY3JlYXRlQmFja3dhcmRzVmlydHVhbEZpZWxkKGZpZWxkTmFtZSwgbW9kZWwsIHRvTW9kZWwsIHRocm91Z2hNb2RlbCkge1xuICAgICAgICBjb25zdCBUaGlzRmllbGQgPSB0aGlzLmdldENsYXNzKCk7XG4gICAgICAgIHJldHVybiBuZXcgVGhpc0ZpZWxkKHtcbiAgICAgICAgICAgIHRvOiBtb2RlbC5tb2RlbE5hbWUsXG4gICAgICAgICAgICByZWxhdGVkTmFtZTogZmllbGROYW1lLFxuICAgICAgICAgICAgdGhyb3VnaDogdGhyb3VnaE1vZGVsLm1vZGVsTmFtZSxcbiAgICAgICAgICAgIHRocm91Z2hGaWVsZHM6IHRoaXMuZ2V0VGhyb3VnaEZpZWxkcyhcbiAgICAgICAgICAgICAgICBmaWVsZE5hbWUsXG4gICAgICAgICAgICAgICAgbW9kZWwsXG4gICAgICAgICAgICAgICAgdG9Nb2RlbCxcbiAgICAgICAgICAgICAgICB0aHJvdWdoTW9kZWxcbiAgICAgICAgICAgICksXG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIGNyZWF0ZUZvcndhcmRzVmlydHVhbEZpZWxkKGZpZWxkTmFtZSwgbW9kZWwsIHRvTW9kZWwsIHRocm91Z2hNb2RlbCkge1xuICAgICAgICBjb25zdCBUaGlzRmllbGQgPSB0aGlzLmdldENsYXNzKCk7XG4gICAgICAgIHJldHVybiBuZXcgVGhpc0ZpZWxkKHtcbiAgICAgICAgICAgIHRvOiB0b01vZGVsLm1vZGVsTmFtZSxcbiAgICAgICAgICAgIHJlbGF0ZWROYW1lOiBmaWVsZE5hbWUsXG4gICAgICAgICAgICB0aHJvdWdoOiB0aGlzLnRocm91Z2gsXG4gICAgICAgICAgICB0aHJvdWdoRmllbGRzOiB0aGlzLmdldFRocm91Z2hGaWVsZHMoXG4gICAgICAgICAgICAgICAgZmllbGROYW1lLFxuICAgICAgICAgICAgICAgIG1vZGVsLFxuICAgICAgICAgICAgICAgIHRvTW9kZWwsXG4gICAgICAgICAgICAgICAgdGhyb3VnaE1vZGVsXG4gICAgICAgICAgICApLFxuICAgICAgICAgICAgYXM6IHRoaXMuYXMsXG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIGdldCBpbnN0YWxsc0ZvcndhcmRzVmlydHVhbEZpZWxkKCkge1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG5cbiAgICBnZXRUaHJvdWdoRmllbGRzKGZpZWxkTmFtZSwgbW9kZWwsIHRvTW9kZWwsIHRocm91Z2hNb2RlbCkge1xuICAgICAgICBpZiAodGhpcy50aHJvdWdoRmllbGRzKSB7XG4gICAgICAgICAgICBjb25zdCBbZmllbGRBTmFtZSwgZmllbGRCTmFtZV0gPSB0aGlzLnRocm91Z2hGaWVsZHM7XG4gICAgICAgICAgICBjb25zdCBmaWVsZEEgPSB0aHJvdWdoTW9kZWwuZmllbGRzW2ZpZWxkQU5hbWVdO1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgICB0bzogZmllbGRBLnJlZmVyZW5jZXModG9Nb2RlbCkgPyBmaWVsZEFOYW1lIDogZmllbGRCTmFtZSxcbiAgICAgICAgICAgICAgICBmcm9tOiBmaWVsZEEucmVmZXJlbmNlcyh0b01vZGVsKSA/IGZpZWxkQk5hbWUgOiBmaWVsZEFOYW1lLFxuICAgICAgICAgICAgfTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChtb2RlbC5tb2RlbE5hbWUgPT09IHRvTW9kZWwubW9kZWxOYW1lKSB7XG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIHdlIGhhdmUgbm8gd2F5IG9mIGRldGVybWluaW5nIHRoZSByZWxhdGlvbnNoaXAnc1xuICAgICAgICAgICAgICogZGlyZWN0aW9uIGhlcmUsIHNvIHdlIG5lZWQgdG8gYXNzdW1lIHRoYXQgdGhlIHVzZXJcbiAgICAgICAgICAgICAqIGRpZCBub3QgdXNlIGEgY3VzdG9tIHRocm91Z2ggbW9kZWxcbiAgICAgICAgICAgICAqIHNlZSBPUk0jcmVnaXN0ZXJNYW55VG9NYW55TW9kZWxzRm9yXG4gICAgICAgICAgICAgKi9cbiAgICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAgICAgdG86IG0ybVRvRmllbGROYW1lKHRvTW9kZWwubW9kZWxOYW1lKSxcbiAgICAgICAgICAgICAgICBmcm9tOiBtMm1Gcm9tRmllbGROYW1lKG1vZGVsLm1vZGVsTmFtZSksXG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIGRldGVybWluZSB3aGljaCBmaWVsZCByZWZlcmVuY2VzIHdoaWNoIG1vZGVsXG4gICAgICAgICAqIGFuZCBpbmZlciB0aGUgZGlyZWN0aW9ucyBmcm9tIHRoYXRcbiAgICAgICAgICovXG4gICAgICAgIGNvbnN0IHRocm91Z2hNb2RlbEZpZWxkUmVmZXJlbmNpbmcgPSAob3RoZXJNb2RlbCkgPT5cbiAgICAgICAgICAgIE9iamVjdC5rZXlzKHRocm91Z2hNb2RlbC5maWVsZHMpLmZpbmQoKHNvbWVGaWVsZE5hbWUpID0+XG4gICAgICAgICAgICAgICAgdGhyb3VnaE1vZGVsLmZpZWxkc1tzb21lRmllbGROYW1lXS5yZWZlcmVuY2VzKG90aGVyTW9kZWwpXG4gICAgICAgICAgICApO1xuXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICB0bzogdGhyb3VnaE1vZGVsRmllbGRSZWZlcmVuY2luZyh0b01vZGVsKSxcbiAgICAgICAgICAgIGZyb206IHRocm91Z2hNb2RlbEZpZWxkUmVmZXJlbmNpbmcobW9kZWwpLFxuICAgICAgICB9O1xuICAgIH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgTWFueVRvTWFueTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/fields/ManyToMany.js\\n\");\n \n /***/ }),\n \n@@ -4630,7 +4652,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"OneToOne\\\", function() { return OneToOne; });\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _RelationalField__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./RelationalField */ \\\"./src/fields/RelationalField.js\\\");\\n/* harmony import */ var _descriptors__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../descriptors */ \\\"./src/descriptors.js\\\");\\n\\n\\n\\n/**\\n * @memberof module:fields\\n */\\n\\nlet OneToOne = /*#__PURE__*/function (_RelationalField) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default()(OneToOne, _RelationalField);\\n\\n  function OneToOne() {\\n    return _RelationalField.apply(this, arguments) || this;\\n  }\\n\\n  var _proto = OneToOne.prototype;\\n\\n  _proto.getBackwardsFieldName = function getBackwardsFieldName(model) {\\n    return this.relatedName || model.modelName.toLowerCase();\\n  };\\n\\n  _proto.createForwardsDescriptor = function createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_2__[\\\"forwardsOneToOneDescriptor\\\"])(fieldName, toModel.modelName);\\n  };\\n\\n  _proto.createBackwardsDescriptor = function createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_2__[\\\"backwardsOneToOneDescriptor\\\"])(fieldName, model.modelName);\\n  };\\n\\n  return OneToOne;\\n}(_RelationalField__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (OneToOne);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvT25lVG9PbmUuanM/YzllZSJdLCJuYW1lcyI6WyJPbmVUb09uZSIsImdldEJhY2t3YXJkc0ZpZWxkTmFtZSIsIm1vZGVsIiwicmVsYXRlZE5hbWUiLCJtb2RlbE5hbWUiLCJ0b0xvd2VyQ2FzZSIsImNyZWF0ZUZvcndhcmRzRGVzY3JpcHRvciIsImZpZWxkTmFtZSIsInRvTW9kZWwiLCJ0aHJvdWdoTW9kZWwiLCJmb3J3YXJkc09uZVRvT25lRGVzY3JpcHRvciIsImNyZWF0ZUJhY2t3YXJkc0Rlc2NyaXB0b3IiLCJiYWNrd2FyZHNPbmVUb09uZURlc2NyaXB0b3IiLCJSZWxhdGlvbmFsRmllbGQiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7QUFBQTtBQUVBO0FBS0E7Ozs7QUFHTyxJQUFNQSxRQUFiO0FBQUE7O0FBQUE7QUFBQTtBQUFBOztBQUFBOztBQUFBLFNBQ0lDLHFCQURKLEdBQ0ksK0JBQXNCQyxLQUF0QixFQUE2QjtBQUN6QixXQUFPLEtBQUtDLFdBQUwsSUFBb0JELEtBQUssQ0FBQ0UsU0FBTixDQUFnQkMsV0FBaEIsRUFBM0I7QUFDSCxHQUhMOztBQUFBLFNBS0lDLHdCQUxKLEdBS0ksa0NBQXlCQyxTQUF6QixFQUFvQ0wsS0FBcEMsRUFBMkNNLE9BQTNDLEVBQW9EQyxZQUFwRCxFQUFrRTtBQUM5RCxXQUFPQywrRUFBMEIsQ0FBQ0gsU0FBRCxFQUFZQyxPQUFPLENBQUNKLFNBQXBCLENBQWpDO0FBQ0gsR0FQTDs7QUFBQSxTQVNJTyx5QkFUSixHQVNJLG1DQUEwQkosU0FBMUIsRUFBcUNMLEtBQXJDLEVBQTRDTSxPQUE1QyxFQUFxREMsWUFBckQsRUFBbUU7QUFDL0QsV0FBT0csZ0ZBQTJCLENBQUNMLFNBQUQsRUFBWUwsS0FBSyxDQUFDRSxTQUFsQixDQUFsQztBQUNILEdBWEw7O0FBQUE7QUFBQSxFQUE4QlMsd0RBQTlCO0FBY2ViLHVFQUFmIiwiZmlsZSI6Ii4vc3JjL2ZpZWxkcy9PbmVUb09uZS5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBSZWxhdGlvbmFsRmllbGQgZnJvbSBcIi4vUmVsYXRpb25hbEZpZWxkXCI7XG5cbmltcG9ydCB7XG4gICAgZm9yd2FyZHNPbmVUb09uZURlc2NyaXB0b3IsXG4gICAgYmFja3dhcmRzT25lVG9PbmVEZXNjcmlwdG9yLFxufSBmcm9tIFwiLi4vZGVzY3JpcHRvcnNcIjtcblxuLyoqXG4gKiBAbWVtYmVyb2YgbW9kdWxlOmZpZWxkc1xuICovXG5leHBvcnQgY2xhc3MgT25lVG9PbmUgZXh0ZW5kcyBSZWxhdGlvbmFsRmllbGQge1xuICAgIGdldEJhY2t3YXJkc0ZpZWxkTmFtZShtb2RlbCkge1xuICAgICAgICByZXR1cm4gdGhpcy5yZWxhdGVkTmFtZSB8fCBtb2RlbC5tb2RlbE5hbWUudG9Mb3dlckNhc2UoKTtcbiAgICB9XG5cbiAgICBjcmVhdGVGb3J3YXJkc0Rlc2NyaXB0b3IoZmllbGROYW1lLCBtb2RlbCwgdG9Nb2RlbCwgdGhyb3VnaE1vZGVsKSB7XG4gICAgICAgIHJldHVybiBmb3J3YXJkc09uZVRvT25lRGVzY3JpcHRvcihmaWVsZE5hbWUsIHRvTW9kZWwubW9kZWxOYW1lKTtcbiAgICB9XG5cbiAgICBjcmVhdGVCYWNrd2FyZHNEZXNjcmlwdG9yKGZpZWxkTmFtZSwgbW9kZWwsIHRvTW9kZWwsIHRocm91Z2hNb2RlbCkge1xuICAgICAgICByZXR1cm4gYmFja3dhcmRzT25lVG9PbmVEZXNjcmlwdG9yKGZpZWxkTmFtZSwgbW9kZWwubW9kZWxOYW1lKTtcbiAgICB9XG59XG5cbmV4cG9ydCBkZWZhdWx0IE9uZVRvT25lO1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/fields/OneToOne.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"OneToOne\\\", function() { return OneToOne; });\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _RelationalField__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./RelationalField */ \\\"./src/fields/RelationalField.js\\\");\\n/* harmony import */ var _descriptors__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../descriptors */ \\\"./src/descriptors.js\\\");\\n\\n\\n\\n/**\\n * @memberof module:fields\\n */\\n\\nlet OneToOne = /*#__PURE__*/function (_RelationalField) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_0___default()(OneToOne, _RelationalField);\\n\\n  function OneToOne() {\\n    return _RelationalField.apply(this, arguments) || this;\\n  }\\n\\n  var _proto = OneToOne.prototype;\\n\\n  _proto.getBackwardsFieldName = function getBackwardsFieldName(model) {\\n    return this.relatedName || model.modelName.toLowerCase();\\n  };\\n\\n  _proto.createForwardsDescriptor = function createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_2__[\\\"forwardsOneToOneDescriptor\\\"])(fieldName, toModel.modelName);\\n  };\\n\\n  _proto.createBackwardsDescriptor = function createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n    return Object(_descriptors__WEBPACK_IMPORTED_MODULE_2__[\\\"backwardsOneToOneDescriptor\\\"])(fieldName, model.modelName);\\n  };\\n\\n  return OneToOne;\\n}(_RelationalField__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (OneToOne);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvT25lVG9PbmUuanM/YzllZSJdLCJuYW1lcyI6WyJPbmVUb09uZSIsImdldEJhY2t3YXJkc0ZpZWxkTmFtZSIsIm1vZGVsIiwicmVsYXRlZE5hbWUiLCJtb2RlbE5hbWUiLCJ0b0xvd2VyQ2FzZSIsImNyZWF0ZUZvcndhcmRzRGVzY3JpcHRvciIsImZpZWxkTmFtZSIsInRvTW9kZWwiLCJ0aHJvdWdoTW9kZWwiLCJmb3J3YXJkc09uZVRvT25lRGVzY3JpcHRvciIsImNyZWF0ZUJhY2t3YXJkc0Rlc2NyaXB0b3IiLCJiYWNrd2FyZHNPbmVUb09uZURlc2NyaXB0b3IiLCJSZWxhdGlvbmFsRmllbGQiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7QUFBQTtBQUVBO0FBS0E7QUFDQTtBQUNBOztBQUNPLElBQU1BLFFBQWI7QUFBQTs7QUFBQTtBQUFBO0FBQUE7O0FBQUE7O0FBQUEsU0FDSUMscUJBREosR0FDSSwrQkFBc0JDLEtBQXRCLEVBQTZCO0FBQ3pCLFdBQU8sS0FBS0MsV0FBTCxJQUFvQkQsS0FBSyxDQUFDRSxTQUFOLENBQWdCQyxXQUFoQixFQUEzQjtBQUNILEdBSEw7O0FBQUEsU0FLSUMsd0JBTEosR0FLSSxrQ0FBeUJDLFNBQXpCLEVBQW9DTCxLQUFwQyxFQUEyQ00sT0FBM0MsRUFBb0RDLFlBQXBELEVBQWtFO0FBQzlELFdBQU9DLCtFQUEwQixDQUFDSCxTQUFELEVBQVlDLE9BQU8sQ0FBQ0osU0FBcEIsQ0FBakM7QUFDSCxHQVBMOztBQUFBLFNBU0lPLHlCQVRKLEdBU0ksbUNBQTBCSixTQUExQixFQUFxQ0wsS0FBckMsRUFBNENNLE9BQTVDLEVBQXFEQyxZQUFyRCxFQUFtRTtBQUMvRCxXQUFPRyxnRkFBMkIsQ0FBQ0wsU0FBRCxFQUFZTCxLQUFLLENBQUNFLFNBQWxCLENBQWxDO0FBQ0gsR0FYTDs7QUFBQTtBQUFBLEVBQThCUyx3REFBOUI7QUFjZWIsdUVBQWYiLCJmaWxlIjoiLi9zcmMvZmllbGRzL09uZVRvT25lLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFJlbGF0aW9uYWxGaWVsZCBmcm9tIFwiLi9SZWxhdGlvbmFsRmllbGRcIjtcblxuaW1wb3J0IHtcbiAgICBmb3J3YXJkc09uZVRvT25lRGVzY3JpcHRvcixcbiAgICBiYWNrd2FyZHNPbmVUb09uZURlc2NyaXB0b3IsXG59IGZyb20gXCIuLi9kZXNjcmlwdG9yc1wiO1xuXG4vKipcbiAqIEBtZW1iZXJvZiBtb2R1bGU6ZmllbGRzXG4gKi9cbmV4cG9ydCBjbGFzcyBPbmVUb09uZSBleHRlbmRzIFJlbGF0aW9uYWxGaWVsZCB7XG4gICAgZ2V0QmFja3dhcmRzRmllbGROYW1lKG1vZGVsKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnJlbGF0ZWROYW1lIHx8IG1vZGVsLm1vZGVsTmFtZS50b0xvd2VyQ2FzZSgpO1xuICAgIH1cblxuICAgIGNyZWF0ZUZvcndhcmRzRGVzY3JpcHRvcihmaWVsZE5hbWUsIG1vZGVsLCB0b01vZGVsLCB0aHJvdWdoTW9kZWwpIHtcbiAgICAgICAgcmV0dXJuIGZvcndhcmRzT25lVG9PbmVEZXNjcmlwdG9yKGZpZWxkTmFtZSwgdG9Nb2RlbC5tb2RlbE5hbWUpO1xuICAgIH1cblxuICAgIGNyZWF0ZUJhY2t3YXJkc0Rlc2NyaXB0b3IoZmllbGROYW1lLCBtb2RlbCwgdG9Nb2RlbCwgdGhyb3VnaE1vZGVsKSB7XG4gICAgICAgIHJldHVybiBiYWNrd2FyZHNPbmVUb09uZURlc2NyaXB0b3IoZmllbGROYW1lLCBtb2RlbC5tb2RlbE5hbWUpO1xuICAgIH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgT25lVG9PbmU7XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./src/fields/OneToOne.js\\n\");\n \n /***/ }),\n \n@@ -4642,7 +4664,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"RelationalField\\\", function() { return RelationalField; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _Field__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Field */ \\\"./src/fields/Field.js\\\");\\n/* harmony import */ var _DefaultFieldInstaller__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./DefaultFieldInstaller */ \\\"./src/fields/DefaultFieldInstaller.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n/* eslint-disable max-classes-per-file */\\n\\n\\n\\n/**\\n * @private\\n * @memberof module:fields\\n */\\n\\nlet RelationalField = /*#__PURE__*/function (_Field) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(RelationalField, _Field);\\n\\n  function RelationalField(...args) {\\n    var _this;\\n\\n    _this = _Field.call(this) || this;\\n\\n    if (args.length === 1 && typeof args[0] === \\\"object\\\") {\\n      const opts = args[0];\\n      _this.toModelName = Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"normalizeModelReference\\\"])(opts.to);\\n      _this.relatedName = opts.relatedName;\\n      _this.through = Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"normalizeModelReference\\\"])(opts.through);\\n      _this.throughFields = opts.throughFields;\\n      _this.as = opts.as;\\n    } else {\\n      [_this.toModelName, _this.relatedName] = [Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"normalizeModelReference\\\"])(args[0]), args[1]];\\n    }\\n\\n    return _this;\\n  }\\n\\n  var _proto = RelationalField.prototype;\\n\\n  _proto.getBackwardsFieldName = function getBackwardsFieldName(model) {\\n    return this.relatedName || Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"reverseFieldName\\\"])(model.modelName);\\n  };\\n\\n  _proto.createBackwardsVirtualField = function createBackwardsVirtualField(fieldName, model, toModel, throughModel) {\\n    const ThisField = this.getClass();\\n    return new ThisField(model.modelName, fieldName);\\n  };\\n\\n  _proto.references = function references(model) {\\n    return this.toModelName === model.modelName;\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(RelationalField, [{\\n    key: \\\"installsBackwardsVirtualField\\\",\\n    get: function () {\\n      return true;\\n    }\\n  }, {\\n    key: \\\"installsBackwardsDescriptor\\\",\\n    get: function () {\\n      return true;\\n    }\\n  }, {\\n    key: \\\"installerClass\\\",\\n    get: function () {\\n      return /*#__PURE__*/function (_DefaultFieldInstalle) {\\n        _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(AliasedForwardsDescriptorInstaller, _DefaultFieldInstalle);\\n\\n        function AliasedForwardsDescriptorInstaller() {\\n          return _DefaultFieldInstalle.apply(this, arguments) || this;\\n        }\\n\\n        var _proto2 = AliasedForwardsDescriptorInstaller.prototype;\\n\\n        _proto2.installForwardsDescriptor = function installForwardsDescriptor() {\\n          Object.defineProperty(this.model.prototype, this.field.as || this.fieldName, // use supplied name if possible\\n          this.field.createForwardsDescriptor(this.fieldName, this.model, this.toModel, this.throughModel));\\n        };\\n\\n        return AliasedForwardsDescriptorInstaller;\\n      }(_DefaultFieldInstaller__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]);\\n    }\\n  }]);\\n\\n  return RelationalField;\\n}(_Field__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (RelationalField);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvUmVsYXRpb25hbEZpZWxkLmpzPzQzMDQiXSwibmFtZXMiOlsiUmVsYXRpb25hbEZpZWxkIiwiYXJncyIsImxlbmd0aCIsIm9wdHMiLCJ0b01vZGVsTmFtZSIsIm5vcm1hbGl6ZU1vZGVsUmVmZXJlbmNlIiwidG8iLCJyZWxhdGVkTmFtZSIsInRocm91Z2giLCJ0aHJvdWdoRmllbGRzIiwiYXMiLCJnZXRCYWNrd2FyZHNGaWVsZE5hbWUiLCJtb2RlbCIsInJldmVyc2VGaWVsZE5hbWUiLCJtb2RlbE5hbWUiLCJjcmVhdGVCYWNrd2FyZHNWaXJ0dWFsRmllbGQiLCJmaWVsZE5hbWUiLCJ0b01vZGVsIiwidGhyb3VnaE1vZGVsIiwiVGhpc0ZpZWxkIiwiZ2V0Q2xhc3MiLCJyZWZlcmVuY2VzIiwiaW5zdGFsbEZvcndhcmRzRGVzY3JpcHRvciIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwicHJvdG90eXBlIiwiZmllbGQiLCJjcmVhdGVGb3J3YXJkc0Rlc2NyaXB0b3IiLCJEZWZhdWx0RmllbGRJbnN0YWxsZXIiLCJGaWVsZCJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQUE7QUFDQTtBQUNBO0FBRUE7QUFFQTs7Ozs7QUFJTyxJQUFNQSxlQUFiO0FBQUE7O0FBQ0ksMkJBQVksR0FBR0MsSUFBZixFQUFxQjtBQUFBOztBQUNqQjs7QUFDQSxRQUFJQSxJQUFJLENBQUNDLE1BQUwsS0FBZ0IsQ0FBaEIsSUFBcUIsT0FBT0QsSUFBSSxDQUFDLENBQUQsQ0FBWCxLQUFtQixRQUE1QyxFQUFzRDtBQUNsRCxZQUFNRSxJQUFJLEdBQUdGLElBQUksQ0FBQyxDQUFELENBQWpCO0FBQ0EsWUFBS0csV0FBTCxHQUFtQkMsc0VBQXVCLENBQUNGLElBQUksQ0FBQ0csRUFBTixDQUExQztBQUNBLFlBQUtDLFdBQUwsR0FBbUJKLElBQUksQ0FBQ0ksV0FBeEI7QUFDQSxZQUFLQyxPQUFMLEdBQWVILHNFQUF1QixDQUFDRixJQUFJLENBQUNLLE9BQU4sQ0FBdEM7QUFDQSxZQUFLQyxhQUFMLEdBQXFCTixJQUFJLENBQUNNLGFBQTFCO0FBQ0EsWUFBS0MsRUFBTCxHQUFVUCxJQUFJLENBQUNPLEVBQWY7QUFDSCxLQVBELE1BT087QUFDSCxPQUFDLE1BQUtOLFdBQU4sRUFBbUIsTUFBS0csV0FBeEIsSUFBdUMsQ0FDbkNGLHNFQUF1QixDQUFDSixJQUFJLENBQUMsQ0FBRCxDQUFMLENBRFksRUFFbkNBLElBQUksQ0FBQyxDQUFELENBRitCLENBQXZDO0FBSUg7O0FBZGdCO0FBZXBCOztBQWhCTDs7QUFBQSxTQWtCSVUscUJBbEJKLEdBa0JJLCtCQUFzQkMsS0FBdEIsRUFBNkI7QUFDekIsV0FBTyxLQUFLTCxXQUFMLElBQW9CTSwrREFBZ0IsQ0FBQ0QsS0FBSyxDQUFDRSxTQUFQLENBQTNDO0FBQ0gsR0FwQkw7O0FBQUEsU0FzQklDLDJCQXRCSixHQXNCSSxxQ0FBNEJDLFNBQTVCLEVBQXVDSixLQUF2QyxFQUE4Q0ssT0FBOUMsRUFBdURDLFlBQXZELEVBQXFFO0FBQ2pFLFVBQU1DLFNBQVMsR0FBRyxLQUFLQyxRQUFMLEVBQWxCO0FBQ0EsV0FBTyxJQUFJRCxTQUFKLENBQWNQLEtBQUssQ0FBQ0UsU0FBcEIsRUFBK0JFLFNBQS9CLENBQVA7QUFDSCxHQXpCTDs7QUFBQSxTQW1DSUssVUFuQ0osR0FtQ0ksb0JBQVdULEtBQVgsRUFBa0I7QUFDZCxXQUFPLEtBQUtSLFdBQUwsS0FBcUJRLEtBQUssQ0FBQ0UsU0FBbEM7QUFDSCxHQXJDTDs7QUFBQTtBQUFBO0FBQUEscUJBMkJ3QztBQUNoQyxhQUFPLElBQVA7QUFDSDtBQTdCTDtBQUFBO0FBQUEscUJBK0JzQztBQUM5QixhQUFPLElBQVA7QUFDSDtBQWpDTDtBQUFBO0FBQUEscUJBdUN5QjtBQUNqQjtBQUFBOztBQUFBO0FBQUE7QUFBQTs7QUFBQTs7QUFBQSxnQkFDSVEseUJBREosR0FDSSxxQ0FBNEI7QUFDeEJDLGdCQUFNLENBQUNDLGNBQVAsQ0FDSSxLQUFLWixLQUFMLENBQVdhLFNBRGYsRUFFSSxLQUFLQyxLQUFMLENBQVdoQixFQUFYLElBQWlCLEtBQUtNLFNBRjFCLEVBRXFDO0FBQ2pDLGVBQUtVLEtBQUwsQ0FBV0Msd0JBQVgsQ0FDSSxLQUFLWCxTQURULEVBRUksS0FBS0osS0FGVCxFQUdJLEtBQUtLLE9BSFQsRUFJSSxLQUFLQyxZQUpULENBSEo7QUFVSCxTQVpMOztBQUFBO0FBQUEsUUFBd0RVLDhEQUF4RDtBQWNIO0FBdERMOztBQUFBO0FBQUEsRUFBcUNDLDhDQUFyQztBQXlEZTdCLDhFQUFmIiwiZmlsZSI6Ii4vc3JjL2ZpZWxkcy9SZWxhdGlvbmFsRmllbGQuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyIvKiBlc2xpbnQtZGlzYWJsZSBtYXgtY2xhc3Nlcy1wZXItZmlsZSAqL1xuaW1wb3J0IEZpZWxkIGZyb20gXCIuL0ZpZWxkXCI7XG5pbXBvcnQgRGVmYXVsdEZpZWxkSW5zdGFsbGVyIGZyb20gXCIuL0RlZmF1bHRGaWVsZEluc3RhbGxlclwiO1xuXG5pbXBvcnQgeyByZXZlcnNlRmllbGROYW1lLCBub3JtYWxpemVNb2RlbFJlZmVyZW5jZSB9IGZyb20gXCIuLi91dGlsc1wiO1xuXG4vKipcbiAqIEBwcml2YXRlXG4gKiBAbWVtYmVyb2YgbW9kdWxlOmZpZWxkc1xuICovXG5leHBvcnQgY2xhc3MgUmVsYXRpb25hbEZpZWxkIGV4dGVuZHMgRmllbGQge1xuICAgIGNvbnN0cnVjdG9yKC4uLmFyZ3MpIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgaWYgKGFyZ3MubGVuZ3RoID09PSAxICYmIHR5cGVvZiBhcmdzWzBdID09PSBcIm9iamVjdFwiKSB7XG4gICAgICAgICAgICBjb25zdCBvcHRzID0gYXJnc1swXTtcbiAgICAgICAgICAgIHRoaXMudG9Nb2RlbE5hbWUgPSBub3JtYWxpemVNb2RlbFJlZmVyZW5jZShvcHRzLnRvKTtcbiAgICAgICAgICAgIHRoaXMucmVsYXRlZE5hbWUgPSBvcHRzLnJlbGF0ZWROYW1lO1xuICAgICAgICAgICAgdGhpcy50aHJvdWdoID0gbm9ybWFsaXplTW9kZWxSZWZlcmVuY2Uob3B0cy50aHJvdWdoKTtcbiAgICAgICAgICAgIHRoaXMudGhyb3VnaEZpZWxkcyA9IG9wdHMudGhyb3VnaEZpZWxkcztcbiAgICAgICAgICAgIHRoaXMuYXMgPSBvcHRzLmFzO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgW3RoaXMudG9Nb2RlbE5hbWUsIHRoaXMucmVsYXRlZE5hbWVdID0gW1xuICAgICAgICAgICAgICAgIG5vcm1hbGl6ZU1vZGVsUmVmZXJlbmNlKGFyZ3NbMF0pLFxuICAgICAgICAgICAgICAgIGFyZ3NbMV0sXG4gICAgICAgICAgICBdO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgZ2V0QmFja3dhcmRzRmllbGROYW1lKG1vZGVsKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnJlbGF0ZWROYW1lIHx8IHJldmVyc2VGaWVsZE5hbWUobW9kZWwubW9kZWxOYW1lKTtcbiAgICB9XG5cbiAgICBjcmVhdGVCYWNrd2FyZHNWaXJ0dWFsRmllbGQoZmllbGROYW1lLCBtb2RlbCwgdG9Nb2RlbCwgdGhyb3VnaE1vZGVsKSB7XG4gICAgICAgIGNvbnN0IFRoaXNGaWVsZCA9IHRoaXMuZ2V0Q2xhc3MoKTtcbiAgICAgICAgcmV0dXJuIG5ldyBUaGlzRmllbGQobW9kZWwubW9kZWxOYW1lLCBmaWVsZE5hbWUpO1xuICAgIH1cblxuICAgIGdldCBpbnN0YWxsc0JhY2t3YXJkc1ZpcnR1YWxGaWVsZCgpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgZ2V0IGluc3RhbGxzQmFja3dhcmRzRGVzY3JpcHRvcigpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgcmVmZXJlbmNlcyhtb2RlbCkge1xuICAgICAgICByZXR1cm4gdGhpcy50b01vZGVsTmFtZSA9PT0gbW9kZWwubW9kZWxOYW1lO1xuICAgIH1cblxuICAgIGdldCBpbnN0YWxsZXJDbGFzcygpIHtcbiAgICAgICAgcmV0dXJuIGNsYXNzIEFsaWFzZWRGb3J3YXJkc0Rlc2NyaXB0b3JJbnN0YWxsZXIgZXh0ZW5kcyBEZWZhdWx0RmllbGRJbnN0YWxsZXIge1xuICAgICAgICAgICAgaW5zdGFsbEZvcndhcmRzRGVzY3JpcHRvcigpIHtcbiAgICAgICAgICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoXG4gICAgICAgICAgICAgICAgICAgIHRoaXMubW9kZWwucHJvdG90eXBlLFxuICAgICAgICAgICAgICAgICAgICB0aGlzLmZpZWxkLmFzIHx8IHRoaXMuZmllbGROYW1lLCAvLyB1c2Ugc3VwcGxpZWQgbmFtZSBpZiBwb3NzaWJsZVxuICAgICAgICAgICAgICAgICAgICB0aGlzLmZpZWxkLmNyZWF0ZUZvcndhcmRzRGVzY3JpcHRvcihcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuZmllbGROYW1lLFxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5tb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudG9Nb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudGhyb3VnaE1vZGVsXG4gICAgICAgICAgICAgICAgICAgIClcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfVxuICAgICAgICB9O1xuICAgIH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgUmVsYXRpb25hbEZpZWxkO1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/fields/RelationalField.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"RelationalField\\\", function() { return RelationalField; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _Field__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Field */ \\\"./src/fields/Field.js\\\");\\n/* harmony import */ var _DefaultFieldInstaller__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./DefaultFieldInstaller */ \\\"./src/fields/DefaultFieldInstaller.js\\\");\\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils */ \\\"./src/utils.js\\\");\\n\\n\\n\\n/* eslint-disable max-classes-per-file */\\n\\n\\n\\n/**\\n * @private\\n * @memberof module:fields\\n */\\n\\nlet RelationalField = /*#__PURE__*/function (_Field) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(RelationalField, _Field);\\n\\n  function RelationalField(...args) {\\n    var _this;\\n\\n    _this = _Field.call(this) || this;\\n\\n    if (args.length === 1 && typeof args[0] === \\\"object\\\") {\\n      const opts = args[0];\\n      _this.toModelName = Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"normalizeModelReference\\\"])(opts.to);\\n      _this.relatedName = opts.relatedName;\\n      _this.through = Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"normalizeModelReference\\\"])(opts.through);\\n      _this.throughFields = opts.throughFields;\\n      _this.as = opts.as;\\n    } else {\\n      [_this.toModelName, _this.relatedName] = [Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"normalizeModelReference\\\"])(args[0]), args[1]];\\n    }\\n\\n    return _this;\\n  }\\n\\n  var _proto = RelationalField.prototype;\\n\\n  _proto.getBackwardsFieldName = function getBackwardsFieldName(model) {\\n    return this.relatedName || Object(_utils__WEBPACK_IMPORTED_MODULE_4__[\\\"reverseFieldName\\\"])(model.modelName);\\n  };\\n\\n  _proto.createBackwardsVirtualField = function createBackwardsVirtualField(fieldName, model, toModel, throughModel) {\\n    const ThisField = this.getClass();\\n    return new ThisField(model.modelName, fieldName);\\n  };\\n\\n  _proto.references = function references(model) {\\n    return this.toModelName === model.modelName;\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(RelationalField, [{\\n    key: \\\"installsBackwardsVirtualField\\\",\\n    get: function () {\\n      return true;\\n    }\\n  }, {\\n    key: \\\"installsBackwardsDescriptor\\\",\\n    get: function () {\\n      return true;\\n    }\\n  }, {\\n    key: \\\"installerClass\\\",\\n    get: function () {\\n      return /*#__PURE__*/function (_DefaultFieldInstalle) {\\n        _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(AliasedForwardsDescriptorInstaller, _DefaultFieldInstalle);\\n\\n        function AliasedForwardsDescriptorInstaller() {\\n          return _DefaultFieldInstalle.apply(this, arguments) || this;\\n        }\\n\\n        var _proto2 = AliasedForwardsDescriptorInstaller.prototype;\\n\\n        _proto2.installForwardsDescriptor = function installForwardsDescriptor() {\\n          Object.defineProperty(this.model.prototype, this.field.as || this.fieldName, // use supplied name if possible\\n          this.field.createForwardsDescriptor(this.fieldName, this.model, this.toModel, this.throughModel));\\n        };\\n\\n        return AliasedForwardsDescriptorInstaller;\\n      }(_DefaultFieldInstaller__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]);\\n    }\\n  }]);\\n\\n  return RelationalField;\\n}(_Field__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n/* harmony default export */ __webpack_exports__[\\\"default\\\"] = (RelationalField);//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvUmVsYXRpb25hbEZpZWxkLmpzPzQzMDQiXSwibmFtZXMiOlsiUmVsYXRpb25hbEZpZWxkIiwiYXJncyIsImxlbmd0aCIsIm9wdHMiLCJ0b01vZGVsTmFtZSIsIm5vcm1hbGl6ZU1vZGVsUmVmZXJlbmNlIiwidG8iLCJyZWxhdGVkTmFtZSIsInRocm91Z2giLCJ0aHJvdWdoRmllbGRzIiwiYXMiLCJnZXRCYWNrd2FyZHNGaWVsZE5hbWUiLCJtb2RlbCIsInJldmVyc2VGaWVsZE5hbWUiLCJtb2RlbE5hbWUiLCJjcmVhdGVCYWNrd2FyZHNWaXJ0dWFsRmllbGQiLCJmaWVsZE5hbWUiLCJ0b01vZGVsIiwidGhyb3VnaE1vZGVsIiwiVGhpc0ZpZWxkIiwiZ2V0Q2xhc3MiLCJyZWZlcmVuY2VzIiwiaW5zdGFsbEZvcndhcmRzRGVzY3JpcHRvciIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwicHJvdG90eXBlIiwiZmllbGQiLCJjcmVhdGVGb3J3YXJkc0Rlc2NyaXB0b3IiLCJEZWZhdWx0RmllbGRJbnN0YWxsZXIiLCJGaWVsZCJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQUE7QUFDQTtBQUNBO0FBRUE7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFDTyxJQUFNQSxlQUFiO0FBQUE7O0FBQ0ksMkJBQVksR0FBR0MsSUFBZixFQUFxQjtBQUFBOztBQUNqQjs7QUFDQSxRQUFJQSxJQUFJLENBQUNDLE1BQUwsS0FBZ0IsQ0FBaEIsSUFBcUIsT0FBT0QsSUFBSSxDQUFDLENBQUQsQ0FBWCxLQUFtQixRQUE1QyxFQUFzRDtBQUNsRCxZQUFNRSxJQUFJLEdBQUdGLElBQUksQ0FBQyxDQUFELENBQWpCO0FBQ0EsWUFBS0csV0FBTCxHQUFtQkMsc0VBQXVCLENBQUNGLElBQUksQ0FBQ0csRUFBTixDQUExQztBQUNBLFlBQUtDLFdBQUwsR0FBbUJKLElBQUksQ0FBQ0ksV0FBeEI7QUFDQSxZQUFLQyxPQUFMLEdBQWVILHNFQUF1QixDQUFDRixJQUFJLENBQUNLLE9BQU4sQ0FBdEM7QUFDQSxZQUFLQyxhQUFMLEdBQXFCTixJQUFJLENBQUNNLGFBQTFCO0FBQ0EsWUFBS0MsRUFBTCxHQUFVUCxJQUFJLENBQUNPLEVBQWY7QUFDSCxLQVBELE1BT087QUFDSCxPQUFDLE1BQUtOLFdBQU4sRUFBbUIsTUFBS0csV0FBeEIsSUFBdUMsQ0FDbkNGLHNFQUF1QixDQUFDSixJQUFJLENBQUMsQ0FBRCxDQUFMLENBRFksRUFFbkNBLElBQUksQ0FBQyxDQUFELENBRitCLENBQXZDO0FBSUg7O0FBZGdCO0FBZXBCOztBQWhCTDs7QUFBQSxTQWtCSVUscUJBbEJKLEdBa0JJLCtCQUFzQkMsS0FBdEIsRUFBNkI7QUFDekIsV0FBTyxLQUFLTCxXQUFMLElBQW9CTSwrREFBZ0IsQ0FBQ0QsS0FBSyxDQUFDRSxTQUFQLENBQTNDO0FBQ0gsR0FwQkw7O0FBQUEsU0FzQklDLDJCQXRCSixHQXNCSSxxQ0FBNEJDLFNBQTVCLEVBQXVDSixLQUF2QyxFQUE4Q0ssT0FBOUMsRUFBdURDLFlBQXZELEVBQXFFO0FBQ2pFLFVBQU1DLFNBQVMsR0FBRyxLQUFLQyxRQUFMLEVBQWxCO0FBQ0EsV0FBTyxJQUFJRCxTQUFKLENBQWNQLEtBQUssQ0FBQ0UsU0FBcEIsRUFBK0JFLFNBQS9CLENBQVA7QUFDSCxHQXpCTDs7QUFBQSxTQW1DSUssVUFuQ0osR0FtQ0ksb0JBQVdULEtBQVgsRUFBa0I7QUFDZCxXQUFPLEtBQUtSLFdBQUwsS0FBcUJRLEtBQUssQ0FBQ0UsU0FBbEM7QUFDSCxHQXJDTDs7QUFBQTtBQUFBO0FBQUEsU0EyQkksWUFBb0M7QUFDaEMsYUFBTyxJQUFQO0FBQ0g7QUE3Qkw7QUFBQTtBQUFBLFNBK0JJLFlBQWtDO0FBQzlCLGFBQU8sSUFBUDtBQUNIO0FBakNMO0FBQUE7QUFBQSxTQXVDSSxZQUFxQjtBQUNqQjtBQUFBOztBQUFBO0FBQUE7QUFBQTs7QUFBQTs7QUFBQSxnQkFDSVEseUJBREosR0FDSSxxQ0FBNEI7QUFDeEJDLGdCQUFNLENBQUNDLGNBQVAsQ0FDSSxLQUFLWixLQUFMLENBQVdhLFNBRGYsRUFFSSxLQUFLQyxLQUFMLENBQVdoQixFQUFYLElBQWlCLEtBQUtNLFNBRjFCLEVBRXFDO0FBQ2pDLGVBQUtVLEtBQUwsQ0FBV0Msd0JBQVgsQ0FDSSxLQUFLWCxTQURULEVBRUksS0FBS0osS0FGVCxFQUdJLEtBQUtLLE9BSFQsRUFJSSxLQUFLQyxZQUpULENBSEo7QUFVSCxTQVpMOztBQUFBO0FBQUEsUUFBd0RVLDhEQUF4RDtBQWNIO0FBdERMOztBQUFBO0FBQUEsRUFBcUNDLDhDQUFyQztBQXlEZTdCLDhFQUFmIiwiZmlsZSI6Ii4vc3JjL2ZpZWxkcy9SZWxhdGlvbmFsRmllbGQuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyIvKiBlc2xpbnQtZGlzYWJsZSBtYXgtY2xhc3Nlcy1wZXItZmlsZSAqL1xuaW1wb3J0IEZpZWxkIGZyb20gXCIuL0ZpZWxkXCI7XG5pbXBvcnQgRGVmYXVsdEZpZWxkSW5zdGFsbGVyIGZyb20gXCIuL0RlZmF1bHRGaWVsZEluc3RhbGxlclwiO1xuXG5pbXBvcnQgeyByZXZlcnNlRmllbGROYW1lLCBub3JtYWxpemVNb2RlbFJlZmVyZW5jZSB9IGZyb20gXCIuLi91dGlsc1wiO1xuXG4vKipcbiAqIEBwcml2YXRlXG4gKiBAbWVtYmVyb2YgbW9kdWxlOmZpZWxkc1xuICovXG5leHBvcnQgY2xhc3MgUmVsYXRpb25hbEZpZWxkIGV4dGVuZHMgRmllbGQge1xuICAgIGNvbnN0cnVjdG9yKC4uLmFyZ3MpIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgaWYgKGFyZ3MubGVuZ3RoID09PSAxICYmIHR5cGVvZiBhcmdzWzBdID09PSBcIm9iamVjdFwiKSB7XG4gICAgICAgICAgICBjb25zdCBvcHRzID0gYXJnc1swXTtcbiAgICAgICAgICAgIHRoaXMudG9Nb2RlbE5hbWUgPSBub3JtYWxpemVNb2RlbFJlZmVyZW5jZShvcHRzLnRvKTtcbiAgICAgICAgICAgIHRoaXMucmVsYXRlZE5hbWUgPSBvcHRzLnJlbGF0ZWROYW1lO1xuICAgICAgICAgICAgdGhpcy50aHJvdWdoID0gbm9ybWFsaXplTW9kZWxSZWZlcmVuY2Uob3B0cy50aHJvdWdoKTtcbiAgICAgICAgICAgIHRoaXMudGhyb3VnaEZpZWxkcyA9IG9wdHMudGhyb3VnaEZpZWxkcztcbiAgICAgICAgICAgIHRoaXMuYXMgPSBvcHRzLmFzO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgW3RoaXMudG9Nb2RlbE5hbWUsIHRoaXMucmVsYXRlZE5hbWVdID0gW1xuICAgICAgICAgICAgICAgIG5vcm1hbGl6ZU1vZGVsUmVmZXJlbmNlKGFyZ3NbMF0pLFxuICAgICAgICAgICAgICAgIGFyZ3NbMV0sXG4gICAgICAgICAgICBdO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgZ2V0QmFja3dhcmRzRmllbGROYW1lKG1vZGVsKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnJlbGF0ZWROYW1lIHx8IHJldmVyc2VGaWVsZE5hbWUobW9kZWwubW9kZWxOYW1lKTtcbiAgICB9XG5cbiAgICBjcmVhdGVCYWNrd2FyZHNWaXJ0dWFsRmllbGQoZmllbGROYW1lLCBtb2RlbCwgdG9Nb2RlbCwgdGhyb3VnaE1vZGVsKSB7XG4gICAgICAgIGNvbnN0IFRoaXNGaWVsZCA9IHRoaXMuZ2V0Q2xhc3MoKTtcbiAgICAgICAgcmV0dXJuIG5ldyBUaGlzRmllbGQobW9kZWwubW9kZWxOYW1lLCBmaWVsZE5hbWUpO1xuICAgIH1cblxuICAgIGdldCBpbnN0YWxsc0JhY2t3YXJkc1ZpcnR1YWxGaWVsZCgpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgZ2V0IGluc3RhbGxzQmFja3dhcmRzRGVzY3JpcHRvcigpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgcmVmZXJlbmNlcyhtb2RlbCkge1xuICAgICAgICByZXR1cm4gdGhpcy50b01vZGVsTmFtZSA9PT0gbW9kZWwubW9kZWxOYW1lO1xuICAgIH1cblxuICAgIGdldCBpbnN0YWxsZXJDbGFzcygpIHtcbiAgICAgICAgcmV0dXJuIGNsYXNzIEFsaWFzZWRGb3J3YXJkc0Rlc2NyaXB0b3JJbnN0YWxsZXIgZXh0ZW5kcyBEZWZhdWx0RmllbGRJbnN0YWxsZXIge1xuICAgICAgICAgICAgaW5zdGFsbEZvcndhcmRzRGVzY3JpcHRvcigpIHtcbiAgICAgICAgICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoXG4gICAgICAgICAgICAgICAgICAgIHRoaXMubW9kZWwucHJvdG90eXBlLFxuICAgICAgICAgICAgICAgICAgICB0aGlzLmZpZWxkLmFzIHx8IHRoaXMuZmllbGROYW1lLCAvLyB1c2Ugc3VwcGxpZWQgbmFtZSBpZiBwb3NzaWJsZVxuICAgICAgICAgICAgICAgICAgICB0aGlzLmZpZWxkLmNyZWF0ZUZvcndhcmRzRGVzY3JpcHRvcihcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuZmllbGROYW1lLFxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5tb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudG9Nb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudGhyb3VnaE1vZGVsXG4gICAgICAgICAgICAgICAgICAgIClcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfVxuICAgICAgICB9O1xuICAgIH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgUmVsYXRpb25hbEZpZWxkO1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/fields/RelationalField.js\\n\");\n \n /***/ }),\n \n@@ -4654,7 +4676,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"fk\\\", function() { return fk; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"attr\\\", function() { return attr; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"many\\\", function() { return many; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"oneToOne\\\", function() { return oneToOne; });\\n/* harmony import */ var _Attribute__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Attribute */ \\\"./src/fields/Attribute.js\\\");\\n/* harmony import */ var _ForeignKey__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ForeignKey */ \\\"./src/fields/ForeignKey.js\\\");\\n/* harmony import */ var _ManyToMany__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ManyToMany */ \\\"./src/fields/ManyToMany.js\\\");\\n/* harmony import */ var _OneToOne__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./OneToOne */ \\\"./src/fields/OneToOne.js\\\");\\n\\n\\n\\n\\n/**\\n * Contains the logic for how fields on {@link Model}s work\\n * and which descriptors must be installed.\\n *\\n * If your goal is to define fields on a Model class,\\n * please use the more convenient methods {@link attr},\\n * {@link fk}, {@link many} and {@link oneToOne}.\\n *\\n * @module fields\\n */\\n\\n/**\\n * Defines a value attribute on the model.\\n * Though not required, it is recommended to define this for each non-foreign key you wish to use.\\n * Getters and setters need to be defined on each Model\\n * instantiation for undeclared data fields, which is slower.\\n * You can use the optional `getDefault` parameter to fill in unpassed values\\n * to {@link Model.create}, such as for generating ID's with UUID:\\n *\\n * ```javascript\\n * import getUUID from 'your-uuid-package-of-choice';\\n *\\n * fields = {\\n *   id: attr({ getDefault: () => getUUID() }),\\n *   title: attr(),\\n * }\\n * ```\\n *\\n * @param  {Object} [opts]\\n * @param {Function} [opts.getDefault] - If you give a function here, its return\\n *                                       value from calling with zero arguments will\\n *                                       be used as the value when creating a new Model\\n *                                       instance with {@link Model#create} if the field\\n *                                       value is not passed.\\n * @return {Attribute}\\n */\\n\\nfunction attr(opts) {\\n  return new _Attribute__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"](opts);\\n}\\n/**\\n * Defines a foreign key on a model, which points\\n * to a single entity on another model.\\n *\\n * You can pass arguments as either a single object,\\n * or two arguments.\\n *\\n * If you pass two arguments, the first one is the name\\n * of the Model the foreign key is pointing to, and\\n * the second one is an optional related name, which will\\n * be used to access the Model the foreign key\\n * is being defined from, from the target Model.\\n *\\n * If the related name is not passed, it will be set as\\n * `${toModelName}Set`.\\n *\\n * If you pass an object to `fk`, it has to be in the form\\n *\\n * ```javascript\\n * fields = {\\n *   author: fk({ to: 'Author', relatedName: 'books' })\\n * }\\n * ```\\n *\\n * Which is equal to\\n *\\n * ```javascript\\n * fields = {\\n *   author: fk('Author', 'books'),\\n * }\\n * ```\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string} [options.relatedName] - The property name that will be used to access\\n *                                         a QuerySet for all source models that reference\\n *                                         the respective target Model's instance.\\n * @param {string} [relatedName] - If you didn't pass an object as the first argument,\\n *                                 this is the property name that will be used to\\n *                                 access a QuerySet for all source models that reference\\n *                                 the respective target Model's instance.\\n * @return {ForeignKey}\\n */\\n\\n\\nfunction fk(...args) {\\n  return new _ForeignKey__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"](...args);\\n}\\n/**\\n * Defines a many-to-many relationship between\\n * this (source) and another (target) model.\\n *\\n * The relationship is modeled with an extra model called the through model.\\n * The through model has foreign keys to both the source and target models.\\n *\\n * You can define your own through model if you want to associate more information\\n * to the relationship. A custom through model must have at least two foreign keys,\\n * one pointing to the source Model, and one pointing to the target Model.\\n *\\n * Like `fk`, this function accepts one or two string arguments specifying the other\\n * Model and the related name, or a single object argument that allows you to pass\\n * a custom through model.\\n *\\n * If you have more than one foreign key pointing to a source or target Model in the\\n * through Model, you must pass the option `throughFields`, which is an array of two\\n * strings, where the strings are the field names that identify the foreign keys to\\n * be used for the many-to-many relationship. Redux-ORM will figure out which field name\\n * points to which model by checking the \\\"through model\\\" definition.\\n *\\n * ```javascript\\n * class Authorship extends Model {}\\n * Authorship.modelName = 'Authorship';\\n * Authorship.fields = {\\n *   author: fk('Author', 'authorships'),\\n *   book: fk('Book', 'authorships'),\\n * };\\n *\\n * class Author extends Model {}\\n * Author.modelName = 'Author';\\n * Author.fields = {\\n *   books: many({\\n *     to: 'Book',\\n *     relatedName: 'authors',\\n *     through: 'Authorship',\\n *\\n *     // here this is optional: Redux-ORM can figure\\n *     // out the through fields itself since the two\\n *     // foreign key fields point to different Models\\n *     throughFields: ['author', 'book'],\\n *   })\\n * };\\n *\\n * class Book extends Model {}\\n * Book.modelName = 'Book';\\n * ```\\n *\\n * You should only define the many-to-many relationship on one side. In the\\n * above case of Authors to Books through Authorships, the relationship is\\n * defined only on the Author model.\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string|Class<Model>} [options.through] - The through Model class or its `modelName`\\n *                                                  attribute. It must declare at least one\\n *                                                  foreign key to both source and target models.\\n *                                                  If not supplied, Redux-ORM will generate one.\\n * @param {string[]} [options.throughFields] - Must be supplied only when a custom through\\n *                                             Model has more than one foreign key pointing to\\n *                                             either the source or target mode. In this case\\n *                                             Redux-ORM can't figure out the correct fields for\\n *                                             you, you must provide them. The supplied array should\\n *                                             have two elements that are the field names for the\\n *                                             through fields you want to declare the many-to-many\\n *                                             relationship with. The order doesn't matter;\\n *                                             Redux-ORM will figure out which field points to\\n *                                             the source Model and which to the target Model.\\n * @param {string} [options.relatedName] - The attribute used to access a QuerySet for all\\n *                                         source models that reference the respective target\\n *                                         Model's instance.\\n * @param {string} [relatedName] - If you didn't pass an object as the first argument,\\n *                                 this is the property name that will be used to\\n *                                 access a QuerySet for all source models that reference\\n *                                 the respective target Model's instance.\\n * @return {ManyToMany}\\n */\\n\\n\\nfunction many(...args) {\\n  return new _ManyToMany__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"](...args);\\n}\\n/**\\n * Defines a one-to-one relationship. In database terms, this is a foreign key with the\\n * added restriction that only one entity can point to single target entity.\\n *\\n * The arguments are the same as with `fk`. If `relatedName` is not supplied,\\n * the source model name in lowercase will be used. Note that with the one-to-one\\n * relationship, the `relatedName` should be in singular, not plural.\\n *\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string} [options.relatedName] - The property name that will be used to access the source\\n *                                         model instance referencing the target model instance.\\n * @param {string} [relatedName] - The property name that will be used to access the source\\n *                                 model instance referencing the target model instance\\n * @return {OneToOne}\\n */\\n\\n\\nfunction oneToOne(...args) {\\n  return new _OneToOne__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"](...args);\\n}\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvaW5kZXguanM/M2Y2ZiJdLCJuYW1lcyI6WyJhdHRyIiwib3B0cyIsIkF0dHJpYnV0ZSIsImZrIiwiYXJncyIsIkZvcmVpZ25LZXkiLCJtYW55IiwiTWFueVRvTWFueSIsIm9uZVRvT25lIiwiT25lVG9PbmUiXSwibWFwcGluZ3MiOiJBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBRUE7Ozs7Ozs7Ozs7O0FBV0E7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBeUJBLFNBQVNBLElBQVQsQ0FBY0MsSUFBZCxFQUFvQjtBQUNoQixTQUFPLElBQUlDLGtEQUFKLENBQWNELElBQWQsQ0FBUDtBQUNIO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBZ0RBLFNBQVNFLEVBQVQsQ0FBWSxHQUFHQyxJQUFmLEVBQXFCO0FBQ2pCLFNBQU8sSUFBSUMsbURBQUosQ0FBZSxHQUFHRCxJQUFsQixDQUFQO0FBQ0g7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBa0ZBLFNBQVNFLElBQVQsQ0FBYyxHQUFHRixJQUFqQixFQUF1QjtBQUNuQixTQUFPLElBQUlHLG1EQUFKLENBQWUsR0FBR0gsSUFBbEIsQ0FBUDtBQUNIO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQXNCQSxTQUFTSSxRQUFULENBQWtCLEdBQUdKLElBQXJCLEVBQTJCO0FBQ3ZCLFNBQU8sSUFBSUssaURBQUosQ0FBYSxHQUFHTCxJQUFoQixDQUFQO0FBQ0giLCJmaWxlIjoiLi9zcmMvZmllbGRzL2luZGV4LmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IEF0dHJpYnV0ZSBmcm9tIFwiLi9BdHRyaWJ1dGVcIjtcbmltcG9ydCBGb3JlaWduS2V5IGZyb20gXCIuL0ZvcmVpZ25LZXlcIjtcbmltcG9ydCBNYW55VG9NYW55IGZyb20gXCIuL01hbnlUb01hbnlcIjtcbmltcG9ydCBPbmVUb09uZSBmcm9tIFwiLi9PbmVUb09uZVwiO1xuXG4vKipcbiAqIENvbnRhaW5zIHRoZSBsb2dpYyBmb3IgaG93IGZpZWxkcyBvbiB7QGxpbmsgTW9kZWx9cyB3b3JrXG4gKiBhbmQgd2hpY2ggZGVzY3JpcHRvcnMgbXVzdCBiZSBpbnN0YWxsZWQuXG4gKlxuICogSWYgeW91ciBnb2FsIGlzIHRvIGRlZmluZSBmaWVsZHMgb24gYSBNb2RlbCBjbGFzcyxcbiAqIHBsZWFzZSB1c2UgdGhlIG1vcmUgY29udmVuaWVudCBtZXRob2RzIHtAbGluayBhdHRyfSxcbiAqIHtAbGluayBma30sIHtAbGluayBtYW55fSBhbmQge0BsaW5rIG9uZVRvT25lfS5cbiAqXG4gKiBAbW9kdWxlIGZpZWxkc1xuICovXG5cbi8qKlxuICogRGVmaW5lcyBhIHZhbHVlIGF0dHJpYnV0ZSBvbiB0aGUgbW9kZWwuXG4gKiBUaG91Z2ggbm90IHJlcXVpcmVkLCBpdCBpcyByZWNvbW1lbmRlZCB0byBkZWZpbmUgdGhpcyBmb3IgZWFjaCBub24tZm9yZWlnbiBrZXkgeW91IHdpc2ggdG8gdXNlLlxuICogR2V0dGVycyBhbmQgc2V0dGVycyBuZWVkIHRvIGJlIGRlZmluZWQgb24gZWFjaCBNb2RlbFxuICogaW5zdGFudGlhdGlvbiBmb3IgdW5kZWNsYXJlZCBkYXRhIGZpZWxkcywgd2hpY2ggaXMgc2xvd2VyLlxuICogWW91IGNhbiB1c2UgdGhlIG9wdGlvbmFsIGBnZXREZWZhdWx0YCBwYXJhbWV0ZXIgdG8gZmlsbCBpbiB1bnBhc3NlZCB2YWx1ZXNcbiAqIHRvIHtAbGluayBNb2RlbC5jcmVhdGV9LCBzdWNoIGFzIGZvciBnZW5lcmF0aW5nIElEJ3Mgd2l0aCBVVUlEOlxuICpcbiAqIGBgYGphdmFzY3JpcHRcbiAqIGltcG9ydCBnZXRVVUlEIGZyb20gJ3lvdXItdXVpZC1wYWNrYWdlLW9mLWNob2ljZSc7XG4gKlxuICogZmllbGRzID0ge1xuICogICBpZDogYXR0cih7IGdldERlZmF1bHQ6ICgpID0+IGdldFVVSUQoKSB9KSxcbiAqICAgdGl0bGU6IGF0dHIoKSxcbiAqIH1cbiAqIGBgYFxuICpcbiAqIEBwYXJhbSAge09iamVjdH0gW29wdHNdXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBbb3B0cy5nZXREZWZhdWx0XSAtIElmIHlvdSBnaXZlIGEgZnVuY3Rpb24gaGVyZSwgaXRzIHJldHVyblxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSBmcm9tIGNhbGxpbmcgd2l0aCB6ZXJvIGFyZ3VtZW50cyB3aWxsXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHVzZWQgYXMgdGhlIHZhbHVlIHdoZW4gY3JlYXRpbmcgYSBuZXcgTW9kZWxcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5zdGFuY2Ugd2l0aCB7QGxpbmsgTW9kZWwjY3JlYXRlfSBpZiB0aGUgZmllbGRcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgaXMgbm90IHBhc3NlZC5cbiAqIEByZXR1cm4ge0F0dHJpYnV0ZX1cbiAqL1xuZnVuY3Rpb24gYXR0cihvcHRzKSB7XG4gICAgcmV0dXJuIG5ldyBBdHRyaWJ1dGUob3B0cyk7XG59XG5cbi8qKlxuICogRGVmaW5lcyBhIGZvcmVpZ24ga2V5IG9uIGEgbW9kZWwsIHdoaWNoIHBvaW50c1xuICogdG8gYSBzaW5nbGUgZW50aXR5IG9uIGFub3RoZXIgbW9kZWwuXG4gKlxuICogWW91IGNhbiBwYXNzIGFyZ3VtZW50cyBhcyBlaXRoZXIgYSBzaW5nbGUgb2JqZWN0LFxuICogb3IgdHdvIGFyZ3VtZW50cy5cbiAqXG4gKiBJZiB5b3UgcGFzcyB0d28gYXJndW1lbnRzLCB0aGUgZmlyc3Qgb25lIGlzIHRoZSBuYW1lXG4gKiBvZiB0aGUgTW9kZWwgdGhlIGZvcmVpZ24ga2V5IGlzIHBvaW50aW5nIHRvLCBhbmRcbiAqIHRoZSBzZWNvbmQgb25lIGlzIGFuIG9wdGlvbmFsIHJlbGF0ZWQgbmFtZSwgd2hpY2ggd2lsbFxuICogYmUgdXNlZCB0byBhY2Nlc3MgdGhlIE1vZGVsIHRoZSBmb3JlaWduIGtleVxuICogaXMgYmVpbmcgZGVmaW5lZCBmcm9tLCBmcm9tIHRoZSB0YXJnZXQgTW9kZWwuXG4gKlxuICogSWYgdGhlIHJlbGF0ZWQgbmFtZSBpcyBub3QgcGFzc2VkLCBpdCB3aWxsIGJlIHNldCBhc1xuICogYCR7dG9Nb2RlbE5hbWV9U2V0YC5cbiAqXG4gKiBJZiB5b3UgcGFzcyBhbiBvYmplY3QgdG8gYGZrYCwgaXQgaGFzIHRvIGJlIGluIHRoZSBmb3JtXG4gKlxuICogYGBgamF2YXNjcmlwdFxuICogZmllbGRzID0ge1xuICogICBhdXRob3I6IGZrKHsgdG86ICdBdXRob3InLCByZWxhdGVkTmFtZTogJ2Jvb2tzJyB9KVxuICogfVxuICogYGBgXG4gKlxuICogV2hpY2ggaXMgZXF1YWwgdG9cbiAqXG4gKiBgYGBqYXZhc2NyaXB0XG4gKiBmaWVsZHMgPSB7XG4gKiAgIGF1dGhvcjogZmsoJ0F1dGhvcicsICdib29rcycpLFxuICogfVxuICogYGBgXG4gKlxuICogQHBhcmFtIHtzdHJpbmd8Q2xhc3M8TW9kZWw+fE9iamVjdH0gb3B0aW9ucyAtIFRoZSB0YXJnZXQgTW9kZWwgY2xhc3MsIGl0cyBgbW9kZWxOYW1lYFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF0dHJpYnV0ZSBvciBhbiBvcHRpb25zIG9iamVjdCB0aGF0XG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGFpbnMgZWl0aGVyIGFzIHRoZSBgdG9gIGtleS5cbiAqIEBwYXJhbSB7c3RyaW5nfENsYXNzPE1vZGVsPn0gb3B0aW9ucy50byAtIFRoZSB0YXJnZXQgTW9kZWwgY2xhc3Mgb3IgaXRzIGBtb2RlbE5hbWVgIGF0dHJpYnV0ZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBbb3B0aW9ucy5hc10gLSBOYW1lIGZvciB0aGUgbmV3IGFjY2Vzc29yIGRlZmluZWQgZm9yIHRoaXMgZmllbGQuIElmIHlvdSBkb24ndFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1cHBseSB0aGlzLCB0aGUga2V5IHRoYXQgdGhpcyBmaWVsZCBpcyBkZWZpbmVkIHVuZGVyIHdpbGwgYmVcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdmVycmlkZGVuLlxuICogQHBhcmFtIHtzdHJpbmd9IFtvcHRpb25zLnJlbGF0ZWROYW1lXSAtIFRoZSBwcm9wZXJ0eSBuYW1lIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGFjY2Vzc1xuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGEgUXVlcnlTZXQgZm9yIGFsbCBzb3VyY2UgbW9kZWxzIHRoYXQgcmVmZXJlbmNlXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHJlc3BlY3RpdmUgdGFyZ2V0IE1vZGVsJ3MgaW5zdGFuY2UuXG4gKiBAcGFyYW0ge3N0cmluZ30gW3JlbGF0ZWROYW1lXSAtIElmIHlvdSBkaWRuJ3QgcGFzcyBhbiBvYmplY3QgYXMgdGhlIGZpcnN0IGFyZ3VtZW50LFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzIGlzIHRoZSBwcm9wZXJ0eSBuYW1lIHRoYXQgd2lsbCBiZSB1c2VkIHRvXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjY2VzcyBhIFF1ZXJ5U2V0IGZvciBhbGwgc291cmNlIG1vZGVscyB0aGF0IHJlZmVyZW5jZVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGUgcmVzcGVjdGl2ZSB0YXJnZXQgTW9kZWwncyBpbnN0YW5jZS5cbiAqIEByZXR1cm4ge0ZvcmVpZ25LZXl9XG4gKi9cbmZ1bmN0aW9uIGZrKC4uLmFyZ3MpIHtcbiAgICByZXR1cm4gbmV3IEZvcmVpZ25LZXkoLi4uYXJncyk7XG59XG5cbi8qKlxuICogRGVmaW5lcyBhIG1hbnktdG8tbWFueSByZWxhdGlvbnNoaXAgYmV0d2VlblxuICogdGhpcyAoc291cmNlKSBhbmQgYW5vdGhlciAodGFyZ2V0KSBtb2RlbC5cbiAqXG4gKiBUaGUgcmVsYXRpb25zaGlwIGlzIG1vZGVsZWQgd2l0aCBhbiBleHRyYSBtb2RlbCBjYWxsZWQgdGhlIHRocm91Z2ggbW9kZWwuXG4gKiBUaGUgdGhyb3VnaCBtb2RlbCBoYXMgZm9yZWlnbiBrZXlzIHRvIGJvdGggdGhlIHNvdXJjZSBhbmQgdGFyZ2V0IG1vZGVscy5cbiAqXG4gKiBZb3UgY2FuIGRlZmluZSB5b3VyIG93biB0aHJvdWdoIG1vZGVsIGlmIHlvdSB3YW50IHRvIGFzc29jaWF0ZSBtb3JlIGluZm9ybWF0aW9uXG4gKiB0byB0aGUgcmVsYXRpb25zaGlwLiBBIGN1c3RvbSB0aHJvdWdoIG1vZGVsIG11c3QgaGF2ZSBhdCBsZWFzdCB0d28gZm9yZWlnbiBrZXlzLFxuICogb25lIHBvaW50aW5nIHRvIHRoZSBzb3VyY2UgTW9kZWwsIGFuZCBvbmUgcG9pbnRpbmcgdG8gdGhlIHRhcmdldCBNb2RlbC5cbiAqXG4gKiBMaWtlIGBma2AsIHRoaXMgZnVuY3Rpb24gYWNjZXB0cyBvbmUgb3IgdHdvIHN0cmluZyBhcmd1bWVudHMgc3BlY2lmeWluZyB0aGUgb3RoZXJcbiAqIE1vZGVsIGFuZCB0aGUgcmVsYXRlZCBuYW1lLCBvciBhIHNpbmdsZSBvYmplY3QgYXJndW1lbnQgdGhhdCBhbGxvd3MgeW91IHRvIHBhc3NcbiAqIGEgY3VzdG9tIHRocm91Z2ggbW9kZWwuXG4gKlxuICogSWYgeW91IGhhdmUgbW9yZSB0aGFuIG9uZSBmb3JlaWduIGtleSBwb2ludGluZyB0byBhIHNvdXJjZSBvciB0YXJnZXQgTW9kZWwgaW4gdGhlXG4gKiB0aHJvdWdoIE1vZGVsLCB5b3UgbXVzdCBwYXNzIHRoZSBvcHRpb24gYHRocm91Z2hGaWVsZHNgLCB3aGljaCBpcyBhbiBhcnJheSBvZiB0d29cbiAqIHN0cmluZ3MsIHdoZXJlIHRoZSBzdHJpbmdzIGFyZSB0aGUgZmllbGQgbmFtZXMgdGhhdCBpZGVudGlmeSB0aGUgZm9yZWlnbiBrZXlzIHRvXG4gKiBiZSB1c2VkIGZvciB0aGUgbWFueS10by1tYW55IHJlbGF0aW9uc2hpcC4gUmVkdXgtT1JNIHdpbGwgZmlndXJlIG91dCB3aGljaCBmaWVsZCBuYW1lXG4gKiBwb2ludHMgdG8gd2hpY2ggbW9kZWwgYnkgY2hlY2tpbmcgdGhlIFwidGhyb3VnaCBtb2RlbFwiIGRlZmluaXRpb24uXG4gKlxuICogYGBgamF2YXNjcmlwdFxuICogY2xhc3MgQXV0aG9yc2hpcCBleHRlbmRzIE1vZGVsIHt9XG4gKiBBdXRob3JzaGlwLm1vZGVsTmFtZSA9ICdBdXRob3JzaGlwJztcbiAqIEF1dGhvcnNoaXAuZmllbGRzID0ge1xuICogICBhdXRob3I6IGZrKCdBdXRob3InLCAnYXV0aG9yc2hpcHMnKSxcbiAqICAgYm9vazogZmsoJ0Jvb2snLCAnYXV0aG9yc2hpcHMnKSxcbiAqIH07XG4gKlxuICogY2xhc3MgQXV0aG9yIGV4dGVuZHMgTW9kZWwge31cbiAqIEF1dGhvci5tb2RlbE5hbWUgPSAnQXV0aG9yJztcbiAqIEF1dGhvci5maWVsZHMgPSB7XG4gKiAgIGJvb2tzOiBtYW55KHtcbiAqICAgICB0bzogJ0Jvb2snLFxuICogICAgIHJlbGF0ZWROYW1lOiAnYXV0aG9ycycsXG4gKiAgICAgdGhyb3VnaDogJ0F1dGhvcnNoaXAnLFxuICpcbiAqICAgICAvLyBoZXJlIHRoaXMgaXMgb3B0aW9uYWw6IFJlZHV4LU9STSBjYW4gZmlndXJlXG4gKiAgICAgLy8gb3V0IHRoZSB0aHJvdWdoIGZpZWxkcyBpdHNlbGYgc2luY2UgdGhlIHR3b1xuICogICAgIC8vIGZvcmVpZ24ga2V5IGZpZWxkcyBwb2ludCB0byBkaWZmZXJlbnQgTW9kZWxzXG4gKiAgICAgdGhyb3VnaEZpZWxkczogWydhdXRob3InLCAnYm9vayddLFxuICogICB9KVxuICogfTtcbiAqXG4gKiBjbGFzcyBCb29rIGV4dGVuZHMgTW9kZWwge31cbiAqIEJvb2subW9kZWxOYW1lID0gJ0Jvb2snO1xuICogYGBgXG4gKlxuICogWW91IHNob3VsZCBvbmx5IGRlZmluZSB0aGUgbWFueS10by1tYW55IHJlbGF0aW9uc2hpcCBvbiBvbmUgc2lkZS4gSW4gdGhlXG4gKiBhYm92ZSBjYXNlIG9mIEF1dGhvcnMgdG8gQm9va3MgdGhyb3VnaCBBdXRob3JzaGlwcywgdGhlIHJlbGF0aW9uc2hpcCBpc1xuICogZGVmaW5lZCBvbmx5IG9uIHRoZSBBdXRob3IgbW9kZWwuXG4gKlxuICogQHBhcmFtIHtzdHJpbmd8Q2xhc3M8TW9kZWw+fE9iamVjdH0gb3B0aW9ucyAtIFRoZSB0YXJnZXQgTW9kZWwgY2xhc3MsIGl0cyBgbW9kZWxOYW1lYFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF0dHJpYnV0ZSBvciBhbiBvcHRpb25zIG9iamVjdCB0aGF0XG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGFpbnMgZWl0aGVyIGFzIHRoZSBgdG9gIGtleS5cbiAqIEBwYXJhbSB7c3RyaW5nfENsYXNzPE1vZGVsPn0gb3B0aW9ucy50byAtIFRoZSB0YXJnZXQgTW9kZWwgY2xhc3Mgb3IgaXRzIGBtb2RlbE5hbWVgIGF0dHJpYnV0ZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBbb3B0aW9ucy5hc10gLSBOYW1lIGZvciB0aGUgbmV3IGFjY2Vzc29yIGRlZmluZWQgZm9yIHRoaXMgZmllbGQuIElmIHlvdSBkb24ndFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1cHBseSB0aGlzLCB0aGUga2V5IHRoYXQgdGhpcyBmaWVsZCBpcyBkZWZpbmVkIHVuZGVyIHdpbGwgYmVcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdmVycmlkZGVuLlxuICogQHBhcmFtIHtzdHJpbmd8Q2xhc3M8TW9kZWw+fSBbb3B0aW9ucy50aHJvdWdoXSAtIFRoZSB0aHJvdWdoIE1vZGVsIGNsYXNzIG9yIGl0cyBgbW9kZWxOYW1lYFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF0dHJpYnV0ZS4gSXQgbXVzdCBkZWNsYXJlIGF0IGxlYXN0IG9uZVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvcmVpZ24ga2V5IHRvIGJvdGggc291cmNlIGFuZCB0YXJnZXQgbW9kZWxzLlxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIG5vdCBzdXBwbGllZCwgUmVkdXgtT1JNIHdpbGwgZ2VuZXJhdGUgb25lLlxuICogQHBhcmFtIHtzdHJpbmdbXX0gW29wdGlvbnMudGhyb3VnaEZpZWxkc10gLSBNdXN0IGJlIHN1cHBsaWVkIG9ubHkgd2hlbiBhIGN1c3RvbSB0aHJvdWdoXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVsIGhhcyBtb3JlIHRoYW4gb25lIGZvcmVpZ24ga2V5IHBvaW50aW5nIHRvXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVpdGhlciB0aGUgc291cmNlIG9yIHRhcmdldCBtb2RlLiBJbiB0aGlzIGNhc2VcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUmVkdXgtT1JNIGNhbid0IGZpZ3VyZSBvdXQgdGhlIGNvcnJlY3QgZmllbGRzIGZvclxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5b3UsIHlvdSBtdXN0IHByb3ZpZGUgdGhlbS4gVGhlIHN1cHBsaWVkIGFycmF5IHNob3VsZFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoYXZlIHR3byBlbGVtZW50cyB0aGF0IGFyZSB0aGUgZmllbGQgbmFtZXMgZm9yIHRoZVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aHJvdWdoIGZpZWxkcyB5b3Ugd2FudCB0byBkZWNsYXJlIHRoZSBtYW55LXRvLW1hbnlcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVsYXRpb25zaGlwIHdpdGguIFRoZSBvcmRlciBkb2Vzbid0IG1hdHRlcjtcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUmVkdXgtT1JNIHdpbGwgZmlndXJlIG91dCB3aGljaCBmaWVsZCBwb2ludHMgdG9cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHNvdXJjZSBNb2RlbCBhbmQgd2hpY2ggdG8gdGhlIHRhcmdldCBNb2RlbC5cbiAqIEBwYXJhbSB7c3RyaW5nfSBbb3B0aW9ucy5yZWxhdGVkTmFtZV0gLSBUaGUgYXR0cmlidXRlIHVzZWQgdG8gYWNjZXNzIGEgUXVlcnlTZXQgZm9yIGFsbFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNvdXJjZSBtb2RlbHMgdGhhdCByZWZlcmVuY2UgdGhlIHJlc3BlY3RpdmUgdGFyZ2V0XG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTW9kZWwncyBpbnN0YW5jZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBbcmVsYXRlZE5hbWVdIC0gSWYgeW91IGRpZG4ndCBwYXNzIGFuIG9iamVjdCBhcyB0aGUgZmlyc3QgYXJndW1lbnQsXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMgaXMgdGhlIHByb3BlcnR5IG5hbWUgdGhhdCB3aWxsIGJlIHVzZWQgdG9cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWNjZXNzIGEgUXVlcnlTZXQgZm9yIGFsbCBzb3VyY2UgbW9kZWxzIHRoYXQgcmVmZXJlbmNlXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSByZXNwZWN0aXZlIHRhcmdldCBNb2RlbCdzIGluc3RhbmNlLlxuICogQHJldHVybiB7TWFueVRvTWFueX1cbiAqL1xuZnVuY3Rpb24gbWFueSguLi5hcmdzKSB7XG4gICAgcmV0dXJuIG5ldyBNYW55VG9NYW55KC4uLmFyZ3MpO1xufVxuXG4vKipcbiAqIERlZmluZXMgYSBvbmUtdG8tb25lIHJlbGF0aW9uc2hpcC4gSW4gZGF0YWJhc2UgdGVybXMsIHRoaXMgaXMgYSBmb3JlaWduIGtleSB3aXRoIHRoZVxuICogYWRkZWQgcmVzdHJpY3Rpb24gdGhhdCBvbmx5IG9uZSBlbnRpdHkgY2FuIHBvaW50IHRvIHNpbmdsZSB0YXJnZXQgZW50aXR5LlxuICpcbiAqIFRoZSBhcmd1bWVudHMgYXJlIHRoZSBzYW1lIGFzIHdpdGggYGZrYC4gSWYgYHJlbGF0ZWROYW1lYCBpcyBub3Qgc3VwcGxpZWQsXG4gKiB0aGUgc291cmNlIG1vZGVsIG5hbWUgaW4gbG93ZXJjYXNlIHdpbGwgYmUgdXNlZC4gTm90ZSB0aGF0IHdpdGggdGhlIG9uZS10by1vbmVcbiAqIHJlbGF0aW9uc2hpcCwgdGhlIGByZWxhdGVkTmFtZWAgc2hvdWxkIGJlIGluIHNpbmd1bGFyLCBub3QgcGx1cmFsLlxuICpcbiAqXG4gKiBAcGFyYW0ge3N0cmluZ3xDbGFzczxNb2RlbD58T2JqZWN0fSBvcHRpb25zIC0gVGhlIHRhcmdldCBNb2RlbCBjbGFzcywgaXRzIGBtb2RlbE5hbWVgXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXR0cmlidXRlIG9yIGFuIG9wdGlvbnMgb2JqZWN0IHRoYXRcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250YWlucyBlaXRoZXIgYXMgdGhlIGB0b2Aga2V5LlxuICogQHBhcmFtIHtzdHJpbmd8Q2xhc3M8TW9kZWw+fSBvcHRpb25zLnRvIC0gVGhlIHRhcmdldCBNb2RlbCBjbGFzcyBvciBpdHMgYG1vZGVsTmFtZWAgYXR0cmlidXRlLlxuICogQHBhcmFtIHtzdHJpbmd9IFtvcHRpb25zLmFzXSAtIE5hbWUgZm9yIHRoZSBuZXcgYWNjZXNzb3IgZGVmaW5lZCBmb3IgdGhpcyBmaWVsZC4gSWYgeW91IGRvbid0XG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VwcGx5IHRoaXMsIHRoZSBrZXkgdGhhdCB0aGlzIGZpZWxkIGlzIGRlZmluZWQgdW5kZXIgd2lsbCBiZVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG92ZXJyaWRkZW4uXG4gKiBAcGFyYW0ge3N0cmluZ30gW29wdGlvbnMucmVsYXRlZE5hbWVdIC0gVGhlIHByb3BlcnR5IG5hbWUgdGhhdCB3aWxsIGJlIHVzZWQgdG8gYWNjZXNzIHRoZSBzb3VyY2VcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBpbnN0YW5jZSByZWZlcmVuY2luZyB0aGUgdGFyZ2V0IG1vZGVsIGluc3RhbmNlLlxuICogQHBhcmFtIHtzdHJpbmd9IFtyZWxhdGVkTmFtZV0gLSBUaGUgcHJvcGVydHkgbmFtZSB0aGF0IHdpbGwgYmUgdXNlZCB0byBhY2Nlc3MgdGhlIHNvdXJjZVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBpbnN0YW5jZSByZWZlcmVuY2luZyB0aGUgdGFyZ2V0IG1vZGVsIGluc3RhbmNlXG4gKiBAcmV0dXJuIHtPbmVUb09uZX1cbiAqL1xuZnVuY3Rpb24gb25lVG9PbmUoLi4uYXJncykge1xuICAgIHJldHVybiBuZXcgT25lVG9PbmUoLi4uYXJncyk7XG59XG5cbmV4cG9ydCB7IGZrLCBhdHRyLCBtYW55LCBvbmVUb09uZSB9O1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/fields/index.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"fk\\\", function() { return fk; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"attr\\\", function() { return attr; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"many\\\", function() { return many; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"oneToOne\\\", function() { return oneToOne; });\\n/* harmony import */ var _Attribute__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Attribute */ \\\"./src/fields/Attribute.js\\\");\\n/* harmony import */ var _ForeignKey__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ForeignKey */ \\\"./src/fields/ForeignKey.js\\\");\\n/* harmony import */ var _ManyToMany__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ManyToMany */ \\\"./src/fields/ManyToMany.js\\\");\\n/* harmony import */ var _OneToOne__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./OneToOne */ \\\"./src/fields/OneToOne.js\\\");\\n\\n\\n\\n\\n/**\\n * Contains the logic for how fields on {@link Model}s work\\n * and which descriptors must be installed.\\n *\\n * If your goal is to define fields on a Model class,\\n * please use the more convenient methods {@link attr},\\n * {@link fk}, {@link many} and {@link oneToOne}.\\n *\\n * @module fields\\n */\\n\\n/**\\n * Defines a value attribute on the model.\\n * Though not required, it is recommended to define this for each non-foreign key you wish to use.\\n * Getters and setters need to be defined on each Model\\n * instantiation for undeclared data fields, which is slower.\\n * You can use the optional `getDefault` parameter to fill in unpassed values\\n * to {@link Model.create}, such as for generating ID's with UUID:\\n *\\n * ```javascript\\n * import getUUID from 'your-uuid-package-of-choice';\\n *\\n * fields = {\\n *   id: attr({ getDefault: () => getUUID() }),\\n *   title: attr(),\\n * }\\n * ```\\n *\\n * @param  {Object} [opts]\\n * @param {Function} [opts.getDefault] - If you give a function here, its return\\n *                                       value from calling with zero arguments will\\n *                                       be used as the value when creating a new Model\\n *                                       instance with {@link Model#create} if the field\\n *                                       value is not passed.\\n * @return {Attribute}\\n */\\n\\nfunction attr(opts) {\\n  return new _Attribute__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"](opts);\\n}\\n/**\\n * Defines a foreign key on a model, which points\\n * to a single entity on another model.\\n *\\n * You can pass arguments as either a single object,\\n * or two arguments.\\n *\\n * If you pass two arguments, the first one is the name\\n * of the Model the foreign key is pointing to, and\\n * the second one is an optional related name, which will\\n * be used to access the Model the foreign key\\n * is being defined from, from the target Model.\\n *\\n * If the related name is not passed, it will be set as\\n * `${toModelName}Set`.\\n *\\n * If you pass an object to `fk`, it has to be in the form\\n *\\n * ```javascript\\n * fields = {\\n *   author: fk({ to: 'Author', relatedName: 'books' })\\n * }\\n * ```\\n *\\n * Which is equal to\\n *\\n * ```javascript\\n * fields = {\\n *   author: fk('Author', 'books'),\\n * }\\n * ```\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string} [options.relatedName] - The property name that will be used to access\\n *                                         a QuerySet for all source models that reference\\n *                                         the respective target Model's instance.\\n * @param {string} [relatedName] - If you didn't pass an object as the first argument,\\n *                                 this is the property name that will be used to\\n *                                 access a QuerySet for all source models that reference\\n *                                 the respective target Model's instance.\\n * @return {ForeignKey}\\n */\\n\\n\\nfunction fk(...args) {\\n  return new _ForeignKey__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"](...args);\\n}\\n/**\\n * Defines a many-to-many relationship between\\n * this (source) and another (target) model.\\n *\\n * The relationship is modeled with an extra model called the through model.\\n * The through model has foreign keys to both the source and target models.\\n *\\n * You can define your own through model if you want to associate more information\\n * to the relationship. A custom through model must have at least two foreign keys,\\n * one pointing to the source Model, and one pointing to the target Model.\\n *\\n * Like `fk`, this function accepts one or two string arguments specifying the other\\n * Model and the related name, or a single object argument that allows you to pass\\n * a custom through model.\\n *\\n * If you have more than one foreign key pointing to a source or target Model in the\\n * through Model, you must pass the option `throughFields`, which is an array of two\\n * strings, where the strings are the field names that identify the foreign keys to\\n * be used for the many-to-many relationship. Redux-ORM will figure out which field name\\n * points to which model by checking the \\\"through model\\\" definition.\\n *\\n * ```javascript\\n * class Authorship extends Model {}\\n * Authorship.modelName = 'Authorship';\\n * Authorship.fields = {\\n *   author: fk('Author', 'authorships'),\\n *   book: fk('Book', 'authorships'),\\n * };\\n *\\n * class Author extends Model {}\\n * Author.modelName = 'Author';\\n * Author.fields = {\\n *   books: many({\\n *     to: 'Book',\\n *     relatedName: 'authors',\\n *     through: 'Authorship',\\n *\\n *     // here this is optional: Redux-ORM can figure\\n *     // out the through fields itself since the two\\n *     // foreign key fields point to different Models\\n *     throughFields: ['author', 'book'],\\n *   })\\n * };\\n *\\n * class Book extends Model {}\\n * Book.modelName = 'Book';\\n * ```\\n *\\n * You should only define the many-to-many relationship on one side. In the\\n * above case of Authors to Books through Authorships, the relationship is\\n * defined only on the Author model.\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string|Class<Model>} [options.through] - The through Model class or its `modelName`\\n *                                                  attribute. It must declare at least one\\n *                                                  foreign key to both source and target models.\\n *                                                  If not supplied, Redux-ORM will generate one.\\n * @param {string[]} [options.throughFields] - Must be supplied only when a custom through\\n *                                             Model has more than one foreign key pointing to\\n *                                             either the source or target mode. In this case\\n *                                             Redux-ORM can't figure out the correct fields for\\n *                                             you, you must provide them. The supplied array should\\n *                                             have two elements that are the field names for the\\n *                                             through fields you want to declare the many-to-many\\n *                                             relationship with. The order doesn't matter;\\n *                                             Redux-ORM will figure out which field points to\\n *                                             the source Model and which to the target Model.\\n * @param {string} [options.relatedName] - The attribute used to access a QuerySet for all\\n *                                         source models that reference the respective target\\n *                                         Model's instance.\\n * @param {string} [relatedName] - If you didn't pass an object as the first argument,\\n *                                 this is the property name that will be used to\\n *                                 access a QuerySet for all source models that reference\\n *                                 the respective target Model's instance.\\n * @return {ManyToMany}\\n */\\n\\n\\nfunction many(...args) {\\n  return new _ManyToMany__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"](...args);\\n}\\n/**\\n * Defines a one-to-one relationship. In database terms, this is a foreign key with the\\n * added restriction that only one entity can point to single target entity.\\n *\\n * The arguments are the same as with `fk`. If `relatedName` is not supplied,\\n * the source model name in lowercase will be used. Note that with the one-to-one\\n * relationship, the `relatedName` should be in singular, not plural.\\n *\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string} [options.relatedName] - The property name that will be used to access the source\\n *                                         model instance referencing the target model instance.\\n * @param {string} [relatedName] - The property name that will be used to access the source\\n *                                 model instance referencing the target model instance\\n * @return {OneToOne}\\n */\\n\\n\\nfunction oneToOne(...args) {\\n  return new _OneToOne__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"](...args);\\n}\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9maWVsZHMvaW5kZXguanM/M2Y2ZiJdLCJuYW1lcyI6WyJhdHRyIiwib3B0cyIsIkF0dHJpYnV0ZSIsImZrIiwiYXJncyIsIkZvcmVpZ25LZXkiLCJtYW55IiwiTWFueVRvTWFueSIsIm9uZVRvT25lIiwiT25lVG9PbmUiXSwibWFwcGluZ3MiOiJBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBQ0EsU0FBU0EsSUFBVCxDQUFjQyxJQUFkLEVBQW9CO0FBQ2hCLFNBQU8sSUFBSUMsa0RBQUosQ0FBY0QsSUFBZCxDQUFQO0FBQ0g7QUFFRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQUNBLFNBQVNFLEVBQVQsQ0FBWSxHQUFHQyxJQUFmLEVBQXFCO0FBQ2pCLFNBQU8sSUFBSUMsbURBQUosQ0FBZSxHQUFHRCxJQUFsQixDQUFQO0FBQ0g7QUFFRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7O0FBQ0EsU0FBU0UsSUFBVCxDQUFjLEdBQUdGLElBQWpCLEVBQXVCO0FBQ25CLFNBQU8sSUFBSUcsbURBQUosQ0FBZSxHQUFHSCxJQUFsQixDQUFQO0FBQ0g7QUFFRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7O0FBQ0EsU0FBU0ksUUFBVCxDQUFrQixHQUFHSixJQUFyQixFQUEyQjtBQUN2QixTQUFPLElBQUlLLGlEQUFKLENBQWEsR0FBR0wsSUFBaEIsQ0FBUDtBQUNIIiwiZmlsZSI6Ii4vc3JjL2ZpZWxkcy9pbmRleC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBBdHRyaWJ1dGUgZnJvbSBcIi4vQXR0cmlidXRlXCI7XG5pbXBvcnQgRm9yZWlnbktleSBmcm9tIFwiLi9Gb3JlaWduS2V5XCI7XG5pbXBvcnQgTWFueVRvTWFueSBmcm9tIFwiLi9NYW55VG9NYW55XCI7XG5pbXBvcnQgT25lVG9PbmUgZnJvbSBcIi4vT25lVG9PbmVcIjtcblxuLyoqXG4gKiBDb250YWlucyB0aGUgbG9naWMgZm9yIGhvdyBmaWVsZHMgb24ge0BsaW5rIE1vZGVsfXMgd29ya1xuICogYW5kIHdoaWNoIGRlc2NyaXB0b3JzIG11c3QgYmUgaW5zdGFsbGVkLlxuICpcbiAqIElmIHlvdXIgZ29hbCBpcyB0byBkZWZpbmUgZmllbGRzIG9uIGEgTW9kZWwgY2xhc3MsXG4gKiBwbGVhc2UgdXNlIHRoZSBtb3JlIGNvbnZlbmllbnQgbWV0aG9kcyB7QGxpbmsgYXR0cn0sXG4gKiB7QGxpbmsgZmt9LCB7QGxpbmsgbWFueX0gYW5kIHtAbGluayBvbmVUb09uZX0uXG4gKlxuICogQG1vZHVsZSBmaWVsZHNcbiAqL1xuXG4vKipcbiAqIERlZmluZXMgYSB2YWx1ZSBhdHRyaWJ1dGUgb24gdGhlIG1vZGVsLlxuICogVGhvdWdoIG5vdCByZXF1aXJlZCwgaXQgaXMgcmVjb21tZW5kZWQgdG8gZGVmaW5lIHRoaXMgZm9yIGVhY2ggbm9uLWZvcmVpZ24ga2V5IHlvdSB3aXNoIHRvIHVzZS5cbiAqIEdldHRlcnMgYW5kIHNldHRlcnMgbmVlZCB0byBiZSBkZWZpbmVkIG9uIGVhY2ggTW9kZWxcbiAqIGluc3RhbnRpYXRpb24gZm9yIHVuZGVjbGFyZWQgZGF0YSBmaWVsZHMsIHdoaWNoIGlzIHNsb3dlci5cbiAqIFlvdSBjYW4gdXNlIHRoZSBvcHRpb25hbCBgZ2V0RGVmYXVsdGAgcGFyYW1ldGVyIHRvIGZpbGwgaW4gdW5wYXNzZWQgdmFsdWVzXG4gKiB0byB7QGxpbmsgTW9kZWwuY3JlYXRlfSwgc3VjaCBhcyBmb3IgZ2VuZXJhdGluZyBJRCdzIHdpdGggVVVJRDpcbiAqXG4gKiBgYGBqYXZhc2NyaXB0XG4gKiBpbXBvcnQgZ2V0VVVJRCBmcm9tICd5b3VyLXV1aWQtcGFja2FnZS1vZi1jaG9pY2UnO1xuICpcbiAqIGZpZWxkcyA9IHtcbiAqICAgaWQ6IGF0dHIoeyBnZXREZWZhdWx0OiAoKSA9PiBnZXRVVUlEKCkgfSksXG4gKiAgIHRpdGxlOiBhdHRyKCksXG4gKiB9XG4gKiBgYGBcbiAqXG4gKiBAcGFyYW0gIHtPYmplY3R9IFtvcHRzXVxuICogQHBhcmFtIHtGdW5jdGlvbn0gW29wdHMuZ2V0RGVmYXVsdF0gLSBJZiB5b3UgZ2l2ZSBhIGZ1bmN0aW9uIGhlcmUsIGl0cyByZXR1cm5cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgZnJvbSBjYWxsaW5nIHdpdGggemVybyBhcmd1bWVudHMgd2lsbFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkIGFzIHRoZSB2YWx1ZSB3aGVuIGNyZWF0aW5nIGEgbmV3IE1vZGVsXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluc3RhbmNlIHdpdGgge0BsaW5rIE1vZGVsI2NyZWF0ZX0gaWYgdGhlIGZpZWxkXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlIGlzIG5vdCBwYXNzZWQuXG4gKiBAcmV0dXJuIHtBdHRyaWJ1dGV9XG4gKi9cbmZ1bmN0aW9uIGF0dHIob3B0cykge1xuICAgIHJldHVybiBuZXcgQXR0cmlidXRlKG9wdHMpO1xufVxuXG4vKipcbiAqIERlZmluZXMgYSBmb3JlaWduIGtleSBvbiBhIG1vZGVsLCB3aGljaCBwb2ludHNcbiAqIHRvIGEgc2luZ2xlIGVudGl0eSBvbiBhbm90aGVyIG1vZGVsLlxuICpcbiAqIFlvdSBjYW4gcGFzcyBhcmd1bWVudHMgYXMgZWl0aGVyIGEgc2luZ2xlIG9iamVjdCxcbiAqIG9yIHR3byBhcmd1bWVudHMuXG4gKlxuICogSWYgeW91IHBhc3MgdHdvIGFyZ3VtZW50cywgdGhlIGZpcnN0IG9uZSBpcyB0aGUgbmFtZVxuICogb2YgdGhlIE1vZGVsIHRoZSBmb3JlaWduIGtleSBpcyBwb2ludGluZyB0bywgYW5kXG4gKiB0aGUgc2Vjb25kIG9uZSBpcyBhbiBvcHRpb25hbCByZWxhdGVkIG5hbWUsIHdoaWNoIHdpbGxcbiAqIGJlIHVzZWQgdG8gYWNjZXNzIHRoZSBNb2RlbCB0aGUgZm9yZWlnbiBrZXlcbiAqIGlzIGJlaW5nIGRlZmluZWQgZnJvbSwgZnJvbSB0aGUgdGFyZ2V0IE1vZGVsLlxuICpcbiAqIElmIHRoZSByZWxhdGVkIG5hbWUgaXMgbm90IHBhc3NlZCwgaXQgd2lsbCBiZSBzZXQgYXNcbiAqIGAke3RvTW9kZWxOYW1lfVNldGAuXG4gKlxuICogSWYgeW91IHBhc3MgYW4gb2JqZWN0IHRvIGBma2AsIGl0IGhhcyB0byBiZSBpbiB0aGUgZm9ybVxuICpcbiAqIGBgYGphdmFzY3JpcHRcbiAqIGZpZWxkcyA9IHtcbiAqICAgYXV0aG9yOiBmayh7IHRvOiAnQXV0aG9yJywgcmVsYXRlZE5hbWU6ICdib29rcycgfSlcbiAqIH1cbiAqIGBgYFxuICpcbiAqIFdoaWNoIGlzIGVxdWFsIHRvXG4gKlxuICogYGBgamF2YXNjcmlwdFxuICogZmllbGRzID0ge1xuICogICBhdXRob3I6IGZrKCdBdXRob3InLCAnYm9va3MnKSxcbiAqIH1cbiAqIGBgYFxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfENsYXNzPE1vZGVsPnxPYmplY3R9IG9wdGlvbnMgLSBUaGUgdGFyZ2V0IE1vZGVsIGNsYXNzLCBpdHMgYG1vZGVsTmFtZWBcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdHRyaWJ1dGUgb3IgYW4gb3B0aW9ucyBvYmplY3QgdGhhdFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRhaW5zIGVpdGhlciBhcyB0aGUgYHRvYCBrZXkuXG4gKiBAcGFyYW0ge3N0cmluZ3xDbGFzczxNb2RlbD59IG9wdGlvbnMudG8gLSBUaGUgdGFyZ2V0IE1vZGVsIGNsYXNzIG9yIGl0cyBgbW9kZWxOYW1lYCBhdHRyaWJ1dGUuXG4gKiBAcGFyYW0ge3N0cmluZ30gW29wdGlvbnMuYXNdIC0gTmFtZSBmb3IgdGhlIG5ldyBhY2Nlc3NvciBkZWZpbmVkIGZvciB0aGlzIGZpZWxkLiBJZiB5b3UgZG9uJ3RcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXBwbHkgdGhpcywgdGhlIGtleSB0aGF0IHRoaXMgZmllbGQgaXMgZGVmaW5lZCB1bmRlciB3aWxsIGJlXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3ZlcnJpZGRlbi5cbiAqIEBwYXJhbSB7c3RyaW5nfSBbb3B0aW9ucy5yZWxhdGVkTmFtZV0gLSBUaGUgcHJvcGVydHkgbmFtZSB0aGF0IHdpbGwgYmUgdXNlZCB0byBhY2Nlc3NcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhIFF1ZXJ5U2V0IGZvciBhbGwgc291cmNlIG1vZGVscyB0aGF0IHJlZmVyZW5jZVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSByZXNwZWN0aXZlIHRhcmdldCBNb2RlbCdzIGluc3RhbmNlLlxuICogQHBhcmFtIHtzdHJpbmd9IFtyZWxhdGVkTmFtZV0gLSBJZiB5b3UgZGlkbid0IHBhc3MgYW4gb2JqZWN0IGFzIHRoZSBmaXJzdCBhcmd1bWVudCxcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcyBpcyB0aGUgcHJvcGVydHkgbmFtZSB0aGF0IHdpbGwgYmUgdXNlZCB0b1xuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY2Nlc3MgYSBRdWVyeVNldCBmb3IgYWxsIHNvdXJjZSBtb2RlbHMgdGhhdCByZWZlcmVuY2VcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHJlc3BlY3RpdmUgdGFyZ2V0IE1vZGVsJ3MgaW5zdGFuY2UuXG4gKiBAcmV0dXJuIHtGb3JlaWduS2V5fVxuICovXG5mdW5jdGlvbiBmayguLi5hcmdzKSB7XG4gICAgcmV0dXJuIG5ldyBGb3JlaWduS2V5KC4uLmFyZ3MpO1xufVxuXG4vKipcbiAqIERlZmluZXMgYSBtYW55LXRvLW1hbnkgcmVsYXRpb25zaGlwIGJldHdlZW5cbiAqIHRoaXMgKHNvdXJjZSkgYW5kIGFub3RoZXIgKHRhcmdldCkgbW9kZWwuXG4gKlxuICogVGhlIHJlbGF0aW9uc2hpcCBpcyBtb2RlbGVkIHdpdGggYW4gZXh0cmEgbW9kZWwgY2FsbGVkIHRoZSB0aHJvdWdoIG1vZGVsLlxuICogVGhlIHRocm91Z2ggbW9kZWwgaGFzIGZvcmVpZ24ga2V5cyB0byBib3RoIHRoZSBzb3VyY2UgYW5kIHRhcmdldCBtb2RlbHMuXG4gKlxuICogWW91IGNhbiBkZWZpbmUgeW91ciBvd24gdGhyb3VnaCBtb2RlbCBpZiB5b3Ugd2FudCB0byBhc3NvY2lhdGUgbW9yZSBpbmZvcm1hdGlvblxuICogdG8gdGhlIHJlbGF0aW9uc2hpcC4gQSBjdXN0b20gdGhyb3VnaCBtb2RlbCBtdXN0IGhhdmUgYXQgbGVhc3QgdHdvIGZvcmVpZ24ga2V5cyxcbiAqIG9uZSBwb2ludGluZyB0byB0aGUgc291cmNlIE1vZGVsLCBhbmQgb25lIHBvaW50aW5nIHRvIHRoZSB0YXJnZXQgTW9kZWwuXG4gKlxuICogTGlrZSBgZmtgLCB0aGlzIGZ1bmN0aW9uIGFjY2VwdHMgb25lIG9yIHR3byBzdHJpbmcgYXJndW1lbnRzIHNwZWNpZnlpbmcgdGhlIG90aGVyXG4gKiBNb2RlbCBhbmQgdGhlIHJlbGF0ZWQgbmFtZSwgb3IgYSBzaW5nbGUgb2JqZWN0IGFyZ3VtZW50IHRoYXQgYWxsb3dzIHlvdSB0byBwYXNzXG4gKiBhIGN1c3RvbSB0aHJvdWdoIG1vZGVsLlxuICpcbiAqIElmIHlvdSBoYXZlIG1vcmUgdGhhbiBvbmUgZm9yZWlnbiBrZXkgcG9pbnRpbmcgdG8gYSBzb3VyY2Ugb3IgdGFyZ2V0IE1vZGVsIGluIHRoZVxuICogdGhyb3VnaCBNb2RlbCwgeW91IG11c3QgcGFzcyB0aGUgb3B0aW9uIGB0aHJvdWdoRmllbGRzYCwgd2hpY2ggaXMgYW4gYXJyYXkgb2YgdHdvXG4gKiBzdHJpbmdzLCB3aGVyZSB0aGUgc3RyaW5ncyBhcmUgdGhlIGZpZWxkIG5hbWVzIHRoYXQgaWRlbnRpZnkgdGhlIGZvcmVpZ24ga2V5cyB0b1xuICogYmUgdXNlZCBmb3IgdGhlIG1hbnktdG8tbWFueSByZWxhdGlvbnNoaXAuIFJlZHV4LU9STSB3aWxsIGZpZ3VyZSBvdXQgd2hpY2ggZmllbGQgbmFtZVxuICogcG9pbnRzIHRvIHdoaWNoIG1vZGVsIGJ5IGNoZWNraW5nIHRoZSBcInRocm91Z2ggbW9kZWxcIiBkZWZpbml0aW9uLlxuICpcbiAqIGBgYGphdmFzY3JpcHRcbiAqIGNsYXNzIEF1dGhvcnNoaXAgZXh0ZW5kcyBNb2RlbCB7fVxuICogQXV0aG9yc2hpcC5tb2RlbE5hbWUgPSAnQXV0aG9yc2hpcCc7XG4gKiBBdXRob3JzaGlwLmZpZWxkcyA9IHtcbiAqICAgYXV0aG9yOiBmaygnQXV0aG9yJywgJ2F1dGhvcnNoaXBzJyksXG4gKiAgIGJvb2s6IGZrKCdCb29rJywgJ2F1dGhvcnNoaXBzJyksXG4gKiB9O1xuICpcbiAqIGNsYXNzIEF1dGhvciBleHRlbmRzIE1vZGVsIHt9XG4gKiBBdXRob3IubW9kZWxOYW1lID0gJ0F1dGhvcic7XG4gKiBBdXRob3IuZmllbGRzID0ge1xuICogICBib29rczogbWFueSh7XG4gKiAgICAgdG86ICdCb29rJyxcbiAqICAgICByZWxhdGVkTmFtZTogJ2F1dGhvcnMnLFxuICogICAgIHRocm91Z2g6ICdBdXRob3JzaGlwJyxcbiAqXG4gKiAgICAgLy8gaGVyZSB0aGlzIGlzIG9wdGlvbmFsOiBSZWR1eC1PUk0gY2FuIGZpZ3VyZVxuICogICAgIC8vIG91dCB0aGUgdGhyb3VnaCBmaWVsZHMgaXRzZWxmIHNpbmNlIHRoZSB0d29cbiAqICAgICAvLyBmb3JlaWduIGtleSBmaWVsZHMgcG9pbnQgdG8gZGlmZmVyZW50IE1vZGVsc1xuICogICAgIHRocm91Z2hGaWVsZHM6IFsnYXV0aG9yJywgJ2Jvb2snXSxcbiAqICAgfSlcbiAqIH07XG4gKlxuICogY2xhc3MgQm9vayBleHRlbmRzIE1vZGVsIHt9XG4gKiBCb29rLm1vZGVsTmFtZSA9ICdCb29rJztcbiAqIGBgYFxuICpcbiAqIFlvdSBzaG91bGQgb25seSBkZWZpbmUgdGhlIG1hbnktdG8tbWFueSByZWxhdGlvbnNoaXAgb24gb25lIHNpZGUuIEluIHRoZVxuICogYWJvdmUgY2FzZSBvZiBBdXRob3JzIHRvIEJvb2tzIHRocm91Z2ggQXV0aG9yc2hpcHMsIHRoZSByZWxhdGlvbnNoaXAgaXNcbiAqIGRlZmluZWQgb25seSBvbiB0aGUgQXV0aG9yIG1vZGVsLlxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfENsYXNzPE1vZGVsPnxPYmplY3R9IG9wdGlvbnMgLSBUaGUgdGFyZ2V0IE1vZGVsIGNsYXNzLCBpdHMgYG1vZGVsTmFtZWBcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdHRyaWJ1dGUgb3IgYW4gb3B0aW9ucyBvYmplY3QgdGhhdFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRhaW5zIGVpdGhlciBhcyB0aGUgYHRvYCBrZXkuXG4gKiBAcGFyYW0ge3N0cmluZ3xDbGFzczxNb2RlbD59IG9wdGlvbnMudG8gLSBUaGUgdGFyZ2V0IE1vZGVsIGNsYXNzIG9yIGl0cyBgbW9kZWxOYW1lYCBhdHRyaWJ1dGUuXG4gKiBAcGFyYW0ge3N0cmluZ30gW29wdGlvbnMuYXNdIC0gTmFtZSBmb3IgdGhlIG5ldyBhY2Nlc3NvciBkZWZpbmVkIGZvciB0aGlzIGZpZWxkLiBJZiB5b3UgZG9uJ3RcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXBwbHkgdGhpcywgdGhlIGtleSB0aGF0IHRoaXMgZmllbGQgaXMgZGVmaW5lZCB1bmRlciB3aWxsIGJlXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3ZlcnJpZGRlbi5cbiAqIEBwYXJhbSB7c3RyaW5nfENsYXNzPE1vZGVsPn0gW29wdGlvbnMudGhyb3VnaF0gLSBUaGUgdGhyb3VnaCBNb2RlbCBjbGFzcyBvciBpdHMgYG1vZGVsTmFtZWBcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdHRyaWJ1dGUuIEl0IG11c3QgZGVjbGFyZSBhdCBsZWFzdCBvbmVcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JlaWduIGtleSB0byBib3RoIHNvdXJjZSBhbmQgdGFyZ2V0IG1vZGVscy5cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBub3Qgc3VwcGxpZWQsIFJlZHV4LU9STSB3aWxsIGdlbmVyYXRlIG9uZS5cbiAqIEBwYXJhbSB7c3RyaW5nW119IFtvcHRpb25zLnRocm91Z2hGaWVsZHNdIC0gTXVzdCBiZSBzdXBwbGllZCBvbmx5IHdoZW4gYSBjdXN0b20gdGhyb3VnaFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNb2RlbCBoYXMgbW9yZSB0aGFuIG9uZSBmb3JlaWduIGtleSBwb2ludGluZyB0b1xuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlaXRoZXIgdGhlIHNvdXJjZSBvciB0YXJnZXQgbW9kZS4gSW4gdGhpcyBjYXNlXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZHV4LU9STSBjYW4ndCBmaWd1cmUgb3V0IHRoZSBjb3JyZWN0IGZpZWxkcyBmb3JcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeW91LCB5b3UgbXVzdCBwcm92aWRlIHRoZW0uIFRoZSBzdXBwbGllZCBhcnJheSBzaG91bGRcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGF2ZSB0d28gZWxlbWVudHMgdGhhdCBhcmUgdGhlIGZpZWxkIG5hbWVzIGZvciB0aGVcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhyb3VnaCBmaWVsZHMgeW91IHdhbnQgdG8gZGVjbGFyZSB0aGUgbWFueS10by1tYW55XG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0aW9uc2hpcCB3aXRoLiBUaGUgb3JkZXIgZG9lc24ndCBtYXR0ZXI7XG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZHV4LU9STSB3aWxsIGZpZ3VyZSBvdXQgd2hpY2ggZmllbGQgcG9pbnRzIHRvXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBzb3VyY2UgTW9kZWwgYW5kIHdoaWNoIHRvIHRoZSB0YXJnZXQgTW9kZWwuXG4gKiBAcGFyYW0ge3N0cmluZ30gW29wdGlvbnMucmVsYXRlZE5hbWVdIC0gVGhlIGF0dHJpYnV0ZSB1c2VkIHRvIGFjY2VzcyBhIFF1ZXJ5U2V0IGZvciBhbGxcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2UgbW9kZWxzIHRoYXQgcmVmZXJlbmNlIHRoZSByZXNwZWN0aXZlIHRhcmdldFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVsJ3MgaW5zdGFuY2UuXG4gKiBAcGFyYW0ge3N0cmluZ30gW3JlbGF0ZWROYW1lXSAtIElmIHlvdSBkaWRuJ3QgcGFzcyBhbiBvYmplY3QgYXMgdGhlIGZpcnN0IGFyZ3VtZW50LFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzIGlzIHRoZSBwcm9wZXJ0eSBuYW1lIHRoYXQgd2lsbCBiZSB1c2VkIHRvXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjY2VzcyBhIFF1ZXJ5U2V0IGZvciBhbGwgc291cmNlIG1vZGVscyB0aGF0IHJlZmVyZW5jZVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGUgcmVzcGVjdGl2ZSB0YXJnZXQgTW9kZWwncyBpbnN0YW5jZS5cbiAqIEByZXR1cm4ge01hbnlUb01hbnl9XG4gKi9cbmZ1bmN0aW9uIG1hbnkoLi4uYXJncykge1xuICAgIHJldHVybiBuZXcgTWFueVRvTWFueSguLi5hcmdzKTtcbn1cblxuLyoqXG4gKiBEZWZpbmVzIGEgb25lLXRvLW9uZSByZWxhdGlvbnNoaXAuIEluIGRhdGFiYXNlIHRlcm1zLCB0aGlzIGlzIGEgZm9yZWlnbiBrZXkgd2l0aCB0aGVcbiAqIGFkZGVkIHJlc3RyaWN0aW9uIHRoYXQgb25seSBvbmUgZW50aXR5IGNhbiBwb2ludCB0byBzaW5nbGUgdGFyZ2V0IGVudGl0eS5cbiAqXG4gKiBUaGUgYXJndW1lbnRzIGFyZSB0aGUgc2FtZSBhcyB3aXRoIGBma2AuIElmIGByZWxhdGVkTmFtZWAgaXMgbm90IHN1cHBsaWVkLFxuICogdGhlIHNvdXJjZSBtb2RlbCBuYW1lIGluIGxvd2VyY2FzZSB3aWxsIGJlIHVzZWQuIE5vdGUgdGhhdCB3aXRoIHRoZSBvbmUtdG8tb25lXG4gKiByZWxhdGlvbnNoaXAsIHRoZSBgcmVsYXRlZE5hbWVgIHNob3VsZCBiZSBpbiBzaW5ndWxhciwgbm90IHBsdXJhbC5cbiAqXG4gKlxuICogQHBhcmFtIHtzdHJpbmd8Q2xhc3M8TW9kZWw+fE9iamVjdH0gb3B0aW9ucyAtIFRoZSB0YXJnZXQgTW9kZWwgY2xhc3MsIGl0cyBgbW9kZWxOYW1lYFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF0dHJpYnV0ZSBvciBhbiBvcHRpb25zIG9iamVjdCB0aGF0XG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGFpbnMgZWl0aGVyIGFzIHRoZSBgdG9gIGtleS5cbiAqIEBwYXJhbSB7c3RyaW5nfENsYXNzPE1vZGVsPn0gb3B0aW9ucy50byAtIFRoZSB0YXJnZXQgTW9kZWwgY2xhc3Mgb3IgaXRzIGBtb2RlbE5hbWVgIGF0dHJpYnV0ZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBbb3B0aW9ucy5hc10gLSBOYW1lIGZvciB0aGUgbmV3IGFjY2Vzc29yIGRlZmluZWQgZm9yIHRoaXMgZmllbGQuIElmIHlvdSBkb24ndFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1cHBseSB0aGlzLCB0aGUga2V5IHRoYXQgdGhpcyBmaWVsZCBpcyBkZWZpbmVkIHVuZGVyIHdpbGwgYmVcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdmVycmlkZGVuLlxuICogQHBhcmFtIHtzdHJpbmd9IFtvcHRpb25zLnJlbGF0ZWROYW1lXSAtIFRoZSBwcm9wZXJ0eSBuYW1lIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGFjY2VzcyB0aGUgc291cmNlXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgaW5zdGFuY2UgcmVmZXJlbmNpbmcgdGhlIHRhcmdldCBtb2RlbCBpbnN0YW5jZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBbcmVsYXRlZE5hbWVdIC0gVGhlIHByb3BlcnR5IG5hbWUgdGhhdCB3aWxsIGJlIHVzZWQgdG8gYWNjZXNzIHRoZSBzb3VyY2VcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgaW5zdGFuY2UgcmVmZXJlbmNpbmcgdGhlIHRhcmdldCBtb2RlbCBpbnN0YW5jZVxuICogQHJldHVybiB7T25lVG9PbmV9XG4gKi9cbmZ1bmN0aW9uIG9uZVRvT25lKC4uLmFyZ3MpIHtcbiAgICByZXR1cm4gbmV3IE9uZVRvT25lKC4uLmFyZ3MpO1xufVxuXG5leHBvcnQgeyBmaywgYXR0ciwgbWFueSwgb25lVG9PbmUgfTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/fields/index.js\\n\");\n \n /***/ }),\n \n@@ -4678,7 +4700,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"eqCheck\\\", function() { return eqCheck; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"memoize\\\", function() { return memoize; });\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./constants */ \\\"./src/constants.js\\\");\\n\\n\\nconst defaultEqualityCheck = (a, b) => a === b;\\n\\nconst eqCheck = defaultEqualityCheck;\\n\\nconst isOrmState = arg => arg && typeof arg === \\\"object\\\" && arg.hasOwnProperty(_constants__WEBPACK_IMPORTED_MODULE_0__[\\\"STATE_FLAG\\\"]);\\n\\nconst argsAreEqual = (lastArgs, nextArgs, equalityCheck) => nextArgs.every((arg, index) => isOrmState(arg) && isOrmState(lastArgs[index]) || equalityCheck(arg, lastArgs[index]));\\n\\nconst rowsAreEqual = (ids, rowsA, rowsB) => ids.every(id => rowsA[id] === rowsB[id]);\\n\\nconst accessedModelInstancesAreEqual = (previous, ormState, orm) => {\\n  const {\\n    accessedInstances\\n  } = previous;\\n  return Object.entries(accessedInstances).every(([modelName, instances]) => {\\n    // if the entire table has not been changed, we have nothing to do\\n    if (previous.ormState[modelName] === ormState[modelName]) {\\n      return true;\\n    }\\n\\n    const {\\n      mapName\\n    } = orm.getDatabase().describe(modelName);\\n    const {\\n      [mapName]: previousRows\\n    } = previous.ormState[modelName];\\n    const {\\n      [mapName]: rows\\n    } = ormState[modelName];\\n    const accessedIds = Object.keys(instances);\\n    return rowsAreEqual(accessedIds, previousRows, rows);\\n  });\\n};\\n\\nconst accessedIndexesAreEqual = (previous, ormState) => {\\n  const {\\n    accessedIndexes\\n  } = previous;\\n  return Object.entries(accessedIndexes).every(([modelName, indexes]) => Object.entries(indexes).every(([column, values]) => values.every(value => previous.ormState[modelName].indexes[column][value] === ormState[modelName].indexes[column][value])));\\n};\\n\\nconst fullTableScannedModelsAreEqual = (previous, ormState) => previous.fullTableScannedModels.every(modelName => previous.ormState[modelName] === ormState[modelName]);\\n/**\\n * A memoizer to use with redux-orm\\n * selectors. When the memoized function is first run,\\n * the memoizer will remember the models that are accessed\\n * during that function run.\\n *\\n * On subsequent runs, the memoizer will check if those\\n * models' states have changed compared to the previous run.\\n *\\n * Memoization algorithm operates like this:\\n *\\n * 1. Has the selector been run before? If not, go to 6.\\n *\\n * 2. If the selector has other input selectors in addition to the\\n *    ORM state selector, check their results for equality with the previous results.\\n *    If they aren't equal, go to 6.\\n *\\n * 3. Some filter queries may have required scanning entire tables during the last run.\\n *    If any of those tables have changed, go to 6.\\n *\\n * 4. Check which foreign key indexes the database has used to speed up queries\\n *    during the last run. If any have changed, go to 6.\\n *\\n * 5. Check which Model's instances the selector has accessed during the last run.\\n *    Check for equality with each of those states versus their states in the\\n *    previous ORM state. If all of them are equal, return the previous result.\\n *\\n * 6. Run the selector. Check the Session object used by the selector for\\n *    which Model's states were accessed, and merge them with the previously\\n *    saved information about accessed models (if-else branching can change\\n *    which models are accessed on different inputs). Save the ORM state and\\n *    other arguments the selector was called with, overriding previously\\n *    saved values. Save the selector result. Return the selector result.\\n *\\n * @private\\n * @param  {Function} func - function to memoize\\n * @param  {Function} argEqualityCheck - equality check function to use with normal\\n *                                       selector args\\n * @param  {ORM} orm - a redux-orm ORM instance\\n * @return {Function} `func` memoized.\\n */\\n\\n\\nfunction memoize(func, argEqualityCheck = defaultEqualityCheck, orm) {\\n  let previous = {\\n    /* Result of the previous function call */\\n    result: null,\\n\\n    /* Arguments to the previous function call (excluding ORM state) */\\n    args: null,\\n\\n    /**\\n     * Snapshot of the previous database.\\n     *\\n     * Lets us know how the tables looked like\\n     * during the previous function call.\\n     */\\n    ormState: null,\\n\\n    /**\\n     * Names of models whose tables have been scanned completely\\n     * during previous function call (contains only model names)\\n     * Format example: ['Book']\\n     */\\n    fullTableScannedModels: [],\\n\\n    /**\\n     * Map of which model instances have been accessed\\n     * during previous function call.\\n     * Contains only PKs of accessed instances.\\n     * Format example: { Book: { 1: true, 3: true } }\\n     */\\n    accessedInstances: {},\\n\\n    /**\\n     * Map of which attribute indexes have been accessed\\n     * during previous function call.\\n     * Contains only attributes that were actually filtered on.\\n     * Author.withId(3).books would add 3 to the authorId index below.\\n     * Format example: { Book: { authorId: [1, 2], publisherId: [5] } }\\n     */\\n    accessedIndexes: {}\\n  };\\n  return (...stateAndArgs) => {\\n    /**\\n     * The first argument to this function needs to be\\n     * the ORM's reducer state in the user's Redux store.\\n     */\\n    const [ormState, ...args] = stateAndArgs;\\n    const selectorWasCalledBefore = Boolean(previous.args);\\n\\n    if (selectorWasCalledBefore && argsAreEqual(previous.args, args, argEqualityCheck) && fullTableScannedModelsAreEqual(previous, ormState) && accessedIndexesAreEqual(previous, ormState) && accessedModelInstancesAreEqual(previous, ormState, orm)) {\\n      /**\\n       * None of this selector's dependencies have changed\\n       * since the last time that we called it.\\n       */\\n      return previous.result;\\n    }\\n    /**\\n     * Start a session so that the selector can access the database.\\n     * Make this session immutable. This way we can find out if\\n     * the operations that the selector performs are cacheable.\\n     */\\n\\n\\n    const session = orm.session(ormState);\\n    /* Replace all ORM state arguments by the session above */\\n\\n    const argsWithSession = args.map(arg => isOrmState(arg) ? session : arg);\\n    /* This is where we call the actual function */\\n\\n    const result = func.apply(null, argsWithSession); // eslint-disable-line prefer-spread\\n\\n    /**\\n     * The metadata for the previous call are no longer valid.\\n     * Update cached values.\\n     */\\n\\n    previous = {\\n      /* Arguments that were passed to the selector */\\n      args,\\n\\n      /* Selector result */\\n      result,\\n\\n      /* Redux state slice for session.state */\\n      ormState,\\n\\n      /* Rows retrieved by resolved primary key */\\n      accessedInstances: session.accessedModelInstances,\\n\\n      /* Foreign key indexes that were used to speed up queries */\\n      accessedIndexes: session.accessedIndexes,\\n\\n      /* Tables that had to be scanned completely */\\n      fullTableScannedModels: session.fullTableScannedModels\\n    };\\n    return result;\\n  };\\n}//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9tZW1vaXplLmpzPzkxY2IiXSwibmFtZXMiOlsiZGVmYXVsdEVxdWFsaXR5Q2hlY2siLCJhIiwiYiIsImVxQ2hlY2siLCJpc09ybVN0YXRlIiwiYXJnIiwiaGFzT3duUHJvcGVydHkiLCJTVEFURV9GTEFHIiwiYXJnc0FyZUVxdWFsIiwibGFzdEFyZ3MiLCJuZXh0QXJncyIsImVxdWFsaXR5Q2hlY2siLCJldmVyeSIsImluZGV4Iiwicm93c0FyZUVxdWFsIiwiaWRzIiwicm93c0EiLCJyb3dzQiIsImlkIiwiYWNjZXNzZWRNb2RlbEluc3RhbmNlc0FyZUVxdWFsIiwicHJldmlvdXMiLCJvcm1TdGF0ZSIsIm9ybSIsImFjY2Vzc2VkSW5zdGFuY2VzIiwiT2JqZWN0IiwiZW50cmllcyIsIm1vZGVsTmFtZSIsImluc3RhbmNlcyIsIm1hcE5hbWUiLCJnZXREYXRhYmFzZSIsImRlc2NyaWJlIiwicHJldmlvdXNSb3dzIiwicm93cyIsImFjY2Vzc2VkSWRzIiwia2V5cyIsImFjY2Vzc2VkSW5kZXhlc0FyZUVxdWFsIiwiYWNjZXNzZWRJbmRleGVzIiwiaW5kZXhlcyIsImNvbHVtbiIsInZhbHVlcyIsInZhbHVlIiwiZnVsbFRhYmxlU2Nhbm5lZE1vZGVsc0FyZUVxdWFsIiwiZnVsbFRhYmxlU2Nhbm5lZE1vZGVscyIsIm1lbW9pemUiLCJmdW5jIiwiYXJnRXF1YWxpdHlDaGVjayIsInJlc3VsdCIsImFyZ3MiLCJzdGF0ZUFuZEFyZ3MiLCJzZWxlY3Rvcldhc0NhbGxlZEJlZm9yZSIsIkJvb2xlYW4iLCJzZXNzaW9uIiwiYXJnc1dpdGhTZXNzaW9uIiwibWFwIiwiYXBwbHkiLCJhY2Nlc3NlZE1vZGVsSW5zdGFuY2VzIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBOztBQUVBLE1BQU1BLG9CQUFvQixHQUFHLENBQUNDLENBQUQsRUFBSUMsQ0FBSixLQUFVRCxDQUFDLEtBQUtDLENBQTdDOztBQUNP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TSxHQUFMLENBQVNoRCxHQUFHLElBQ2hDRCxVQUFVLENBQUNDLEdBQUQsQ0FBVixHQUFrQjhDLE9BQWxCLEdBQTRCOUMsR0FEUixDQUF4QjtBQUlBOztBQUNBLFVBQU15QyxNQUFNLEdBQUdGLElBQUksQ0FBQ1UsS0FBTCxDQUFXLElBQVgsRUFBaUJGLGVBQWpCLENBQWYsQ0FsQ3dCLENBa0MwQjs7QUFFbEQ7Ozs7O0FBSUFoQyxZQUFRLEdBQUc7QUFDUDtBQUNBMkIsVUFGTzs7QUFHUDtBQUNBRCxZQUpPOztBQUtQO0FBQ0F6QixjQU5POztBQU9QO0FBQ0FFLHVCQUFpQixFQUFFNEIsT0FBTyxDQUFDSSxzQkFScEI7O0FBU1A7QUFDQW5CLHFCQUFlLEVBQUVlLE9BQU8sQ0FBQ2YsZUFWbEI7O0FBV1A7QUFDQU0sNEJBQXNCLEVBQUVTLE9BQU8sQ0FBQ1Q7QUFaekIsS0FBWDtBQWVBLFdBQU9JLE1BQVA7QUFDSCxHQXhERDtBQXlESCIsImZpbGUiOiIuL3NyYy9tZW1vaXplLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgU1RBVEVfRkxBRyB9IGZyb20gXCIuL2NvbnN0YW50c1wiO1xuXG5jb25zdCBkZWZhdWx0RXF1YWxpdHlDaGVjayA9IChhLCBiKSA9PiBhID09PSBiO1xuZXhwb3J0IGNvbnN0IGVxQ2hlY2sgPSBkZWZhdWx0RXF1YWxpdHlDaGVjaztcblxuY29uc3QgaXNPcm1TdGF0ZSA9IGFyZyA9PlxuICAgIGFyZyAmJiB0eXBlb2YgYXJnID09PSBcIm9iamVjdFwiICYmIGFyZy5oYXNPd25Qcm9wZXJ0eShTVEFURV9GTEFHKTtcblxuY29uc3QgYXJnc0FyZUVxdWFsID0gKGxhc3RBcmdzLCBuZXh0QXJncywgZXF1YWxpdHlDaGVjaykgPT5cbiAgICBuZXh0QXJncy5ldmVyeShcbiAgICAgICAgKGFyZywgaW5kZXgpID0+XG4gICAgICAgICAgICAoaXNPcm1TdGF0ZShhcmcpICYmIGlzT3JtU3RhdGUobGFzdEFyZ3NbaW5kZXhdKSkgfHxcbiAgICAgICAgICAgIGVxdWFsaXR5Q2hlY2soYXJnLCBsYXN0QXJnc1tpbmRleF0pXG4gICAgKTtcblxuY29uc3Qgcm93c0FyZUVxdWFsID0gKGlkcywgcm93c0EsIHJvd3NCKSA9PlxuICAgIGlkcy5ldmVyeShpZCA9PiByb3dzQVtpZF0gPT09IHJvd3NCW2lkXSk7XG5cbmNvbnN0IGFjY2Vzc2VkTW9kZWxJbnN0YW5jZXNBcmVFcXVhbCA9IChwcmV2aW91cywgb3JtU3RhdGUsIG9ybSkgPT4ge1xuICAgIGNvbnN0IHsgYWNjZXNzZWRJbnN0YW5jZXMgfSA9IHByZXZpb3VzO1xuXG4gICAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKGFjY2Vzc2VkSW5zdGFuY2VzKS5ldmVyeSgoW21vZGVsTmFtZSwgaW5zdGFuY2VzXSkgPT4ge1xuICAgICAgICAvLyBpZiB0aGUgZW50aXJlIHRhYmxlIGhhcyBub3QgYmVlbiBjaGFuZ2VkLCB3ZSBoYXZlIG5vdGhpbmcgdG8gZG9cbiAgICAgICAgaWYgKHByZXZpb3VzLm9ybVN0YXRlW21vZGVsTmFtZV0gPT09IG9ybVN0YXRlW21vZGVsTmFtZV0pIHtcbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgeyBtYXBOYW1lIH0gPSBvcm0uZ2V0RGF0YWJhc2UoKS5kZXNjcmliZShtb2RlbE5hbWUpO1xuXG4gICAgICAgIGNvbnN0IHsgW21hcE5hbWVdOiBwcmV2aW91c1Jvd3MgfSA9IHByZXZpb3VzLm9ybVN0YXRlW21vZGVsTmFtZV07XG4gICAgICAgIGNvbnN0IHsgW21hcE5hbWVdOiByb3dzIH0gPSBvcm1TdGF0ZVttb2RlbE5hbWVdO1xuXG4gICAgICAgIGNvbnN0IGFjY2Vzc2VkSWRzID0gT2JqZWN0LmtleXMoaW5zdGFuY2VzKTtcbiAgICAgICAgcmV0dXJuIHJvd3NBcmVFcXVhbChhY2Nlc3NlZElkcywgcHJldmlvdXNSb3dzLCByb3dzKTtcbiAgICB9KTtcbn07XG5cbmNvbnN0IGFjY2Vzc2VkSW5kZXhlc0FyZUVxdWFsID0gKHByZXZpb3VzLCBvcm1TdGF0ZSkgPT4ge1xuICAgIGNvbnN0IHsgYWNjZXNzZWRJbmRleGVzIH0gPSBwcmV2aW91cztcblxuICAgIHJldHVybiBPYmplY3QuZW50cmllcyhhY2Nlc3NlZEluZGV4ZXMpLmV2ZXJ5KChbbW9kZWxOYW1lLCBpbmRleGVzXSkgPT5cbiAgICAgICAgT2JqZWN0LmVudHJpZXMoaW5kZXhlcykuZXZlcnkoKFtjb2x1bW4sIHZhbHVlc10pID0+XG4gICAgICAgICAgICB2YWx1ZXMuZXZlcnkoXG4gICAgICAgICAgICAgICAgdmFsdWUgPT5cbiAgICAgICAgICAgICAgICAgICAgcHJldmlvdXMub3JtU3RhdGVbbW9kZWxOYW1lXS5pbmRleGVzW2NvbHVtbl1bdmFsdWVdID09PVxuICAgICAgICAgICAgICAgICAgICBvcm1TdGF0ZVttb2RlbE5hbWVdLmluZGV4ZXNbY29sdW1uXVt2YWx1ZV1cbiAgICAgICAgICAgIClcbiAgICAgICAgKVxuICAgICk7XG59O1xuXG5jb25zdCBmdWxsVGFibGVTY2FubmVkTW9kZWxzQXJlRXF1YWwgPSAocHJldmlvdXMsIG9ybVN0YXRlKSA9PlxuICAgIHByZXZpb3VzLmZ1bGxUYWJsZVNjYW5uZWRNb2RlbHMuZXZlcnkoXG4gICAgICAgIG1vZGVsTmFtZSA9PiBwcmV2aW91cy5vcm1TdGF0ZVttb2RlbE5hbWVdID09PSBvcm1TdGF0ZVttb2RlbE5hbWVdXG4gICAgKTtcblxuLyoqXG4gKiBBIG1lbW9pemVyIHRvIHVzZSB3aXRoIHJlZHV4LW9ybVxuICogc2VsZWN0b3JzLiBXaGVuIHRoZSBtZW1vaXplZCBmdW5jdGlvbiBpcyBmaXJzdCBydW4sXG4gKiB0aGUgbWVtb2l6ZXIgd2lsbCByZW1lbWJlciB0aGUgbW9kZWxzIHRoYXQgYXJlIGFjY2Vzc2VkXG4gKiBkdXJpbmcgdGhhdCBmdW5jdGlvbiBydW4uXG4gKlxuICogT24gc3Vic2VxdWVudCBydW5zLCB0aGUgbWVtb2l6ZXIgd2lsbCBjaGVjayBpZiB0aG9zZVxuICogbW9kZWxzJyBzdGF0ZXMgaGF2ZSBjaGFuZ2VkIGNvbXBhcmVkIHRvIHRoZSBwcmV2aW91cyBydW4uXG4gKlxuICogTWVtb2l6YXRpb24gYWxnb3JpdGhtIG9wZXJhdGVzIGxpa2UgdGhpczpcbiAqXG4gKiAxLiBIYXMgdGhlIHNlbGVjdG9yIGJlZW4gcnVuIGJlZm9yZT8gSWYgbm90LCBnbyB0byA2LlxuICpcbiAqIDIuIElmIHRoZSBzZWxlY3RvciBoYXMgb3RoZXIgaW5wdXQgc2VsZWN0b3JzIGluIGFkZGl0aW9uIHRvIHRoZVxuICogICAgT1JNIHN0YXRlIHNlbGVjdG9yLCBjaGVjayB0aGVpciByZXN1bHRzIGZvciBlcXVhbGl0eSB3aXRoIHRoZSBwcmV2aW91cyByZXN1bHRzLlxuICogICAgSWYgdGhleSBhcmVuJ3QgZXF1YWwsIGdvIHRvIDYuXG4gKlxuICogMy4gU29tZSBmaWx0ZXIgcXVlcmllcyBtYXkgaGF2ZSByZXF1aXJlZCBzY2FubmluZyBlbnRpcmUgdGFibGVzIGR1cmluZyB0aGUgbGFzdCBydW4uXG4gKiAgICBJZiBhbnkgb2YgdGhvc2UgdGFibGVzIGhhdmUgY2hhbmdlZCwgZ28gdG8gNi5cbiAqXG4gKiA0LiBDaGVjayB3aGljaCBmb3JlaWduIGtleSBpbmRleGVzIHRoZSBkYXRhYmFzZSBoYXMgdXNlZCB0byBzcGVlZCB1cCBxdWVyaWVzXG4gKiAgICBkdXJpbmcgdGhlIGxhc3QgcnVuLiBJZiBhbnkgaGF2ZSBjaGFuZ2VkLCBnbyB0byA2LlxuICpcbiAqIDUuIENoZWNrIHdoaWNoIE1vZGVsJ3MgaW5zdGFuY2VzIHRoZSBzZWxlY3RvciBoYXMgYWNjZXNzZWQgZHVyaW5nIHRoZSBsYXN0IHJ1bi5cbiAqICAgIENoZWNrIGZvciBlcXVhbGl0eSB3aXRoIGVhY2ggb2YgdGhvc2Ugc3RhdGVzIHZlcnN1cyB0aGVpciBzdGF0ZXMgaW4gdGhlXG4gKiAgICBwcmV2aW91cyBPUk0gc3RhdGUuIElmIGFsbCBvZiB0aGVtIGFyZSBlcXVhbCwgcmV0dXJuIHRoZSBwcmV2aW91cyByZXN1bHQuXG4gKlxuICogNi4gUnVuIHRoZSBzZWxlY3Rvci4gQ2hlY2sgdGhlIFNlc3Npb24gb2JqZWN0IHVzZWQgYnkgdGhlIHNlbGVjdG9yIGZvclxuICogICAgd2hpY2ggTW9kZWwncyBzdGF0ZXMgd2VyZSBhY2Nlc3NlZCwgYW5kIG1lcmdlIHRoZW0gd2l0aCB0aGUgcHJldmlvdXNseVxuICogICAgc2F2ZWQgaW5mb3JtYXRpb24gYWJvdXQgYWNjZXNzZWQgbW9kZWxzIChpZi1lbHNlIGJyYW5jaGluZyBjYW4gY2hhbmdlXG4gKiAgICB3aGljaCBtb2RlbHMgYXJlIGFjY2Vzc2VkIG9uIGRpZmZlcmVudCBpbnB1dHMpLiBTYXZlIHRoZSBPUk0gc3RhdGUgYW5kXG4gKiAgICBvdGhlciBhcmd1bWVudHMgdGhlIHNlbGVjdG9yIHdhcyBjYWxsZWQgd2l0aCwgb3ZlcnJpZGluZyBwcmV2aW91c2x5XG4gKiAgICBzYXZlZCB2YWx1ZXMuIFNhdmUgdGhlIHNlbGVjdG9yIHJlc3VsdC4gUmV0dXJuIHRoZSBzZWxlY3RvciByZXN1bHQuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSAge0Z1bmN0aW9ufSBmdW5jIC0gZnVuY3Rpb24gdG8gbWVtb2l6ZVxuICogQHBhcmFtICB7RnVuY3Rpb259IGFyZ0VxdWFsaXR5Q2hlY2sgLSBlcXVhbGl0eSBjaGVjayBmdW5jdGlvbiB0byB1c2Ugd2l0aCBub3JtYWxcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0b3IgYXJnc1xuICogQHBhcmFtICB7T1JNfSBvcm0gLSBhIHJlZHV4LW9ybSBPUk0gaW5zdGFuY2VcbiAqIEByZXR1cm4ge0Z1bmN0aW9ufSBgZnVuY2AgbWVtb2l6ZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBtZW1vaXplKGZ1bmMsIGFyZ0VxdWFsaXR5Q2hlY2sgPSBkZWZhdWx0RXF1YWxpdHlDaGVjaywgb3JtKSB7XG4gICAgbGV0IHByZXZpb3VzID0ge1xuICAgICAgICAvKiBSZXN1bHQgb2YgdGhlIHByZXZpb3VzIGZ1bmN0aW9uIGNhbGwgKi9cbiAgICAgICAgcmVzdWx0OiBudWxsLFxuICAgICAgICAvKiBBcmd1bWVudHMgdG8gdGhlIHByZXZpb3VzIGZ1bmN0aW9uIGNhbGwgKGV4Y2x1ZGluZyBPUk0gc3RhdGUpICovXG4gICAgICAgIGFyZ3M6IG51bGwsXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBTbmFwc2hvdCBvZiB0aGUgcHJldmlvdXMgZGF0YWJhc2UuXG4gICAgICAgICAqXG4gICAgICAgICAqIExldHMgdXMga25vdyBob3cgdGhlIHRhYmxlcyBsb29rZWQgbGlrZVxuICAgICAgICAgKiBkdXJpbmcgdGhlIHByZXZpb3VzIGZ1bmN0aW9uIGNhbGwuXG4gICAgICAgICAqL1xuICAgICAgICBvcm1TdGF0ZTogbnVsbCxcbiAgICAgICAgLyoqXG4gICAgICAgICAqIE5hbWVzIG9mIG1vZGVscyB3aG9zZSB0YWJsZXMgaGF2ZSBiZWVuIHNjYW5uZWQgY29tcGxldGVseVxuICAgICAgICAgKiBkdXJpbmcgcHJldmlvdXMgZnVuY3Rpb24gY2FsbCAoY29udGFpbnMgb25seSBtb2RlbCBuYW1lcylcbiAgICAgICAgICogRm9ybWF0IGV4YW1wbGU6IFsnQm9vayddXG4gICAgICAgICAqL1xuICAgICAgICBmdWxsVGFibGVTY2FubmVkTW9kZWxzOiBbXSxcbiAgICAgICAgLyoqXG4gICAgICAgICAqIE1hcCBvZiB3aGljaCBtb2RlbCBpbnN0YW5jZXMgaGF2ZSBiZWVuIGFjY2Vzc2VkXG4gICAgICAgICAqIGR1cmluZyBwcmV2aW91cyBmdW5jdGlvbiBjYWxsLlxuICAgICAgICAgKiBDb250YWlucyBvbmx5IFBLcyBvZiBhY2Nlc3NlZCBpbnN0YW5jZXMuXG4gICAgICAgICAqIEZvcm1hdCBleGFtcGxlOiB7IEJvb2s6IHsgMTogdHJ1ZSwgMzogdHJ1ZSB9IH1cbiAgICAgICAgICovXG4gICAgICAgIGFjY2Vzc2VkSW5zdGFuY2VzOiB7fSxcbiAgICAgICAgLyoqXG4gICAgICAgICAqIE1hcCBvZiB3aGljaCBhdHRyaWJ1dGUgaW5kZXhlcyBoYXZlIGJlZW4gYWNjZXNzZWRcbiAgICAgICAgICogZHVyaW5nIHByZXZpb3VzIGZ1bmN0aW9uIGNhbGwuXG4gICAgICAgICAqIENvbnRhaW5zIG9ubHkgYXR0cmlidXRlcyB0aGF0IHdlcmUgYWN0dWFsbHkgZmlsdGVyZWQgb24uXG4gICAgICAgICAqIEF1dGhvci53aXRoSWQoMykuYm9va3Mgd291bGQgYWRkIDMgdG8gdGhlIGF1dGhvcklkIGluZGV4IGJlbG93LlxuICAgICAgICAgKiBGb3JtYXQgZXhhbXBsZTogeyBCb29rOiB7IGF1dGhvcklkOiBbMSwgMl0sIHB1Ymxpc2hlcklkOiBbNV0gfSB9XG4gICAgICAgICAqL1xuICAgICAgICBhY2Nlc3NlZEluZGV4ZXM6IHt9LFxuICAgIH07XG5cbiAgICByZXR1cm4gKC4uLnN0YXRlQW5kQXJncykgPT4ge1xuICAgICAgICAvKipcbiAgICAgICAgICogVGhlIGZpcnN0IGFyZ3VtZW50IHRvIHRoaXMgZnVuY3Rpb24gbmVlZHMgdG8gYmVcbiAgICAgICAgICogdGhlIE9STSdzIHJlZHVjZXIgc3RhdGUgaW4gdGhlIHVzZXIncyBSZWR1eCBzdG9yZS5cbiAgICAgICAgICovXG4gICAgICAgIGNvbnN0IFtvcm1TdGF0ZSwgLi4uYXJnc10gPSBzdGF0ZUFuZEFyZ3M7XG5cbiAgICAgICAgY29uc3Qgc2VsZWN0b3JXYXNDYWxsZWRCZWZvcmUgPSBCb29sZWFuKHByZXZpb3VzLmFyZ3MpO1xuICAgICAgICBpZiAoXG4gICAgICAgICAgICBzZWxlY3Rvcldhc0NhbGxlZEJlZm9yZSAmJlxuICAgICAgICAgICAgYXJnc0FyZUVxdWFsKHByZXZpb3VzLmFyZ3MsIGFyZ3MsIGFyZ0VxdWFsaXR5Q2hlY2spICYmXG4gICAgICAgICAgICBmdWxsVGFibGVTY2FubmVkTW9kZWxzQXJlRXF1YWwocHJldmlvdXMsIG9ybVN0YXRlKSAmJlxuICAgICAgICAgICAgYWNjZXNzZWRJbmRleGVzQXJlRXF1YWwocHJldmlvdXMsIG9ybVN0YXRlKSAmJlxuICAgICAgICAgICAgYWNjZXNzZWRNb2RlbEluc3RhbmNlc0FyZUVxdWFsKHByZXZpb3VzLCBvcm1TdGF0ZSwgb3JtKVxuICAgICAgICApIHtcbiAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICogTm9uZSBvZiB0aGlzIHNlbGVjdG9yJ3MgZGVwZW5kZW5jaWVzIGhhdmUgY2hhbmdlZFxuICAgICAgICAgICAgICogc2luY2UgdGhlIGxhc3QgdGltZSB0aGF0IHdlIGNhbGxlZCBpdC5cbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgcmV0dXJuIHByZXZpb3VzLnJlc3VsdDtcbiAgICAgICAgfVxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBTdGFydCBhIHNlc3Npb24gc28gdGhhdCB0aGUgc2VsZWN0b3IgY2FuIGFjY2VzcyB0aGUgZGF0YWJhc2UuXG4gICAgICAgICAqIE1ha2UgdGhpcyBzZXNzaW9uIGltbXV0YWJsZS4gVGhpcyB3YXkgd2UgY2FuIGZpbmQgb3V0IGlmXG4gICAgICAgICAqIHRoZSBvcGVyYXRpb25zIHRoYXQgdGhlIHNlbGVjdG9yIHBlcmZvcm1zIGFyZSBjYWNoZWFibGUuXG4gICAgICAgICAqL1xuICAgICAgICBjb25zdCBzZXNzaW9uID0gb3JtLnNlc3Npb24ob3JtU3RhdGUpO1xuICAgICAgICAvKiBSZXBsYWNlIGFsbCBPUk0gc3RhdGUgYXJndW1lbnRzIGJ5IHRoZSBzZXNzaW9uIGFib3ZlICovXG4gICAgICAgIGNvbnN0IGFyZ3NXaXRoU2Vzc2lvbiA9IGFyZ3MubWFwKGFyZyA9PlxuICAgICAgICAgICAgaXNPcm1TdGF0ZShhcmcpID8gc2Vzc2lvbiA6IGFyZ1xuICAgICAgICApO1xuXG4gICAgICAgIC8qIFRoaXMgaXMgd2hlcmUgd2UgY2FsbCB0aGUgYWN0dWFsIGZ1bmN0aW9uICovXG4gICAgICAgIGNvbnN0IHJlc3VsdCA9IGZ1bmMuYXBwbHkobnVsbCwgYXJnc1dpdGhTZXNzaW9uKTsgLy8gZXNsaW50LWRpc2FibGUtbGluZSBwcmVmZXItc3ByZWFkXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFRoZSBtZXRhZGF0YSBmb3IgdGhlIHByZXZpb3VzIGNhbGwgYXJlIG5vIGxvbmdlciB2YWxpZC5cbiAgICAgICAgICogVXBkYXRlIGNhY2hlZCB2YWx1ZXMuXG4gICAgICAgICAqL1xuICAgICAgICBwcmV2aW91cyA9IHtcbiAgICAgICAgICAgIC8qIEFyZ3VtZW50cyB0aGF0IHdlcmUgcGFzc2VkIHRvIHRoZSBzZWxlY3RvciAqL1xuICAgICAgICAgICAgYXJncyxcbiAgICAgICAgICAgIC8qIFNlbGVjdG9yIHJlc3VsdCAqL1xuICAgICAgICAgICAgcmVzdWx0LFxuICAgICAgICAgICAgLyogUmVkdXggc3RhdGUgc2xpY2UgZm9yIHNlc3Npb24uc3RhdGUgKi9cbiAgICAgICAgICAgIG9ybVN0YXRlLFxuICAgICAgICAgICAgLyogUm93cyByZXRyaWV2ZWQgYnkgcmVzb2x2ZWQgcHJpbWFyeSBrZXkgKi9cbiAgICAgICAgICAgIGFjY2Vzc2VkSW5zdGFuY2VzOiBzZXNzaW9uLmFjY2Vzc2VkTW9kZWxJbnN0YW5jZXMsXG4gICAgICAgICAgICAvKiBGb3JlaWduIGtleSBpbmRleGVzIHRoYXQgd2VyZSB1c2VkIHRvIHNwZWVkIHVwIHF1ZXJpZXMgKi9cbiAgICAgICAgICAgIGFjY2Vzc2VkSW5kZXhlczogc2Vzc2lvbi5hY2Nlc3NlZEluZGV4ZXMsXG4gICAgICAgICAgICAvKiBUYWJsZXMgdGhhdCBoYWQgdG8gYmUgc2Nhbm5lZCBjb21wbGV0ZWx5ICovXG4gICAgICAgICAgICBmdWxsVGFibGVTY2FubmVkTW9kZWxzOiBzZXNzaW9uLmZ1bGxUYWJsZVNjYW5uZWRNb2RlbHMsXG4gICAgICAgIH07XG5cbiAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9O1xufVxuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/memoize.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"eqCheck\\\", function() { return eqCheck; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"memoize\\\", function() { return memoize; });\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./constants */ \\\"./src/constants.js\\\");\\n\\n\\nconst defaultEqualityCheck = (a, b) => a === b;\\n\\nconst eqCheck = defaultEqualityCheck;\\n\\nconst isOrmState = arg => arg && typeof arg === \\\"object\\\" && arg.hasOwnProperty(_constants__WEBPACK_IMPORTED_MODULE_0__[\\\"STATE_FLAG\\\"]);\\n\\nconst argsAreEqual = (lastArgs, nextArgs, equalityCheck) => nextArgs.every((arg, index) => isOrmState(arg) && isOrmState(lastArgs[index]) || equalityCheck(arg, lastArgs[index]));\\n\\nconst rowsAreEqual = (ids, rowsA, rowsB) => ids.every(id => rowsA[id] === rowsB[id]);\\n\\nconst accessedModelInstancesAreEqual = (previous, ormState, orm) => {\\n  const {\\n    accessedInstances\\n  } = previous;\\n  return Object.entries(accessedInstances).every(([modelName, instances]) => {\\n    // if the entire table has not been changed, we have nothing to do\\n    if (previous.ormState[modelName] === ormState[modelName]) {\\n      return true;\\n    }\\n\\n    const {\\n      mapName\\n    } = orm.getDatabase().describe(modelName);\\n    const {\\n      [mapName]: previousRows\\n    } = previous.ormState[modelName];\\n    const {\\n      [mapName]: rows\\n    } = ormState[modelName];\\n    const accessedIds = Object.keys(instances);\\n    return rowsAreEqual(accessedIds, previousRows, rows);\\n  });\\n};\\n\\nconst accessedIndexesAreEqual = (previous, ormState) => {\\n  const {\\n    accessedIndexes\\n  } = previous;\\n  return Object.entries(accessedIndexes).every(([modelName, indexes]) => Object.entries(indexes).every(([column, values]) => values.every(value => previous.ormState[modelName].indexes[column][value] === ormState[modelName].indexes[column][value])));\\n};\\n\\nconst fullTableScannedModelsAreEqual = (previous, ormState) => previous.fullTableScannedModels.every(modelName => previous.ormState[modelName] === ormState[modelName]);\\n/**\\n * A memoizer to use with redux-orm\\n * selectors. When the memoized function is first run,\\n * the memoizer will remember the models that are accessed\\n * during that function run.\\n *\\n * On subsequent runs, the memoizer will check if those\\n * models' states have changed compared to the previous run.\\n *\\n * Memoization algorithm operates like this:\\n *\\n * 1. Has the selector been run before? If not, go to 6.\\n *\\n * 2. If the selector has other input selectors in addition to the\\n *    ORM state selector, check their results for equality with the previous results.\\n *    If they aren't equal, go to 6.\\n *\\n * 3. Some filter queries may have required scanning entire tables during the last run.\\n *    If any of those tables have changed, go to 6.\\n *\\n * 4. Check which foreign key indexes the database has used to speed up queries\\n *    during the last run. If any have changed, go to 6.\\n *\\n * 5. Check which Model's instances the selector has accessed during the last run.\\n *    Check for equality with each of those states versus their states in the\\n *    previous ORM state. If all of them are equal, return the previous result.\\n *\\n * 6. Run the selector. Check the Session object used by the selector for\\n *    which Model's states were accessed, and merge them with the previously\\n *    saved information about accessed models (if-else branching can change\\n *    which models are accessed on different inputs). Save the ORM state and\\n *    other arguments the selector was called with, overriding previously\\n *    saved values. Save the selector result. Return the selector result.\\n *\\n * @private\\n * @param  {Function} func - function to memoize\\n * @param  {Function} argEqualityCheck - equality check function to use with normal\\n *                                       selector args\\n * @param  {ORM} orm - a redux-orm ORM instance\\n * @return {Function} `func` memoized.\\n */\\n\\n\\nfunction memoize(func, argEqualityCheck = defaultEqualityCheck, orm) {\\n  let previous = {\\n    /* Result of the previous function call */\\n    result: null,\\n\\n    /* Arguments to the previous function call (excluding ORM state) */\\n    args: null,\\n\\n    /**\\n     * Snapshot of the previous database.\\n     *\\n     * Lets us know how the tables looked like\\n     * during the previous function call.\\n     */\\n    ormState: null,\\n\\n    /**\\n     * Names of models whose tables have been scanned completely\\n     * during previous function call (contains only model names)\\n     * Format example: ['Book']\\n     */\\n    fullTableScannedModels: [],\\n\\n    /**\\n     * Map of which model instances have been accessed\\n     * during previous function call.\\n     * Contains only PKs of accessed instances.\\n     * Format example: { Book: { 1: true, 3: true } }\\n     */\\n    accessedInstances: {},\\n\\n    /**\\n     * Map of which attribute indexes have been accessed\\n     * during previous function call.\\n     * Contains only attributes that were actually filtered on.\\n     * Author.withId(3).books would add 3 to the authorId index below.\\n     * Format example: { Book: { authorId: [1, 2], publisherId: [5] } }\\n     */\\n    accessedIndexes: {}\\n  };\\n  return (...stateAndArgs) => {\\n    /**\\n     * The first argument to this function needs to be\\n     * the ORM's reducer state in the user's Redux store.\\n     */\\n    const [ormState, ...args] = stateAndArgs;\\n    const selectorWasCalledBefore = Boolean(previous.args);\\n\\n    if (selectorWasCalledBefore && argsAreEqual(previous.args, args, argEqualityCheck) && fullTableScannedModelsAreEqual(previous, ormState) && accessedIndexesAreEqual(previous, ormState) && accessedModelInstancesAreEqual(previous, ormState, orm)) {\\n      /**\\n       * None of this selector's dependencies have changed\\n       * since the last time that we called it.\\n       */\\n      return previous.result;\\n    }\\n    /**\\n     * Start a session so that the selector can access the database.\\n     * Make this session immutable. This way we can find out if\\n     * the operations that the selector performs are cacheable.\\n     */\\n\\n\\n    const session = orm.session(ormState);\\n    /* Replace all ORM state arguments by the session above */\\n\\n    const argsWithSession = args.map(arg => isOrmState(arg) ? session : arg);\\n    /* This is where we call the actual function */\\n\\n    const result = func.apply(null, argsWithSession); // eslint-disable-line prefer-spread\\n\\n    /**\\n     * The metadata for the previous call are no longer valid.\\n     * Update cached values.\\n     */\\n\\n    previous = {\\n      /* Arguments that were passed to the selector */\\n      args,\\n\\n      /* Selector result */\\n      result,\\n\\n      /* Redux state slice for session.state */\\n      ormState,\\n\\n      /* Rows retrieved by resolved primary key */\\n      accessedInstances: session.accessedModelInstances,\\n\\n      /* Foreign key indexes that were used to speed up queries */\\n      accessedIndexes: session.accessedIndexes,\\n\\n      /* Tables that had to be scanned completely */\\n      fullTableScannedModels: session.fullTableScannedModels\\n    };\\n    return result;\\n  };\\n}//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9tZW1vaXplLmpzPzkxY2IiXSwibmFtZXMiOlsiZGVmYXVsdEVxdWFsaXR5Q2hlY2siLCJhIiwiYiIsImVxQ2hlY2siLCJpc09ybVN0YXRlIiwiYXJnIiwiaGFzT3duUHJvcGVydHkiLCJTVEFURV9GTEFHIiwiYXJnc0FyZUVxdWFsIiwibGFzdEFyZ3MiLCJuZXh0QXJncyIsImVxdWFsaXR5Q2hlY2siLCJldmVyeSIsImluZGV4Iiwicm93c0FyZUVxdWFsIiwiaWRzIiwicm93c0EiLCJyb3dzQiIsImlkIiwiYWNjZXNzZWRNb2RlbEluc3RhbmNlc0FyZUVxdWFsIiwicHJldmlvdXMiLCJvcm1TdGF0ZSIsIm9ybSIsImFjY2Vzc2VkSW5zdGFuY2VzIiwiT2JqZWN0IiwiZW50cmllcyIsIm1vZGVsTmFtZSIsImluc3RhbmNlcyIsIm1hcE5hbWUiLCJnZXREYXRhYmFzZSIsImRlc2NyaWJlIiwicHJldmlvdXNSb3dzIiwicm93cyIsImFjY2Vzc2VkSWRzIiwia2V5cyIsImFjY2Vzc2VkSW5kZXhlc0FyZUVxdWFsIiwiYWNjZXNzZWRJbmRleGVzIiwiaW5kZXhlcyIsImNvbHVtbiIsInZhbHVlcyIsInZhbHVlIiwiZnVsbFRhYmxlU2Nhbm5lZE1vZGVsc0FyZUVxdWFsIiwiZnVsbFRhYmxlU2Nhbm5lZE1vZGVscyIsIm1lbW9pemUiLCJmdW5jIiwiYXJnRXF1YWxpdHlDaGVjayIsInJlc3VsdCIsImFyZ3MiLCJzdGF0ZUFuZEFyZ3MiLCJzZWxlY3Rvcldhc0NhbGxlZEJlZm9yZSIsIkJvb2xlYW4iLCJzZXNzaW9uIiwiYXJnc1dpdGhTZXNzaW9uIiwibWFwIiwiYXBwbHkiLCJhY2Nlc3NlZE1vZGVsSW5zdGFuY2VzIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBOztBQUVBLE1BQU1BLG9CQUFvQixHQUFHLENBQUNDLENBQUQsRUFBSUMsQ0FBSixLQUFVRCxDQUFDLEtBQUtDLENBQTdDOztBQUNP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LElBR0FjLHVCQUF1QixDQUFDZixRQUFELEVBQVdDLFFBQVgsQ0FIdkIsSUFJQUYsOEJBQThCLENBQUNDLFFBQUQsRUFBV0MsUUFBWCxFQUFxQkMsR0FBckIsQ0FMbEMsRUFNRTtBQUNFO0FBQ1o7QUFDQTtBQUNBO0FBQ1ksYUFBT0YsUUFBUSxDQUFDMEIsTUFBaEI7QUFDSDtBQUVEO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7OztBQUNRLFVBQU1LLE9BQU8sR0FBRzdCLEdBQUcsQ0FBQzZCLE9BQUosQ0FBWTlCLFFBQVosQ0FBaEI7QUFDQTs7QUFDQSxVQUFNK0IsZUFBZSxHQUFHTCxJQUFJLENBQUNNLEdBQUwsQ0FBVWhELEdBQUQsSUFDN0JELFVBQVUsQ0FBQ0MsR0FBRCxDQUFWLEdBQWtCOEMsT0FBbEIsR0FBNEI5QyxHQURSLENBQXhCO0FBSUE7O0FBQ0EsVUFBTXlDLE1BQU0sR0FBR0YsSUFBSSxDQUFDVSxLQUFMLENBQVcsSUFBWCxFQUFpQkYsZUFBakIsQ0FBZixDQWxDd0IsQ0FrQzBCOztBQUVsRDtBQUNSO0FBQ0E7QUFDQTs7QUFDUWhDLFlBQVEsR0FBRztBQUNQO0FBQ0EyQixVQUZPOztBQUdQO0FBQ0FELFlBSk87O0FBS1A7QUFDQXpCLGNBTk87O0FBT1A7QUFDQUUsdUJBQWlCLEVBQUU0QixPQUFPLENBQUNJLHNCQVJwQjs7QUFTUDtBQUNBbkIscUJBQWUsRUFBRWUsT0FBTyxDQUFDZixlQVZsQjs7QUFXUDtBQUNBTSw0QkFBc0IsRUFBRVMsT0FBTyxDQUFDVDtBQVp6QixLQUFYO0FBZUEsV0FBT0ksTUFBUDtBQUNILEdBeEREO0FBeURIIiwiZmlsZSI6Ii4vc3JjL21lbW9pemUuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBTVEFURV9GTEFHIH0gZnJvbSBcIi4vY29uc3RhbnRzXCI7XG5cbmNvbnN0IGRlZmF1bHRFcXVhbGl0eUNoZWNrID0gKGEsIGIpID0+IGEgPT09IGI7XG5leHBvcnQgY29uc3QgZXFDaGVjayA9IGRlZmF1bHRFcXVhbGl0eUNoZWNrO1xuXG5jb25zdCBpc09ybVN0YXRlID0gKGFyZykgPT5cbiAgICBhcmcgJiYgdHlwZW9mIGFyZyA9PT0gXCJvYmplY3RcIiAmJiBhcmcuaGFzT3duUHJvcGVydHkoU1RBVEVfRkxBRyk7XG5cbmNvbnN0IGFyZ3NBcmVFcXVhbCA9IChsYXN0QXJncywgbmV4dEFyZ3MsIGVxdWFsaXR5Q2hlY2spID0+XG4gICAgbmV4dEFyZ3MuZXZlcnkoXG4gICAgICAgIChhcmcsIGluZGV4KSA9PlxuICAgICAgICAgICAgKGlzT3JtU3RhdGUoYXJnKSAmJiBpc09ybVN0YXRlKGxhc3RBcmdzW2luZGV4XSkpIHx8XG4gICAgICAgICAgICBlcXVhbGl0eUNoZWNrKGFyZywgbGFzdEFyZ3NbaW5kZXhdKVxuICAgICk7XG5cbmNvbnN0IHJvd3NBcmVFcXVhbCA9IChpZHMsIHJvd3NBLCByb3dzQikgPT5cbiAgICBpZHMuZXZlcnkoKGlkKSA9PiByb3dzQVtpZF0gPT09IHJvd3NCW2lkXSk7XG5cbmNvbnN0IGFjY2Vzc2VkTW9kZWxJbnN0YW5jZXNBcmVFcXVhbCA9IChwcmV2aW91cywgb3JtU3RhdGUsIG9ybSkgPT4ge1xuICAgIGNvbnN0IHsgYWNjZXNzZWRJbnN0YW5jZXMgfSA9IHByZXZpb3VzO1xuXG4gICAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKGFjY2Vzc2VkSW5zdGFuY2VzKS5ldmVyeSgoW21vZGVsTmFtZSwgaW5zdGFuY2VzXSkgPT4ge1xuICAgICAgICAvLyBpZiB0aGUgZW50aXJlIHRhYmxlIGhhcyBub3QgYmVlbiBjaGFuZ2VkLCB3ZSBoYXZlIG5vdGhpbmcgdG8gZG9cbiAgICAgICAgaWYgKHByZXZpb3VzLm9ybVN0YXRlW21vZGVsTmFtZV0gPT09IG9ybVN0YXRlW21vZGVsTmFtZV0pIHtcbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgeyBtYXBOYW1lIH0gPSBvcm0uZ2V0RGF0YWJhc2UoKS5kZXNjcmliZShtb2RlbE5hbWUpO1xuXG4gICAgICAgIGNvbnN0IHsgW21hcE5hbWVdOiBwcmV2aW91c1Jvd3MgfSA9IHByZXZpb3VzLm9ybVN0YXRlW21vZGVsTmFtZV07XG4gICAgICAgIGNvbnN0IHsgW21hcE5hbWVdOiByb3dzIH0gPSBvcm1TdGF0ZVttb2RlbE5hbWVdO1xuXG4gICAgICAgIGNvbnN0IGFjY2Vzc2VkSWRzID0gT2JqZWN0LmtleXMoaW5zdGFuY2VzKTtcbiAgICAgICAgcmV0dXJuIHJvd3NBcmVFcXVhbChhY2Nlc3NlZElkcywgcHJldmlvdXNSb3dzLCByb3dzKTtcbiAgICB9KTtcbn07XG5cbmNvbnN0IGFjY2Vzc2VkSW5kZXhlc0FyZUVxdWFsID0gKHByZXZpb3VzLCBvcm1TdGF0ZSkgPT4ge1xuICAgIGNvbnN0IHsgYWNjZXNzZWRJbmRleGVzIH0gPSBwcmV2aW91cztcblxuICAgIHJldHVybiBPYmplY3QuZW50cmllcyhhY2Nlc3NlZEluZGV4ZXMpLmV2ZXJ5KChbbW9kZWxOYW1lLCBpbmRleGVzXSkgPT5cbiAgICAgICAgT2JqZWN0LmVudHJpZXMoaW5kZXhlcykuZXZlcnkoKFtjb2x1bW4sIHZhbHVlc10pID0+XG4gICAgICAgICAgICB2YWx1ZXMuZXZlcnkoXG4gICAgICAgICAgICAgICAgKHZhbHVlKSA9PlxuICAgICAgICAgICAgICAgICAgICBwcmV2aW91cy5vcm1TdGF0ZVttb2RlbE5hbWVdLmluZGV4ZXNbY29sdW1uXVt2YWx1ZV0gPT09XG4gICAgICAgICAgICAgICAgICAgIG9ybVN0YXRlW21vZGVsTmFtZV0uaW5kZXhlc1tjb2x1bW5dW3ZhbHVlXVxuICAgICAgICAgICAgKVxuICAgICAgICApXG4gICAgKTtcbn07XG5cbmNvbnN0IGZ1bGxUYWJsZVNjYW5uZWRNb2RlbHNBcmVFcXVhbCA9IChwcmV2aW91cywgb3JtU3RhdGUpID0+XG4gICAgcHJldmlvdXMuZnVsbFRhYmxlU2Nhbm5lZE1vZGVscy5ldmVyeShcbiAgICAgICAgKG1vZGVsTmFtZSkgPT4gcHJldmlvdXMub3JtU3RhdGVbbW9kZWxOYW1lXSA9PT0gb3JtU3RhdGVbbW9kZWxOYW1lXVxuICAgICk7XG5cbi8qKlxuICogQSBtZW1vaXplciB0byB1c2Ugd2l0aCByZWR1eC1vcm1cbiAqIHNlbGVjdG9ycy4gV2hlbiB0aGUgbWVtb2l6ZWQgZnVuY3Rpb24gaXMgZmlyc3QgcnVuLFxuICogdGhlIG1lbW9pemVyIHdpbGwgcmVtZW1iZXIgdGhlIG1vZGVscyB0aGF0IGFyZSBhY2Nlc3NlZFxuICogZHVyaW5nIHRoYXQgZnVuY3Rpb24gcnVuLlxuICpcbiAqIE9uIHN1YnNlcXVlbnQgcnVucywgdGhlIG1lbW9pemVyIHdpbGwgY2hlY2sgaWYgdGhvc2VcbiAqIG1vZGVscycgc3RhdGVzIGhhdmUgY2hhbmdlZCBjb21wYXJlZCB0byB0aGUgcHJldmlvdXMgcnVuLlxuICpcbiAqIE1lbW9pemF0aW9uIGFsZ29yaXRobSBvcGVyYXRlcyBsaWtlIHRoaXM6XG4gKlxuICogMS4gSGFzIHRoZSBzZWxlY3RvciBiZWVuIHJ1biBiZWZvcmU/IElmIG5vdCwgZ28gdG8gNi5cbiAqXG4gKiAyLiBJZiB0aGUgc2VsZWN0b3IgaGFzIG90aGVyIGlucHV0IHNlbGVjdG9ycyBpbiBhZGRpdGlvbiB0byB0aGVcbiAqICAgIE9STSBzdGF0ZSBzZWxlY3RvciwgY2hlY2sgdGhlaXIgcmVzdWx0cyBmb3IgZXF1YWxpdHkgd2l0aCB0aGUgcHJldmlvdXMgcmVzdWx0cy5cbiAqICAgIElmIHRoZXkgYXJlbid0IGVxdWFsLCBnbyB0byA2LlxuICpcbiAqIDMuIFNvbWUgZmlsdGVyIHF1ZXJpZXMgbWF5IGhhdmUgcmVxdWlyZWQgc2Nhbm5pbmcgZW50aXJlIHRhYmxlcyBkdXJpbmcgdGhlIGxhc3QgcnVuLlxuICogICAgSWYgYW55IG9mIHRob3NlIHRhYmxlcyBoYXZlIGNoYW5nZWQsIGdvIHRvIDYuXG4gKlxuICogNC4gQ2hlY2sgd2hpY2ggZm9yZWlnbiBrZXkgaW5kZXhlcyB0aGUgZGF0YWJhc2UgaGFzIHVzZWQgdG8gc3BlZWQgdXAgcXVlcmllc1xuICogICAgZHVyaW5nIHRoZSBsYXN0IHJ1bi4gSWYgYW55IGhhdmUgY2hhbmdlZCwgZ28gdG8gNi5cbiAqXG4gKiA1LiBDaGVjayB3aGljaCBNb2RlbCdzIGluc3RhbmNlcyB0aGUgc2VsZWN0b3IgaGFzIGFjY2Vzc2VkIGR1cmluZyB0aGUgbGFzdCBydW4uXG4gKiAgICBDaGVjayBmb3IgZXF1YWxpdHkgd2l0aCBlYWNoIG9mIHRob3NlIHN0YXRlcyB2ZXJzdXMgdGhlaXIgc3RhdGVzIGluIHRoZVxuICogICAgcHJldmlvdXMgT1JNIHN0YXRlLiBJZiBhbGwgb2YgdGhlbSBhcmUgZXF1YWwsIHJldHVybiB0aGUgcHJldmlvdXMgcmVzdWx0LlxuICpcbiAqIDYuIFJ1biB0aGUgc2VsZWN0b3IuIENoZWNrIHRoZSBTZXNzaW9uIG9iamVjdCB1c2VkIGJ5IHRoZSBzZWxlY3RvciBmb3JcbiAqICAgIHdoaWNoIE1vZGVsJ3Mgc3RhdGVzIHdlcmUgYWNjZXNzZWQsIGFuZCBtZXJnZSB0aGVtIHdpdGggdGhlIHByZXZpb3VzbHlcbiAqICAgIHNhdmVkIGluZm9ybWF0aW9uIGFib3V0IGFjY2Vzc2VkIG1vZGVscyAoaWYtZWxzZSBicmFuY2hpbmcgY2FuIGNoYW5nZVxuICogICAgd2hpY2ggbW9kZWxzIGFyZSBhY2Nlc3NlZCBvbiBkaWZmZXJlbnQgaW5wdXRzKS4gU2F2ZSB0aGUgT1JNIHN0YXRlIGFuZFxuICogICAgb3RoZXIgYXJndW1lbnRzIHRoZSBzZWxlY3RvciB3YXMgY2FsbGVkIHdpdGgsIG92ZXJyaWRpbmcgcHJldmlvdXNseVxuICogICAgc2F2ZWQgdmFsdWVzLiBTYXZlIHRoZSBzZWxlY3RvciByZXN1bHQuIFJldHVybiB0aGUgc2VsZWN0b3IgcmVzdWx0LlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0gIHtGdW5jdGlvbn0gZnVuYyAtIGZ1bmN0aW9uIHRvIG1lbW9pemVcbiAqIEBwYXJhbSAge0Z1bmN0aW9ufSBhcmdFcXVhbGl0eUNoZWNrIC0gZXF1YWxpdHkgY2hlY2sgZnVuY3Rpb24gdG8gdXNlIHdpdGggbm9ybWFsXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdG9yIGFyZ3NcbiAqIEBwYXJhbSAge09STX0gb3JtIC0gYSByZWR1eC1vcm0gT1JNIGluc3RhbmNlXG4gKiBAcmV0dXJuIHtGdW5jdGlvbn0gYGZ1bmNgIG1lbW9pemVkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gbWVtb2l6ZShmdW5jLCBhcmdFcXVhbGl0eUNoZWNrID0gZGVmYXVsdEVxdWFsaXR5Q2hlY2ssIG9ybSkge1xuICAgIGxldCBwcmV2aW91cyA9IHtcbiAgICAgICAgLyogUmVzdWx0IG9mIHRoZSBwcmV2aW91cyBmdW5jdGlvbiBjYWxsICovXG4gICAgICAgIHJlc3VsdDogbnVsbCxcbiAgICAgICAgLyogQXJndW1lbnRzIHRvIHRoZSBwcmV2aW91cyBmdW5jdGlvbiBjYWxsIChleGNsdWRpbmcgT1JNIHN0YXRlKSAqL1xuICAgICAgICBhcmdzOiBudWxsLFxuICAgICAgICAvKipcbiAgICAgICAgICogU25hcHNob3Qgb2YgdGhlIHByZXZpb3VzIGRhdGFiYXNlLlxuICAgICAgICAgKlxuICAgICAgICAgKiBMZXRzIHVzIGtub3cgaG93IHRoZSB0YWJsZXMgbG9va2VkIGxpa2VcbiAgICAgICAgICogZHVyaW5nIHRoZSBwcmV2aW91cyBmdW5jdGlvbiBjYWxsLlxuICAgICAgICAgKi9cbiAgICAgICAgb3JtU3RhdGU6IG51bGwsXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBOYW1lcyBvZiBtb2RlbHMgd2hvc2UgdGFibGVzIGhhdmUgYmVlbiBzY2FubmVkIGNvbXBsZXRlbHlcbiAgICAgICAgICogZHVyaW5nIHByZXZpb3VzIGZ1bmN0aW9uIGNhbGwgKGNvbnRhaW5zIG9ubHkgbW9kZWwgbmFtZXMpXG4gICAgICAgICAqIEZvcm1hdCBleGFtcGxlOiBbJ0Jvb2snXVxuICAgICAgICAgKi9cbiAgICAgICAgZnVsbFRhYmxlU2Nhbm5lZE1vZGVsczogW10sXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBNYXAgb2Ygd2hpY2ggbW9kZWwgaW5zdGFuY2VzIGhhdmUgYmVlbiBhY2Nlc3NlZFxuICAgICAgICAgKiBkdXJpbmcgcHJldmlvdXMgZnVuY3Rpb24gY2FsbC5cbiAgICAgICAgICogQ29udGFpbnMgb25seSBQS3Mgb2YgYWNjZXNzZWQgaW5zdGFuY2VzLlxuICAgICAgICAgKiBGb3JtYXQgZXhhbXBsZTogeyBCb29rOiB7IDE6IHRydWUsIDM6IHRydWUgfSB9XG4gICAgICAgICAqL1xuICAgICAgICBhY2Nlc3NlZEluc3RhbmNlczoge30sXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBNYXAgb2Ygd2hpY2ggYXR0cmlidXRlIGluZGV4ZXMgaGF2ZSBiZWVuIGFjY2Vzc2VkXG4gICAgICAgICAqIGR1cmluZyBwcmV2aW91cyBmdW5jdGlvbiBjYWxsLlxuICAgICAgICAgKiBDb250YWlucyBvbmx5IGF0dHJpYnV0ZXMgdGhhdCB3ZXJlIGFjdHVhbGx5IGZpbHRlcmVkIG9uLlxuICAgICAgICAgKiBBdXRob3Iud2l0aElkKDMpLmJvb2tzIHdvdWxkIGFkZCAzIHRvIHRoZSBhdXRob3JJZCBpbmRleCBiZWxvdy5cbiAgICAgICAgICogRm9ybWF0IGV4YW1wbGU6IHsgQm9vazogeyBhdXRob3JJZDogWzEsIDJdLCBwdWJsaXNoZXJJZDogWzVdIH0gfVxuICAgICAgICAgKi9cbiAgICAgICAgYWNjZXNzZWRJbmRleGVzOiB7fSxcbiAgICB9O1xuXG4gICAgcmV0dXJuICguLi5zdGF0ZUFuZEFyZ3MpID0+IHtcbiAgICAgICAgLyoqXG4gICAgICAgICAqIFRoZSBmaXJzdCBhcmd1bWVudCB0byB0aGlzIGZ1bmN0aW9uIG5lZWRzIHRvIGJlXG4gICAgICAgICAqIHRoZSBPUk0ncyByZWR1Y2VyIHN0YXRlIGluIHRoZSB1c2VyJ3MgUmVkdXggc3RvcmUuXG4gICAgICAgICAqL1xuICAgICAgICBjb25zdCBbb3JtU3RhdGUsIC4uLmFyZ3NdID0gc3RhdGVBbmRBcmdzO1xuXG4gICAgICAgIGNvbnN0IHNlbGVjdG9yV2FzQ2FsbGVkQmVmb3JlID0gQm9vbGVhbihwcmV2aW91cy5hcmdzKTtcbiAgICAgICAgaWYgKFxuICAgICAgICAgICAgc2VsZWN0b3JXYXNDYWxsZWRCZWZvcmUgJiZcbiAgICAgICAgICAgIGFyZ3NBcmVFcXVhbChwcmV2aW91cy5hcmdzLCBhcmdzLCBhcmdFcXVhbGl0eUNoZWNrKSAmJlxuICAgICAgICAgICAgZnVsbFRhYmxlU2Nhbm5lZE1vZGVsc0FyZUVxdWFsKHByZXZpb3VzLCBvcm1TdGF0ZSkgJiZcbiAgICAgICAgICAgIGFjY2Vzc2VkSW5kZXhlc0FyZUVxdWFsKHByZXZpb3VzLCBvcm1TdGF0ZSkgJiZcbiAgICAgICAgICAgIGFjY2Vzc2VkTW9kZWxJbnN0YW5jZXNBcmVFcXVhbChwcmV2aW91cywgb3JtU3RhdGUsIG9ybSlcbiAgICAgICAgKSB7XG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIE5vbmUgb2YgdGhpcyBzZWxlY3RvcidzIGRlcGVuZGVuY2llcyBoYXZlIGNoYW5nZWRcbiAgICAgICAgICAgICAqIHNpbmNlIHRoZSBsYXN0IHRpbWUgdGhhdCB3ZSBjYWxsZWQgaXQuXG4gICAgICAgICAgICAgKi9cbiAgICAgICAgICAgIHJldHVybiBwcmV2aW91cy5yZXN1bHQ7XG4gICAgICAgIH1cblxuICAgICAgICAvKipcbiAgICAgICAgICogU3RhcnQgYSBzZXNzaW9uIHNvIHRoYXQgdGhlIHNlbGVjdG9yIGNhbiBhY2Nlc3MgdGhlIGRhdGFiYXNlLlxuICAgICAgICAgKiBNYWtlIHRoaXMgc2Vzc2lvbiBpbW11dGFibGUuIFRoaXMgd2F5IHdlIGNhbiBmaW5kIG91dCBpZlxuICAgICAgICAgKiB0aGUgb3BlcmF0aW9ucyB0aGF0IHRoZSBzZWxlY3RvciBwZXJmb3JtcyBhcmUgY2FjaGVhYmxlLlxuICAgICAgICAgKi9cbiAgICAgICAgY29uc3Qgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKG9ybVN0YXRlKTtcbiAgICAgICAgLyogUmVwbGFjZSBhbGwgT1JNIHN0YXRlIGFyZ3VtZW50cyBieSB0aGUgc2Vzc2lvbiBhYm92ZSAqL1xuICAgICAgICBjb25zdCBhcmdzV2l0aFNlc3Npb24gPSBhcmdzLm1hcCgoYXJnKSA9PlxuICAgICAgICAgICAgaXNPcm1TdGF0ZShhcmcpID8gc2Vzc2lvbiA6IGFyZ1xuICAgICAgICApO1xuXG4gICAgICAgIC8qIFRoaXMgaXMgd2hlcmUgd2UgY2FsbCB0aGUgYWN0dWFsIGZ1bmN0aW9uICovXG4gICAgICAgIGNvbnN0IHJlc3VsdCA9IGZ1bmMuYXBwbHkobnVsbCwgYXJnc1dpdGhTZXNzaW9uKTsgLy8gZXNsaW50LWRpc2FibGUtbGluZSBwcmVmZXItc3ByZWFkXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFRoZSBtZXRhZGF0YSBmb3IgdGhlIHByZXZpb3VzIGNhbGwgYXJlIG5vIGxvbmdlciB2YWxpZC5cbiAgICAgICAgICogVXBkYXRlIGNhY2hlZCB2YWx1ZXMuXG4gICAgICAgICAqL1xuICAgICAgICBwcmV2aW91cyA9IHtcbiAgICAgICAgICAgIC8qIEFyZ3VtZW50cyB0aGF0IHdlcmUgcGFzc2VkIHRvIHRoZSBzZWxlY3RvciAqL1xuICAgICAgICAgICAgYXJncyxcbiAgICAgICAgICAgIC8qIFNlbGVjdG9yIHJlc3VsdCAqL1xuICAgICAgICAgICAgcmVzdWx0LFxuICAgICAgICAgICAgLyogUmVkdXggc3RhdGUgc2xpY2UgZm9yIHNlc3Npb24uc3RhdGUgKi9cbiAgICAgICAgICAgIG9ybVN0YXRlLFxuICAgICAgICAgICAgLyogUm93cyByZXRyaWV2ZWQgYnkgcmVzb2x2ZWQgcHJpbWFyeSBrZXkgKi9cbiAgICAgICAgICAgIGFjY2Vzc2VkSW5zdGFuY2VzOiBzZXNzaW9uLmFjY2Vzc2VkTW9kZWxJbnN0YW5jZXMsXG4gICAgICAgICAgICAvKiBGb3JlaWduIGtleSBpbmRleGVzIHRoYXQgd2VyZSB1c2VkIHRvIHNwZWVkIHVwIHF1ZXJpZXMgKi9cbiAgICAgICAgICAgIGFjY2Vzc2VkSW5kZXhlczogc2Vzc2lvbi5hY2Nlc3NlZEluZGV4ZXMsXG4gICAgICAgICAgICAvKiBUYWJsZXMgdGhhdCBoYWQgdG8gYmUgc2Nhbm5lZCBjb21wbGV0ZWx5ICovXG4gICAgICAgICAgICBmdWxsVGFibGVTY2FubmVkTW9kZWxzOiBzZXNzaW9uLmZ1bGxUYWJsZVNjYW5uZWRNb2RlbHMsXG4gICAgICAgIH07XG5cbiAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9O1xufVxuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/memoize.js\\n\");\n \n /***/ }),\n \n@@ -4690,7 +4712,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"defaultUpdater\\\", function() { return defaultUpdater; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"createReducer\\\", function() { return createReducer; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"createSelector\\\", function() { return createSelector; });\\n/* harmony import */ var reselect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! reselect */ \\\"./node_modules/reselect/lib/index.js\\\");\\n/* harmony import */ var reselect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(reselect__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var re_reselect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! re-reselect */ \\\"./node_modules/re-reselect/dist/index.js\\\");\\n/* harmony import */ var re_reselect__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(re_reselect__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _memoize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./memoize */ \\\"./src/memoize.js\\\");\\n/* harmony import */ var _ORM__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./ORM */ \\\"./src/ORM.js\\\");\\n/* harmony import */ var _selectors_SelectorSpec__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./selectors/SelectorSpec */ \\\"./src/selectors/SelectorSpec.js\\\");\\n/* harmony import */ var _selectors_MapSelectorSpec__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./selectors/MapSelectorSpec */ \\\"./src/selectors/MapSelectorSpec.js\\\");\\n\\n\\n\\n\\n\\n\\n/**\\n * @module redux\\n * @desc Provides functions for integration with Redux.\\n */\\n\\n/**\\n * Calls all models' reducers if they exist.\\n *\\n * @return {undefined}\\n * @global\\n */\\n\\nfunction defaultUpdater(session, action) {\\n  session.sessionBoundModels.forEach(modelClass => {\\n    if (typeof modelClass.reducer === \\\"function\\\") {\\n      // This calls this.applyUpdate to update this.state\\n      modelClass.reducer(action, modelClass, session);\\n    }\\n  });\\n}\\n/**\\n * Call the returned function to pass actions to Redux-ORM.\\n *\\n * @global\\n *\\n * @param {ORM} orm - the ORM instance.\\n * @param {Function} [updater] - the function updating the ORM state based on the given action.\\n * @return {Function} reducer that will update the ORM state.\\n */\\n\\nfunction createReducer(orm, updater = defaultUpdater) {\\n  return (state, action) => {\\n    const session = orm.session(state || orm.getEmptyState());\\n    updater(session, action);\\n    return session.state;\\n  };\\n}\\n/**\\n * @private\\n * @param {SelectorSpec} spec\\n */\\n\\nfunction createSelectorFromSpec(spec) {\\n  if (spec instanceof _selectors_MapSelectorSpec__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n    const parentSelector = createSelectorFromSpec(spec.parent);\\n    return spec.createResultFunc(parentSelector);\\n  }\\n\\n  return re_reselect__WEBPACK_IMPORTED_MODULE_1___default()(spec.dependencies, spec.resultFunc)({\\n    keySelector: spec.keySelector,\\n    cacheObject: new re_reselect__WEBPACK_IMPORTED_MODULE_1__[\\\"FlatMapCache\\\"](),\\n    selectorCreator: createSelector // eslint-disable-line no-use-before-define\\n\\n  });\\n}\\n/**\\n * Tries to find ORM instance using the argument.\\n * @private\\n * @param {*} arg\\n */\\n\\n\\nfunction toORM(arg) {\\n  /* eslint-disable no-underscore-dangle */\\n  if (arg instanceof _ORM__WEBPACK_IMPORTED_MODULE_3__[\\\"ORM\\\"]) {\\n    return arg;\\n  }\\n\\n  if (arg instanceof _selectors_SelectorSpec__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"]) {\\n    return arg._orm;\\n  }\\n\\n  return false;\\n}\\n\\nconst selectorCache = new Map();\\nconst SELECTOR_KEY = Symbol.for(\\\"REDUX_ORM_SELECTOR\\\");\\n/**\\n * @private\\n * @param {function|ORM|SelectorSpec} arg\\n */\\n\\nfunction toSelector(arg) {\\n  if (typeof arg === \\\"function\\\") {\\n    return arg;\\n  }\\n\\n  if (arg instanceof _ORM__WEBPACK_IMPORTED_MODULE_3__[\\\"ORM\\\"]) {\\n    return arg.stateSelector;\\n  }\\n\\n  if (arg instanceof _selectors_MapSelectorSpec__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n    // the argument to map() needs to be callable\\n    arg.selector = toSelector(arg.selector);\\n  }\\n\\n  if (arg instanceof _selectors_SelectorSpec__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"]) {\\n    const {\\n      orm,\\n      cachePath\\n    } = arg;\\n    let level; // the selector cache for the spec's ORM\\n\\n    if (!selectorCache.has(orm)) {\\n      selectorCache.set(orm, new Map());\\n    }\\n\\n    const ormSelectors = selectorCache.get(orm);\\n    /**\\n     * Drill down into selector map by cachePath.\\n     *\\n     * The selector itself is stored under a special SELECTOR_KEY\\n     * so that we can store selectors below it as well.\\n     */\\n\\n    level = ormSelectors;\\n\\n    for (let i = 0; i < cachePath.length; ++i) {\\n      const storageKey = cachePath[i];\\n\\n      if (!level.has(storageKey)) {\\n        level.set(storageKey, new Map());\\n      }\\n\\n      level = level.get(storageKey);\\n    }\\n\\n    if (level && level.has(SELECTOR_KEY)) {\\n      // Cache hit: the selector has been created before\\n      return level.get(SELECTOR_KEY);\\n    } // Cache miss: the selector needs to be created\\n\\n\\n    const selector = createSelectorFromSpec(arg); // Save the selector at the cachePath position\\n\\n    level.set(SELECTOR_KEY, selector);\\n    return selector;\\n  }\\n\\n  throw new Error(`Failed to interpret selector argument: ${JSON.stringify(arg)} of type ${typeof arg}`);\\n}\\n/**\\n * Returns a memoized selector based on passed arguments.\\n * This is similar to `reselect`'s `createSelector`,\\n * except you can also pass a single function to be memoized.\\n *\\n * If you pass multiple functions, the format will be the\\n * same as in `reselect`. The last argument is the selector\\n * function and the previous are input selectors.\\n *\\n * When you use this method to create a selector, the returned selector\\n * expects the whole `redux-orm` state branch as input. In the selector\\n * function that you pass as the last argument, any of the arguments\\n * you pass first will be considered selectors and mapped\\n * to their outputs, like in `reselect`.\\n *\\n * Here are some example selectors:\\n *\\n * ```javascript\\n * // orm is an instance of ORM\\n * // reduxState is the state of a Redux store\\n * const books = createSelector(orm.Book);\\n * books(reduxState) // array of book refs\\n *\\n * const bookAuthors = createSelector(orm.Book.authors);\\n * bookAuthors(reduxState) // two-dimensional array of author refs for each book\\n * ```\\n * Selectors can easily be applied to related models:\\n * ```javascript\\n * const bookAuthorNames = createSelector(\\n *     orm.Book.authors.map(orm.Author.name),\\n * );\\n * bookAuthorNames(reduxState, 8) // names of all authors of book with ID 8\\n * bookAuthorNames(reduxState, [8, 9]) // 2D array of names of all authors of books with IDs 8 and 9\\n * ```\\n * Also note that `orm.Author.name` did not need to be wrapped in another `createSelector` call,\\n * although that would be possible.\\n *\\n * For more complex calculations you can access\\n * entire session objects by passing an ORM instance.\\n * ```javascript\\n * const freshBananasCost = createSelector(\\n *     orm,\\n *     session => {\\n *        const banana = session.Product.get({\\n *            name: \\\"Banana\\\",\\n *        });\\n *        // amount of fresh bananas in shopping cart\\n *        const amount = session.ShoppingCart.filter({\\n *            product_id: banana.id,\\n *            is_fresh: true,\\n *        }).count();\\n *        return `USD ${amount * banana.price}`;\\n *     }\\n * );\\n * ```\\n *\\n * redux-orm uses a special memoization function to avoid recomputations.\\n *\\n * Everytime a selector runs, this function records which instances\\n * of your `Model`s were accessed.<br>\\n * On subsequent runs, the selector first checks if the previously\\n * accessed instances or `args` have changed in any way:\\n * <ul>\\n *     <li>If yes, the selector calls the function you passed to it.</li>\\n *     <li>If not, it just returns the previous result\\n *         (unless you call it for the first time).</li>\\n * </ul>\\n *\\n * This way you can use pure rendering in your React components\\n * for performance gains.\\n *\\n * @global\\n *\\n * @param  {...Function} args - zero or more input selectors\\n *                              and the selector function.\\n * @return {Function} memoized selector\\n */\\n\\n\\nfunction createSelector(...args) {\\n  if (!args.length) {\\n    throw new Error(\\\"Cannot create a selector without arguments.\\\");\\n  }\\n\\n  const resultArg = args.pop();\\n  const dependencies = Array.isArray(args[0]) ? args[0] : args;\\n  const orm = dependencies.map(toORM).find(Boolean);\\n  const inputFuncs = dependencies.map(toSelector);\\n\\n  if (typeof resultArg === \\\"function\\\") {\\n    if (!orm) {\\n      throw new Error(\\\"Failed to resolve the current ORM database state. Please pass an ORM instance or an ORM selector as an argument to `createSelector()`.\\\");\\n    } else if (!orm.stateSelector) {\\n      throw new Error(\\\"Failed to resolve the current ORM database state. Please pass an object to the ORM constructor that specifies a `stateSelector` function.\\\");\\n    } else if (typeof orm.stateSelector !== \\\"function\\\") {\\n      throw new Error(`Failed to resolve the current ORM database state. Please pass a function when specifying the ORM's \\\\`stateSelector\\\\`. Received: ${JSON.stringify(orm.stateSelector)} of type ${typeof orm.stateSelector}`);\\n    }\\n\\n    return Object(reselect__WEBPACK_IMPORTED_MODULE_0__[\\\"createSelectorCreator\\\"])(_memoize__WEBPACK_IMPORTED_MODULE_2__[\\\"memoize\\\"], undefined, orm)([orm.stateSelector, ...inputFuncs], resultArg);\\n  }\\n\\n  if (resultArg instanceof _ORM__WEBPACK_IMPORTED_MODULE_3__[\\\"ORM\\\"]) {\\n    throw new Error(\\\"ORM instances cannot be the result function of selectors. You can access your models in the last function that you pass to `createSelector()`.\\\");\\n  }\\n\\n  if (inputFuncs.length) {\\n    console.warn(\\\"Your input selectors will be ignored: the passed result function does not require any input.\\\");\\n  }\\n\\n  return toSelector(resultArg);\\n}//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9yZWR1eC5qcz8wYjAwIl0sIm5hbWVzIjpbImRlZmF1bHRVcGRhdGVyIiwic2Vzc2lvbiIsImFjdGlvbiIsInNlc3Npb25Cb3VuZE1vZGVscyIsImZvckVhY2giLCJtb2RlbENsYXNzIiwicmVkdWNlciIsImNyZWF0ZVJlZHVjZXIiLCJvcm0iLCJ1cGRhdGVyIiwic3RhdGUiLCJnZXRFbXB0eVN0YXRlIiwiY3JlYXRlU2VsZWN0b3JGcm9tU3BlYyIsInNwZWMiLCJNYXBTZWxlY3RvclNwZWMiLCJwYXJlbnRTZWxlY3RvciIsInBhcmVudCIsImNyZWF0ZVJlc3VsdEZ1bmMiLCJjcmVhdGVDYWNoZWRTZWxlY3RvciIsImRlcGVuZGVuY2llcyIsInJlc3VsdEZ1bmMiLCJrZXlTZWxlY3RvciIsImNhY2hlT2JqZWN0IiwiRmxhdE1hcENhY2hlIiwic2VsZWN0b3JDcmVhdG9yIiwiY3JlYXRlU2VsZWN0b3IiLCJ0b09STSIsImFyZyIsIk9STSIsIlNlbGVjdG9yU3BlYyIsIl9vcm0iLCJzZWxlY3RvckNhY2hlIiwiTWFwIiwiU0VMRUNUT1JfS0VZIiwiU3ltYm9sIiwiZm9yIiwidG9TZWxlY3RvciIsInN0YXRlU2VsZWN0b3IiLCJzZWxlY3RvciIsImNhY2hlUGF0aCIsImxldmVsIiwiaGFzIiwic2V0Iiwib3JtU2VsZWN0b3JzIiwiZ2V0IiwiaSIsImxlbmd0aCIsInN0b3JhZ2VLZXkiLCJFcnJvciIsIkpTT04iLCJzdHJpbmdpZnkiLCJhcmdzIiwicmVzdWx0QXJnIiwicG9wIiwiQXJyYXkiLCJpc0FycmF5IiwibWFwIiwiZmluZCIsIkJvb2xlYW4iLCJpbnB1dEZ1bmNzIiwiY3JlYXRlU2VsZWN0b3JDcmVhdG9yIiwibWVtb2l6ZSIsInVuZGVmaW5lZCIsImNvbnNvbGUiLCJ3YXJuIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7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QUMxQixVQUFNLElBQUlvQixLQUFKLENBQ0YsZ0pBREUsQ0FBTjtBQUdIOztBQUNELE1BQUlXLFVBQVUsQ0FBQ2IsTUFBZixFQUF1QjtBQUNuQmlCLFdBQU8sQ0FBQ0MsSUFBUixDQUNJLDhGQURKO0FBR0g7O0FBRUQsU0FBTzVCLFVBQVUsQ0FBQ2dCLFNBQUQsQ0FBakI7QUFDSCIsImZpbGUiOiIuL3NyYy9yZWR1eC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNyZWF0ZVNlbGVjdG9yQ3JlYXRvciB9IGZyb20gXCJyZXNlbGVjdFwiO1xuaW1wb3J0IGNyZWF0ZUNhY2hlZFNlbGVjdG9yLCB7IEZsYXRNYXBDYWNoZSB9IGZyb20gXCJyZS1yZXNlbGVjdFwiO1xuXG5pbXBvcnQgeyBtZW1vaXplIH0gZnJvbSBcIi4vbWVtb2l6ZVwiO1xuXG5pbXBvcnQgeyBPUk0gfSBmcm9tIFwiLi9PUk1cIjtcbmltcG9ydCBTZWxlY3RvclNwZWMgZnJvbSBcIi4vc2VsZWN0b3JzL1NlbGVjdG9yU3BlY1wiO1xuaW1wb3J0IE1hcFNlbGVjdG9yU3BlYyBmcm9tIFwiLi9zZWxlY3RvcnMvTWFwU2VsZWN0b3JTcGVjXCI7XG5cbi8qKlxuICogQG1vZHVsZSByZWR1eFxuICogQGRlc2MgUHJvdmlkZXMgZnVuY3Rpb25zIGZvciBpbnRlZ3JhdGlvbiB3aXRoIFJlZHV4LlxuICovXG5cbi8qKlxuICogQ2FsbHMgYWxsIG1vZGVscycgcmVkdWNlcnMgaWYgdGhleSBleGlzdC5cbiAqXG4gKiBAcmV0dXJuIHt1bmRlZmluZWR9XG4gKiBAZ2xvYmFsXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBkZWZhdWx0VXBkYXRlcihzZXNzaW9uLCBhY3Rpb24pIHtcbiAgICBzZXNzaW9uLnNlc3Npb25Cb3VuZE1vZGVscy5mb3JFYWNoKG1vZGVsQ2xhc3MgPT4ge1xuICAgICAgICBpZiAodHlwZW9mIG1vZGVsQ2xhc3MucmVkdWNlciA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgICAvLyBUaGlzIGNhbGxzIHRoaXMuYXBwbHlVcGRhdGUgdG8gdXBkYXRlIHRoaXMuc3RhdGVcbiAgICAgICAgICAgIG1vZGVsQ2xhc3MucmVkdWNlcihhY3Rpb24sIG1vZGVsQ2xhc3MsIHNlc3Npb24pO1xuICAgICAgICB9XG4gICAgfSk7XG59XG5cbi8qKlxuICogQ2FsbCB0aGUgcmV0dXJuZWQgZnVuY3Rpb24gdG8gcGFzcyBhY3Rpb25zIHRvIFJlZHV4LU9STS5cbiAqXG4gKiBAZ2xvYmFsXG4gKlxuICogQHBhcmFtIHtPUk19IG9ybSAtIHRoZSBPUk0gaW5zdGFuY2UuXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBbdXBkYXRlcl0gLSB0aGUgZnVuY3Rpb24gdXBkYXRpbmcgdGhlIE9STSBzdGF0ZSBiYXNlZCBvbiB0aGUgZ2l2ZW4gYWN0aW9uLlxuICogQHJldHVybiB7RnVuY3Rpb259IHJlZHVjZXIgdGhhdCB3aWxsIHVwZGF0ZSB0aGUgT1JNIHN0YXRlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlUmVkdWNlcihvcm0sIHVwZGF0ZXIgPSBkZWZhdWx0VXBkYXRlcikge1xuICAgIHJldHVybiAoc3RhdGUsIGFjdGlvbikgPT4ge1xuICAgICAgICBjb25zdCBzZXNzaW9uID0gb3JtLnNlc3Npb24oc3RhdGUgfHwgb3JtLmdldEVtcHR5U3RhdGUoKSk7XG4gICAgICAgIHVwZGF0ZXIoc2Vzc2lvbiwgYWN0aW9uKTtcbiAgICAgICAgcmV0dXJuIHNlc3Npb24uc3RhdGU7XG4gICAgfTtcbn1cblxuLyoqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtTZWxlY3RvclNwZWN9IHNwZWNcbiAqL1xuZnVuY3Rpb24gY3JlYXRlU2VsZWN0b3JGcm9tU3BlYyhzcGVjKSB7XG4gICAgaWYgKHNwZWMgaW5zdGFuY2VvZiBNYXBTZWxlY3RvclNwZWMpIHtcbiAgICAgICAgY29uc3QgcGFyZW50U2VsZWN0b3IgPSBjcmVhdGVTZWxlY3RvckZyb21TcGVjKHNwZWMucGFyZW50KTtcbiAgICAgICAgcmV0dXJuIHNwZWMuY3JlYXRlUmVzdWx0RnVuYyhwYXJlbnRTZWxlY3Rvcik7XG4gICAgfVxuICAgIHJldHVybiBjcmVhdGVDYWNoZWRTZWxlY3RvcihcbiAgICAgICAgc3BlYy5kZXBlbmRlbmNpZXMsXG4gICAgICAgIHNwZWMucmVzdWx0RnVuY1xuICAgICkoe1xuICAgICAgICBrZXlTZWxlY3Rvcjogc3BlYy5rZXlTZWxlY3RvcixcbiAgICAgICAgY2FjaGVPYmplY3Q6IG5ldyBGbGF0TWFwQ2FjaGUoKSxcbiAgICAgICAgc2VsZWN0b3JDcmVhdG9yOiBjcmVhdGVTZWxlY3RvciwgLy8gZXNsaW50LWRpc2FibGUtbGluZSBuby11c2UtYmVmb3JlLWRlZmluZVxuICAgIH0pO1xufVxuXG4vKipcbiAqIFRyaWVzIHRvIGZpbmQgT1JNIGluc3RhbmNlIHVzaW5nIHRoZSBhcmd1bWVudC5cbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0geyp9IGFyZ1xuICovXG5mdW5jdGlvbiB0b09STShhcmcpIHtcbiAgICAvKiBlc2xpbnQtZGlzYWJsZSBuby11bmRlcnNjb3JlLWRhbmdsZSAqL1xuICAgIGlmIChhcmcgaW5zdGFuY2VvZiBPUk0pIHtcbiAgICAgICAgcmV0dXJuIGFyZztcbiAgICB9XG4gICAgaWYgKGFyZyBpbnN0YW5jZW9mIFNlbGVjdG9yU3BlYykge1xuICAgICAgICByZXR1cm4gYXJnLl9vcm07XG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbn1cblxuY29uc3Qgc2VsZWN0b3JDYWNoZSA9IG5ldyBNYXAoKTtcbmNvbnN0IFNFTEVDVE9SX0tFWSA9IFN5bWJvbC5mb3IoXCJSRURVWF9PUk1fU0VMRUNUT1JcIik7XG5cbi8qKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7ZnVuY3Rpb258T1JNfFNlbGVjdG9yU3BlY30gYXJnXG4gKi9cbmZ1bmN0aW9uIHRvU2VsZWN0b3IoYXJnKSB7XG4gICAgaWYgKHR5cGVvZiBhcmcgPT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICByZXR1cm4gYXJnO1xuICAgIH1cbiAgICBpZiAoYXJnIGluc3RhbmNlb2YgT1JNKSB7XG4gICAgICAgIHJldHVybiBhcmcuc3RhdGVTZWxlY3RvcjtcbiAgICB9XG4gICAgaWYgKGFyZyBpbnN0YW5jZW9mIE1hcFNlbGVjdG9yU3BlYykge1xuICAgICAgICAvLyB0aGUgYXJndW1lbnQgdG8gbWFwKCkgbmVlZHMgdG8gYmUgY2FsbGFibGVcbiAgICAgICAgYXJnLnNlbGVjdG9yID0gdG9TZWxlY3Rvcihhcmcuc2VsZWN0b3IpO1xuICAgIH1cbiAgICBpZiAoYXJnIGluc3RhbmNlb2YgU2VsZWN0b3JTcGVjKSB7XG4gICAgICAgIGNvbnN0IHsgb3JtLCBjYWNoZVBhdGggfSA9IGFyZztcbiAgICAgICAgbGV0IGxldmVsO1xuXG4gICAgICAgIC8vIHRoZSBzZWxlY3RvciBjYWNoZSBmb3IgdGhlIHNwZWMncyBPUk1cbiAgICAgICAgaWYgKCFzZWxlY3RvckNhY2hlLmhhcyhvcm0pKSB7XG4gICAgICAgICAgICBzZWxlY3RvckNhY2hlLnNldChvcm0sIG5ldyBNYXAoKSk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3Qgb3JtU2VsZWN0b3JzID0gc2VsZWN0b3JDYWNoZS5nZXQob3JtKTtcblxuICAgICAgICAvKipcbiAgICAgICAgICogRHJpbGwgZG93biBpbnRvIHNlbGVjdG9yIG1hcCBieSBjYWNoZVBhdGguXG4gICAgICAgICAqXG4gICAgICAgICAqIFRoZSBzZWxlY3RvciBpdHNlbGYgaXMgc3RvcmVkIHVuZGVyIGEgc3BlY2lhbCBTRUxFQ1RPUl9LRVlcbiAgICAgICAgICogc28gdGhhdCB3ZSBjYW4gc3RvcmUgc2VsZWN0b3JzIGJlbG93IGl0IGFzIHdlbGwuXG4gICAgICAgICAqL1xuICAgICAgICBsZXZlbCA9IG9ybVNlbGVjdG9ycztcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBjYWNoZVBhdGgubGVuZ3RoOyArK2kpIHtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JhZ2VLZXkgPSBjYWNoZVBhdGhbaV07XG4gICAgICAgICAgICBpZiAoIWxldmVsLmhhcyhzdG9yYWdlS2V5KSkge1xuICAgICAgICAgICAgICAgIGxldmVsLnNldChzdG9yYWdlS2V5LCBuZXcgTWFwKCkpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgbGV2ZWwgPSBsZXZlbC5nZXQoc3RvcmFnZUtleSk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGxldmVsICYmIGxldmVsLmhhcyhTRUxFQ1RPUl9LRVkpKSB7XG4gICAgICAgICAgICAvLyBDYWNoZSBoaXQ6IHRoZSBzZWxlY3RvciBoYXMgYmVlbiBjcmVhdGVkIGJlZm9yZVxuICAgICAgICAgICAgcmV0dXJuIGxldmVsLmdldChTRUxFQ1RPUl9LRVkpO1xuICAgICAgICB9XG4gICAgICAgIC8vIENhY2hlIG1pc3M6IHRoZSBzZWxlY3RvciBuZWVkcyB0byBiZSBjcmVhdGVkXG4gICAgICAgIGNvbnN0IHNlbGVjdG9yID0gY3JlYXRlU2VsZWN0b3JGcm9tU3BlYyhhcmcpO1xuICAgICAgICAvLyBTYXZlIHRoZSBzZWxlY3RvciBhdCB0aGUgY2FjaGVQYXRoIHBvc2l0aW9uXG4gICAgICAgIGxldmVsLnNldChTRUxFQ1RPUl9LRVksIHNlbGVjdG9yKTtcblxuICAgICAgICByZXR1cm4gc2VsZWN0b3I7XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgYEZhaWxlZCB0byBpbnRlcnByZXQgc2VsZWN0b3IgYXJndW1lbnQ6ICR7SlNPTi5zdHJpbmdpZnkoXG4gICAgICAgICAgICBhcmdcbiAgICAgICAgKX0gb2YgdHlwZSAke3R5cGVvZiBhcmd9YFxuICAgICk7XG59XG5cbi8qKlxuICogUmV0dXJucyBhIG1lbW9pemVkIHNlbGVjdG9yIGJhc2VkIG9uIHBhc3NlZCBhcmd1bWVudHMuXG4gKiBUaGlzIGlzIHNpbWlsYXIgdG8gYHJlc2VsZWN0YCdzIGBjcmVhdGVTZWxlY3RvcmAsXG4gKiBleGNlcHQgeW91IGNhbiBhbHNvIHBhc3MgYSBzaW5nbGUgZnVuY3Rpb24gdG8gYmUgbWVtb2l6ZWQuXG4gKlxuICogSWYgeW91IHBhc3MgbXVsdGlwbGUgZnVuY3Rpb25zLCB0aGUgZm9ybWF0IHdpbGwgYmUgdGhlXG4gKiBzYW1lIGFzIGluIGByZXNlbGVjdGAuIFRoZSBsYXN0IGFyZ3VtZW50IGlzIHRoZSBzZWxlY3RvclxuICogZnVuY3Rpb24gYW5kIHRoZSBwcmV2aW91cyBhcmUgaW5wdXQgc2VsZWN0b3JzLlxuICpcbiAqIFdoZW4geW91IHVzZSB0aGlzIG1ldGhvZCB0byBjcmVhdGUgYSBzZWxlY3RvciwgdGhlIHJldHVybmVkIHNlbGVjdG9yXG4gKiBleHBlY3RzIHRoZSB3aG9sZSBgcmVkdXgtb3JtYCBzdGF0ZSBicmFuY2ggYXMgaW5wdXQuIEluIHRoZSBzZWxlY3RvclxuICogZnVuY3Rpb24gdGhhdCB5b3UgcGFzcyBhcyB0aGUgbGFzdCBhcmd1bWVudCwgYW55IG9mIHRoZSBhcmd1bWVudHNcbiAqIHlvdSBwYXNzIGZpcnN0IHdpbGwgYmUgY29uc2lkZXJlZCBzZWxlY3RvcnMgYW5kIG1hcHBlZFxuICogdG8gdGhlaXIgb3V0cHV0cywgbGlrZSBpbiBgcmVzZWxlY3RgLlxuICpcbiAqIEhlcmUgYXJlIHNvbWUgZXhhbXBsZSBzZWxlY3RvcnM6XG4gKlxuICogYGBgamF2YXNjcmlwdFxuICogLy8gb3JtIGlzIGFuIGluc3RhbmNlIG9mIE9STVxuICogLy8gcmVkdXhTdGF0ZSBpcyB0aGUgc3RhdGUgb2YgYSBSZWR1eCBzdG9yZVxuICogY29uc3QgYm9va3MgPSBjcmVhdGVTZWxlY3Rvcihvcm0uQm9vayk7XG4gKiBib29rcyhyZWR1eFN0YXRlKSAvLyBhcnJheSBvZiBib29rIHJlZnNcbiAqXG4gKiBjb25zdCBib29rQXV0aG9ycyA9IGNyZWF0ZVNlbGVjdG9yKG9ybS5Cb29rLmF1dGhvcnMpO1xuICogYm9va0F1dGhvcnMocmVkdXhTdGF0ZSkgLy8gdHdvLWRpbWVuc2lvbmFsIGFycmF5IG9mIGF1dGhvciByZWZzIGZvciBlYWNoIGJvb2tcbiAqIGBgYFxuICogU2VsZWN0b3JzIGNhbiBlYXNpbHkgYmUgYXBwbGllZCB0byByZWxhdGVkIG1vZGVsczpcbiAqIGBgYGphdmFzY3JpcHRcbiAqIGNvbnN0IGJvb2tBdXRob3JOYW1lcyA9IGNyZWF0ZVNlbGVjdG9yKFxuICogICAgIG9ybS5Cb29rLmF1dGhvcnMubWFwKG9ybS5BdXRob3IubmFtZSksXG4gKiApO1xuICogYm9va0F1dGhvck5hbWVzKHJlZHV4U3RhdGUsIDgpIC8vIG5hbWVzIG9mIGFsbCBhdXRob3JzIG9mIGJvb2sgd2l0aCBJRCA4XG4gKiBib29rQXV0aG9yTmFtZXMocmVkdXhTdGF0ZSwgWzgsIDldKSAvLyAyRCBhcnJheSBvZiBuYW1lcyBvZiBhbGwgYXV0aG9ycyBvZiBib29rcyB3aXRoIElEcyA4IGFuZCA5XG4gKiBgYGBcbiAqIEFsc28gbm90ZSB0aGF0IGBvcm0uQXV0aG9yLm5hbWVgIGRpZCBub3QgbmVlZCB0byBiZSB3cmFwcGVkIGluIGFub3RoZXIgYGNyZWF0ZVNlbGVjdG9yYCBjYWxsLFxuICogYWx0aG91Z2ggdGhhdCB3b3VsZCBiZSBwb3NzaWJsZS5cbiAqXG4gKiBGb3IgbW9yZSBjb21wbGV4IGNhbGN1bGF0aW9ucyB5b3UgY2FuIGFjY2Vzc1xuICogZW50aXJlIHNlc3Npb24gb2JqZWN0cyBieSBwYXNzaW5nIGFuIE9STSBpbnN0YW5jZS5cbiAqIGBgYGphdmFzY3JpcHRcbiAqIGNvbnN0IGZyZXNoQmFuYW5hc0Nvc3QgPSBjcmVhdGVTZWxlY3RvcihcbiAqICAgICBvcm0sXG4gKiAgICAgc2Vzc2lvbiA9PiB7XG4gKiAgICAgICAgY29uc3QgYmFuYW5hID0gc2Vzc2lvbi5Qcm9kdWN0LmdldCh7XG4gKiAgICAgICAgICAgIG5hbWU6IFwiQmFuYW5hXCIsXG4gKiAgICAgICAgfSk7XG4gKiAgICAgICAgLy8gYW1vdW50IG9mIGZyZXNoIGJhbmFuYXMgaW4gc2hvcHBpbmcgY2FydFxuICogICAgICAgIGNvbnN0IGFtb3VudCA9IHNlc3Npb24uU2hvcHBpbmdDYXJ0LmZpbHRlcih7XG4gKiAgICAgICAgICAgIHByb2R1Y3RfaWQ6IGJhbmFuYS5pZCxcbiAqICAgICAgICAgICAgaXNfZnJlc2g6IHRydWUsXG4gKiAgICAgICAgfSkuY291bnQoKTtcbiAqICAgICAgICByZXR1cm4gYFVTRCAke2Ftb3VudCAqIGJhbmFuYS5wcmljZX1gO1xuICogICAgIH1cbiAqICk7XG4gKiBgYGBcbiAqXG4gKiByZWR1eC1vcm0gdXNlcyBhIHNwZWNpYWwgbWVtb2l6YXRpb24gZnVuY3Rpb24gdG8gYXZvaWQgcmVjb21wdXRhdGlvbnMuXG4gKlxuICogRXZlcnl0aW1lIGEgc2VsZWN0b3IgcnVucywgdGhpcyBmdW5jdGlvbiByZWNvcmRzIHdoaWNoIGluc3RhbmNlc1xuICogb2YgeW91ciBgTW9kZWxgcyB3ZXJlIGFjY2Vzc2VkLjxicj5cbiAqIE9uIHN1YnNlcXVlbnQgcnVucywgdGhlIHNlbGVjdG9yIGZpcnN0IGNoZWNrcyBpZiB0aGUgcHJldmlvdXNseVxuICogYWNjZXNzZWQgaW5zdGFuY2VzIG9yIGBhcmdzYCBoYXZlIGNoYW5nZWQgaW4gYW55IHdheTpcbiAqIDx1bD5cbiAqICAgICA8bGk+SWYgeWVzLCB0aGUgc2VsZWN0b3IgY2FsbHMgdGhlIGZ1bmN0aW9uIHlvdSBwYXNzZWQgdG8gaXQuPC9saT5cbiAqICAgICA8bGk+SWYgbm90LCBpdCBqdXN0IHJldHVybnMgdGhlIHByZXZpb3VzIHJlc3VsdFxuICogICAgICAgICAodW5sZXNzIHlvdSBjYWxsIGl0IGZvciB0aGUgZmlyc3QgdGltZSkuPC9saT5cbiAqIDwvdWw+XG4gKlxuICogVGhpcyB3YXkgeW91IGNhbiB1c2UgcHVyZSByZW5kZXJpbmcgaW4geW91ciBSZWFjdCBjb21wb25lbnRzXG4gKiBmb3IgcGVyZm9ybWFuY2UgZ2FpbnMuXG4gKlxuICogQGdsb2JhbFxuICpcbiAqIEBwYXJhbSAgey4uLkZ1bmN0aW9ufSBhcmdzIC0gemVybyBvciBtb3JlIGlucHV0IHNlbGVjdG9yc1xuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbmQgdGhlIHNlbGVjdG9yIGZ1bmN0aW9uLlxuICogQHJldHVybiB7RnVuY3Rpb259IG1lbW9pemVkIHNlbGVjdG9yXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVTZWxlY3RvciguLi5hcmdzKSB7XG4gICAgaWYgKCFhcmdzLmxlbmd0aCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJDYW5ub3QgY3JlYXRlIGEgc2VsZWN0b3Igd2l0aG91dCBhcmd1bWVudHMuXCIpO1xuICAgIH1cblxuICAgIGNvbnN0IHJlc3VsdEFyZyA9IGFyZ3MucG9wKCk7XG4gICAgY29uc3QgZGVwZW5kZW5jaWVzID0gQXJyYXkuaXNBcnJheShhcmdzWzBdKSA/IGFyZ3NbMF0gOiBhcmdzO1xuXG4gICAgY29uc3Qgb3JtID0gZGVwZW5kZW5jaWVzLm1hcCh0b09STSkuZmluZChCb29sZWFuKTtcbiAgICBjb25zdCBpbnB1dEZ1bmNzID0gZGVwZW5kZW5jaWVzLm1hcCh0b1NlbGVjdG9yKTtcblxuICAgIGlmICh0eXBlb2YgcmVzdWx0QXJnID09PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgaWYgKCFvcm0pIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBcIkZhaWxlZCB0byByZXNvbHZlIHRoZSBjdXJyZW50IE9STSBkYXRhYmFzZSBzdGF0ZS4gUGxlYXNlIHBhc3MgYW4gT1JNIGluc3RhbmNlIG9yIGFuIE9STSBzZWxlY3RvciBhcyBhbiBhcmd1bWVudCB0byBgY3JlYXRlU2VsZWN0b3IoKWAuXCJcbiAgICAgICAgICAgICk7XG4gICAgICAgIH0gZWxzZSBpZiAoIW9ybS5zdGF0ZVNlbGVjdG9yKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgXCJGYWlsZWQgdG8gcmVzb2x2ZSB0aGUgY3VycmVudCBPUk0gZGF0YWJhc2Ugc3RhdGUuIFBsZWFzZSBwYXNzIGFuIG9iamVjdCB0byB0aGUgT1JNIGNvbnN0cnVjdG9yIHRoYXQgc3BlY2lmaWVzIGEgYHN0YXRlU2VsZWN0b3JgIGZ1bmN0aW9uLlwiXG4gICAgICAgICAgICApO1xuICAgICAgICB9IGVsc2UgaWYgKHR5cGVvZiBvcm0uc3RhdGVTZWxlY3RvciAhPT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgYEZhaWxlZCB0byByZXNvbHZlIHRoZSBjdXJyZW50IE9STSBkYXRhYmFzZSBzdGF0ZS4gUGxlYXNlIHBhc3MgYSBmdW5jdGlvbiB3aGVuIHNwZWNpZnlpbmcgdGhlIE9STSdzIFxcYHN0YXRlU2VsZWN0b3JcXGAuIFJlY2VpdmVkOiAke0pTT04uc3RyaW5naWZ5KFxuICAgICAgICAgICAgICAgICAgICBvcm0uc3RhdGVTZWxlY3RvclxuICAgICAgICAgICAgICAgICl9IG9mIHR5cGUgJHt0eXBlb2Ygb3JtLnN0YXRlU2VsZWN0b3J9YFxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBjcmVhdGVTZWxlY3RvckNyZWF0b3IoXG4gICAgICAgICAgICBtZW1vaXplLFxuICAgICAgICAgICAgdW5kZWZpbmVkLFxuICAgICAgICAgICAgb3JtXG4gICAgICAgICkoW29ybS5zdGF0ZVNlbGVjdG9yLCAuLi5pbnB1dEZ1bmNzXSwgcmVzdWx0QXJnKTtcbiAgICB9XG5cbiAgICBpZiAocmVzdWx0QXJnIGluc3RhbmNlb2YgT1JNKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIFwiT1JNIGluc3RhbmNlcyBjYW5ub3QgYmUgdGhlIHJlc3VsdCBmdW5jdGlvbiBvZiBzZWxlY3RvcnMuIFlvdSBjYW4gYWNjZXNzIHlvdXIgbW9kZWxzIGluIHRoZSBsYXN0IGZ1bmN0aW9uIHRoYXQgeW91IHBhc3MgdG8gYGNyZWF0ZVNlbGVjdG9yKClgLlwiXG4gICAgICAgICk7XG4gICAgfVxuICAgIGlmIChpbnB1dEZ1bmNzLmxlbmd0aCkge1xuICAgICAgICBjb25zb2xlLndhcm4oXG4gICAgICAgICAgICBcIllvdXIgaW5wdXQgc2VsZWN0b3JzIHdpbGwgYmUgaWdub3JlZDogdGhlIHBhc3NlZCByZXN1bHQgZnVuY3Rpb24gZG9lcyBub3QgcmVxdWlyZSBhbnkgaW5wdXQuXCJcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdG9TZWxlY3RvcihyZXN1bHRBcmcpO1xufVxuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/redux.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"defaultUpdater\\\", function() { return defaultUpdater; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"createReducer\\\", function() { return createReducer; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"createSelector\\\", function() { return createSelector; });\\n/* harmony import */ var reselect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! reselect */ \\\"./node_modules/reselect/lib/index.js\\\");\\n/* harmony import */ var reselect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(reselect__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var re_reselect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! re-reselect */ \\\"./node_modules/re-reselect/dist/index.js\\\");\\n/* harmony import */ var re_reselect__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(re_reselect__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _memoize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./memoize */ \\\"./src/memoize.js\\\");\\n/* harmony import */ var _ORM__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./ORM */ \\\"./src/ORM.js\\\");\\n/* harmony import */ var _selectors_SelectorSpec__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./selectors/SelectorSpec */ \\\"./src/selectors/SelectorSpec.js\\\");\\n/* harmony import */ var _selectors_MapSelectorSpec__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./selectors/MapSelectorSpec */ \\\"./src/selectors/MapSelectorSpec.js\\\");\\n\\n\\n\\n\\n\\n\\n/**\\n * @module redux\\n * @desc Provides functions for integration with Redux.\\n */\\n\\n/**\\n * Calls all models' reducers if they exist.\\n *\\n * @return {undefined}\\n * @global\\n */\\n\\nfunction defaultUpdater(session, action) {\\n  session.sessionBoundModels.forEach(modelClass => {\\n    if (typeof modelClass.reducer === \\\"function\\\") {\\n      // This calls this.applyUpdate to update this.state\\n      modelClass.reducer(action, modelClass, session);\\n    }\\n  });\\n}\\n/**\\n * Call the returned function to pass actions to Redux-ORM.\\n *\\n * @global\\n *\\n * @param {ORM} orm - the ORM instance.\\n * @param {Function} [updater] - the function updating the ORM state based on the given action.\\n * @return {Function} reducer that will update the ORM state.\\n */\\n\\nfunction createReducer(orm, updater = defaultUpdater) {\\n  return (state, action) => {\\n    const session = orm.session(state || orm.getEmptyState());\\n    updater(session, action);\\n    return session.state;\\n  };\\n}\\n/**\\n * @private\\n * @param {SelectorSpec} spec\\n */\\n\\nfunction createSelectorFromSpec(spec) {\\n  if (spec instanceof _selectors_MapSelectorSpec__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n    const parentSelector = createSelectorFromSpec(spec.parent);\\n    return spec.createResultFunc(parentSelector);\\n  }\\n\\n  return re_reselect__WEBPACK_IMPORTED_MODULE_1___default()(spec.dependencies, spec.resultFunc)({\\n    keySelector: spec.keySelector,\\n    cacheObject: new re_reselect__WEBPACK_IMPORTED_MODULE_1__[\\\"FlatMapCache\\\"](),\\n    selectorCreator: createSelector // eslint-disable-line no-use-before-define\\n\\n  });\\n}\\n/**\\n * Tries to find ORM instance using the argument.\\n * @private\\n * @param {*} arg\\n */\\n\\n\\nfunction toORM(arg) {\\n  /* eslint-disable no-underscore-dangle */\\n  if (arg instanceof _ORM__WEBPACK_IMPORTED_MODULE_3__[\\\"ORM\\\"]) {\\n    return arg;\\n  }\\n\\n  if (arg instanceof _selectors_SelectorSpec__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"]) {\\n    return arg._orm;\\n  }\\n\\n  return false;\\n}\\n\\nconst selectorCache = new Map();\\nconst SELECTOR_KEY = Symbol.for(\\\"REDUX_ORM_SELECTOR\\\");\\n/**\\n * @private\\n * @param {function|ORM|SelectorSpec} arg\\n */\\n\\nfunction toSelector(arg) {\\n  if (typeof arg === \\\"function\\\") {\\n    return arg;\\n  }\\n\\n  if (arg instanceof _ORM__WEBPACK_IMPORTED_MODULE_3__[\\\"ORM\\\"]) {\\n    return arg.stateSelector;\\n  }\\n\\n  if (arg instanceof _selectors_MapSelectorSpec__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]) {\\n    // the argument to map() needs to be callable\\n    arg.selector = toSelector(arg.selector);\\n  }\\n\\n  if (arg instanceof _selectors_SelectorSpec__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"]) {\\n    const {\\n      orm,\\n      cachePath\\n    } = arg;\\n    let level; // the selector cache for the spec's ORM\\n\\n    if (!selectorCache.has(orm)) {\\n      selectorCache.set(orm, new Map());\\n    }\\n\\n    const ormSelectors = selectorCache.get(orm);\\n    /**\\n     * Drill down into selector map by cachePath.\\n     *\\n     * The selector itself is stored under a special SELECTOR_KEY\\n     * so that we can store selectors below it as well.\\n     */\\n\\n    level = ormSelectors;\\n\\n    for (let i = 0; i < cachePath.length; ++i) {\\n      const storageKey = cachePath[i];\\n\\n      if (!level.has(storageKey)) {\\n        level.set(storageKey, new Map());\\n      }\\n\\n      level = level.get(storageKey);\\n    }\\n\\n    if (level && level.has(SELECTOR_KEY)) {\\n      // Cache hit: the selector has been created before\\n      return level.get(SELECTOR_KEY);\\n    } // Cache miss: the selector needs to be created\\n\\n\\n    const selector = createSelectorFromSpec(arg); // Save the selector at the cachePath position\\n\\n    level.set(SELECTOR_KEY, selector);\\n    return selector;\\n  }\\n\\n  throw new Error(`Failed to interpret selector argument: ${JSON.stringify(arg)} of type ${typeof arg}`);\\n}\\n/**\\n * Returns a memoized selector based on passed arguments.\\n * This is similar to `reselect`'s `createSelector`,\\n * except you can also pass a single function to be memoized.\\n *\\n * If you pass multiple functions, the format will be the\\n * same as in `reselect`. The last argument is the selector\\n * function and the previous are input selectors.\\n *\\n * When you use this method to create a selector, the returned selector\\n * expects the whole `redux-orm` state branch as input. In the selector\\n * function that you pass as the last argument, any of the arguments\\n * you pass first will be considered selectors and mapped\\n * to their outputs, like in `reselect`.\\n *\\n * Here are some example selectors:\\n *\\n * ```javascript\\n * // orm is an instance of ORM\\n * // reduxState is the state of a Redux store\\n * const books = createSelector(orm.Book);\\n * books(reduxState) // array of book refs\\n *\\n * const bookAuthors = createSelector(orm.Book.authors);\\n * bookAuthors(reduxState) // two-dimensional array of author refs for each book\\n * ```\\n * Selectors can easily be applied to related models:\\n * ```javascript\\n * const bookAuthorNames = createSelector(\\n *     orm.Book.authors.map(orm.Author.name),\\n * );\\n * bookAuthorNames(reduxState, 8) // names of all authors of book with ID 8\\n * bookAuthorNames(reduxState, [8, 9]) // 2D array of names of all authors of books with IDs 8 and 9\\n * ```\\n * Also note that `orm.Author.name` did not need to be wrapped in another `createSelector` call,\\n * although that would be possible.\\n *\\n * For more complex calculations you can access\\n * entire session objects by passing an ORM instance.\\n * ```javascript\\n * const freshBananasCost = createSelector(\\n *     orm,\\n *     session => {\\n *        const banana = session.Product.get({\\n *            name: \\\"Banana\\\",\\n *        });\\n *        // amount of fresh bananas in shopping cart\\n *        const amount = session.ShoppingCart.filter({\\n *            product_id: banana.id,\\n *            is_fresh: true,\\n *        }).count();\\n *        return `USD ${amount * banana.price}`;\\n *     }\\n * );\\n * ```\\n *\\n * redux-orm uses a special memoization function to avoid recomputations.\\n *\\n * Everytime a selector runs, this function records which instances\\n * of your `Model`s were accessed.<br>\\n * On subsequent runs, the selector first checks if the previously\\n * accessed instances or `args` have changed in any way:\\n * <ul>\\n *     <li>If yes, the selector calls the function you passed to it.</li>\\n *     <li>If not, it just returns the previous result\\n *         (unless you call it for the first time).</li>\\n * </ul>\\n *\\n * This way you can use pure rendering in your React components\\n * for performance gains.\\n *\\n * @global\\n *\\n * @param  {...Function} args - zero or more input selectors\\n *                              and the selector function.\\n * @return {Function} memoized selector\\n */\\n\\n\\nfunction createSelector(...args) {\\n  if (!args.length) {\\n    throw new Error(\\\"Cannot create a selector without arguments.\\\");\\n  }\\n\\n  const resultArg = args.pop();\\n  const dependencies = Array.isArray(args[0]) ? args[0] : args;\\n  const orm = dependencies.map(toORM).find(Boolean);\\n  const inputFuncs = dependencies.map(toSelector);\\n\\n  if (typeof resultArg === \\\"function\\\") {\\n    if (!orm) {\\n      throw new Error(\\\"Failed to resolve the current ORM database state. Please pass an ORM instance or an ORM selector as an argument to `createSelector()`.\\\");\\n    } else if (!orm.stateSelector) {\\n      throw new Error(\\\"Failed to resolve the current ORM database state. Please pass an object to the ORM constructor that specifies a `stateSelector` function.\\\");\\n    } else if (typeof orm.stateSelector !== \\\"function\\\") {\\n      throw new Error(`Failed to resolve the current ORM database state. Please pass a function when specifying the ORM's \\\\`stateSelector\\\\`. Received: ${JSON.stringify(orm.stateSelector)} of type ${typeof orm.stateSelector}`);\\n    }\\n\\n    return Object(reselect__WEBPACK_IMPORTED_MODULE_0__[\\\"createSelectorCreator\\\"])(_memoize__WEBPACK_IMPORTED_MODULE_2__[\\\"memoize\\\"], undefined, orm)([orm.stateSelector, ...inputFuncs], resultArg);\\n  }\\n\\n  if (resultArg instanceof _ORM__WEBPACK_IMPORTED_MODULE_3__[\\\"ORM\\\"]) {\\n    throw new Error(\\\"ORM instances cannot be the result function of selectors. You can access your models in the last function that you pass to `createSelector()`.\\\");\\n  }\\n\\n  if (inputFuncs.length) {\\n    console.warn(\\\"Your input selectors will be ignored: the passed result function does not require any input.\\\");\\n  }\\n\\n  return toSelector(resultArg);\\n}//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9yZWR1eC5qcz8wYjAwIl0sIm5hbWVzIjpbImRlZmF1bHRVcGRhdGVyIiwic2Vzc2lvbiIsImFjdGlvbiIsInNlc3Npb25Cb3VuZE1vZGVscyIsImZvckVhY2giLCJtb2RlbENsYXNzIiwicmVkdWNlciIsImNyZWF0ZVJlZHVjZXIiLCJvcm0iLCJ1cGRhdGVyIiwic3RhdGUiLCJnZXRFbXB0eVN0YXRlIiwiY3JlYXRlU2VsZWN0b3JGcm9tU3BlYyIsInNwZWMiLCJNYXBTZWxlY3RvclNwZWMiLCJwYXJlbnRTZWxlY3RvciIsInBhcmVudCIsImNyZWF0ZVJlc3VsdEZ1bmMiLCJjcmVhdGVDYWNoZWRTZWxlY3RvciIsImRlcGVuZGVuY2llcyIsInJlc3VsdEZ1bmMiLCJrZXlTZWxlY3RvciIsImNhY2hlT2JqZWN0IiwiRmxhdE1hcENhY2hlIiwic2VsZWN0b3JDcmVhdG9yIiwiY3JlYXRlU2VsZWN0b3IiLCJ0b09STSIsImFyZyIsIk9STSIsIlNlbGVjdG9yU3BlYyIsIl9vcm0iLCJzZWxlY3RvckNhY2hlIiwiTWFwIiwiU0VMRUNUT1JfS0VZIiwiU3ltYm9sIiwiZm9yIiwidG9TZWxlY3RvciIsInN0YXRlU2VsZWN0b3IiLCJzZWxlY3RvciIsImNhY2hlUGF0aCIsImxldmVsIiwiaGFzIiwic2V0Iiwib3JtU2VsZWN0b3JzIiwiZ2V0IiwiaSIsImxlbmd0aCIsInN0b3JhZ2VLZXkiLCJFcnJvciIsIkpTT04iLCJzdHJpbmdpZnkiLCJhcmdzIiwicmVzdWx0QXJnIiwicG9wIiwiQXJyYXkiLCJpc0FycmF5IiwibWFwIiwiZmluZCIsIkJvb2xlYW4iLCJpbnB1dEZ1bmNzIiwiY3JlYXRlU2VsZWN0b3JDcmVhdG9yIiwibWVtb2l6ZSIsInVuZGVmaW5lZCIsImNvbnNvbGUiLCJ3YXJuIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7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RyxDQUFDNkIsYUFBVCxFQUF3QjtBQUMzQixZQUFNLElBQUlXLEtBQUosQ0FDRiwySUFERSxDQUFOO0FBR0gsS0FKTSxNQUlBLElBQUksT0FBT3hDLEdBQUcsQ0FBQzZCLGFBQVgsS0FBNkIsVUFBakMsRUFBNkM7QUFDaEQsWUFBTSxJQUFJVyxLQUFKLENBQ0QsbUlBQWtJQyxJQUFJLENBQUNDLFNBQUwsQ0FDL0gxQyxHQUFHLENBQUM2QixhQUQySCxDQUVqSSxZQUFXLE9BQU83QixHQUFHLENBQUM2QixhQUFjLEVBSHBDLENBQU47QUFLSDs7QUFFRCxXQUFPdUIsc0VBQXFCLENBQ3hCQyxnREFEd0IsRUFFeEJDLFNBRndCLEVBR3hCdEQsR0FId0IsQ0FBckIsQ0FJTCxDQUFDQSxHQUFHLENBQUM2QixhQUFMLEVBQW9CLEdBQUdzQixVQUF2QixDQUpLLEVBSStCUCxTQUovQixDQUFQO0FBS0g7O0FBRUQsTUFBSUEsU0FBUyxZQUFZeEIsd0NBQXpCLEVBQThCO0FBQzFCLFVBQU0sSUFBSW9CLEtBQUosQ0FDRixnSkFERSxDQUFOO0FBR0g7O0FBQ0QsTUFBSVcsVUFBVSxDQUFDYixNQUFmLEVBQXVCO0FBQ25CaUIsV0FBTyxDQUFDQyxJQUFSLENBQ0ksOEZBREo7QUFHSDs7QUFFRCxTQUFPNUIsVUFBVSxDQUFDZ0IsU0FBRCxDQUFqQjtBQUNIIiwiZmlsZSI6Ii4vc3JjL3JlZHV4LmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3JlYXRlU2VsZWN0b3JDcmVhdG9yIH0gZnJvbSBcInJlc2VsZWN0XCI7XG5pbXBvcnQgY3JlYXRlQ2FjaGVkU2VsZWN0b3IsIHsgRmxhdE1hcENhY2hlIH0gZnJvbSBcInJlLXJlc2VsZWN0XCI7XG5cbmltcG9ydCB7IG1lbW9pemUgfSBmcm9tIFwiLi9tZW1vaXplXCI7XG5cbmltcG9ydCB7IE9STSB9IGZyb20gXCIuL09STVwiO1xuaW1wb3J0IFNlbGVjdG9yU3BlYyBmcm9tIFwiLi9zZWxlY3RvcnMvU2VsZWN0b3JTcGVjXCI7XG5pbXBvcnQgTWFwU2VsZWN0b3JTcGVjIGZyb20gXCIuL3NlbGVjdG9ycy9NYXBTZWxlY3RvclNwZWNcIjtcblxuLyoqXG4gKiBAbW9kdWxlIHJlZHV4XG4gKiBAZGVzYyBQcm92aWRlcyBmdW5jdGlvbnMgZm9yIGludGVncmF0aW9uIHdpdGggUmVkdXguXG4gKi9cblxuLyoqXG4gKiBDYWxscyBhbGwgbW9kZWxzJyByZWR1Y2VycyBpZiB0aGV5IGV4aXN0LlxuICpcbiAqIEByZXR1cm4ge3VuZGVmaW5lZH1cbiAqIEBnbG9iYWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRlZmF1bHRVcGRhdGVyKHNlc3Npb24sIGFjdGlvbikge1xuICAgIHNlc3Npb24uc2Vzc2lvbkJvdW5kTW9kZWxzLmZvckVhY2goKG1vZGVsQ2xhc3MpID0+IHtcbiAgICAgICAgaWYgKHR5cGVvZiBtb2RlbENsYXNzLnJlZHVjZXIgPT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICAgICAgLy8gVGhpcyBjYWxscyB0aGlzLmFwcGx5VXBkYXRlIHRvIHVwZGF0ZSB0aGlzLnN0YXRlXG4gICAgICAgICAgICBtb2RlbENsYXNzLnJlZHVjZXIoYWN0aW9uLCBtb2RlbENsYXNzLCBzZXNzaW9uKTtcbiAgICAgICAgfVxuICAgIH0pO1xufVxuXG4vKipcbiAqIENhbGwgdGhlIHJldHVybmVkIGZ1bmN0aW9uIHRvIHBhc3MgYWN0aW9ucyB0byBSZWR1eC1PUk0uXG4gKlxuICogQGdsb2JhbFxuICpcbiAqIEBwYXJhbSB7T1JNfSBvcm0gLSB0aGUgT1JNIGluc3RhbmNlLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gW3VwZGF0ZXJdIC0gdGhlIGZ1bmN0aW9uIHVwZGF0aW5nIHRoZSBPUk0gc3RhdGUgYmFzZWQgb24gdGhlIGdpdmVuIGFjdGlvbi5cbiAqIEByZXR1cm4ge0Z1bmN0aW9ufSByZWR1Y2VyIHRoYXQgd2lsbCB1cGRhdGUgdGhlIE9STSBzdGF0ZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZVJlZHVjZXIob3JtLCB1cGRhdGVyID0gZGVmYXVsdFVwZGF0ZXIpIHtcbiAgICByZXR1cm4gKHN0YXRlLCBhY3Rpb24pID0+IHtcbiAgICAgICAgY29uc3Qgc2Vzc2lvbiA9IG9ybS5zZXNzaW9uKHN0YXRlIHx8IG9ybS5nZXRFbXB0eVN0YXRlKCkpO1xuICAgICAgICB1cGRhdGVyKHNlc3Npb24sIGFjdGlvbik7XG4gICAgICAgIHJldHVybiBzZXNzaW9uLnN0YXRlO1xuICAgIH07XG59XG5cbi8qKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7U2VsZWN0b3JTcGVjfSBzcGVjXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZVNlbGVjdG9yRnJvbVNwZWMoc3BlYykge1xuICAgIGlmIChzcGVjIGluc3RhbmNlb2YgTWFwU2VsZWN0b3JTcGVjKSB7XG4gICAgICAgIGNvbnN0IHBhcmVudFNlbGVjdG9yID0gY3JlYXRlU2VsZWN0b3JGcm9tU3BlYyhzcGVjLnBhcmVudCk7XG4gICAgICAgIHJldHVybiBzcGVjLmNyZWF0ZVJlc3VsdEZ1bmMocGFyZW50U2VsZWN0b3IpO1xuICAgIH1cbiAgICByZXR1cm4gY3JlYXRlQ2FjaGVkU2VsZWN0b3IoXG4gICAgICAgIHNwZWMuZGVwZW5kZW5jaWVzLFxuICAgICAgICBzcGVjLnJlc3VsdEZ1bmNcbiAgICApKHtcbiAgICAgICAga2V5U2VsZWN0b3I6IHNwZWMua2V5U2VsZWN0b3IsXG4gICAgICAgIGNhY2hlT2JqZWN0OiBuZXcgRmxhdE1hcENhY2hlKCksXG4gICAgICAgIHNlbGVjdG9yQ3JlYXRvcjogY3JlYXRlU2VsZWN0b3IsIC8vIGVzbGludC1kaXNhYmxlLWxpbmUgbm8tdXNlLWJlZm9yZS1kZWZpbmVcbiAgICB9KTtcbn1cblxuLyoqXG4gKiBUcmllcyB0byBmaW5kIE9STSBpbnN0YW5jZSB1c2luZyB0aGUgYXJndW1lbnQuXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHsqfSBhcmdcbiAqL1xuZnVuY3Rpb24gdG9PUk0oYXJnKSB7XG4gICAgLyogZXNsaW50LWRpc2FibGUgbm8tdW5kZXJzY29yZS1kYW5nbGUgKi9cbiAgICBpZiAoYXJnIGluc3RhbmNlb2YgT1JNKSB7XG4gICAgICAgIHJldHVybiBhcmc7XG4gICAgfVxuICAgIGlmIChhcmcgaW5zdGFuY2VvZiBTZWxlY3RvclNwZWMpIHtcbiAgICAgICAgcmV0dXJuIGFyZy5fb3JtO1xuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG59XG5cbmNvbnN0IHNlbGVjdG9yQ2FjaGUgPSBuZXcgTWFwKCk7XG5jb25zdCBTRUxFQ1RPUl9LRVkgPSBTeW1ib2wuZm9yKFwiUkVEVVhfT1JNX1NFTEVDVE9SXCIpO1xuXG4vKipcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge2Z1bmN0aW9ufE9STXxTZWxlY3RvclNwZWN9IGFyZ1xuICovXG5mdW5jdGlvbiB0b1NlbGVjdG9yKGFyZykge1xuICAgIGlmICh0eXBlb2YgYXJnID09PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgcmV0dXJuIGFyZztcbiAgICB9XG4gICAgaWYgKGFyZyBpbnN0YW5jZW9mIE9STSkge1xuICAgICAgICByZXR1cm4gYXJnLnN0YXRlU2VsZWN0b3I7XG4gICAgfVxuICAgIGlmIChhcmcgaW5zdGFuY2VvZiBNYXBTZWxlY3RvclNwZWMpIHtcbiAgICAgICAgLy8gdGhlIGFyZ3VtZW50IHRvIG1hcCgpIG5lZWRzIHRvIGJlIGNhbGxhYmxlXG4gICAgICAgIGFyZy5zZWxlY3RvciA9IHRvU2VsZWN0b3IoYXJnLnNlbGVjdG9yKTtcbiAgICB9XG4gICAgaWYgKGFyZyBpbnN0YW5jZW9mIFNlbGVjdG9yU3BlYykge1xuICAgICAgICBjb25zdCB7IG9ybSwgY2FjaGVQYXRoIH0gPSBhcmc7XG4gICAgICAgIGxldCBsZXZlbDtcblxuICAgICAgICAvLyB0aGUgc2VsZWN0b3IgY2FjaGUgZm9yIHRoZSBzcGVjJ3MgT1JNXG4gICAgICAgIGlmICghc2VsZWN0b3JDYWNoZS5oYXMob3JtKSkge1xuICAgICAgICAgICAgc2VsZWN0b3JDYWNoZS5zZXQob3JtLCBuZXcgTWFwKCkpO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IG9ybVNlbGVjdG9ycyA9IHNlbGVjdG9yQ2FjaGUuZ2V0KG9ybSk7XG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIERyaWxsIGRvd24gaW50byBzZWxlY3RvciBtYXAgYnkgY2FjaGVQYXRoLlxuICAgICAgICAgKlxuICAgICAgICAgKiBUaGUgc2VsZWN0b3IgaXRzZWxmIGlzIHN0b3JlZCB1bmRlciBhIHNwZWNpYWwgU0VMRUNUT1JfS0VZXG4gICAgICAgICAqIHNvIHRoYXQgd2UgY2FuIHN0b3JlIHNlbGVjdG9ycyBiZWxvdyBpdCBhcyB3ZWxsLlxuICAgICAgICAgKi9cbiAgICAgICAgbGV2ZWwgPSBvcm1TZWxlY3RvcnM7XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgY2FjaGVQYXRoLmxlbmd0aDsgKytpKSB7XG4gICAgICAgICAgICBjb25zdCBzdG9yYWdlS2V5ID0gY2FjaGVQYXRoW2ldO1xuICAgICAgICAgICAgaWYgKCFsZXZlbC5oYXMoc3RvcmFnZUtleSkpIHtcbiAgICAgICAgICAgICAgICBsZXZlbC5zZXQoc3RvcmFnZUtleSwgbmV3IE1hcCgpKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGxldmVsID0gbGV2ZWwuZ2V0KHN0b3JhZ2VLZXkpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChsZXZlbCAmJiBsZXZlbC5oYXMoU0VMRUNUT1JfS0VZKSkge1xuICAgICAgICAgICAgLy8gQ2FjaGUgaGl0OiB0aGUgc2VsZWN0b3IgaGFzIGJlZW4gY3JlYXRlZCBiZWZvcmVcbiAgICAgICAgICAgIHJldHVybiBsZXZlbC5nZXQoU0VMRUNUT1JfS0VZKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBDYWNoZSBtaXNzOiB0aGUgc2VsZWN0b3IgbmVlZHMgdG8gYmUgY3JlYXRlZFxuICAgICAgICBjb25zdCBzZWxlY3RvciA9IGNyZWF0ZVNlbGVjdG9yRnJvbVNwZWMoYXJnKTtcbiAgICAgICAgLy8gU2F2ZSB0aGUgc2VsZWN0b3IgYXQgdGhlIGNhY2hlUGF0aCBwb3NpdGlvblxuICAgICAgICBsZXZlbC5zZXQoU0VMRUNUT1JfS0VZLCBzZWxlY3Rvcik7XG5cbiAgICAgICAgcmV0dXJuIHNlbGVjdG9yO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIGBGYWlsZWQgdG8gaW50ZXJwcmV0IHNlbGVjdG9yIGFyZ3VtZW50OiAke0pTT04uc3RyaW5naWZ5KFxuICAgICAgICAgICAgYXJnXG4gICAgICAgICl9IG9mIHR5cGUgJHt0eXBlb2YgYXJnfWBcbiAgICApO1xufVxuXG4vKipcbiAqIFJldHVybnMgYSBtZW1vaXplZCBzZWxlY3RvciBiYXNlZCBvbiBwYXNzZWQgYXJndW1lbnRzLlxuICogVGhpcyBpcyBzaW1pbGFyIHRvIGByZXNlbGVjdGAncyBgY3JlYXRlU2VsZWN0b3JgLFxuICogZXhjZXB0IHlvdSBjYW4gYWxzbyBwYXNzIGEgc2luZ2xlIGZ1bmN0aW9uIHRvIGJlIG1lbW9pemVkLlxuICpcbiAqIElmIHlvdSBwYXNzIG11bHRpcGxlIGZ1bmN0aW9ucywgdGhlIGZvcm1hdCB3aWxsIGJlIHRoZVxuICogc2FtZSBhcyBpbiBgcmVzZWxlY3RgLiBUaGUgbGFzdCBhcmd1bWVudCBpcyB0aGUgc2VsZWN0b3JcbiAqIGZ1bmN0aW9uIGFuZCB0aGUgcHJldmlvdXMgYXJlIGlucHV0IHNlbGVjdG9ycy5cbiAqXG4gKiBXaGVuIHlvdSB1c2UgdGhpcyBtZXRob2QgdG8gY3JlYXRlIGEgc2VsZWN0b3IsIHRoZSByZXR1cm5lZCBzZWxlY3RvclxuICogZXhwZWN0cyB0aGUgd2hvbGUgYHJlZHV4LW9ybWAgc3RhdGUgYnJhbmNoIGFzIGlucHV0LiBJbiB0aGUgc2VsZWN0b3JcbiAqIGZ1bmN0aW9uIHRoYXQgeW91IHBhc3MgYXMgdGhlIGxhc3QgYXJndW1lbnQsIGFueSBvZiB0aGUgYXJndW1lbnRzXG4gKiB5b3UgcGFzcyBmaXJzdCB3aWxsIGJlIGNvbnNpZGVyZWQgc2VsZWN0b3JzIGFuZCBtYXBwZWRcbiAqIHRvIHRoZWlyIG91dHB1dHMsIGxpa2UgaW4gYHJlc2VsZWN0YC5cbiAqXG4gKiBIZXJlIGFyZSBzb21lIGV4YW1wbGUgc2VsZWN0b3JzOlxuICpcbiAqIGBgYGphdmFzY3JpcHRcbiAqIC8vIG9ybSBpcyBhbiBpbnN0YW5jZSBvZiBPUk1cbiAqIC8vIHJlZHV4U3RhdGUgaXMgdGhlIHN0YXRlIG9mIGEgUmVkdXggc3RvcmVcbiAqIGNvbnN0IGJvb2tzID0gY3JlYXRlU2VsZWN0b3Iob3JtLkJvb2spO1xuICogYm9va3MocmVkdXhTdGF0ZSkgLy8gYXJyYXkgb2YgYm9vayByZWZzXG4gKlxuICogY29uc3QgYm9va0F1dGhvcnMgPSBjcmVhdGVTZWxlY3Rvcihvcm0uQm9vay5hdXRob3JzKTtcbiAqIGJvb2tBdXRob3JzKHJlZHV4U3RhdGUpIC8vIHR3by1kaW1lbnNpb25hbCBhcnJheSBvZiBhdXRob3IgcmVmcyBmb3IgZWFjaCBib29rXG4gKiBgYGBcbiAqIFNlbGVjdG9ycyBjYW4gZWFzaWx5IGJlIGFwcGxpZWQgdG8gcmVsYXRlZCBtb2RlbHM6XG4gKiBgYGBqYXZhc2NyaXB0XG4gKiBjb25zdCBib29rQXV0aG9yTmFtZXMgPSBjcmVhdGVTZWxlY3RvcihcbiAqICAgICBvcm0uQm9vay5hdXRob3JzLm1hcChvcm0uQXV0aG9yLm5hbWUpLFxuICogKTtcbiAqIGJvb2tBdXRob3JOYW1lcyhyZWR1eFN0YXRlLCA4KSAvLyBuYW1lcyBvZiBhbGwgYXV0aG9ycyBvZiBib29rIHdpdGggSUQgOFxuICogYm9va0F1dGhvck5hbWVzKHJlZHV4U3RhdGUsIFs4LCA5XSkgLy8gMkQgYXJyYXkgb2YgbmFtZXMgb2YgYWxsIGF1dGhvcnMgb2YgYm9va3Mgd2l0aCBJRHMgOCBhbmQgOVxuICogYGBgXG4gKiBBbHNvIG5vdGUgdGhhdCBgb3JtLkF1dGhvci5uYW1lYCBkaWQgbm90IG5lZWQgdG8gYmUgd3JhcHBlZCBpbiBhbm90aGVyIGBjcmVhdGVTZWxlY3RvcmAgY2FsbCxcbiAqIGFsdGhvdWdoIHRoYXQgd291bGQgYmUgcG9zc2libGUuXG4gKlxuICogRm9yIG1vcmUgY29tcGxleCBjYWxjdWxhdGlvbnMgeW91IGNhbiBhY2Nlc3NcbiAqIGVudGlyZSBzZXNzaW9uIG9iamVjdHMgYnkgcGFzc2luZyBhbiBPUk0gaW5zdGFuY2UuXG4gKiBgYGBqYXZhc2NyaXB0XG4gKiBjb25zdCBmcmVzaEJhbmFuYXNDb3N0ID0gY3JlYXRlU2VsZWN0b3IoXG4gKiAgICAgb3JtLFxuICogICAgIHNlc3Npb24gPT4ge1xuICogICAgICAgIGNvbnN0IGJhbmFuYSA9IHNlc3Npb24uUHJvZHVjdC5nZXQoe1xuICogICAgICAgICAgICBuYW1lOiBcIkJhbmFuYVwiLFxuICogICAgICAgIH0pO1xuICogICAgICAgIC8vIGFtb3VudCBvZiBmcmVzaCBiYW5hbmFzIGluIHNob3BwaW5nIGNhcnRcbiAqICAgICAgICBjb25zdCBhbW91bnQgPSBzZXNzaW9uLlNob3BwaW5nQ2FydC5maWx0ZXIoe1xuICogICAgICAgICAgICBwcm9kdWN0X2lkOiBiYW5hbmEuaWQsXG4gKiAgICAgICAgICAgIGlzX2ZyZXNoOiB0cnVlLFxuICogICAgICAgIH0pLmNvdW50KCk7XG4gKiAgICAgICAgcmV0dXJuIGBVU0QgJHthbW91bnQgKiBiYW5hbmEucHJpY2V9YDtcbiAqICAgICB9XG4gKiApO1xuICogYGBgXG4gKlxuICogcmVkdXgtb3JtIHVzZXMgYSBzcGVjaWFsIG1lbW9pemF0aW9uIGZ1bmN0aW9uIHRvIGF2b2lkIHJlY29tcHV0YXRpb25zLlxuICpcbiAqIEV2ZXJ5dGltZSBhIHNlbGVjdG9yIHJ1bnMsIHRoaXMgZnVuY3Rpb24gcmVjb3JkcyB3aGljaCBpbnN0YW5jZXNcbiAqIG9mIHlvdXIgYE1vZGVsYHMgd2VyZSBhY2Nlc3NlZC48YnI+XG4gKiBPbiBzdWJzZXF1ZW50IHJ1bnMsIHRoZSBzZWxlY3RvciBmaXJzdCBjaGVja3MgaWYgdGhlIHByZXZpb3VzbHlcbiAqIGFjY2Vzc2VkIGluc3RhbmNlcyBvciBgYXJnc2AgaGF2ZSBjaGFuZ2VkIGluIGFueSB3YXk6XG4gKiA8dWw+XG4gKiAgICAgPGxpPklmIHllcywgdGhlIHNlbGVjdG9yIGNhbGxzIHRoZSBmdW5jdGlvbiB5b3UgcGFzc2VkIHRvIGl0LjwvbGk+XG4gKiAgICAgPGxpPklmIG5vdCwgaXQganVzdCByZXR1cm5zIHRoZSBwcmV2aW91cyByZXN1bHRcbiAqICAgICAgICAgKHVubGVzcyB5b3UgY2FsbCBpdCBmb3IgdGhlIGZpcnN0IHRpbWUpLjwvbGk+XG4gKiA8L3VsPlxuICpcbiAqIFRoaXMgd2F5IHlvdSBjYW4gdXNlIHB1cmUgcmVuZGVyaW5nIGluIHlvdXIgUmVhY3QgY29tcG9uZW50c1xuICogZm9yIHBlcmZvcm1hbmNlIGdhaW5zLlxuICpcbiAqIEBnbG9iYWxcbiAqXG4gKiBAcGFyYW0gIHsuLi5GdW5jdGlvbn0gYXJncyAtIHplcm8gb3IgbW9yZSBpbnB1dCBzZWxlY3RvcnNcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYW5kIHRoZSBzZWxlY3RvciBmdW5jdGlvbi5cbiAqIEByZXR1cm4ge0Z1bmN0aW9ufSBtZW1vaXplZCBzZWxlY3RvclxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlU2VsZWN0b3IoLi4uYXJncykge1xuICAgIGlmICghYXJncy5sZW5ndGgpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiQ2Fubm90IGNyZWF0ZSBhIHNlbGVjdG9yIHdpdGhvdXQgYXJndW1lbnRzLlwiKTtcbiAgICB9XG5cbiAgICBjb25zdCByZXN1bHRBcmcgPSBhcmdzLnBvcCgpO1xuICAgIGNvbnN0IGRlcGVuZGVuY2llcyA9IEFycmF5LmlzQXJyYXkoYXJnc1swXSkgPyBhcmdzWzBdIDogYXJncztcblxuICAgIGNvbnN0IG9ybSA9IGRlcGVuZGVuY2llcy5tYXAodG9PUk0pLmZpbmQoQm9vbGVhbik7XG4gICAgY29uc3QgaW5wdXRGdW5jcyA9IGRlcGVuZGVuY2llcy5tYXAodG9TZWxlY3Rvcik7XG5cbiAgICBpZiAodHlwZW9mIHJlc3VsdEFyZyA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgIGlmICghb3JtKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgXCJGYWlsZWQgdG8gcmVzb2x2ZSB0aGUgY3VycmVudCBPUk0gZGF0YWJhc2Ugc3RhdGUuIFBsZWFzZSBwYXNzIGFuIE9STSBpbnN0YW5jZSBvciBhbiBPUk0gc2VsZWN0b3IgYXMgYW4gYXJndW1lbnQgdG8gYGNyZWF0ZVNlbGVjdG9yKClgLlwiXG4gICAgICAgICAgICApO1xuICAgICAgICB9IGVsc2UgaWYgKCFvcm0uc3RhdGVTZWxlY3Rvcikge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIFwiRmFpbGVkIHRvIHJlc29sdmUgdGhlIGN1cnJlbnQgT1JNIGRhdGFiYXNlIHN0YXRlLiBQbGVhc2UgcGFzcyBhbiBvYmplY3QgdG8gdGhlIE9STSBjb25zdHJ1Y3RvciB0aGF0IHNwZWNpZmllcyBhIGBzdGF0ZVNlbGVjdG9yYCBmdW5jdGlvbi5cIlxuICAgICAgICAgICAgKTtcbiAgICAgICAgfSBlbHNlIGlmICh0eXBlb2Ygb3JtLnN0YXRlU2VsZWN0b3IgIT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIGBGYWlsZWQgdG8gcmVzb2x2ZSB0aGUgY3VycmVudCBPUk0gZGF0YWJhc2Ugc3RhdGUuIFBsZWFzZSBwYXNzIGEgZnVuY3Rpb24gd2hlbiBzcGVjaWZ5aW5nIHRoZSBPUk0ncyBcXGBzdGF0ZVNlbGVjdG9yXFxgLiBSZWNlaXZlZDogJHtKU09OLnN0cmluZ2lmeShcbiAgICAgICAgICAgICAgICAgICAgb3JtLnN0YXRlU2VsZWN0b3JcbiAgICAgICAgICAgICAgICApfSBvZiB0eXBlICR7dHlwZW9mIG9ybS5zdGF0ZVNlbGVjdG9yfWBcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gY3JlYXRlU2VsZWN0b3JDcmVhdG9yKFxuICAgICAgICAgICAgbWVtb2l6ZSxcbiAgICAgICAgICAgIHVuZGVmaW5lZCxcbiAgICAgICAgICAgIG9ybVxuICAgICAgICApKFtvcm0uc3RhdGVTZWxlY3RvciwgLi4uaW5wdXRGdW5jc10sIHJlc3VsdEFyZyk7XG4gICAgfVxuXG4gICAgaWYgKHJlc3VsdEFyZyBpbnN0YW5jZW9mIE9STSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBcIk9STSBpbnN0YW5jZXMgY2Fubm90IGJlIHRoZSByZXN1bHQgZnVuY3Rpb24gb2Ygc2VsZWN0b3JzLiBZb3UgY2FuIGFjY2VzcyB5b3VyIG1vZGVscyBpbiB0aGUgbGFzdCBmdW5jdGlvbiB0aGF0IHlvdSBwYXNzIHRvIGBjcmVhdGVTZWxlY3RvcigpYC5cIlxuICAgICAgICApO1xuICAgIH1cbiAgICBpZiAoaW5wdXRGdW5jcy5sZW5ndGgpIHtcbiAgICAgICAgY29uc29sZS53YXJuKFxuICAgICAgICAgICAgXCJZb3VyIGlucHV0IHNlbGVjdG9ycyB3aWxsIGJlIGlnbm9yZWQ6IHRoZSBwYXNzZWQgcmVzdWx0IGZ1bmN0aW9uIGRvZXMgbm90IHJlcXVpcmUgYW55IGlucHV0LlwiXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRvU2VsZWN0b3IocmVzdWx0QXJnKTtcbn1cbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/redux.js\\n\");\n \n /***/ }),\n \n@@ -4702,7 +4724,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"default\\\", function() { return FieldSelectorSpec; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _MapSelectorSpec__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./MapSelectorSpec */ \\\"./src/selectors/MapSelectorSpec.js\\\");\\n/* harmony import */ var _ModelSelectorSpec__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./ModelSelectorSpec */ \\\"./src/selectors/ModelSelectorSpec.js\\\");\\n/* harmony import */ var _ModelBasedSelectorSpec__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ModelBasedSelectorSpec */ \\\"./src/selectors/ModelBasedSelectorSpec.js\\\");\\n/* harmony import */ var _idArgSelector__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./idArgSelector */ \\\"./src/selectors/idArgSelector.js\\\");\\n/* harmony import */ var _QuerySet__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../QuerySet */ \\\"./src/QuerySet.js\\\");\\n/* harmony import */ var _Model__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../Model */ \\\"./src/Model.js\\\");\\n/* harmony import */ var _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../fields/ForeignKey */ \\\"./src/fields/ForeignKey.js\\\");\\n/* harmony import */ var _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../fields/ManyToMany */ \\\"./src/fields/ManyToMany.js\\\");\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nlet FieldSelectorSpec = /*#__PURE__*/function (_ModelBasedSelectorSp) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(FieldSelectorSpec, _ModelBasedSelectorSp);\\n\\n  function FieldSelectorSpec({\\n    field,\\n    fieldModel,\\n    accessorName,\\n    isVirtual,\\n    ...other\\n  }) {\\n    var _this;\\n\\n    _this = _ModelBasedSelectorSp.call(this, other) || this;\\n    _this._field = field;\\n    _this._fieldModel = fieldModel;\\n    _this._accessorName = accessorName;\\n    _this._isVirtual = isVirtual;\\n    return _this;\\n  }\\n\\n  var _proto = FieldSelectorSpec.prototype;\\n\\n  _proto.valueForInstance = function valueForInstance(instance, session) {\\n    if (!instance) {\\n      return null;\\n    }\\n\\n    let value;\\n\\n    if (this._parent instanceof _ModelSelectorSpec__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]) {\\n      /* orm.Model.field */\\n      value = instance[this._accessorName];\\n    } else {\\n      /* orm.Model.field1.field2..fieldN.field */\\n      const {\\n        [this._parent.toModelName]: ParentToModel\\n      } = session;\\n\\n      const parentRef = this._parent.valueForInstance(instance, session);\\n\\n      const parentInstance = parentRef ? new ParentToModel(parentRef) : null;\\n      value = parentInstance ? parentInstance[this._accessorName] : null;\\n    }\\n\\n    if (value instanceof _Model__WEBPACK_IMPORTED_MODULE_7__[\\\"default\\\"]) {\\n      return value.ref;\\n    }\\n\\n    if (value instanceof _QuerySet__WEBPACK_IMPORTED_MODULE_6__[\\\"default\\\"]) {\\n      return value.toRefArray();\\n    }\\n\\n    return value;\\n  };\\n\\n  _proto.map = function map(selector) {\\n    if (selector instanceof _ModelSelectorSpec__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]) {\\n      if (this.toModelName === selector.model.modelName) {\\n        throw new Error(`Cannot select models in a \\\\`map()\\\\` call. If you just want the \\\\`${this._accessorName}\\\\` as a ref array then you can simply drop the \\\\`map()\\\\`. Otherwise make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`);\\n      } else {\\n        throw new Error(`Cannot select \\\\`${selector.model.modelName}\\\\` models in this \\\\`map()\\\\` call. Make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`);\\n      }\\n    } else if (selector instanceof FieldSelectorSpec || selector instanceof _MapSelectorSpec__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]) {\\n      if (this.toModelName !== selector.model.modelName) {\\n        throw new Error(`Cannot select fields of the \\\\`${selector.model.modelName}\\\\` model in this \\\\`map()\\\\` call. Make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`);\\n      }\\n    } else if (!selector || typeof selector !== \\\"function\\\" || !selector.recomputations) {\\n      throw new Error(`\\\\`map()\\\\` requires a selector as an input. Received: ${JSON.stringify(selector)} of type ${typeof selector}`);\\n    }\\n\\n    if (!(this._field instanceof _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_8__[\\\"default\\\"]) && !(this._field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_9__[\\\"default\\\"])) {\\n      throw new Error(\\\"Cannot map selectors for non-collection fields\\\");\\n    }\\n\\n    return new _MapSelectorSpec__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]({\\n      parent: this,\\n      model: this._model,\\n      orm: this._orm,\\n      field: this._field,\\n      selector\\n    });\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(FieldSelectorSpec, [{\\n    key: \\\"key\\\",\\n    get: function () {\\n      return this._accessorName;\\n    }\\n  }, {\\n    key: \\\"dependencies\\\",\\n    get: function () {\\n      return [this._orm, _idArgSelector__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]];\\n    }\\n  }, {\\n    key: \\\"toModelName\\\",\\n    get: function () {\\n      return this._field.toModelName === \\\"this\\\" ? this._fieldModel.modelName : this._field.toModelName;\\n    }\\n  }, {\\n    key: \\\"toModel\\\",\\n    get: function () {\\n      const db = this._orm.getDatabase();\\n\\n      return db.describe(this.toModelName);\\n    }\\n  }]);\\n\\n  return FieldSelectorSpec;\\n}(_ModelBasedSelectorSpec__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"]);\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvRmllbGRTZWxlY3RvclNwZWMuanM/OTg2YSJdLCJuYW1lcyI6WyJGaWVsZFNlbGVjdG9yU3BlYyIsImZpZWxkIiwiZmllbGRNb2RlbCIsImFjY2Vzc29yTmFtZSIsImlzVmlydHVhbCIsIm90aGVyIiwiX2ZpZWxkIiwiX2ZpZWxkTW9kZWwiLCJfYWNjZXNzb3JOYW1lIiwiX2lzVmlydHVhbCIsInZhbHVlRm9ySW5zdGFuY2UiLCJpbnN0YW5jZSIsInNlc3Npb24iLCJ2YWx1ZSIsIl9wYXJlbnQiLCJNb2RlbFNlbGVjdG9yU3BlYyIsInRvTW9kZWxOYW1lIiwiUGFyZW50VG9Nb2RlbCIsInBhcmVudFJlZiIsInBhcmVudEluc3RhbmNlIiwiTW9kZWwiLCJyZWYiLCJRdWVyeVNldCIsInRvUmVmQXJyYXkiLCJtYXAiLCJzZWxlY3RvciIsIm1vZGVsIiwibW9kZWxOYW1lIiwiRXJyb3IiLCJNYXBTZWxlY3RvclNwZWMiLCJyZWNvbXB1dGF0aW9ucyIsIkpTT04iLCJzdHJpbmdpZnkiLCJGb3JlaWduS2V5IiwiTWFueVRvTWFueSIsInBhcmVudCIsIl9tb2RlbCIsIm9ybSIsIl9vcm0iLCJpZEFyZ1NlbGVjdG9yIiwiZGIiLCJnZXREYXRhYmFzZSIsImRlc2NyaWJlIiwiTW9kZWxCYXNlZFNlbGVjdG9yU3BlYyJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBRUE7QUFDQTtBQUVBO0FBQ0E7O0lBRXFCQSxpQjs7O0FBQ2pCLDZCQUFZO0FBQUVDLFNBQUY7QUFBU0MsY0FBVDtBQUFxQkMsZ0JBQXJCO0FBQW1DQyxhQUFuQztBQUE4QyxPQUFHQztBQUFqRCxHQUFaLEVBQXNFO0FBQUE7O0FBQ2xFLDZDQUFNQSxLQUFOO0FBQ0EsVUFBS0MsTUFBTCxHQUFjTCxLQUFkO0FBQ0EsVUFBS00sV0FBTCxHQUFtQkwsVUFBbkI7QUFDQSxVQUFLTSxhQUFMLEdBQXFC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WjtBQUNIOzs7cUJBRWtCO0FBQ2YsYUFBTyxDQUFDLEtBQUs4QixJQUFOLEVBQVlDLHNEQUFaLENBQVA7QUFDSDs7O3FCQTBFaUI7QUFDZCxhQUFPLEtBQUtqQyxNQUFMLENBQVlVLFdBQVosS0FBNEIsTUFBNUIsR0FDRCxLQUFLVCxXQUFMLENBQWlCb0IsU0FEaEIsR0FFRCxLQUFLckIsTUFBTCxDQUFZVSxXQUZsQjtBQUdIOzs7cUJBRWE7QUFDVixZQUFNd0IsRUFBRSxHQUFHLEtBQUtGLElBQUwsQ0FBVUcsV0FBVixFQUFYOztBQUNBLGFBQU9ELEVBQUUsQ0FBQ0UsUUFBSCxDQUFZLEtBQUsxQixXQUFqQixDQUFQO0FBQ0g7Ozs7RUFsRzBDMkIsK0QiLCJmaWxlIjoiLi9zcmMvc2VsZWN0b3JzL0ZpZWxkU2VsZWN0b3JTcGVjLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IE1hcFNlbGVjdG9yU3BlYyBmcm9tIFwiLi9NYXBTZWxlY3RvclNwZWNcIjtcbmltcG9ydCBNb2RlbFNlbGVjdG9yU3BlYyBmcm9tIFwiLi9Nb2RlbFNlbGVjdG9yU3BlY1wiO1xuaW1wb3J0IE1vZGVsQmFzZWRTZWxlY3RvclNwZWMgZnJvbSBcIi4vTW9kZWxCYXNlZFNlbGVjdG9yU3BlY1wiO1xuaW1wb3J0IGlkQXJnU2VsZWN0b3IgZnJvbSBcIi4vaWRBcmdTZWxlY3RvclwiO1xuXG5pbXBvcnQgUXVlcnlTZXQgZnJvbSBcIi4uL1F1ZXJ5U2V0XCI7XG5pbXBvcnQgTW9kZWwgZnJvbSBcIi4uL01vZGVsXCI7XG5cbmltcG9ydCBGb3JlaWduS2V5IGZyb20gXCIuLi9maWVsZHMvRm9yZWlnbktleVwiO1xuaW1wb3J0IE1hbnlUb01hbnkgZnJvbSBcIi4uL2ZpZWxkcy9NYW55VG9NYW55XCI7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEZpZWxkU2VsZWN0b3JTcGVjIGV4dGVuZHMgTW9kZWxCYXNlZFNlbGVjdG9yU3BlYyB7XG4gICAgY29uc3RydWN0b3IoeyBmaWVsZCwgZmllbGRNb2RlbCwgYWNjZXNzb3JOYW1lLCBpc1ZpcnR1YWwsIC4uLm90aGVyIH0pIHtcbiAgICAgICAgc3VwZXIob3RoZXIpO1xuICAgICAgICB0aGlzLl9maWVsZCA9IGZpZWxkO1xuICAgICAgICB0aGlzLl9maWVsZE1vZGVsID0gZmllbGRNb2RlbDtcbiAgICAgICAgdGhpcy5fYWNjZXNzb3JOYW1lID0gYWNjZXNzb3JOYW1lO1xuICAgICAgICB0aGlzLl9pc1ZpcnR1YWwgPSBpc1ZpcnR1YWw7XG4gICAgfVxuXG4gICAgZ2V0IGtleSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2FjY2Vzc29yTmFtZTtcbiAgICB9XG5cbiAgICBnZXQgZGVwZW5kZW5jaWVzKCkge1xuICAgICAgICByZXR1cm4gW3RoaXMuX29ybSwgaWRBcmdTZWxlY3Rvcl07XG4gICAgfVxuXG4gICAgdmFsdWVGb3JJbnN0YW5jZShpbnN0YW5jZSwgc2Vzc2lvbikge1xuICAgICAgICBpZiAoIWluc3RhbmNlKSB7XG4gICAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgfVxuICAgICAgICBsZXQgdmFsdWU7XG4gICAgICAgIGlmICh0aGlzLl9wYXJlbnQgaW5zdGFuY2VvZiBNb2RlbFNlbGVjdG9yU3BlYykge1xuICAgICAgICAgICAgLyogb3JtLk1vZGVsLmZpZWxkICovXG4gICAgICAgICAgICB2YWx1ZSA9IGluc3RhbmNlW3RoaXMuX2FjY2Vzc29yTmFtZV07XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvKiBvcm0uTW9kZWwuZmllbGQxLmZpZWxkMi4uZmllbGROLmZpZWxkICovXG4gICAgICAgICAgICBjb25zdCB7IFt0aGlzLl9wYXJlbnQudG9Nb2RlbE5hbWVdOiBQYXJlbnRUb01vZGVsIH0gPSBzZXNzaW9uO1xuICAgICAgICAgICAgY29uc3QgcGFyZW50UmVmID0gdGhpcy5fcGFyZW50LnZhbHVlRm9ySW5zdGFuY2UoaW5zdGFuY2UsIHNlc3Npb24pO1xuICAgICAgICAgICAgY29uc3QgcGFyZW50SW5zdGFuY2UgPSBwYXJlbnRSZWZcbiAgICAgICAgICAgICAgICA/IG5ldyBQYXJlbnRUb01vZGVsKHBhcmVudFJlZilcbiAgICAgICAgICAgICAgICA6IG51bGw7XG4gICAgICAgICAgICB2YWx1ZSA9IHBhcmVudEluc3RhbmNlID8gcGFyZW50SW5zdGFuY2VbdGhpcy5fYWNjZXNzb3JOYW1lXSA6IG51bGw7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHZhbHVlIGluc3RhbmNlb2YgTW9kZWwpIHtcbiAgICAgICAgICAgIHJldHVybiB2YWx1ZS5yZWY7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHZhbHVlIGluc3RhbmNlb2YgUXVlcnlTZXQpIHtcbiAgICAgICAgICAgIHJldHVybiB2YWx1ZS50b1JlZkFycmF5KCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgIH1cblxuICAgIG1hcChzZWxlY3Rvcikge1xuICAgICAgICBpZiAoc2VsZWN0b3IgaW5zdGFuY2VvZiBNb2RlbFNlbGVjdG9yU3BlYykge1xuICAgICAgICAgICAgaWYgKHRoaXMudG9Nb2RlbE5hbWUgPT09IHNlbGVjdG9yLm1vZGVsLm1vZGVsTmFtZSkge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICAgICAgYENhbm5vdCBzZWxlY3QgbW9kZWxzIGluIGEgXFxgbWFwKClcXGAgY2FsbC4gSWYgeW91IGp1c3Qgd2FudCB0aGUgXFxgJHt0aGlzLl9hY2Nlc3Nvck5hbWV9XFxgIGFzIGEgcmVmIGFycmF5IHRoZW4geW91IGNhbiBzaW1wbHkgZHJvcCB0aGUgXFxgbWFwKClcXGAuIE90aGVyd2lzZSBtYWtlIHN1cmUgeW91J3JlIHBhc3NpbmcgYSBmaWVsZCBzZWxlY3RvciBvZiB0aGUgZm9ybSBcXGAke3RoaXMudG9Nb2RlbE5hbWV9LjxmaWVsZD5cXGAgb3IgYSBjdXN0b20gc2VsZWN0b3IgaW5zdGVhZC5gXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgICAgICBgQ2Fubm90IHNlbGVjdCBcXGAke3NlbGVjdG9yLm1vZGVsLm1vZGVsTmFtZX1cXGAgbW9kZWxzIGluIHRoaXMgXFxgbWFwKClcXGAgY2FsbC4gTWFrZSBzdXJlIHlvdSdyZSBwYXNzaW5nIGEgZmllbGQgc2VsZWN0b3Igb2YgdGhlIGZvcm0gXFxgJHt0aGlzLnRvTW9kZWxOYW1lfS48ZmllbGQ+XFxgIG9yIGEgY3VzdG9tIHNlbGVjdG9yIGluc3RlYWQuYFxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSBpZiAoXG4gICAgICAgICAgICBzZWxlY3RvciBpbnN0YW5jZW9mIEZpZWxkU2VsZWN0b3JTcGVjIHx8XG4gICAgICAgICAgICBzZWxlY3RvciBpbnN0YW5jZW9mIE1hcFNlbGVjdG9yU3BlY1xuICAgICAgICApIHtcbiAgICAgICAgICAgIGlmICh0aGlzLnRvTW9kZWxOYW1lICE9PSBzZWxlY3Rvci5tb2RlbC5tb2RlbE5hbWUpIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgICAgIGBDYW5ub3Qgc2VsZWN0IGZpZWxkcyBvZiB0aGUgXFxgJHtzZWxlY3Rvci5tb2RlbC5tb2RlbE5hbWV9XFxgIG1vZGVsIGluIHRoaXMgXFxgbWFwKClcXGAgY2FsbC4gTWFrZSBzdXJlIHlvdSdyZSBwYXNzaW5nIGEgZmllbGQgc2VsZWN0b3Igb2YgdGhlIGZvcm0gXFxgJHt0aGlzLnRvTW9kZWxOYW1lfS48ZmllbGQ+XFxgIG9yIGEgY3VzdG9tIHNlbGVjdG9yIGluc3RlYWQuYFxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSBpZiAoXG4gICAgICAgICAgICAhc2VsZWN0b3IgfHxcbiAgICAgICAgICAgIHR5cGVvZiBzZWxlY3RvciAhPT0gXCJmdW5jdGlvblwiIHx8XG4gICAgICAgICAgICAhc2VsZWN0b3IucmVjb21wdXRhdGlvbnNcbiAgICAgICAgKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgYFxcYG1hcCgpXFxgIHJlcXVpcmVzIGEgc2VsZWN0b3IgYXMgYW4gaW5wdXQuIFJlY2VpdmVkOiAke0pTT04uc3RyaW5naWZ5KFxuICAgICAgICAgICAgICAgICAgICBzZWxlY3RvclxuICAgICAgICAgICAgICAgICl9IG9mIHR5cGUgJHt0eXBlb2Ygc2VsZWN0b3J9YFxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoXG4gICAgICAgICAgICAhKHRoaXMuX2ZpZWxkIGluc3RhbmNlb2YgRm9yZWlnbktleSkgJiZcbiAgICAgICAgICAgICEodGhpcy5fZmllbGQgaW5zdGFuY2VvZiBNYW55VG9NYW55KVxuICAgICAgICApIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcIkNhbm5vdCBtYXAgc2VsZWN0b3JzIGZvciBub24tY29sbGVjdGlvbiBmaWVsZHNcIik7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG5ldyBNYXBTZWxlY3RvclNwZWMoe1xuICAgICAgICAgICAgcGFyZW50OiB0aGlzLFxuICAgICAgICAgICAgbW9kZWw6IHRoaXMuX21vZGVsLFxuICAgICAgICAgICAgb3JtOiB0aGlzLl9vcm0sXG4gICAgICAgICAgICBmaWVsZDogdGhpcy5fZmllbGQsXG4gICAgICAgICAgICBzZWxlY3RvcixcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgZ2V0IHRvTW9kZWxOYW1lKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fZmllbGQudG9Nb2RlbE5hbWUgPT09IFwidGhpc1wiXG4gICAgICAgICAgICA/IHRoaXMuX2ZpZWxkTW9kZWwubW9kZWxOYW1lXG4gICAgICAgICAgICA6IHRoaXMuX2ZpZWxkLnRvTW9kZWxOYW1lO1xuICAgIH1cblxuICAgIGdldCB0b01vZGVsKCkge1xuICAgICAgICBjb25zdCBkYiA9IHRoaXMuX29ybS5nZXREYXRhYmFzZSgpO1xuICAgICAgICByZXR1cm4gZGIuZGVzY3JpYmUodGhpcy50b01vZGVsTmFtZSk7XG4gICAgfVxufVxuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/selectors/FieldSelectorSpec.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"default\\\", function() { return FieldSelectorSpec; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _MapSelectorSpec__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./MapSelectorSpec */ \\\"./src/selectors/MapSelectorSpec.js\\\");\\n/* harmony import */ var _ModelSelectorSpec__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./ModelSelectorSpec */ \\\"./src/selectors/ModelSelectorSpec.js\\\");\\n/* harmony import */ var _ModelBasedSelectorSpec__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ModelBasedSelectorSpec */ \\\"./src/selectors/ModelBasedSelectorSpec.js\\\");\\n/* harmony import */ var _idArgSelector__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./idArgSelector */ \\\"./src/selectors/idArgSelector.js\\\");\\n/* harmony import */ var _QuerySet__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../QuerySet */ \\\"./src/QuerySet.js\\\");\\n/* harmony import */ var _Model__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../Model */ \\\"./src/Model.js\\\");\\n/* harmony import */ var _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../fields/ForeignKey */ \\\"./src/fields/ForeignKey.js\\\");\\n/* harmony import */ var _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../fields/ManyToMany */ \\\"./src/fields/ManyToMany.js\\\");\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nlet FieldSelectorSpec = /*#__PURE__*/function (_ModelBasedSelectorSp) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(FieldSelectorSpec, _ModelBasedSelectorSp);\\n\\n  function FieldSelectorSpec({\\n    field,\\n    fieldModel,\\n    accessorName,\\n    isVirtual,\\n    ...other\\n  }) {\\n    var _this;\\n\\n    _this = _ModelBasedSelectorSp.call(this, other) || this;\\n    _this._field = field;\\n    _this._fieldModel = fieldModel;\\n    _this._accessorName = accessorName;\\n    _this._isVirtual = isVirtual;\\n    return _this;\\n  }\\n\\n  var _proto = FieldSelectorSpec.prototype;\\n\\n  _proto.valueForInstance = function valueForInstance(instance, session) {\\n    if (!instance) {\\n      return null;\\n    }\\n\\n    let value;\\n\\n    if (this._parent instanceof _ModelSelectorSpec__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]) {\\n      /* orm.Model.field */\\n      value = instance[this._accessorName];\\n    } else {\\n      /* orm.Model.field1.field2..fieldN.field */\\n      const {\\n        [this._parent.toModelName]: ParentToModel\\n      } = session;\\n\\n      const parentRef = this._parent.valueForInstance(instance, session);\\n\\n      const parentInstance = parentRef ? new ParentToModel(parentRef) : null;\\n      value = parentInstance ? parentInstance[this._accessorName] : null;\\n    }\\n\\n    if (value instanceof _Model__WEBPACK_IMPORTED_MODULE_7__[\\\"default\\\"]) {\\n      return value.ref;\\n    }\\n\\n    if (value instanceof _QuerySet__WEBPACK_IMPORTED_MODULE_6__[\\\"default\\\"]) {\\n      return value.toRefArray();\\n    }\\n\\n    return value;\\n  };\\n\\n  _proto.map = function map(selector) {\\n    if (selector instanceof _ModelSelectorSpec__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]) {\\n      if (this.toModelName === selector.model.modelName) {\\n        throw new Error(`Cannot select models in a \\\\`map()\\\\` call. If you just want the \\\\`${this._accessorName}\\\\` as a ref array then you can simply drop the \\\\`map()\\\\`. Otherwise make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`);\\n      } else {\\n        throw new Error(`Cannot select \\\\`${selector.model.modelName}\\\\` models in this \\\\`map()\\\\` call. Make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`);\\n      }\\n    } else if (selector instanceof FieldSelectorSpec || selector instanceof _MapSelectorSpec__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]) {\\n      if (this.toModelName !== selector.model.modelName) {\\n        throw new Error(`Cannot select fields of the \\\\`${selector.model.modelName}\\\\` model in this \\\\`map()\\\\` call. Make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`);\\n      }\\n    } else if (!selector || typeof selector !== \\\"function\\\" || !selector.recomputations) {\\n      throw new Error(`\\\\`map()\\\\` requires a selector as an input. Received: ${JSON.stringify(selector)} of type ${typeof selector}`);\\n    }\\n\\n    if (!(this._field instanceof _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_8__[\\\"default\\\"]) && !(this._field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_9__[\\\"default\\\"])) {\\n      throw new Error(\\\"Cannot map selectors for non-collection fields\\\");\\n    }\\n\\n    return new _MapSelectorSpec__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]({\\n      parent: this,\\n      model: this._model,\\n      orm: this._orm,\\n      field: this._field,\\n      selector\\n    });\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(FieldSelectorSpec, [{\\n    key: \\\"key\\\",\\n    get: function () {\\n      return this._accessorName;\\n    }\\n  }, {\\n    key: \\\"dependencies\\\",\\n    get: function () {\\n      return [this._orm, _idArgSelector__WEBPACK_IMPORTED_MODULE_5__[\\\"default\\\"]];\\n    }\\n  }, {\\n    key: \\\"toModelName\\\",\\n    get: function () {\\n      return this._field.toModelName === \\\"this\\\" ? this._fieldModel.modelName : this._field.toModelName;\\n    }\\n  }, {\\n    key: \\\"toModel\\\",\\n    get: function () {\\n      const db = this._orm.getDatabase();\\n\\n      return db.describe(this.toModelName);\\n    }\\n  }]);\\n\\n  return FieldSelectorSpec;\\n}(_ModelBasedSelectorSpec__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"]);\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvRmllbGRTZWxlY3RvclNwZWMuanM/OTg2YSJdLCJuYW1lcyI6WyJGaWVsZFNlbGVjdG9yU3BlYyIsImZpZWxkIiwiZmllbGRNb2RlbCIsImFjY2Vzc29yTmFtZSIsImlzVmlydHVhbCIsIm90aGVyIiwiX2ZpZWxkIiwiX2ZpZWxkTW9kZWwiLCJfYWNjZXNzb3JOYW1lIiwiX2lzVmlydHVhbCIsInZhbHVlRm9ySW5zdGFuY2UiLCJpbnN0YW5jZSIsInNlc3Npb24iLCJ2YWx1ZSIsIl9wYXJlbnQiLCJNb2RlbFNlbGVjdG9yU3BlYyIsInRvTW9kZWxOYW1lIiwiUGFyZW50VG9Nb2RlbCIsInBhcmVudFJlZiIsInBhcmVudEluc3RhbmNlIiwiTW9kZWwiLCJyZWYiLCJRdWVyeVNldCIsInRvUmVmQXJyYXkiLCJtYXAiLCJzZWxlY3RvciIsIm1vZGVsIiwibW9kZWxOYW1lIiwiRXJyb3IiLCJNYXBTZWxlY3RvclNwZWMiLCJyZWNvbXB1dGF0aW9ucyIsIkpTT04iLCJzdHJpbmdpZnkiLCJGb3JlaWduS2V5IiwiTWFueVRvTWFueSIsInBhcmVudCIsIl9tb2RlbCIsIm9ybSIsIl9vcm0iLCJpZEFyZ1NlbGVjdG9yIiwiZGIiLCJnZXREYXRhYmFzZSIsImRlc2NyaWJlIiwiTW9kZWxCYXNlZFNlbGVjdG9yU3BlYyJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBRUE7QUFDQTtBQUVBO0FBQ0E7O0lBRXFCQSxpQjs7O0FBQ2pCLDZCQUFZO0FBQUVDLFNBQUY7QUFBU0MsY0FBVDtBQUFxQkMsZ0JBQXJCO0FBQW1DQyxhQUFuQztBQUE4QyxPQUFHQztBQUFqRCxHQUFaLEVBQXNFO0FBQUE7O0FBQ2xFLDZDQUFNQSxLQUFOO0FBQ0EsVUFBS0MsTUFBTCxHQUFjTCxLQUFkO0FBQ0EsVUFBS00sV0FBTCxHQUFtQkwsVUFBbkI7QUFDQSxVQUFLTSxhQUFMLEdBQXFC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LGFBQVo7QUFDSDs7O1NBRUQsWUFBbUI7QUFDZixhQUFPLENBQUMsS0FBSzhCLElBQU4sRUFBWUMsc0RBQVosQ0FBUDtBQUNIOzs7U0EwRUQsWUFBa0I7QUFDZCxhQUFPLEtBQUtqQyxNQUFMLENBQVlVLFdBQVosS0FBNEIsTUFBNUIsR0FDRCxLQUFLVCxXQUFMLENBQWlCb0IsU0FEaEIsR0FFRCxLQUFLckIsTUFBTCxDQUFZVSxXQUZsQjtBQUdIOzs7U0FFRCxZQUFjO0FBQ1YsWUFBTXdCLEVBQUUsR0FBRyxLQUFLRixJQUFMLENBQVVHLFdBQVYsRUFBWDs7QUFDQSxhQUFPRCxFQUFFLENBQUNFLFFBQUgsQ0FBWSxLQUFLMUIsV0FBakIsQ0FBUDtBQUNIOzs7O0VBbEcwQzJCLCtEIiwiZmlsZSI6Ii4vc3JjL3NlbGVjdG9ycy9GaWVsZFNlbGVjdG9yU3BlYy5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBNYXBTZWxlY3RvclNwZWMgZnJvbSBcIi4vTWFwU2VsZWN0b3JTcGVjXCI7XG5pbXBvcnQgTW9kZWxTZWxlY3RvclNwZWMgZnJvbSBcIi4vTW9kZWxTZWxlY3RvclNwZWNcIjtcbmltcG9ydCBNb2RlbEJhc2VkU2VsZWN0b3JTcGVjIGZyb20gXCIuL01vZGVsQmFzZWRTZWxlY3RvclNwZWNcIjtcbmltcG9ydCBpZEFyZ1NlbGVjdG9yIGZyb20gXCIuL2lkQXJnU2VsZWN0b3JcIjtcblxuaW1wb3J0IFF1ZXJ5U2V0IGZyb20gXCIuLi9RdWVyeVNldFwiO1xuaW1wb3J0IE1vZGVsIGZyb20gXCIuLi9Nb2RlbFwiO1xuXG5pbXBvcnQgRm9yZWlnbktleSBmcm9tIFwiLi4vZmllbGRzL0ZvcmVpZ25LZXlcIjtcbmltcG9ydCBNYW55VG9NYW55IGZyb20gXCIuLi9maWVsZHMvTWFueVRvTWFueVwiO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBGaWVsZFNlbGVjdG9yU3BlYyBleHRlbmRzIE1vZGVsQmFzZWRTZWxlY3RvclNwZWMge1xuICAgIGNvbnN0cnVjdG9yKHsgZmllbGQsIGZpZWxkTW9kZWwsIGFjY2Vzc29yTmFtZSwgaXNWaXJ0dWFsLCAuLi5vdGhlciB9KSB7XG4gICAgICAgIHN1cGVyKG90aGVyKTtcbiAgICAgICAgdGhpcy5fZmllbGQgPSBmaWVsZDtcbiAgICAgICAgdGhpcy5fZmllbGRNb2RlbCA9IGZpZWxkTW9kZWw7XG4gICAgICAgIHRoaXMuX2FjY2Vzc29yTmFtZSA9IGFjY2Vzc29yTmFtZTtcbiAgICAgICAgdGhpcy5faXNWaXJ0dWFsID0gaXNWaXJ0dWFsO1xuICAgIH1cblxuICAgIGdldCBrZXkoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9hY2Nlc3Nvck5hbWU7XG4gICAgfVxuXG4gICAgZ2V0IGRlcGVuZGVuY2llcygpIHtcbiAgICAgICAgcmV0dXJuIFt0aGlzLl9vcm0sIGlkQXJnU2VsZWN0b3JdO1xuICAgIH1cblxuICAgIHZhbHVlRm9ySW5zdGFuY2UoaW5zdGFuY2UsIHNlc3Npb24pIHtcbiAgICAgICAgaWYgKCFpbnN0YW5jZSkge1xuICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIH1cbiAgICAgICAgbGV0IHZhbHVlO1xuICAgICAgICBpZiAodGhpcy5fcGFyZW50IGluc3RhbmNlb2YgTW9kZWxTZWxlY3RvclNwZWMpIHtcbiAgICAgICAgICAgIC8qIG9ybS5Nb2RlbC5maWVsZCAqL1xuICAgICAgICAgICAgdmFsdWUgPSBpbnN0YW5jZVt0aGlzLl9hY2Nlc3Nvck5hbWVdO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLyogb3JtLk1vZGVsLmZpZWxkMS5maWVsZDIuLmZpZWxkTi5maWVsZCAqL1xuICAgICAgICAgICAgY29uc3QgeyBbdGhpcy5fcGFyZW50LnRvTW9kZWxOYW1lXTogUGFyZW50VG9Nb2RlbCB9ID0gc2Vzc2lvbjtcbiAgICAgICAgICAgIGNvbnN0IHBhcmVudFJlZiA9IHRoaXMuX3BhcmVudC52YWx1ZUZvckluc3RhbmNlKGluc3RhbmNlLCBzZXNzaW9uKTtcbiAgICAgICAgICAgIGNvbnN0IHBhcmVudEluc3RhbmNlID0gcGFyZW50UmVmXG4gICAgICAgICAgICAgICAgPyBuZXcgUGFyZW50VG9Nb2RlbChwYXJlbnRSZWYpXG4gICAgICAgICAgICAgICAgOiBudWxsO1xuICAgICAgICAgICAgdmFsdWUgPSBwYXJlbnRJbnN0YW5jZSA/IHBhcmVudEluc3RhbmNlW3RoaXMuX2FjY2Vzc29yTmFtZV0gOiBudWxsO1xuICAgICAgICB9XG4gICAgICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIE1vZGVsKSB7XG4gICAgICAgICAgICByZXR1cm4gdmFsdWUucmVmO1xuICAgICAgICB9XG4gICAgICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIFF1ZXJ5U2V0KSB7XG4gICAgICAgICAgICByZXR1cm4gdmFsdWUudG9SZWZBcnJheSgpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB2YWx1ZTtcbiAgICB9XG5cbiAgICBtYXAoc2VsZWN0b3IpIHtcbiAgICAgICAgaWYgKHNlbGVjdG9yIGluc3RhbmNlb2YgTW9kZWxTZWxlY3RvclNwZWMpIHtcbiAgICAgICAgICAgIGlmICh0aGlzLnRvTW9kZWxOYW1lID09PSBzZWxlY3Rvci5tb2RlbC5tb2RlbE5hbWUpIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgICAgIGBDYW5ub3Qgc2VsZWN0IG1vZGVscyBpbiBhIFxcYG1hcCgpXFxgIGNhbGwuIElmIHlvdSBqdXN0IHdhbnQgdGhlIFxcYCR7dGhpcy5fYWNjZXNzb3JOYW1lfVxcYCBhcyBhIHJlZiBhcnJheSB0aGVuIHlvdSBjYW4gc2ltcGx5IGRyb3AgdGhlIFxcYG1hcCgpXFxgLiBPdGhlcndpc2UgbWFrZSBzdXJlIHlvdSdyZSBwYXNzaW5nIGEgZmllbGQgc2VsZWN0b3Igb2YgdGhlIGZvcm0gXFxgJHt0aGlzLnRvTW9kZWxOYW1lfS48ZmllbGQ+XFxgIG9yIGEgY3VzdG9tIHNlbGVjdG9yIGluc3RlYWQuYFxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICAgICAgYENhbm5vdCBzZWxlY3QgXFxgJHtzZWxlY3Rvci5tb2RlbC5tb2RlbE5hbWV9XFxgIG1vZGVscyBpbiB0aGlzIFxcYG1hcCgpXFxgIGNhbGwuIE1ha2Ugc3VyZSB5b3UncmUgcGFzc2luZyBhIGZpZWxkIHNlbGVjdG9yIG9mIHRoZSBmb3JtIFxcYCR7dGhpcy50b01vZGVsTmFtZX0uPGZpZWxkPlxcYCBvciBhIGN1c3RvbSBzZWxlY3RvciBpbnN0ZWFkLmBcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfVxuICAgICAgICB9IGVsc2UgaWYgKFxuICAgICAgICAgICAgc2VsZWN0b3IgaW5zdGFuY2VvZiBGaWVsZFNlbGVjdG9yU3BlYyB8fFxuICAgICAgICAgICAgc2VsZWN0b3IgaW5zdGFuY2VvZiBNYXBTZWxlY3RvclNwZWNcbiAgICAgICAgKSB7XG4gICAgICAgICAgICBpZiAodGhpcy50b01vZGVsTmFtZSAhPT0gc2VsZWN0b3IubW9kZWwubW9kZWxOYW1lKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgICAgICBgQ2Fubm90IHNlbGVjdCBmaWVsZHMgb2YgdGhlIFxcYCR7c2VsZWN0b3IubW9kZWwubW9kZWxOYW1lfVxcYCBtb2RlbCBpbiB0aGlzIFxcYG1hcCgpXFxgIGNhbGwuIE1ha2Ugc3VyZSB5b3UncmUgcGFzc2luZyBhIGZpZWxkIHNlbGVjdG9yIG9mIHRoZSBmb3JtIFxcYCR7dGhpcy50b01vZGVsTmFtZX0uPGZpZWxkPlxcYCBvciBhIGN1c3RvbSBzZWxlY3RvciBpbnN0ZWFkLmBcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfVxuICAgICAgICB9IGVsc2UgaWYgKFxuICAgICAgICAgICAgIXNlbGVjdG9yIHx8XG4gICAgICAgICAgICB0eXBlb2Ygc2VsZWN0b3IgIT09IFwiZnVuY3Rpb25cIiB8fFxuICAgICAgICAgICAgIXNlbGVjdG9yLnJlY29tcHV0YXRpb25zXG4gICAgICAgICkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIGBcXGBtYXAoKVxcYCByZXF1aXJlcyBhIHNlbGVjdG9yIGFzIGFuIGlucHV0LiBSZWNlaXZlZDogJHtKU09OLnN0cmluZ2lmeShcbiAgICAgICAgICAgICAgICAgICAgc2VsZWN0b3JcbiAgICAgICAgICAgICAgICApfSBvZiB0eXBlICR7dHlwZW9mIHNlbGVjdG9yfWBcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKFxuICAgICAgICAgICAgISh0aGlzLl9maWVsZCBpbnN0YW5jZW9mIEZvcmVpZ25LZXkpICYmXG4gICAgICAgICAgICAhKHRoaXMuX2ZpZWxkIGluc3RhbmNlb2YgTWFueVRvTWFueSlcbiAgICAgICAgKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJDYW5ub3QgbWFwIHNlbGVjdG9ycyBmb3Igbm9uLWNvbGxlY3Rpb24gZmllbGRzXCIpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBuZXcgTWFwU2VsZWN0b3JTcGVjKHtcbiAgICAgICAgICAgIHBhcmVudDogdGhpcyxcbiAgICAgICAgICAgIG1vZGVsOiB0aGlzLl9tb2RlbCxcbiAgICAgICAgICAgIG9ybTogdGhpcy5fb3JtLFxuICAgICAgICAgICAgZmllbGQ6IHRoaXMuX2ZpZWxkLFxuICAgICAgICAgICAgc2VsZWN0b3IsXG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIGdldCB0b01vZGVsTmFtZSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2ZpZWxkLnRvTW9kZWxOYW1lID09PSBcInRoaXNcIlxuICAgICAgICAgICAgPyB0aGlzLl9maWVsZE1vZGVsLm1vZGVsTmFtZVxuICAgICAgICAgICAgOiB0aGlzLl9maWVsZC50b01vZGVsTmFtZTtcbiAgICB9XG5cbiAgICBnZXQgdG9Nb2RlbCgpIHtcbiAgICAgICAgY29uc3QgZGIgPSB0aGlzLl9vcm0uZ2V0RGF0YWJhc2UoKTtcbiAgICAgICAgcmV0dXJuIGRiLmRlc2NyaWJlKHRoaXMudG9Nb2RlbE5hbWUpO1xuICAgIH1cbn1cbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/selectors/FieldSelectorSpec.js\\n\");\n \n /***/ }),\n \n@@ -4714,7 +4736,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"default\\\", function() { return MapSelectorSpec; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _ModelBasedSelectorSpec__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ModelBasedSelectorSpec */ \\\"./src/selectors/ModelBasedSelectorSpec.js\\\");\\n/* harmony import */ var _idArgSelector__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./idArgSelector */ \\\"./src/selectors/idArgSelector.js\\\");\\n\\n\\n\\n\\n\\nlet MapSelectorSpec = /*#__PURE__*/function (_ModelBasedSelectorSp) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(MapSelectorSpec, _ModelBasedSelectorSp);\\n\\n  function MapSelectorSpec({\\n    field,\\n    selector,\\n    ...other\\n  }) {\\n    var _this;\\n\\n    _this = _ModelBasedSelectorSp.call(this, other) || this;\\n    _this._field = field;\\n    _this._selector = selector;\\n    return _this;\\n  }\\n\\n  var _proto = MapSelectorSpec.prototype;\\n\\n  _proto.createResultFunc = function createResultFunc(parentSelector) {\\n    const {\\n      idAttribute\\n    } = this._parent.toModel;\\n    return (state, ...other) => {\\n      /**\\n       * The parent selector should return a ref array\\n       * in case of a single ID being passed.\\n       * Otherwise it should return an array of ref arrays.\\n       */\\n      const parentResult = parentSelector(state, ...other);\\n      const idArg = Object(_idArgSelector__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"])(state, ...other);\\n\\n      const single = refArray => {\\n        if (refArray === null) {\\n          // an intermediate field could not be resolved\\n          return null;\\n        }\\n\\n        return refArray.map(ref => this._selector(state, ref[idAttribute]));\\n      };\\n\\n      if (typeof idArg === \\\"undefined\\\" || Array.isArray(idArg)) {\\n        return parentResult.map(single);\\n      }\\n\\n      return single(parentResult);\\n    };\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(MapSelectorSpec, [{\\n    key: \\\"selector\\\",\\n    get: function () {\\n      return this._selector;\\n    },\\n    set: function (selector) {\\n      this._selector = selector;\\n    }\\n  }, {\\n    key: \\\"key\\\",\\n    get: function () {\\n      return this._selector;\\n    }\\n  }]);\\n\\n  return MapSelectorSpec;\\n}(_ModelBasedSelectorSpec__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvTWFwU2VsZWN0b3JTcGVjLmpzP2E4MDUiXSwibmFtZXMiOlsiTWFwU2VsZWN0b3JTcGVjIiwiZmllbGQiLCJzZWxlY3RvciIsIm90aGVyIiwiX2ZpZWxkIiwiX3NlbGVjdG9yIiwiY3JlYXRlUmVzdWx0RnVuYyIsInBhcmVudFNlbGVjdG9yIiwiaWRBdHRyaWJ1dGUiLCJfcGFyZW50IiwidG9Nb2RlbCIsInN0YXRlIiwicGFyZW50UmVzdWx0IiwiaWRBcmciLCJpZEFyZ1NlbGVjdG9yIiwic2luZ2xlIiwicmVmQXJyYXkiLCJtYXAiLCJyZWYiLCJBcnJheSIsImlzQXJyYXkiLCJNb2RlbEJhc2VkU2VsZWN0b3JTcGVjIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7O0FBQUE7QUFDQTs7SUFFcUJBLGU7OztBQUNqQiwyQkFBWTtBQUFFQyxTQUFGO0FBQVNDLFlBQVQ7QUFBbUIsT0FBR0M7QUFBdEIsR0FBWixFQUEyQztBQUFBOztBQUN2Qyw2Q0FBTUEsS0FBTjtBQUNBLFVBQUtDLE1BQUwsR0FBY0gsS0FBZDtBQUNBLFVBQUtJLFNBQUwsR0FBaUJILFFBQWpCO0FBSHVDO0FBSTFDOzs7O1NBRURJLGdCLEdBQUEsMEJBQWlCQyxjQUFqQixFQUFpQztBQUM3QixVQUFNO0FBQUVDO0FBQUYsUUFBa0IsS0FBS0MsT0FBTCxDQUFhQyxPQUFyQztBQUNBLFdBQU8sQ0FBQ0MsS0FBRCxFQUFRLEdBQUdSLEtBQVgsS0FBcUI7QUFDeEI7Ozs7O0FBS0EsWUFBTVMsWUFBWSxHQUFHTCxjQUFjLENBQUNJLEtBQUQsRUFBUSxHQUFHUixLQUFYLENBQW5DO0FBQ0EsWUFBTVUsS0FBSyxHQUFHQyw4REFBYSxDQUFDSCxLQUFELEVBQVEsR0FBR1IsS0FBWCxDQUEzQjs7QUFDQSxZQUFNWSxNQUFNLEdBQUdDLFFBQVEsSUFBSTtBQUN2QixZQUFJQSxRQUFRLEtBQUssSUFBakIsRUFBdUI7QUFDbkI7QUFDQSxpQkFBTyxJQUFQO0FBQ0g7O0FBQ0QsZUFBT0EsUUFBUSxDQUFDQyxHQUFULENBQWFDLEdBQUcsSUFDbkIsS0FBS2IsU0FBTCxDQUFlTSxLQUFmLEVBQXNCTyxHQUFHLENBQUNWLFdBQUQsQ0FBekIsQ0FERyxDQUFQO0FBR0gsT0FSRDs7QUFTQSxVQUFJLE9BQU9LLEtBQVAsS0FBaUIsV0FBakIsSUFBZ0NNLEtBQUssQ0FBQ0MsT0FBTixDQUFjUCxLQUFkLENBQXBDLEVBQTBEO0FBQ3RELGVBQU9ELFlBQVksQ0FBQ0ssR0FBYixDQUFpQkYsTUFBakIsQ0FBUDtBQUNIOztBQUNELGFBQU9BLE1BQU0sQ0FBQ0gsWUFBRCxDQUFiO0FBQ0gsS0FyQkQ7QUFzQkgsRzs7OztxQkFFYztBQUNYLGFBQU8sS0FBS1AsU0FBWjtBQUNILEs7bUJBRVlILFEsRUFBVTtBQUNuQixXQUFLRyxTQUFMLEdBQWlCSCxRQUFqQjtBQUNIOzs7cUJBRVM7QUFDTixhQUFPLEtBQUtHLFNBQVo7QUFDSDs7OztFQTNDd0NnQiwrRCIsImZpbGUiOiIuL3NyYy9zZWxlY3RvcnMvTWFwU2VsZWN0b3JTcGVjLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IE1vZGVsQmFzZWRTZWxlY3RvclNwZWMgZnJvbSBcIi4vTW9kZWxCYXNlZFNlbGVjdG9yU3BlY1wiO1xuaW1wb3J0IGlkQXJnU2VsZWN0b3IgZnJvbSBcIi4vaWRBcmdTZWxlY3RvclwiO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBNYXBTZWxlY3RvclNwZWMgZXh0ZW5kcyBNb2RlbEJhc2VkU2VsZWN0b3JTcGVjIHtcbiAgICBjb25zdHJ1Y3Rvcih7IGZpZWxkLCBzZWxlY3RvciwgLi4ub3RoZXIgfSkge1xuICAgICAgICBzdXBlcihvdGhlcik7XG4gICAgICAgIHRoaXMuX2ZpZWxkID0gZmllbGQ7XG4gICAgICAgIHRoaXMuX3NlbGVjdG9yID0gc2VsZWN0b3I7XG4gICAgfVxuXG4gICAgY3JlYXRlUmVzdWx0RnVuYyhwYXJlbnRTZWxlY3Rvcikge1xuICAgICAgICBjb25zdCB7IGlkQXR0cmlidXRlIH0gPSB0aGlzLl9wYXJlbnQudG9Nb2RlbDtcbiAgICAgICAgcmV0dXJuIChzdGF0ZSwgLi4ub3RoZXIpID0+IHtcbiAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICogVGhlIHBhcmVudCBzZWxlY3RvciBzaG91bGQgcmV0dXJuIGEgcmVmIGFycmF5XG4gICAgICAgICAgICAgKiBpbiBjYXNlIG9mIGEgc2luZ2xlIElEIGJlaW5nIHBhc3NlZC5cbiAgICAgICAgICAgICAqIE90aGVyd2lzZSBpdCBzaG91bGQgcmV0dXJuIGFuIGFycmF5IG9mIHJlZiBhcnJheXMuXG4gICAgICAgICAgICAgKi9cbiAgICAgICAgICAgIGNvbnN0IHBhcmVudFJlc3VsdCA9IHBhcmVudFNlbGVjdG9yKHN0YXRlLCAuLi5vdGhlcik7XG4gICAgICAgICAgICBjb25zdCBpZEFyZyA9IGlkQXJnU2VsZWN0b3Ioc3RhdGUsIC4uLm90aGVyKTtcbiAgICAgICAgICAgIGNvbnN0IHNpbmdsZSA9IHJlZkFycmF5ID0+IHtcbiAgICAgICAgICAgICAgICBpZiAocmVmQXJyYXkgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gYW4gaW50ZXJtZWRpYXRlIGZpZWxkIGNvdWxkIG5vdCBiZSByZXNvbHZlZFxuICAgICAgICAgICAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmV0dXJuIHJlZkFycmF5Lm1hcChyZWYgPT5cbiAgICAgICAgICAgICAgICAgICAgdGhpcy5fc2VsZWN0b3Ioc3RhdGUsIHJlZltpZEF0dHJpYnV0ZV0pXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICBpZiAodHlwZW9mIGlkQXJnID09PSBcInVuZGVmaW5lZFwiIHx8IEFycmF5LmlzQXJyYXkoaWRBcmcpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHBhcmVudFJlc3VsdC5tYXAoc2luZ2xlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiBzaW5nbGUocGFyZW50UmVzdWx0KTtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICBnZXQgc2VsZWN0b3IoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9zZWxlY3RvcjtcbiAgICB9XG5cbiAgICBzZXQgc2VsZWN0b3Ioc2VsZWN0b3IpIHtcbiAgICAgICAgdGhpcy5fc2VsZWN0b3IgPSBzZWxlY3RvcjtcbiAgICB9XG5cbiAgICBnZXQga2V5KCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fc2VsZWN0b3I7XG4gICAgfVxufVxuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/selectors/MapSelectorSpec.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"default\\\", function() { return MapSelectorSpec; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _ModelBasedSelectorSpec__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ModelBasedSelectorSpec */ \\\"./src/selectors/ModelBasedSelectorSpec.js\\\");\\n/* harmony import */ var _idArgSelector__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./idArgSelector */ \\\"./src/selectors/idArgSelector.js\\\");\\n\\n\\n\\n\\n\\nlet MapSelectorSpec = /*#__PURE__*/function (_ModelBasedSelectorSp) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(MapSelectorSpec, _ModelBasedSelectorSp);\\n\\n  function MapSelectorSpec({\\n    field,\\n    selector,\\n    ...other\\n  }) {\\n    var _this;\\n\\n    _this = _ModelBasedSelectorSp.call(this, other) || this;\\n    _this._field = field;\\n    _this._selector = selector;\\n    return _this;\\n  }\\n\\n  var _proto = MapSelectorSpec.prototype;\\n\\n  _proto.createResultFunc = function createResultFunc(parentSelector) {\\n    const {\\n      idAttribute\\n    } = this._parent.toModel;\\n    return (state, ...other) => {\\n      /**\\n       * The parent selector should return a ref array\\n       * in case of a single ID being passed.\\n       * Otherwise it should return an array of ref arrays.\\n       */\\n      const parentResult = parentSelector(state, ...other);\\n      const idArg = Object(_idArgSelector__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"])(state, ...other);\\n\\n      const single = refArray => {\\n        if (refArray === null) {\\n          // an intermediate field could not be resolved\\n          return null;\\n        }\\n\\n        return refArray.map(ref => this._selector(state, ref[idAttribute]));\\n      };\\n\\n      if (typeof idArg === \\\"undefined\\\" || Array.isArray(idArg)) {\\n        return parentResult.map(single);\\n      }\\n\\n      return single(parentResult);\\n    };\\n  };\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(MapSelectorSpec, [{\\n    key: \\\"selector\\\",\\n    get: function () {\\n      return this._selector;\\n    },\\n    set: function (selector) {\\n      this._selector = selector;\\n    }\\n  }, {\\n    key: \\\"key\\\",\\n    get: function () {\\n      return this._selector;\\n    }\\n  }]);\\n\\n  return MapSelectorSpec;\\n}(_ModelBasedSelectorSpec__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvTWFwU2VsZWN0b3JTcGVjLmpzP2E4MDUiXSwibmFtZXMiOlsiTWFwU2VsZWN0b3JTcGVjIiwiZmllbGQiLCJzZWxlY3RvciIsIm90aGVyIiwiX2ZpZWxkIiwiX3NlbGVjdG9yIiwiY3JlYXRlUmVzdWx0RnVuYyIsInBhcmVudFNlbGVjdG9yIiwiaWRBdHRyaWJ1dGUiLCJfcGFyZW50IiwidG9Nb2RlbCIsInN0YXRlIiwicGFyZW50UmVzdWx0IiwiaWRBcmciLCJpZEFyZ1NlbGVjdG9yIiwic2luZ2xlIiwicmVmQXJyYXkiLCJtYXAiLCJyZWYiLCJBcnJheSIsImlzQXJyYXkiLCJNb2RlbEJhc2VkU2VsZWN0b3JTcGVjIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7O0FBQUE7QUFDQTs7SUFFcUJBLGU7OztBQUNqQiwyQkFBWTtBQUFFQyxTQUFGO0FBQVNDLFlBQVQ7QUFBbUIsT0FBR0M7QUFBdEIsR0FBWixFQUEyQztBQUFBOztBQUN2Qyw2Q0FBTUEsS0FBTjtBQUNBLFVBQUtDLE1BQUwsR0FBY0gsS0FBZDtBQUNBLFVBQUtJLFNBQUwsR0FBaUJILFFBQWpCO0FBSHVDO0FBSTFDOzs7O1NBRURJLGdCLEdBQUEsMEJBQWlCQyxjQUFqQixFQUFpQztBQUM3QixVQUFNO0FBQUVDO0FBQUYsUUFBa0IsS0FBS0MsT0FBTCxDQUFhQyxPQUFyQztBQUNBLFdBQU8sQ0FBQ0MsS0FBRCxFQUFRLEdBQUdSLEtBQVgsS0FBcUI7QUFDeEI7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNZLFlBQU1TLFlBQVksR0FBR0wsY0FBYyxDQUFDSSxLQUFELEVBQVEsR0FBR1IsS0FBWCxDQUFuQztBQUNBLFlBQU1VLEtBQUssR0FBR0MsOERBQWEsQ0FBQ0gsS0FBRCxFQUFRLEdBQUdSLEtBQVgsQ0FBM0I7O0FBQ0EsWUFBTVksTUFBTSxHQUFJQyxRQUFELElBQWM7QUFDekIsWUFBSUEsUUFBUSxLQUFLLElBQWpCLEVBQXVCO0FBQ25CO0FBQ0EsaUJBQU8sSUFBUDtBQUNIOztBQUNELGVBQU9BLFFBQVEsQ0FBQ0MsR0FBVCxDQUFjQyxHQUFELElBQ2hCLEtBQUtiLFNBQUwsQ0FBZU0sS0FBZixFQUFzQk8sR0FBRyxDQUFDVixXQUFELENBQXpCLENBREcsQ0FBUDtBQUdILE9BUkQ7O0FBU0EsVUFBSSxPQUFPSyxLQUFQLEtBQWlCLFdBQWpCLElBQWdDTSxLQUFLLENBQUNDLE9BQU4sQ0FBY1AsS0FBZCxDQUFwQyxFQUEwRDtBQUN0RCxlQUFPRCxZQUFZLENBQUNLLEdBQWIsQ0FBaUJGLE1BQWpCLENBQVA7QUFDSDs7QUFDRCxhQUFPQSxNQUFNLENBQUNILFlBQUQsQ0FBYjtBQUNILEtBckJEO0FBc0JILEc7Ozs7U0FFRCxZQUFlO0FBQ1gsYUFBTyxLQUFLUCxTQUFaO0FBQ0gsSztTQUVELFVBQWFILFFBQWIsRUFBdUI7QUFDbkIsV0FBS0csU0FBTCxHQUFpQkgsUUFBakI7QUFDSDs7O1NBRUQsWUFBVTtBQUNOLGFBQU8sS0FBS0csU0FBWjtBQUNIOzs7O0VBM0N3Q2dCLCtEIiwiZmlsZSI6Ii4vc3JjL3NlbGVjdG9ycy9NYXBTZWxlY3RvclNwZWMuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgTW9kZWxCYXNlZFNlbGVjdG9yU3BlYyBmcm9tIFwiLi9Nb2RlbEJhc2VkU2VsZWN0b3JTcGVjXCI7XG5pbXBvcnQgaWRBcmdTZWxlY3RvciBmcm9tIFwiLi9pZEFyZ1NlbGVjdG9yXCI7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIE1hcFNlbGVjdG9yU3BlYyBleHRlbmRzIE1vZGVsQmFzZWRTZWxlY3RvclNwZWMge1xuICAgIGNvbnN0cnVjdG9yKHsgZmllbGQsIHNlbGVjdG9yLCAuLi5vdGhlciB9KSB7XG4gICAgICAgIHN1cGVyKG90aGVyKTtcbiAgICAgICAgdGhpcy5fZmllbGQgPSBmaWVsZDtcbiAgICAgICAgdGhpcy5fc2VsZWN0b3IgPSBzZWxlY3RvcjtcbiAgICB9XG5cbiAgICBjcmVhdGVSZXN1bHRGdW5jKHBhcmVudFNlbGVjdG9yKSB7XG4gICAgICAgIGNvbnN0IHsgaWRBdHRyaWJ1dGUgfSA9IHRoaXMuX3BhcmVudC50b01vZGVsO1xuICAgICAgICByZXR1cm4gKHN0YXRlLCAuLi5vdGhlcikgPT4ge1xuICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgKiBUaGUgcGFyZW50IHNlbGVjdG9yIHNob3VsZCByZXR1cm4gYSByZWYgYXJyYXlcbiAgICAgICAgICAgICAqIGluIGNhc2Ugb2YgYSBzaW5nbGUgSUQgYmVpbmcgcGFzc2VkLlxuICAgICAgICAgICAgICogT3RoZXJ3aXNlIGl0IHNob3VsZCByZXR1cm4gYW4gYXJyYXkgb2YgcmVmIGFycmF5cy5cbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgY29uc3QgcGFyZW50UmVzdWx0ID0gcGFyZW50U2VsZWN0b3Ioc3RhdGUsIC4uLm90aGVyKTtcbiAgICAgICAgICAgIGNvbnN0IGlkQXJnID0gaWRBcmdTZWxlY3RvcihzdGF0ZSwgLi4ub3RoZXIpO1xuICAgICAgICAgICAgY29uc3Qgc2luZ2xlID0gKHJlZkFycmF5KSA9PiB7XG4gICAgICAgICAgICAgICAgaWYgKHJlZkFycmF5ID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIGFuIGludGVybWVkaWF0ZSBmaWVsZCBjb3VsZCBub3QgYmUgcmVzb2x2ZWRcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiByZWZBcnJheS5tYXAoKHJlZikgPT5cbiAgICAgICAgICAgICAgICAgICAgdGhpcy5fc2VsZWN0b3Ioc3RhdGUsIHJlZltpZEF0dHJpYnV0ZV0pXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICBpZiAodHlwZW9mIGlkQXJnID09PSBcInVuZGVmaW5lZFwiIHx8IEFycmF5LmlzQXJyYXkoaWRBcmcpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHBhcmVudFJlc3VsdC5tYXAoc2luZ2xlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiBzaW5nbGUocGFyZW50UmVzdWx0KTtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICBnZXQgc2VsZWN0b3IoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9zZWxlY3RvcjtcbiAgICB9XG5cbiAgICBzZXQgc2VsZWN0b3Ioc2VsZWN0b3IpIHtcbiAgICAgICAgdGhpcy5fc2VsZWN0b3IgPSBzZWxlY3RvcjtcbiAgICB9XG5cbiAgICBnZXQga2V5KCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fc2VsZWN0b3I7XG4gICAgfVxufVxuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/selectors/MapSelectorSpec.js\\n\");\n \n /***/ }),\n \n@@ -4726,7 +4748,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"default\\\", function() { return ModelBasedSelectorSpec; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _SelectorSpec__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./SelectorSpec */ \\\"./src/selectors/SelectorSpec.js\\\");\\n\\n\\n\\n\\nlet ModelBasedSelectorSpec = /*#__PURE__*/function (_SelectorSpec) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(ModelBasedSelectorSpec, _SelectorSpec);\\n\\n  function ModelBasedSelectorSpec({\\n    model,\\n    ...other\\n  }) {\\n    var _this;\\n\\n    _this = _SelectorSpec.call(this, other) || this;\\n    _this._model = model;\\n    return _this;\\n  }\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(ModelBasedSelectorSpec, [{\\n    key: \\\"resultFunc\\\",\\n    get: function () {\\n      return (session, idArg, ...other) => {\\n        const {\\n          [this._model.modelName]: ModelClass\\n        } = session;\\n\\n        if (typeof idArg === \\\"undefined\\\") {\\n          return ModelClass.all().toModelArray().map(instance => this.valueForInstance(instance, session, ...other));\\n        }\\n\\n        if (Array.isArray(idArg)) {\\n          return idArg.map(id => this.valueForInstance(ModelClass.withId(id), session, ...other));\\n        }\\n\\n        return this.valueForInstance(ModelClass.withId(idArg), session, ...other);\\n      };\\n    }\\n  }, {\\n    key: \\\"model\\\",\\n    get: function () {\\n      return this._model;\\n    }\\n  }]);\\n\\n  return ModelBasedSelectorSpec;\\n}(_SelectorSpec__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvTW9kZWxCYXNlZFNlbGVjdG9yU3BlYy5qcz8wODc2Il0sIm5hbWVzIjpbIk1vZGVsQmFzZWRTZWxlY3RvclNwZWMiLCJtb2RlbCIsIm90aGVyIiwiX21vZGVsIiwic2Vzc2lvbiIsImlkQXJnIiwibW9kZWxOYW1lIiwiTW9kZWxDbGFzcyIsImFsbCIsInRvTW9kZWxBcnJheSIsIm1hcCIsImluc3RhbmNlIiwidmFsdWVGb3JJbnN0YW5jZSIsIkFycmF5IiwiaXNBcnJheSIsImlkIiwid2l0aElkIiwiU2VsZWN0b3JTcGVjIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQTs7SUFFcUJBLHNCOzs7QUFDakIsa0NBQVk7QUFBRUMsU0FBRjtBQUFTLE9BQUdDO0FBQVosR0FBWixFQUFpQztBQUFBOztBQUM3QixxQ0FBTUEsS0FBTjtBQUNBLFVBQUtDLE1BQUwsR0FBY0YsS0FBZDtBQUY2QjtBQUdoQzs7OztxQkFFZ0I7QUFDYixhQUFPLENBQUNHLE9BQUQsRUFBVUMsS0FBVixFQUFpQixHQUFHSCxLQUFwQixLQUE4QjtBQUNqQyxjQUFNO0FBQUUsV0FBQyxLQUFLQyxNQUFMLENBQVlHLFNBQWIsR0FBeUJDO0FBQTNCLFlBQTBDSCxPQUFoRDs7QUFDQSxZQUFJLE9BQU9DLEtBQVAsS0FBaUIsV0FBckIsRUFBa0M7QUFDOUIsaUJBQU9FLFVBQVUsQ0FBQ0MsR0FBWCxHQUNGQyxZQURFLEdBRUZDLEdBRkUsQ0FFRUMsUUFBUSxJQUNULEtBQUtDLGdCQUFMLENBQXNCRCxRQUF0QixFQUFnQ1AsT0FBaEMsRUFBeUMsR0FBR0YsS0FBNUMsQ0FIRCxDQUFQO0FBS0g7O0FBQ0QsWUFBSVcsS0FBSyxDQUFDQyxPQUFOLENBQWNULEtBQWQsQ0FBSixFQUEwQjtBQUN0QixpQkFBT0EsS0FBSyxDQUFDSyxHQUFOLENBQVVLLEVBQUUsSUFDZixLQUFLSCxnQkFBTCxDQUNJTCxVQUFVLENBQUNTLE1BQVgsQ0FBa0JELEVBQWxCLENBREosRUFFSVgsT0FGSixFQUdJLEdBQUdGLEtBSFAsQ0FERyxDQUFQO0FBT0g7O0FBQ0QsZUFBTyxLQUFLVSxnQkFBTCxDQUNITCxVQUFVLENBQUNTLE1BQVgsQ0FBa0JYLEtBQWxCLENBREcsRUFFSEQsT0FGRyxFQUdILEdBQUdGLEtBSEEsQ0FBUDtBQUtILE9BdkJEO0FBd0JIOzs7cUJBRVc7QUFDUixhQUFPLEtBQUtDLE1BQVo7QUFDSDs7OztFQW5DK0NjLHFEIiwiZmlsZSI6Ii4vc3JjL3NlbGVjdG9ycy9Nb2RlbEJhc2VkU2VsZWN0b3JTcGVjLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFNlbGVjdG9yU3BlYyBmcm9tIFwiLi9TZWxlY3RvclNwZWNcIjtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgTW9kZWxCYXNlZFNlbGVjdG9yU3BlYyBleHRlbmRzIFNlbGVjdG9yU3BlYyB7XG4gICAgY29uc3RydWN0b3IoeyBtb2RlbCwgLi4ub3RoZXIgfSkge1xuICAgICAgICBzdXBlcihvdGhlcik7XG4gICAgICAgIHRoaXMuX21vZGVsID0gbW9kZWw7XG4gICAgfVxuXG4gICAgZ2V0IHJlc3VsdEZ1bmMoKSB7XG4gICAgICAgIHJldHVybiAoc2Vzc2lvbiwgaWRBcmcsIC4uLm90aGVyKSA9PiB7XG4gICAgICAgICAgICBjb25zdCB7IFt0aGlzLl9tb2RlbC5tb2RlbE5hbWVdOiBNb2RlbENsYXNzIH0gPSBzZXNzaW9uO1xuICAgICAgICAgICAgaWYgKHR5cGVvZiBpZEFyZyA9PT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgICAgIHJldHVybiBNb2RlbENsYXNzLmFsbCgpXG4gICAgICAgICAgICAgICAgICAgIC50b01vZGVsQXJyYXkoKVxuICAgICAgICAgICAgICAgICAgICAubWFwKGluc3RhbmNlID0+XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbHVlRm9ySW5zdGFuY2UoaW5zdGFuY2UsIHNlc3Npb24sIC4uLm90aGVyKVxuICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKEFycmF5LmlzQXJyYXkoaWRBcmcpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGlkQXJnLm1hcChpZCA9PlxuICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbHVlRm9ySW5zdGFuY2UoXG4gICAgICAgICAgICAgICAgICAgICAgICBNb2RlbENsYXNzLndpdGhJZChpZCksXG4gICAgICAgICAgICAgICAgICAgICAgICBzZXNzaW9uLFxuICAgICAgICAgICAgICAgICAgICAgICAgLi4ub3RoZXJcbiAgICAgICAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gdGhpcy52YWx1ZUZvckluc3RhbmNlKFxuICAgICAgICAgICAgICAgIE1vZGVsQ2xhc3Mud2l0aElkKGlkQXJnKSxcbiAgICAgICAgICAgICAgICBzZXNzaW9uLFxuICAgICAgICAgICAgICAgIC4uLm90aGVyXG4gICAgICAgICAgICApO1xuICAgICAgICB9O1xuICAgIH1cblxuICAgIGdldCBtb2RlbCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX21vZGVsO1xuICAgIH1cbn1cbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/selectors/ModelBasedSelectorSpec.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"default\\\", function() { return ModelBasedSelectorSpec; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _SelectorSpec__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./SelectorSpec */ \\\"./src/selectors/SelectorSpec.js\\\");\\n\\n\\n\\n\\nlet ModelBasedSelectorSpec = /*#__PURE__*/function (_SelectorSpec) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(ModelBasedSelectorSpec, _SelectorSpec);\\n\\n  function ModelBasedSelectorSpec({\\n    model,\\n    ...other\\n  }) {\\n    var _this;\\n\\n    _this = _SelectorSpec.call(this, other) || this;\\n    _this._model = model;\\n    return _this;\\n  }\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(ModelBasedSelectorSpec, [{\\n    key: \\\"resultFunc\\\",\\n    get: function () {\\n      return (session, idArg, ...other) => {\\n        const {\\n          [this._model.modelName]: ModelClass\\n        } = session;\\n\\n        if (typeof idArg === \\\"undefined\\\") {\\n          return ModelClass.all().toModelArray().map(instance => this.valueForInstance(instance, session, ...other));\\n        }\\n\\n        if (Array.isArray(idArg)) {\\n          return idArg.map(id => this.valueForInstance(ModelClass.withId(id), session, ...other));\\n        }\\n\\n        return this.valueForInstance(ModelClass.withId(idArg), session, ...other);\\n      };\\n    }\\n  }, {\\n    key: \\\"model\\\",\\n    get: function () {\\n      return this._model;\\n    }\\n  }]);\\n\\n  return ModelBasedSelectorSpec;\\n}(_SelectorSpec__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvTW9kZWxCYXNlZFNlbGVjdG9yU3BlYy5qcz8wODc2Il0sIm5hbWVzIjpbIk1vZGVsQmFzZWRTZWxlY3RvclNwZWMiLCJtb2RlbCIsIm90aGVyIiwiX21vZGVsIiwic2Vzc2lvbiIsImlkQXJnIiwibW9kZWxOYW1lIiwiTW9kZWxDbGFzcyIsImFsbCIsInRvTW9kZWxBcnJheSIsIm1hcCIsImluc3RhbmNlIiwidmFsdWVGb3JJbnN0YW5jZSIsIkFycmF5IiwiaXNBcnJheSIsImlkIiwid2l0aElkIiwiU2VsZWN0b3JTcGVjIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQTs7SUFFcUJBLHNCOzs7QUFDakIsa0NBQVk7QUFBRUMsU0FBRjtBQUFTLE9BQUdDO0FBQVosR0FBWixFQUFpQztBQUFBOztBQUM3QixxQ0FBTUEsS0FBTjtBQUNBLFVBQUtDLE1BQUwsR0FBY0YsS0FBZDtBQUY2QjtBQUdoQzs7OztTQUVELFlBQWlCO0FBQ2IsYUFBTyxDQUFDRyxPQUFELEVBQVVDLEtBQVYsRUFBaUIsR0FBR0gsS0FBcEIsS0FBOEI7QUFDakMsY0FBTTtBQUFFLFdBQUMsS0FBS0MsTUFBTCxDQUFZRyxTQUFiLEdBQXlCQztBQUEzQixZQUEwQ0gsT0FBaEQ7O0FBQ0EsWUFBSSxPQUFPQyxLQUFQLEtBQWlCLFdBQXJCLEVBQWtDO0FBQzlCLGlCQUFPRSxVQUFVLENBQUNDLEdBQVgsR0FDRkMsWUFERSxHQUVGQyxHQUZFLENBRUdDLFFBQUQsSUFDRCxLQUFLQyxnQkFBTCxDQUFzQkQsUUFBdEIsRUFBZ0NQLE9BQWhDLEVBQXlDLEdBQUdGLEtBQTVDLENBSEQsQ0FBUDtBQUtIOztBQUNELFlBQUlXLEtBQUssQ0FBQ0MsT0FBTixDQUFjVCxLQUFkLENBQUosRUFBMEI7QUFDdEIsaUJBQU9BLEtBQUssQ0FBQ0ssR0FBTixDQUFXSyxFQUFELElBQ2IsS0FBS0gsZ0JBQUwsQ0FDSUwsVUFBVSxDQUFDUyxNQUFYLENBQWtCRCxFQUFsQixDQURKLEVBRUlYLE9BRkosRUFHSSxHQUFHRixLQUhQLENBREcsQ0FBUDtBQU9IOztBQUNELGVBQU8sS0FBS1UsZ0JBQUwsQ0FDSEwsVUFBVSxDQUFDUyxNQUFYLENBQWtCWCxLQUFsQixDQURHLEVBRUhELE9BRkcsRUFHSCxHQUFHRixLQUhBLENBQVA7QUFLSCxPQXZCRDtBQXdCSDs7O1NBRUQsWUFBWTtBQUNSLGFBQU8sS0FBS0MsTUFBWjtBQUNIOzs7O0VBbkMrQ2MscUQiLCJmaWxlIjoiLi9zcmMvc2VsZWN0b3JzL01vZGVsQmFzZWRTZWxlY3RvclNwZWMuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgU2VsZWN0b3JTcGVjIGZyb20gXCIuL1NlbGVjdG9yU3BlY1wiO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBNb2RlbEJhc2VkU2VsZWN0b3JTcGVjIGV4dGVuZHMgU2VsZWN0b3JTcGVjIHtcbiAgICBjb25zdHJ1Y3Rvcih7IG1vZGVsLCAuLi5vdGhlciB9KSB7XG4gICAgICAgIHN1cGVyKG90aGVyKTtcbiAgICAgICAgdGhpcy5fbW9kZWwgPSBtb2RlbDtcbiAgICB9XG5cbiAgICBnZXQgcmVzdWx0RnVuYygpIHtcbiAgICAgICAgcmV0dXJuIChzZXNzaW9uLCBpZEFyZywgLi4ub3RoZXIpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHsgW3RoaXMuX21vZGVsLm1vZGVsTmFtZV06IE1vZGVsQ2xhc3MgfSA9IHNlc3Npb247XG4gICAgICAgICAgICBpZiAodHlwZW9mIGlkQXJnID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIE1vZGVsQ2xhc3MuYWxsKClcbiAgICAgICAgICAgICAgICAgICAgLnRvTW9kZWxBcnJheSgpXG4gICAgICAgICAgICAgICAgICAgIC5tYXAoKGluc3RhbmNlKSA9PlxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy52YWx1ZUZvckluc3RhbmNlKGluc3RhbmNlLCBzZXNzaW9uLCAuLi5vdGhlcilcbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmIChBcnJheS5pc0FycmF5KGlkQXJnKSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBpZEFyZy5tYXAoKGlkKSA9PlxuICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbHVlRm9ySW5zdGFuY2UoXG4gICAgICAgICAgICAgICAgICAgICAgICBNb2RlbENsYXNzLndpdGhJZChpZCksXG4gICAgICAgICAgICAgICAgICAgICAgICBzZXNzaW9uLFxuICAgICAgICAgICAgICAgICAgICAgICAgLi4ub3RoZXJcbiAgICAgICAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gdGhpcy52YWx1ZUZvckluc3RhbmNlKFxuICAgICAgICAgICAgICAgIE1vZGVsQ2xhc3Mud2l0aElkKGlkQXJnKSxcbiAgICAgICAgICAgICAgICBzZXNzaW9uLFxuICAgICAgICAgICAgICAgIC4uLm90aGVyXG4gICAgICAgICAgICApO1xuICAgICAgICB9O1xuICAgIH1cblxuICAgIGdldCBtb2RlbCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX21vZGVsO1xuICAgIH1cbn1cbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/selectors/ModelBasedSelectorSpec.js\\n\");\n \n /***/ }),\n \n@@ -4738,7 +4760,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"default\\\", function() { return ModelSelectorSpec; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _SelectorSpec__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./SelectorSpec */ \\\"./src/selectors/SelectorSpec.js\\\");\\n/* harmony import */ var _idArgSelector__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./idArgSelector */ \\\"./src/selectors/idArgSelector.js\\\");\\n\\n\\n\\n\\n\\nlet ModelSelectorSpec = /*#__PURE__*/function (_SelectorSpec) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(ModelSelectorSpec, _SelectorSpec);\\n\\n  function ModelSelectorSpec({\\n    model,\\n    ...other\\n  }) {\\n    var _this;\\n\\n    _this = _SelectorSpec.call(this, other) || this;\\n    _this._model = model;\\n    return _this;\\n  }\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(ModelSelectorSpec, [{\\n    key: \\\"key\\\",\\n    get: function () {\\n      return this._model.modelName;\\n    }\\n  }, {\\n    key: \\\"dependencies\\\",\\n    get: function () {\\n      return [this._orm, _idArgSelector__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]];\\n    }\\n  }, {\\n    key: \\\"resultFunc\\\",\\n    get: function () {\\n      return ({\\n        [this._model.modelName]: ModelClass\\n      }, idArg) => {\\n        if (typeof idArg === \\\"undefined\\\") {\\n          return ModelClass.all().toRefArray();\\n        }\\n\\n        if (Array.isArray(idArg)) {\\n          return idArg.map(id => {\\n            const instance = ModelClass.withId(id);\\n            return instance ? instance.ref : null;\\n          });\\n        }\\n\\n        const instance = ModelClass.withId(idArg);\\n        return instance ? instance.ref : null;\\n      };\\n    }\\n  }, {\\n    key: \\\"model\\\",\\n    get: function () {\\n      return this._model;\\n    }\\n  }]);\\n\\n  return ModelSelectorSpec;\\n}(_SelectorSpec__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvTW9kZWxTZWxlY3RvclNwZWMuanM/YWE1YiJdLCJuYW1lcyI6WyJNb2RlbFNlbGVjdG9yU3BlYyIsIm1vZGVsIiwib3RoZXIiLCJfbW9kZWwiLCJtb2RlbE5hbWUiLCJfb3JtIiwiaWRBcmdTZWxlY3RvciIsIk1vZGVsQ2xhc3MiLCJpZEFyZyIsImFsbCIsInRvUmVmQXJyYXkiLCJBcnJheSIsImlzQXJyYXkiLCJtYXAiLCJpZCIsImluc3RhbmNlIiwid2l0aElkIiwicmVmIiwiU2VsZWN0b3JTcGVjIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7O0FBQUE7QUFDQTs7SUFFcUJBLGlCOzs7QUFDakIsNkJBQVk7QUFBRUMsU0FBRjtBQUFTLE9BQUdDO0FBQVosR0FBWixFQUFpQztBQUFBOztBQUM3QixxQ0FBTUEsS0FBTjtBQUNBLFVBQUtDLE1BQUwsR0FBY0YsS0FBZDtBQUY2QjtBQUdoQzs7OztxQkFFUztBQUNOLGFBQU8sS0FBS0UsTUFBTCxDQUFZQyxTQUFuQjtBQUNIOzs7cUJBRWtCO0FBQ2YsYUFBTyxDQUFDLEtBQUtDLElBQU4sRUFBWUMsc0RBQVosQ0FBUDtBQUNIOzs7cUJBRWdCO0FBQ2IsYUFBTyxDQUFDO0FBQUUsU0FBQyxLQUFLSCxNQUFMLENBQVlDLFNBQWIsR0FBeUJHO0FBQTNCLE9BQUQsRUFBMENDLEtBQTFDLEtBQW9EO0FBQ3ZELFlBQUksT0FBT0EsS0FBUCxLQUFpQixXQUFyQixFQUFrQztBQUM5QixpQkFBT0QsVUFBVSxDQUFDRSxHQUFYLEdBQWlCQyxVQUFqQixFQUFQO0FBQ0g7O0FBQ0QsWUFBSUMsS0FBSyxDQUFDQyxPQUFOLENBQWNKLEtBQWQsQ0FBSixFQUEwQjtBQUN0QixpQkFBT0EsS0FBSyxDQUFDSyxHQUFOLENBQVVDLEVBQUUsSUFBSTtBQUNuQixrQkFBTUMsUUFBUSxHQUFHUixVQUFVLENBQUNTLE1BQVgsQ0FBa0JGLEVBQWxCLENBQWpCO0FBQ0EsbUJBQU9DLFFBQVEsR0FBR0EsUUFBUSxDQUFDRSxHQUFaLEdBQWtCLElBQWpDO0FBQ0gsV0FITSxDQUFQO0FBSUg7O0FBQ0QsY0FBTUYsUUFBUSxHQUFHUixVQUFVLENBQUNTLE1BQVgsQ0FBa0JSLEtBQWxCLENBQWpCO0FBQ0EsZUFBT08sUUFBUSxHQUFHQSxRQUFRLENBQUNFLEdBQVosR0FBa0IsSUFBakM7QUFDSCxPQVpEO0FBYUg7OztxQkFFVztBQUNSLGFBQU8sS0FBS2QsTUFBWjtBQUNIOzs7O0VBaEMwQ2UscUQiLCJmaWxlIjoiLi9zcmMvc2VsZWN0b3JzL01vZGVsU2VsZWN0b3JTcGVjLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFNlbGVjdG9yU3BlYyBmcm9tIFwiLi9TZWxlY3RvclNwZWNcIjtcbmltcG9ydCBpZEFyZ1NlbGVjdG9yIGZyb20gXCIuL2lkQXJnU2VsZWN0b3JcIjtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgTW9kZWxTZWxlY3RvclNwZWMgZXh0ZW5kcyBTZWxlY3RvclNwZWMge1xuICAgIGNvbnN0cnVjdG9yKHsgbW9kZWwsIC4uLm90aGVyIH0pIHtcbiAgICAgICAgc3VwZXIob3RoZXIpO1xuICAgICAgICB0aGlzLl9tb2RlbCA9IG1vZGVsO1xuICAgIH1cblxuICAgIGdldCBrZXkoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9tb2RlbC5tb2RlbE5hbWU7XG4gICAgfVxuXG4gICAgZ2V0IGRlcGVuZGVuY2llcygpIHtcbiAgICAgICAgcmV0dXJuIFt0aGlzLl9vcm0sIGlkQXJnU2VsZWN0b3JdO1xuICAgIH1cblxuICAgIGdldCByZXN1bHRGdW5jKCkge1xuICAgICAgICByZXR1cm4gKHsgW3RoaXMuX21vZGVsLm1vZGVsTmFtZV06IE1vZGVsQ2xhc3MgfSwgaWRBcmcpID0+IHtcbiAgICAgICAgICAgIGlmICh0eXBlb2YgaWRBcmcgPT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gTW9kZWxDbGFzcy5hbGwoKS50b1JlZkFycmF5KCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoQXJyYXkuaXNBcnJheShpZEFyZykpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gaWRBcmcubWFwKGlkID0+IHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgaW5zdGFuY2UgPSBNb2RlbENsYXNzLndpdGhJZChpZCk7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBpbnN0YW5jZSA/IGluc3RhbmNlLnJlZiA6IG51bGw7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCBpbnN0YW5jZSA9IE1vZGVsQ2xhc3Mud2l0aElkKGlkQXJnKTtcbiAgICAgICAgICAgIHJldHVybiBpbnN0YW5jZSA/IGluc3RhbmNlLnJlZiA6IG51bGw7XG4gICAgICAgIH07XG4gICAgfVxuXG4gICAgZ2V0IG1vZGVsKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fbW9kZWw7XG4gICAgfVxufVxuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/selectors/ModelSelectorSpec.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"default\\\", function() { return ModelSelectorSpec; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/inheritsLoose */ \\\"./node_modules/@babel/runtime/helpers/inheritsLoose.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1__);\\n/* harmony import */ var _SelectorSpec__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./SelectorSpec */ \\\"./src/selectors/SelectorSpec.js\\\");\\n/* harmony import */ var _idArgSelector__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./idArgSelector */ \\\"./src/selectors/idArgSelector.js\\\");\\n\\n\\n\\n\\n\\nlet ModelSelectorSpec = /*#__PURE__*/function (_SelectorSpec) {\\n  _babel_runtime_helpers_inheritsLoose__WEBPACK_IMPORTED_MODULE_1___default()(ModelSelectorSpec, _SelectorSpec);\\n\\n  function ModelSelectorSpec({\\n    model,\\n    ...other\\n  }) {\\n    var _this;\\n\\n    _this = _SelectorSpec.call(this, other) || this;\\n    _this._model = model;\\n    return _this;\\n  }\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(ModelSelectorSpec, [{\\n    key: \\\"key\\\",\\n    get: function () {\\n      return this._model.modelName;\\n    }\\n  }, {\\n    key: \\\"dependencies\\\",\\n    get: function () {\\n      return [this._orm, _idArgSelector__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]];\\n    }\\n  }, {\\n    key: \\\"resultFunc\\\",\\n    get: function () {\\n      return ({\\n        [this._model.modelName]: ModelClass\\n      }, idArg) => {\\n        if (typeof idArg === \\\"undefined\\\") {\\n          return ModelClass.all().toRefArray();\\n        }\\n\\n        if (Array.isArray(idArg)) {\\n          return idArg.map(id => {\\n            const instance = ModelClass.withId(id);\\n            return instance ? instance.ref : null;\\n          });\\n        }\\n\\n        const instance = ModelClass.withId(idArg);\\n        return instance ? instance.ref : null;\\n      };\\n    }\\n  }, {\\n    key: \\\"model\\\",\\n    get: function () {\\n      return this._model;\\n    }\\n  }]);\\n\\n  return ModelSelectorSpec;\\n}(_SelectorSpec__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"]);\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvTW9kZWxTZWxlY3RvclNwZWMuanM/YWE1YiJdLCJuYW1lcyI6WyJNb2RlbFNlbGVjdG9yU3BlYyIsIm1vZGVsIiwib3RoZXIiLCJfbW9kZWwiLCJtb2RlbE5hbWUiLCJfb3JtIiwiaWRBcmdTZWxlY3RvciIsIk1vZGVsQ2xhc3MiLCJpZEFyZyIsImFsbCIsInRvUmVmQXJyYXkiLCJBcnJheSIsImlzQXJyYXkiLCJtYXAiLCJpZCIsImluc3RhbmNlIiwid2l0aElkIiwicmVmIiwiU2VsZWN0b3JTcGVjIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7O0FBQUE7QUFDQTs7SUFFcUJBLGlCOzs7QUFDakIsNkJBQVk7QUFBRUMsU0FBRjtBQUFTLE9BQUdDO0FBQVosR0FBWixFQUFpQztBQUFBOztBQUM3QixxQ0FBTUEsS0FBTjtBQUNBLFVBQUtDLE1BQUwsR0FBY0YsS0FBZDtBQUY2QjtBQUdoQzs7OztTQUVELFlBQVU7QUFDTixhQUFPLEtBQUtFLE1BQUwsQ0FBWUMsU0FBbkI7QUFDSDs7O1NBRUQsWUFBbUI7QUFDZixhQUFPLENBQUMsS0FBS0MsSUFBTixFQUFZQyxzREFBWixDQUFQO0FBQ0g7OztTQUVELFlBQWlCO0FBQ2IsYUFBTyxDQUFDO0FBQUUsU0FBQyxLQUFLSCxNQUFMLENBQVlDLFNBQWIsR0FBeUJHO0FBQTNCLE9BQUQsRUFBMENDLEtBQTFDLEtBQW9EO0FBQ3ZELFlBQUksT0FBT0EsS0FBUCxLQUFpQixXQUFyQixFQUFrQztBQUM5QixpQkFBT0QsVUFBVSxDQUFDRSxHQUFYLEdBQWlCQyxVQUFqQixFQUFQO0FBQ0g7O0FBQ0QsWUFBSUMsS0FBSyxDQUFDQyxPQUFOLENBQWNKLEtBQWQsQ0FBSixFQUEwQjtBQUN0QixpQkFBT0EsS0FBSyxDQUFDSyxHQUFOLENBQVdDLEVBQUQsSUFBUTtBQUNyQixrQkFBTUMsUUFBUSxHQUFHUixVQUFVLENBQUNTLE1BQVgsQ0FBa0JGLEVBQWxCLENBQWpCO0FBQ0EsbUJBQU9DLFFBQVEsR0FBR0EsUUFBUSxDQUFDRSxHQUFaLEdBQWtCLElBQWpDO0FBQ0gsV0FITSxDQUFQO0FBSUg7O0FBQ0QsY0FBTUYsUUFBUSxHQUFHUixVQUFVLENBQUNTLE1BQVgsQ0FBa0JSLEtBQWxCLENBQWpCO0FBQ0EsZUFBT08sUUFBUSxHQUFHQSxRQUFRLENBQUNFLEdBQVosR0FBa0IsSUFBakM7QUFDSCxPQVpEO0FBYUg7OztTQUVELFlBQVk7QUFDUixhQUFPLEtBQUtkLE1BQVo7QUFDSDs7OztFQWhDMENlLHFEIiwiZmlsZSI6Ii4vc3JjL3NlbGVjdG9ycy9Nb2RlbFNlbGVjdG9yU3BlYy5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBTZWxlY3RvclNwZWMgZnJvbSBcIi4vU2VsZWN0b3JTcGVjXCI7XG5pbXBvcnQgaWRBcmdTZWxlY3RvciBmcm9tIFwiLi9pZEFyZ1NlbGVjdG9yXCI7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIE1vZGVsU2VsZWN0b3JTcGVjIGV4dGVuZHMgU2VsZWN0b3JTcGVjIHtcbiAgICBjb25zdHJ1Y3Rvcih7IG1vZGVsLCAuLi5vdGhlciB9KSB7XG4gICAgICAgIHN1cGVyKG90aGVyKTtcbiAgICAgICAgdGhpcy5fbW9kZWwgPSBtb2RlbDtcbiAgICB9XG5cbiAgICBnZXQga2V5KCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fbW9kZWwubW9kZWxOYW1lO1xuICAgIH1cblxuICAgIGdldCBkZXBlbmRlbmNpZXMoKSB7XG4gICAgICAgIHJldHVybiBbdGhpcy5fb3JtLCBpZEFyZ1NlbGVjdG9yXTtcbiAgICB9XG5cbiAgICBnZXQgcmVzdWx0RnVuYygpIHtcbiAgICAgICAgcmV0dXJuICh7IFt0aGlzLl9tb2RlbC5tb2RlbE5hbWVdOiBNb2RlbENsYXNzIH0sIGlkQXJnKSA9PiB7XG4gICAgICAgICAgICBpZiAodHlwZW9mIGlkQXJnID09PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIE1vZGVsQ2xhc3MuYWxsKCkudG9SZWZBcnJheSgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKEFycmF5LmlzQXJyYXkoaWRBcmcpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGlkQXJnLm1hcCgoaWQpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgaW5zdGFuY2UgPSBNb2RlbENsYXNzLndpdGhJZChpZCk7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBpbnN0YW5jZSA/IGluc3RhbmNlLnJlZiA6IG51bGw7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCBpbnN0YW5jZSA9IE1vZGVsQ2xhc3Mud2l0aElkKGlkQXJnKTtcbiAgICAgICAgICAgIHJldHVybiBpbnN0YW5jZSA/IGluc3RhbmNlLnJlZiA6IG51bGw7XG4gICAgICAgIH07XG4gICAgfVxuXG4gICAgZ2V0IG1vZGVsKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fbW9kZWw7XG4gICAgfVxufVxuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/selectors/ModelSelectorSpec.js\\n\");\n \n /***/ }),\n \n@@ -4750,7 +4772,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"default\\\", function() { return SelectorSpec; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../constants */ \\\"./src/constants.js\\\");\\n\\n\\n\\nlet SelectorSpec = /*#__PURE__*/function () {\\n  function SelectorSpec({\\n    parent,\\n    orm\\n  }) {\\n    this._parent = parent;\\n    this._orm = orm;\\n    this.keySelector = _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"ID_ARG_KEY_SELECTOR\\\"];\\n  }\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(SelectorSpec, [{\\n    key: \\\"cachePath\\\",\\n    get: function () {\\n      const basePath = this._parent ? this._parent.cachePath : [];\\n      return [...basePath, this.key];\\n    }\\n  }, {\\n    key: \\\"orm\\\",\\n    get: function () {\\n      return this._orm;\\n    }\\n  }, {\\n    key: \\\"parent\\\",\\n    get: function () {\\n      return this._parent;\\n    }\\n  }]);\\n\\n  return SelectorSpec;\\n}();\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvU2VsZWN0b3JTcGVjLmpzP2Q5MjUiXSwibmFtZXMiOlsiU2VsZWN0b3JTcGVjIiwicGFyZW50Iiwib3JtIiwiX3BhcmVudCIsIl9vcm0iLCJrZXlTZWxlY3RvciIsIklEX0FSR19LRVlfU0VMRUNUT1IiLCJiYXNlUGF0aCIsImNhY2hlUGF0aCIsImtleSJdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUE7O0lBRXFCQSxZO0FBQ2pCLHdCQUFZO0FBQUVDLFVBQUY7QUFBVUM7QUFBVixHQUFaLEVBQTZCO0FBQ3pCLFNBQUtDLE9BQUwsR0FBZUYsTUFBZjtBQUNBLFNBQUtHLElBQUwsR0FBWUYsR0FBWjtBQUNBLFNBQUtHLFdBQUwsR0FBbUJDLDhEQUFuQjtBQUNIOzs7O3FCQUVlO0FBQ1osWUFBTUMsUUFBUSxHQUFHLEtBQUtKLE9BQUwsR0FBZSxLQUFLQSxPQUFMLENBQWFLLFNBQTVCLEdBQXdDLEVBQXpEO0FBQ0EsYUFBTyxDQUFDLEdBQUdELFFBQUosRUFBYyxLQUFLRSxHQUFuQixDQUFQO0FBQ0g7OztxQkFFUztBQUNOLGFBQU8sS0FBS0wsSUFBWjtBQUNIOzs7cUJBRVk7QUFDVCxhQUFPLEtBQUtELE9BQVo7QUFDSCIsImZpbGUiOiIuL3NyYy9zZWxlY3RvcnMvU2VsZWN0b3JTcGVjLmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSURfQVJHX0tFWV9TRUxFQ1RPUiB9IGZyb20gXCIuLi9jb25zdGFudHNcIjtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgU2VsZWN0b3JTcGVjIHtcbiAgICBjb25zdHJ1Y3Rvcih7IHBhcmVudCwgb3JtIH0pIHtcbiAgICAgICAgdGhpcy5fcGFyZW50ID0gcGFyZW50O1xuICAgICAgICB0aGlzLl9vcm0gPSBvcm07XG4gICAgICAgIHRoaXMua2V5U2VsZWN0b3IgPSBJRF9BUkdfS0VZX1NFTEVDVE9SO1xuICAgIH1cblxuICAgIGdldCBjYWNoZVBhdGgoKSB7XG4gICAgICAgIGNvbnN0IGJhc2VQYXRoID0gdGhpcy5fcGFyZW50ID8gdGhpcy5fcGFyZW50LmNhY2hlUGF0aCA6IFtdO1xuICAgICAgICByZXR1cm4gWy4uLmJhc2VQYXRoLCB0aGlzLmtleV07XG4gICAgfVxuXG4gICAgZ2V0IG9ybSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX29ybTtcbiAgICB9XG5cbiAgICBnZXQgcGFyZW50KCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fcGFyZW50O1xuICAgIH1cbn1cbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/selectors/SelectorSpec.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"default\\\", function() { return SelectorSpec; });\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/createClass */ \\\"./node_modules/@babel/runtime/helpers/createClass.js\\\");\\n/* harmony import */ var _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0__);\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../constants */ \\\"./src/constants.js\\\");\\n\\n\\n\\nlet SelectorSpec = /*#__PURE__*/function () {\\n  function SelectorSpec({\\n    parent,\\n    orm\\n  }) {\\n    this._parent = parent;\\n    this._orm = orm;\\n    this.keySelector = _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"ID_ARG_KEY_SELECTOR\\\"];\\n  }\\n\\n  _babel_runtime_helpers_createClass__WEBPACK_IMPORTED_MODULE_0___default()(SelectorSpec, [{\\n    key: \\\"cachePath\\\",\\n    get: function () {\\n      const basePath = this._parent ? this._parent.cachePath : [];\\n      return [...basePath, this.key];\\n    }\\n  }, {\\n    key: \\\"orm\\\",\\n    get: function () {\\n      return this._orm;\\n    }\\n  }, {\\n    key: \\\"parent\\\",\\n    get: function () {\\n      return this._parent;\\n    }\\n  }]);\\n\\n  return SelectorSpec;\\n}();\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvU2VsZWN0b3JTcGVjLmpzP2Q5MjUiXSwibmFtZXMiOlsiU2VsZWN0b3JTcGVjIiwicGFyZW50Iiwib3JtIiwiX3BhcmVudCIsIl9vcm0iLCJrZXlTZWxlY3RvciIsIklEX0FSR19LRVlfU0VMRUNUT1IiLCJiYXNlUGF0aCIsImNhY2hlUGF0aCIsImtleSJdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUE7O0lBRXFCQSxZO0FBQ2pCLHdCQUFZO0FBQUVDLFVBQUY7QUFBVUM7QUFBVixHQUFaLEVBQTZCO0FBQ3pCLFNBQUtDLE9BQUwsR0FBZUYsTUFBZjtBQUNBLFNBQUtHLElBQUwsR0FBWUYsR0FBWjtBQUNBLFNBQUtHLFdBQUwsR0FBbUJDLDhEQUFuQjtBQUNIOzs7O1NBRUQsWUFBZ0I7QUFDWixZQUFNQyxRQUFRLEdBQUcsS0FBS0osT0FBTCxHQUFlLEtBQUtBLE9BQUwsQ0FBYUssU0FBNUIsR0FBd0MsRUFBekQ7QUFDQSxhQUFPLENBQUMsR0FBR0QsUUFBSixFQUFjLEtBQUtFLEdBQW5CLENBQVA7QUFDSDs7O1NBRUQsWUFBVTtBQUNOLGFBQU8sS0FBS0wsSUFBWjtBQUNIOzs7U0FFRCxZQUFhO0FBQ1QsYUFBTyxLQUFLRCxPQUFaO0FBQ0giLCJmaWxlIjoiLi9zcmMvc2VsZWN0b3JzL1NlbGVjdG9yU3BlYy5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IElEX0FSR19LRVlfU0VMRUNUT1IgfSBmcm9tIFwiLi4vY29uc3RhbnRzXCI7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFNlbGVjdG9yU3BlYyB7XG4gICAgY29uc3RydWN0b3IoeyBwYXJlbnQsIG9ybSB9KSB7XG4gICAgICAgIHRoaXMuX3BhcmVudCA9IHBhcmVudDtcbiAgICAgICAgdGhpcy5fb3JtID0gb3JtO1xuICAgICAgICB0aGlzLmtleVNlbGVjdG9yID0gSURfQVJHX0tFWV9TRUxFQ1RPUjtcbiAgICB9XG5cbiAgICBnZXQgY2FjaGVQYXRoKCkge1xuICAgICAgICBjb25zdCBiYXNlUGF0aCA9IHRoaXMuX3BhcmVudCA/IHRoaXMuX3BhcmVudC5jYWNoZVBhdGggOiBbXTtcbiAgICAgICAgcmV0dXJuIFsuLi5iYXNlUGF0aCwgdGhpcy5rZXldO1xuICAgIH1cblxuICAgIGdldCBvcm0oKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9vcm07XG4gICAgfVxuXG4gICAgZ2V0IHBhcmVudCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3BhcmVudDtcbiAgICB9XG59XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./src/selectors/SelectorSpec.js\\n\");\n \n /***/ }),\n \n@@ -4774,7 +4796,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"createFieldSelectorSpec\\\", function() { return createFieldSelectorSpec; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"createModelSelectorSpec\\\", function() { return createModelSelectorSpec; });\\n/* harmony import */ var _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../fields/ForeignKey */ \\\"./src/fields/ForeignKey.js\\\");\\n/* harmony import */ var _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../fields/ManyToMany */ \\\"./src/fields/ManyToMany.js\\\");\\n/* harmony import */ var _fields_RelationalField__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../fields/RelationalField */ \\\"./src/fields/RelationalField.js\\\");\\n/* harmony import */ var _FieldSelectorSpec__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./FieldSelectorSpec */ \\\"./src/selectors/FieldSelectorSpec.js\\\");\\n/* harmony import */ var _ModelSelectorSpec__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ModelSelectorSpec */ \\\"./src/selectors/ModelSelectorSpec.js\\\");\\n\\n\\n\\n\\n\\n/**\\n * @module selectors\\n * @private\\n */\\n\\nfunction createFieldSelectorSpec({\\n  parent,\\n  model,\\n  field,\\n  fieldModel,\\n  accessorName,\\n  orm,\\n  isVirtual\\n}) {\\n  const fieldSelectorSpec = new _FieldSelectorSpec__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]({\\n    parent,\\n    model,\\n    field,\\n    fieldModel,\\n    accessorName,\\n    orm,\\n    isVirtual\\n  });\\n  /* Do not even try to create field selectors below attributes. */\\n\\n  if (!(field instanceof _fields_RelationalField__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"])) {\\n    // \\\"orm.Author.name.publisher\\\" would be nonsense\\n    return fieldSelectorSpec;\\n  }\\n  /* Prevent field selectors below collections. */\\n\\n\\n  if (parent instanceof _FieldSelectorSpec__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]) {\\n    /* eslint-disable no-underscore-dangle */\\n    if ( // \\\"orm.Author.books.publisher\\\" would be nonsense\\n    parent._field instanceof _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"] && parent._isVirtual || // \\\"orm.Genre.books.publisher\\\" would be nonsense\\n    parent._field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"]) {\\n      throw new Error(`Cannot create a selector for \\\\`${parent._accessorName}.${accessorName}\\\\` because \\\\`${parent._accessorName}\\\\` is a collection field.`);\\n    }\\n  }\\n\\n  const {\\n    toModelName\\n  } = field;\\n  const toModel = orm.get(toModelName === \\\"this\\\" ? model.modelName : toModelName);\\n  Object.entries(toModel.fields).forEach(([relatedFieldName, relatedField]) => {\\n    const fieldAccessorName = relatedField.as || relatedFieldName;\\n    Object.defineProperty(fieldSelectorSpec, fieldAccessorName, {\\n      get: () => createFieldSelectorSpec({\\n        parent: fieldSelectorSpec,\\n        model,\\n        fieldModel: toModel,\\n        field: relatedField,\\n        accessorName: fieldAccessorName,\\n        orm,\\n        isVirtual: false\\n      })\\n    });\\n  });\\n  Object.entries(toModel.virtualFields).forEach(([relatedFieldName, relatedField]) => {\\n    const fieldAccessorName = relatedField.as || relatedFieldName;\\n\\n    if (fieldSelectorSpec.hasOwnProperty(fieldAccessorName)) {\\n      return;\\n    }\\n\\n    Object.defineProperty(fieldSelectorSpec, fieldAccessorName, {\\n      get: () => createFieldSelectorSpec({\\n        parent: fieldSelectorSpec,\\n        model,\\n        fieldModel: toModel,\\n        field: relatedField,\\n        accessorName: fieldAccessorName,\\n        orm,\\n        isVirtual: true\\n      })\\n    });\\n  });\\n  return fieldSelectorSpec;\\n}\\nfunction createModelSelectorSpec({\\n  model,\\n  orm\\n}) {\\n  const modelSelectorSpec = new _ModelSelectorSpec__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"]({\\n    parent: null,\\n    orm,\\n    model\\n  });\\n  Object.entries(model.fields).forEach(([fieldName, field]) => {\\n    const fieldAccessorName = field.as || fieldName;\\n    Object.defineProperty(modelSelectorSpec, fieldAccessorName, {\\n      get: () => createFieldSelectorSpec({\\n        parent: modelSelectorSpec,\\n        model,\\n        fieldModel: model,\\n        field,\\n        accessorName: fieldAccessorName,\\n        orm,\\n        isVirtual: false\\n      })\\n    });\\n  });\\n  Object.entries(model.virtualFields).forEach(([fieldName, field]) => {\\n    const fieldAccessorName = field.as || fieldName;\\n\\n    if (modelSelectorSpec.hasOwnProperty(fieldAccessorName)) {\\n      return;\\n    }\\n\\n    Object.defineProperty(modelSelectorSpec, fieldAccessorName, {\\n      get: () => createFieldSelectorSpec({\\n        parent: modelSelectorSpec,\\n        model,\\n        fieldModel: model,\\n        field,\\n        accessorName: fieldAccessorName,\\n        orm,\\n        isVirtual: true\\n      })\\n    });\\n  });\\n  return modelSelectorSpec;\\n}//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvaW5kZXguanM/YTA3NiJdLCJuYW1lcyI6WyJjcmVhdGVGaWVsZFNlbGVjdG9yU3BlYyIsInBhcmVudCIsIm1vZGVsIiwiZmllbGQiLCJmaWVsZE1vZGVsIiwiYWNjZXNzb3JOYW1lIiwib3JtIiwiaXNWaXJ0dWFsIiwiZmllbGRTZWxlY3RvclNwZWMiLCJGaWVsZFNlbGVjdG9yU3BlYyIsIlJlbGF0aW9uYWxGaWVsZCIsIl9maWVsZCIsIkZvcmVpZ25LZXkiLCJfaXNWaXJ0dWFsIiwiTWFueVRvTWFueSIsIkVycm9yIiwiX2FjY2Vzc29yTmFtZSIsInRvTW9kZWxOYW1lIiwidG9Nb2RlbCIsImdldCIsIm1vZGVsTmFtZSIsIk9iamVjdCIsImVudHJpZXMiLCJmaWVsZHMiLCJmb3JFYWNoIiwicmVsYXRlZEZpZWxkTmFtZSIsInJlbGF0ZWRGaWVsZCIsImZpZWxkQWNjZXNzb3JOYW1lIiwiYXMiLCJkZWZpbmVQcm9wZXJ0eSIsInZpcnR1YWxGaWVsZHMiLCJoYXNPd25Qcm9wZXJ0eSIsImNyZWF0ZU1vZGVsU2VsZWN0b3JTcGVjIiwibW9kZWxTZWxlY3RvclNwZWMiLCJNb2RlbFNlbGVjdG9yU3BlYyIsImZpZWxkTmFtZSJdLCJtYXBwaW5ncyI6IkFBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUVBO0FBQ0E7QUFFQTs7Ozs7QUFLTyxTQUFTQSx1QkFBVCxDQUFpQztBQUNwQ0MsUUFEb0M7QUFFcENDLE9BRm9DO0FBR3BDQyxPQUhvQztBQUlwQ0MsWUFKb0M7QUFLcENDLGNBTG9DO0FBTXBDQyxLQU5vQztBQU9wQ0M7QUFQb0MsQ0FBakMsRUFRSjtBQUNDLFFBQU1DLGlCQUFpQixHQUFHLElBQUlDLDBEQUFKLENBQXNCO0FBQzVDUixVQUQ0QztBQUU1Q0MsU0FGNEM7QUFHNUNDLFNBSDRDO0FBSTVDQyxjQUo0QztBQUs1Q0MsZ0JBTDRDO0FB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dEM7QUFDQWQsVUFBTSxDQUFDUSxjQUFQLENBQXNCSSxpQkFBdEIsRUFBeUNOLGlCQUF6QyxFQUE0RDtBQUN4RFIsU0FBRyxFQUFFLE1BQ0RuQix1QkFBdUIsQ0FBQztBQUNwQkMsY0FBTSxFQUFFZ0MsaUJBRFk7QUFFcEIvQixhQUZvQjtBQUdwQkUsa0JBQVUsRUFBRUYsS0FIUTtBQUlwQkMsYUFKb0I7QUFLcEJFLG9CQUFZLEVBQUVzQixpQkFMTTtBQU1wQnJCLFdBTm9CO0FBT3BCQyxpQkFBUyxFQUFFO0FBUFMsT0FBRDtBQUY2QixLQUE1RDtBQVlILEdBZEQ7QUFnQkFjLFFBQU0sQ0FBQ0MsT0FBUCxDQUFlcEIsS0FBSyxDQUFDNEIsYUFBckIsRUFBb0NOLE9BQXBDLENBQTRDLENBQUMsQ0FBQ1csU0FBRCxFQUFZaEMsS0FBWixDQUFELEtBQXdCO0FBQ2hFLFVBQU13QixpQkFBaUIsR0FBR3hCLEtBQUssQ0FBQ3lCLEVBQU4sSUFBWU8sU0FBdEM7O0FBQ0EsUUFBSUYsaUJBQWlCLENBQUNGLGNBQWxCLENBQWlDSixpQkFBakMsQ0FBSixFQUF5RDtBQUNyRDtBQUNIOztBQUNETixVQUFNLENBQUNRLGNBQVAsQ0FBc0JJLGlCQUF0QixFQUF5Q04saUJBQXpDLEVBQTREO0FBQ3hEUixTQUFHLEVBQUUsTUFDRG5CLHVCQUF1QixDQUFDO0FBQ3BCQyxjQUFNLEVBQUVnQyxpQkFEWTtBQUVwQi9CLGFBRm9CO0FBR3BCRSxrQkFBVSxFQUFFRixLQUhRO0FBSXBCQyxhQUpvQjtBQUtwQkUsb0JBQVksRUFBRXNCLGlCQUxNO0FBTXBCckIsV0FOb0I7QUFPcEJDLGlCQUFTLEVBQUU7QUFQUyxPQUFEO0FBRjZCLEtBQTVEO0FBWUgsR0FqQkQ7QUFtQkEsU0FBTzBCLGlCQUFQO0FBQ0giLCJmaWxlIjoiLi9zcmMvc2VsZWN0b3JzL2luZGV4LmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IEZvcmVpZ25LZXkgZnJvbSBcIi4uL2ZpZWxkcy9Gb3JlaWduS2V5XCI7XG5pbXBvcnQgTWFueVRvTWFueSBmcm9tIFwiLi4vZmllbGRzL01hbnlUb01hbnlcIjtcbmltcG9ydCBSZWxhdGlvbmFsRmllbGQgZnJvbSBcIi4uL2ZpZWxkcy9SZWxhdGlvbmFsRmllbGRcIjtcblxuaW1wb3J0IEZpZWxkU2VsZWN0b3JTcGVjIGZyb20gXCIuL0ZpZWxkU2VsZWN0b3JTcGVjXCI7XG5pbXBvcnQgTW9kZWxTZWxlY3RvclNwZWMgZnJvbSBcIi4vTW9kZWxTZWxlY3RvclNwZWNcIjtcblxuLyoqXG4gKiBAbW9kdWxlIHNlbGVjdG9yc1xuICogQHByaXZhdGVcbiAqL1xuXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlRmllbGRTZWxlY3RvclNwZWMoe1xuICAgIHBhcmVudCxcbiAgICBtb2RlbCxcbiAgICBmaWVsZCxcbiAgICBmaWVsZE1vZGVsLFxuICAgIGFjY2Vzc29yTmFtZSxcbiAgICBvcm0sXG4gICAgaXNWaXJ0dWFsLFxufSkge1xuICAgIGNvbnN0IGZpZWxkU2VsZWN0b3JTcGVjID0gbmV3IEZpZWxkU2VsZWN0b3JTcGVjKHtcbiAgICAgICAgcGFyZW50LFxuICAgICAgICBtb2RlbCxcbiAgICAgICAgZmllbGQsXG4gICAgICAgIGZpZWxkTW9kZWwsXG4gICAgICAgIGFjY2Vzc29yTmFtZSxcbiAgICAgICAgb3JtLFxuICAgICAgICBpc1ZpcnR1YWwsXG4gICAgfSk7XG4gICAgLyogRG8gbm90IGV2ZW4gdHJ5IHRvIGNyZWF0ZSBmaWVsZCBzZWxlY3RvcnMgYmVsb3cgYXR0cmlidXRlcy4gKi9cbiAgICBpZiAoIShmaWVsZCBpbnN0YW5jZW9mIFJlbGF0aW9uYWxGaWVsZCkpIHtcbiAgICAgICAgLy8gXCJvcm0uQXV0aG9yLm5hbWUucHVibGlzaGVyXCIgd291bGQgYmUgbm9uc2Vuc2VcbiAgICAgICAgcmV0dXJuIGZpZWxkU2VsZWN0b3JTcGVjO1xuICAgIH1cbiAgICAvKiBQcmV2ZW50IGZpZWxkIHNlbGVjdG9ycyBiZWxvdyBjb2xsZWN0aW9ucy4gKi9cbiAgICBpZiAocGFyZW50IGluc3RhbmNlb2YgRmllbGRTZWxlY3RvclNwZWMpIHtcbiAgICAgICAgLyogZXNsaW50LWRpc2FibGUgbm8tdW5kZXJzY29yZS1kYW5nbGUgKi9cbiAgICAgICAgaWYgKFxuICAgICAgICAgICAgLy8gXCJvcm0uQXV0aG9yLmJvb2tzLnB1Ymxpc2hlclwiIHdvdWxkIGJlIG5vbnNlbnNlXG4gICAgICAgICAgICAocGFyZW50Ll9maWVsZCBpbnN0YW5jZW9mIEZvcmVpZ25LZXkgJiYgcGFyZW50Ll9pc1ZpcnR1YWwpIHx8XG4gICAgICAgICAgICAvLyBcIm9ybS5HZW5yZS5ib29rcy5wdWJsaXNoZXJcIiB3b3VsZCBiZSBub25zZW5zZVxuICAgICAgICAgICAgcGFyZW50Ll9maWVsZCBpbnN0YW5jZW9mIE1hbnlUb01hbnlcbiAgICAgICAgKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgYENhbm5vdCBjcmVhdGUgYSBzZWxlY3RvciBmb3IgXFxgJHtwYXJlbnQuX2FjY2Vzc29yTmFtZX0uJHthY2Nlc3Nvck5hbWV9XFxgIGJlY2F1c2UgXFxgJHtwYXJlbnQuX2FjY2Vzc29yTmFtZX1cXGAgaXMgYSBjb2xsZWN0aW9uIGZpZWxkLmBcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgY29uc3QgeyB0b01vZGVsTmFtZSB9ID0gZmllbGQ7XG4gICAgY29uc3QgdG9Nb2RlbCA9IG9ybS5nZXQoXG4gICAgICAgIHRvTW9kZWxOYW1lID09PSBcInRoaXNcIiA/IG1vZGVsLm1vZGVsTmFtZSA6IHRvTW9kZWxOYW1lXG4gICAgKTtcbiAgICBPYmplY3QuZW50cmllcyh0b01vZGVsLmZpZWxkcykuZm9yRWFjaChcbiAgICAgICAgKFtyZWxhdGVkRmllbGROYW1lLCByZWxhdGVkRmllbGRdKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBmaWVsZEFjY2Vzc29yTmFtZSA9IHJlbGF0ZWRGaWVsZC5hcyB8fCByZWxhdGVkRmllbGROYW1lO1xuICAgICAgICAgICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KGZpZWxkU2VsZWN0b3JTcGVjLCBmaWVsZEFjY2Vzc29yTmFtZSwge1xuICAgICAgICAgICAgICAgIGdldDogKCkgPT5cbiAgICAgICAgICAgICAgICAgICAgY3JlYXRlRmllbGRTZWxlY3RvclNwZWMoe1xuICAgICAgICAgICAgICAgICAgICAgICAgcGFyZW50OiBmaWVsZFNlbGVjdG9yU3BlYyxcbiAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLFxuICAgICAgICAgICAgICAgICAgICAgICAgZmllbGRNb2RlbDogdG9Nb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgICAgIGZpZWxkOiByZWxhdGVkRmllbGQsXG4gICAgICAgICAgICAgICAgICAgICAgICBhY2Nlc3Nvck5hbWU6IGZpZWxkQWNjZXNzb3JOYW1lLFxuICAgICAgICAgICAgICAgICAgICAgICAgb3JtLFxuICAgICAgICAgICAgICAgICAgICAgICAgaXNWaXJ0dWFsOiBmYWxzZSxcbiAgICAgICAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICk7XG4gICAgT2JqZWN0LmVudHJpZXModG9Nb2RlbC52aXJ0dWFsRmllbGRzKS5mb3JFYWNoKFxuICAgICAgICAoW3JlbGF0ZWRGaWVsZE5hbWUsIHJlbGF0ZWRGaWVsZF0pID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGZpZWxkQWNjZXNzb3JOYW1lID0gcmVsYXRlZEZpZWxkLmFzIHx8IHJlbGF0ZWRGaWVsZE5hbWU7XG4gICAgICAgICAgICBpZiAoZmllbGRTZWxlY3RvclNwZWMuaGFzT3duUHJvcGVydHkoZmllbGRBY2Nlc3Nvck5hbWUpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KGZpZWxkU2VsZWN0b3JTcGVjLCBmaWVsZEFjY2Vzc29yTmFtZSwge1xuICAgICAgICAgICAgICAgIGdldDogKCkgPT5cbiAgICAgICAgICAgICAgICAgICAgY3JlYXRlRmllbGRTZWxlY3RvclNwZWMoe1xuICAgICAgICAgICAgICAgICAgICAgICAgcGFyZW50OiBmaWVsZFNlbGVjdG9yU3BlYyxcbiAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLFxuICAgICAgICAgICAgICAgICAgICAgICAgZmllbGRNb2RlbDogdG9Nb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgICAgIGZpZWxkOiByZWxhdGVkRmllbGQsXG4gICAgICAgICAgICAgICAgICAgICAgICBhY2Nlc3Nvck5hbWU6IGZpZWxkQWNjZXNzb3JOYW1lLFxuICAgICAgICAgICAgICAgICAgICAgICAgb3JtLFxuICAgICAgICAgICAgICAgICAgICAgICAgaXNWaXJ0dWFsOiB0cnVlLFxuICAgICAgICAgICAgICAgICAgICB9KSxcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgKTtcbiAgICByZXR1cm4gZmllbGRTZWxlY3RvclNwZWM7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVNb2RlbFNlbGVjdG9yU3BlYyh7IG1vZGVsLCBvcm0gfSkge1xuICAgIGNvbnN0IG1vZGVsU2VsZWN0b3JTcGVjID0gbmV3IE1vZGVsU2VsZWN0b3JTcGVjKHtcbiAgICAgICAgcGFyZW50OiBudWxsLFxuICAgICAgICBvcm0sXG4gICAgICAgIG1vZGVsLFxuICAgIH0pO1xuXG4gICAgT2JqZWN0LmVudHJpZXMobW9kZWwuZmllbGRzKS5mb3JFYWNoKChbZmllbGROYW1lLCBmaWVsZF0pID0+IHtcbiAgICAgICAgY29uc3QgZmllbGRBY2Nlc3Nvck5hbWUgPSBmaWVsZC5hcyB8fCBmaWVsZE5hbWU7XG4gICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShtb2RlbFNlbGVjdG9yU3BlYywgZmllbGRBY2Nlc3Nvck5hbWUsIHtcbiAgICAgICAgICAgIGdldDogKCkgPT5cbiAgICAgICAgICAgICAgICBjcmVhdGVGaWVsZFNlbGVjdG9yU3BlYyh7XG4gICAgICAgICAgICAgICAgICAgIHBhcmVudDogbW9kZWxTZWxlY3RvclNwZWMsXG4gICAgICAgICAgICAgICAgICAgIG1vZGVsLFxuICAgICAgICAgICAgICAgICAgICBmaWVsZE1vZGVsOiBtb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgZmllbGQsXG4gICAgICAgICAgICAgICAgICAgIGFjY2Vzc29yTmFtZTogZmllbGRBY2Nlc3Nvck5hbWUsXG4gICAgICAgICAgICAgICAgICAgIG9ybSxcbiAgICAgICAgICAgICAgICAgICAgaXNWaXJ0dWFsOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB9KSxcbiAgICAgICAgfSk7XG4gICAgfSk7XG5cbiAgICBPYmplY3QuZW50cmllcyhtb2RlbC52aXJ0dWFsRmllbGRzKS5mb3JFYWNoKChbZmllbGROYW1lLCBmaWVsZF0pID0+IHtcbiAgICAgICAgY29uc3QgZmllbGRBY2Nlc3Nvck5hbWUgPSBmaWVsZC5hcyB8fCBmaWVsZE5hbWU7XG4gICAgICAgIGlmIChtb2RlbFNlbGVjdG9yU3BlYy5oYXNPd25Qcm9wZXJ0eShmaWVsZEFjY2Vzc29yTmFtZSkpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkobW9kZWxTZWxlY3RvclNwZWMsIGZpZWxkQWNjZXNzb3JOYW1lLCB7XG4gICAgICAgICAgICBnZXQ6ICgpID0+XG4gICAgICAgICAgICAgICAgY3JlYXRlRmllbGRTZWxlY3RvclNwZWMoe1xuICAgICAgICAgICAgICAgICAgICBwYXJlbnQ6IG1vZGVsU2VsZWN0b3JTcGVjLFxuICAgICAgICAgICAgICAgICAgICBtb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgZmllbGRNb2RlbDogbW9kZWwsXG4gICAgICAgICAgICAgICAgICAgIGZpZWxkLFxuICAgICAgICAgICAgICAgICAgICBhY2Nlc3Nvck5hbWU6IGZpZWxkQWNjZXNzb3JOYW1lLFxuICAgICAgICAgICAgICAgICAgICBvcm0sXG4gICAgICAgICAgICAgICAgICAgIGlzVmlydHVhbDogdHJ1ZSxcbiAgICAgICAgICAgICAgICB9KSxcbiAgICAgICAgfSk7XG4gICAgfSk7XG5cbiAgICByZXR1cm4gbW9kZWxTZWxlY3RvclNwZWM7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./src/selectors/index.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"createFieldSelectorSpec\\\", function() { return createFieldSelectorSpec; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"createModelSelectorSpec\\\", function() { return createModelSelectorSpec; });\\n/* harmony import */ var _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../fields/ForeignKey */ \\\"./src/fields/ForeignKey.js\\\");\\n/* harmony import */ var _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../fields/ManyToMany */ \\\"./src/fields/ManyToMany.js\\\");\\n/* harmony import */ var _fields_RelationalField__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../fields/RelationalField */ \\\"./src/fields/RelationalField.js\\\");\\n/* harmony import */ var _FieldSelectorSpec__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./FieldSelectorSpec */ \\\"./src/selectors/FieldSelectorSpec.js\\\");\\n/* harmony import */ var _ModelSelectorSpec__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ModelSelectorSpec */ \\\"./src/selectors/ModelSelectorSpec.js\\\");\\n\\n\\n\\n\\n\\n/**\\n * @module selectors\\n * @private\\n */\\n\\nfunction createFieldSelectorSpec({\\n  parent,\\n  model,\\n  field,\\n  fieldModel,\\n  accessorName,\\n  orm,\\n  isVirtual\\n}) {\\n  const fieldSelectorSpec = new _FieldSelectorSpec__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]({\\n    parent,\\n    model,\\n    field,\\n    fieldModel,\\n    accessorName,\\n    orm,\\n    isVirtual\\n  });\\n  /* Do not even try to create field selectors below attributes. */\\n\\n  if (!(field instanceof _fields_RelationalField__WEBPACK_IMPORTED_MODULE_2__[\\\"default\\\"])) {\\n    // \\\"orm.Author.name.publisher\\\" would be nonsense\\n    return fieldSelectorSpec;\\n  }\\n  /* Prevent field selectors below collections. */\\n\\n\\n  if (parent instanceof _FieldSelectorSpec__WEBPACK_IMPORTED_MODULE_3__[\\\"default\\\"]) {\\n    /* eslint-disable no-underscore-dangle */\\n    if ( // \\\"orm.Author.books.publisher\\\" would be nonsense\\n    parent._field instanceof _fields_ForeignKey__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"] && parent._isVirtual || // \\\"orm.Genre.books.publisher\\\" would be nonsense\\n    parent._field instanceof _fields_ManyToMany__WEBPACK_IMPORTED_MODULE_1__[\\\"default\\\"]) {\\n      throw new Error(`Cannot create a selector for \\\\`${parent._accessorName}.${accessorName}\\\\` because \\\\`${parent._accessorName}\\\\` is a collection field.`);\\n    }\\n  }\\n\\n  const {\\n    toModelName\\n  } = field;\\n  const toModel = orm.get(toModelName === \\\"this\\\" ? model.modelName : toModelName);\\n  Object.entries(toModel.fields).forEach(([relatedFieldName, relatedField]) => {\\n    const fieldAccessorName = relatedField.as || relatedFieldName;\\n    Object.defineProperty(fieldSelectorSpec, fieldAccessorName, {\\n      get: () => createFieldSelectorSpec({\\n        parent: fieldSelectorSpec,\\n        model,\\n        fieldModel: toModel,\\n        field: relatedField,\\n        accessorName: fieldAccessorName,\\n        orm,\\n        isVirtual: false\\n      })\\n    });\\n  });\\n  Object.entries(toModel.virtualFields).forEach(([relatedFieldName, relatedField]) => {\\n    const fieldAccessorName = relatedField.as || relatedFieldName;\\n\\n    if (fieldSelectorSpec.hasOwnProperty(fieldAccessorName)) {\\n      return;\\n    }\\n\\n    Object.defineProperty(fieldSelectorSpec, fieldAccessorName, {\\n      get: () => createFieldSelectorSpec({\\n        parent: fieldSelectorSpec,\\n        model,\\n        fieldModel: toModel,\\n        field: relatedField,\\n        accessorName: fieldAccessorName,\\n        orm,\\n        isVirtual: true\\n      })\\n    });\\n  });\\n  return fieldSelectorSpec;\\n}\\nfunction createModelSelectorSpec({\\n  model,\\n  orm\\n}) {\\n  const modelSelectorSpec = new _ModelSelectorSpec__WEBPACK_IMPORTED_MODULE_4__[\\\"default\\\"]({\\n    parent: null,\\n    orm,\\n    model\\n  });\\n  Object.entries(model.fields).forEach(([fieldName, field]) => {\\n    const fieldAccessorName = field.as || fieldName;\\n    Object.defineProperty(modelSelectorSpec, fieldAccessorName, {\\n      get: () => createFieldSelectorSpec({\\n        parent: modelSelectorSpec,\\n        model,\\n        fieldModel: model,\\n        field,\\n        accessorName: fieldAccessorName,\\n        orm,\\n        isVirtual: false\\n      })\\n    });\\n  });\\n  Object.entries(model.virtualFields).forEach(([fieldName, field]) => {\\n    const fieldAccessorName = field.as || fieldName;\\n\\n    if (modelSelectorSpec.hasOwnProperty(fieldAccessorName)) {\\n      return;\\n    }\\n\\n    Object.defineProperty(modelSelectorSpec, fieldAccessorName, {\\n      get: () => createFieldSelectorSpec({\\n        parent: modelSelectorSpec,\\n        model,\\n        fieldModel: model,\\n        field,\\n        accessorName: fieldAccessorName,\\n        orm,\\n        isVirtual: true\\n      })\\n    });\\n  });\\n  return modelSelectorSpec;\\n}//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy9zZWxlY3RvcnMvaW5kZXguanM/YTA3NiJdLCJuYW1lcyI6WyJjcmVhdGVGaWVsZFNlbGVjdG9yU3BlYyIsInBhcmVudCIsIm1vZGVsIiwiZmllbGQiLCJmaWVsZE1vZGVsIiwiYWNjZXNzb3JOYW1lIiwib3JtIiwiaXNWaXJ0dWFsIiwiZmllbGRTZWxlY3RvclNwZWMiLCJGaWVsZFNlbGVjdG9yU3BlYyIsIlJlbGF0aW9uYWxGaWVsZCIsIl9maWVsZCIsIkZvcmVpZ25LZXkiLCJfaXNWaXJ0dWFsIiwiTWFueVRvTWFueSIsIkVycm9yIiwiX2FjY2Vzc29yTmFtZSIsInRvTW9kZWxOYW1lIiwidG9Nb2RlbCIsImdldCIsIm1vZGVsTmFtZSIsIk9iamVjdCIsImVudHJpZXMiLCJmaWVsZHMiLCJmb3JFYWNoIiwicmVsYXRlZEZpZWxkTmFtZSIsInJlbGF0ZWRGaWVsZCIsImZpZWxkQWNjZXNzb3JOYW1lIiwiYXMiLCJkZWZpbmVQcm9wZXJ0eSIsInZpcnR1YWxGaWVsZHMiLCJoYXNPd25Qcm9wZXJ0eSIsImNyZWF0ZU1vZGVsU2VsZWN0b3JTcGVjIiwibW9kZWxTZWxlY3RvclNwZWMiLCJNb2RlbFNlbGVjdG9yU3BlYyIsImZpZWxkTmFtZSJdLCJtYXBwaW5ncyI6IkFBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUVBO0FBQ0E7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFTyxTQUFTQSx1QkFBVCxDQUFpQztBQUNwQ0MsUUFEb0M7QUFFcENDLE9BRm9DO0FBR3BDQyxPQUhvQztBQUlwQ0MsWUFKb0M7QUFLcENDLGNBTG9DO0FBTXBDQyxLQU5vQztBQU9wQ0M7QUFQb0MsQ0FBakMsRUFRSjtBQUNDLFFBQU1DLGlCQUFpQixHQUFHLElBQUlDLDBEQUFKLENBQXNCO0FBQzVDUixVQUQ0QztBQUU1Q0MsU0FGNEM7QUFHNUNDLFNBSDRDO0FBSTVDQyxjQUo0QztBQUs1Q0MsZ0JBTDRDO0FBTTVDQyxPQU40QztBQU81Q0M7QUFQNEMsR0FBdEIsQ0FBMUI7QUFTQTs7QUFDQSxNQUFJLEVBQUVKLEtBQUssWUFBWU8sK0RBQW5CLENBQUosRUFBeUM7QUFDckM7QUFDQSxXQUFPRixpQkFBUDtBQUNIO0FBQ0Q7OztBQUNBLE1BQUlQLE1BQU0sWUFBWVEsMERBQXRCLEVBQXlDO0FBQ3JDO0FBQ0EsU0FDSTtBQUNDUixVQUFNLENBQUNVLE1BQVAsWUFBeUJDLDBEQUF6QixJQUF1Q1gsTUFBTSxDQUFDWSxVQUEvQyxJQUNBO0FBQ0FaLFVBQU0sQ0FBQ1UsTUFBUCxZQUF5QkcsMERBSjdCLEVBS0U7QUFDRSxZQUFNLElBQUlDLEtBQUosQ0FDRCxrQ0FBaUNkLE1BQU0sQ0FBQ2UsYUFBYyxJQUFHWCxZQUFhLGdCQUFlSixNQUFNLENBQUNlLGFBQWMsMkJBRHpHLENBQU47QUFHSDtBQUNKOztBQUNELFFBQU07QUFBRUM7QUFBRixNQUFrQmQsS0FBeEI7QUFDQSxRQUFNZSxPQUFPLEdBQUdaLEdBQUcsQ0FBQ2EsR0FBSixDQUNaRixXQUFXLEtBQUssTUFBaEIsR0FBeUJmLEtBQUssQ0FBQ2tCLFNBQS9CLEdBQTJDSCxXQUQvQixDQUFoQjtBQUdBSSxRQUFNLENBQUNDLE9BQVAsQ0FBZUosT0FBTyxDQUFDSyxNQUF2QixFQUErQkMsT0FBL0IsQ0FDSSxDQUFDLENBQUNDLGdCQUFELEVBQW1CQyxZQUFuQixDQUFELEtBQXNDO0FBQ2xDLFVBQU1DLGlCQUFpQixHQUFHRCxZQUFZLENBQUNFLEVBQWIsSUFBbUJILGdCQUE3QztBQUNBSixVQUFNLENBQUNRLGNBQVAsQ0FBc0JyQixpQkFBdEIsRUFBeUNtQixpQkFBekMsRUFBNEQ7QUFDeERSLFNBQUcsRUFBRSxNQUNEbkIsdUJBQXVCLENBQUM7QUFDcEJDLGNBQU0sRUFBRU8saUJBRFk7QUFFcEJOLGFBRm9CO0FBR3BCRSxrQkFBVSxFQUFFYyxPQUhRO0FBSXBCZixhQUFLLEVBQUV1QixZQUphO0FBS3BCckIsb0JBQVksRUFBRXNCLGlCQUxNO0FBTXBCckIsV0FOb0I7QUFPcEJDLGlCQUFTLEVBQUU7QUFQUyxPQUFEO0FBRjZCLEtBQTVEO0FBWUgsR0FmTDtBQWlCQWMsUUFBTSxDQUFDQyxPQUFQLENBQWVKLE9BQU8sQ0FBQ1ksYUFBdkIsRUFBc0NOLE9BQXRDLENBQ0ksQ0FBQyxDQUFDQyxnQkFBRCxFQUFtQkMsWUFBbkIsQ0FBRCxLQUFzQztBQUNsQyxVQUFNQyxpQkFBaUIsR0FBR0QsWUFBWSxDQUFDRSxFQUFiLElBQW1CSCxnQkFBN0M7O0FBQ0EsUUFBSWpCLGlCQUFpQixDQUFDdUIsY0FBbEIsQ0FBaUNKLGlCQUFqQyxDQUFKLEVBQXlEO0FBQ3JEO0FBQ0g7O0FBQ0ROLFVBQU0sQ0FBQ1EsY0FBUCxDQUFzQnJCLGlCQUF0QixFQUF5Q21CLGlCQUF6QyxFQUE0RDtBQUN4RFIsU0FBRyxFQUFFLE1BQ0RuQix1QkFBdUIsQ0FBQztBQUNwQkMsY0FBTSxFQUFFTyxpQkFEWTtBQUVwQk4sYUFGb0I7QUFHcEJFLGtCQUFVLEVBQUVjLE9BSFE7QUFJcEJmLGFBQUssRUFBRXVCLFlBSmE7QUFLcEJyQixvQkFBWSxFQUFFc0IsaUJBTE07QUFNcEJyQixXQU5vQjtBQU9wQkMsaUJBQVMsRUFBRTtBQVBTLE9BQUQ7QUFGNkIsS0FBNUQ7QUFZSCxHQWxCTDtBQW9CQSxTQUFPQyxpQkFBUDtBQUNIO0FBRU0sU0FBU3dCLHVCQUFULENBQWlDO0FBQUU5QixPQUFGO0FBQVNJO0FBQVQsQ0FBakMsRUFBaUQ7QUFDcEQsUUFBTTJCLGlCQUFpQixHQUFHLElBQUlDLDBEQUFKLENBQXNCO0FBQzVDakMsVUFBTSxFQUFFLElBRG9DO0FBRTVDSyxPQUY0QztBQUc1Q0o7QUFINEMsR0FBdEIsQ0FBMUI7QUFNQW1CLFFBQU0sQ0FBQ0MsT0FBUCxDQUFlcEIsS0FBSyxDQUFDcUIsTUFBckIsRUFBNkJDLE9BQTdCLENBQXFDLENBQUMsQ0FBQ1csU0FBRCxFQUFZaEMsS0FBWixDQUFELEtBQXdCO0FBQ3pELFVBQU13QixpQkFBaUIsR0FBR3hCLEtBQUssQ0FBQ3lCLEVBQU4sSUFBWU8sU0FBdEM7QUFDQWQsVUFBTSxDQUFDUSxjQUFQLENBQXNCSSxpQkFBdEIsRUFBeUNOLGlCQUF6QyxFQUE0RDtBQUN4RFIsU0FBRyxFQUFFLE1BQ0RuQix1QkFBdUIsQ0FBQztBQUNwQkMsY0FBTSxFQUFFZ0MsaUJBRFk7QUFFcEIvQixhQUZvQjtBQUdwQkUsa0JBQVUsRUFBRUYsS0FIUTtBQUlwQkMsYUFKb0I7QUFLcEJFLG9CQUFZLEVBQUVzQixpQkFMTTtBQU1wQnJCLFdBTm9CO0FBT3BCQyxpQkFBUyxFQUFFO0FBUFMsT0FBRDtBQUY2QixLQUE1RDtBQVlILEdBZEQ7QUFnQkFjLFFBQU0sQ0FBQ0MsT0FBUCxDQUFlcEIsS0FBSyxDQUFDNEIsYUFBckIsRUFBb0NOLE9BQXBDLENBQTRDLENBQUMsQ0FBQ1csU0FBRCxFQUFZaEMsS0FBWixDQUFELEtBQXdCO0FBQ2hFLFVBQU13QixpQkFBaUIsR0FBR3hCLEtBQUssQ0FBQ3lCLEVBQU4sSUFBWU8sU0FBdEM7O0FBQ0EsUUFBSUYsaUJBQWlCLENBQUNGLGNBQWxCLENBQWlDSixpQkFBakMsQ0FBSixFQUF5RDtBQUNyRDtBQUNIOztBQUNETixVQUFNLENBQUNRLGNBQVAsQ0FBc0JJLGlCQUF0QixFQUF5Q04saUJBQXpDLEVBQTREO0FBQ3hEUixTQUFHLEVBQUUsTUFDRG5CLHVCQUF1QixDQUFDO0FBQ3BCQyxjQUFNLEVBQUVnQyxpQkFEWTtBQUVwQi9CLGFBRm9CO0FBR3BCRSxrQkFBVSxFQUFFRixLQUhRO0FBSXBCQyxhQUpvQjtBQUtwQkUsb0JBQVksRUFBRXNCLGlCQUxNO0FBTXBCckIsV0FOb0I7QUFPcEJDLGlCQUFTLEVBQUU7QUFQUyxPQUFEO0FBRjZCLEtBQTVEO0FBWUgsR0FqQkQ7QUFtQkEsU0FBTzBCLGlCQUFQO0FBQ0giLCJmaWxlIjoiLi9zcmMvc2VsZWN0b3JzL2luZGV4LmpzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IEZvcmVpZ25LZXkgZnJvbSBcIi4uL2ZpZWxkcy9Gb3JlaWduS2V5XCI7XG5pbXBvcnQgTWFueVRvTWFueSBmcm9tIFwiLi4vZmllbGRzL01hbnlUb01hbnlcIjtcbmltcG9ydCBSZWxhdGlvbmFsRmllbGQgZnJvbSBcIi4uL2ZpZWxkcy9SZWxhdGlvbmFsRmllbGRcIjtcblxuaW1wb3J0IEZpZWxkU2VsZWN0b3JTcGVjIGZyb20gXCIuL0ZpZWxkU2VsZWN0b3JTcGVjXCI7XG5pbXBvcnQgTW9kZWxTZWxlY3RvclNwZWMgZnJvbSBcIi4vTW9kZWxTZWxlY3RvclNwZWNcIjtcblxuLyoqXG4gKiBAbW9kdWxlIHNlbGVjdG9yc1xuICogQHByaXZhdGVcbiAqL1xuXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlRmllbGRTZWxlY3RvclNwZWMoe1xuICAgIHBhcmVudCxcbiAgICBtb2RlbCxcbiAgICBmaWVsZCxcbiAgICBmaWVsZE1vZGVsLFxuICAgIGFjY2Vzc29yTmFtZSxcbiAgICBvcm0sXG4gICAgaXNWaXJ0dWFsLFxufSkge1xuICAgIGNvbnN0IGZpZWxkU2VsZWN0b3JTcGVjID0gbmV3IEZpZWxkU2VsZWN0b3JTcGVjKHtcbiAgICAgICAgcGFyZW50LFxuICAgICAgICBtb2RlbCxcbiAgICAgICAgZmllbGQsXG4gICAgICAgIGZpZWxkTW9kZWwsXG4gICAgICAgIGFjY2Vzc29yTmFtZSxcbiAgICAgICAgb3JtLFxuICAgICAgICBpc1ZpcnR1YWwsXG4gICAgfSk7XG4gICAgLyogRG8gbm90IGV2ZW4gdHJ5IHRvIGNyZWF0ZSBmaWVsZCBzZWxlY3RvcnMgYmVsb3cgYXR0cmlidXRlcy4gKi9cbiAgICBpZiAoIShmaWVsZCBpbnN0YW5jZW9mIFJlbGF0aW9uYWxGaWVsZCkpIHtcbiAgICAgICAgLy8gXCJvcm0uQXV0aG9yLm5hbWUucHVibGlzaGVyXCIgd291bGQgYmUgbm9uc2Vuc2VcbiAgICAgICAgcmV0dXJuIGZpZWxkU2VsZWN0b3JTcGVjO1xuICAgIH1cbiAgICAvKiBQcmV2ZW50IGZpZWxkIHNlbGVjdG9ycyBiZWxvdyBjb2xsZWN0aW9ucy4gKi9cbiAgICBpZiAocGFyZW50IGluc3RhbmNlb2YgRmllbGRTZWxlY3RvclNwZWMpIHtcbiAgICAgICAgLyogZXNsaW50LWRpc2FibGUgbm8tdW5kZXJzY29yZS1kYW5nbGUgKi9cbiAgICAgICAgaWYgKFxuICAgICAgICAgICAgLy8gXCJvcm0uQXV0aG9yLmJvb2tzLnB1Ymxpc2hlclwiIHdvdWxkIGJlIG5vbnNlbnNlXG4gICAgICAgICAgICAocGFyZW50Ll9maWVsZCBpbnN0YW5jZW9mIEZvcmVpZ25LZXkgJiYgcGFyZW50Ll9pc1ZpcnR1YWwpIHx8XG4gICAgICAgICAgICAvLyBcIm9ybS5HZW5yZS5ib29rcy5wdWJsaXNoZXJcIiB3b3VsZCBiZSBub25zZW5zZVxuICAgICAgICAgICAgcGFyZW50Ll9maWVsZCBpbnN0YW5jZW9mIE1hbnlUb01hbnlcbiAgICAgICAgKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgYENhbm5vdCBjcmVhdGUgYSBzZWxlY3RvciBmb3IgXFxgJHtwYXJlbnQuX2FjY2Vzc29yTmFtZX0uJHthY2Nlc3Nvck5hbWV9XFxgIGJlY2F1c2UgXFxgJHtwYXJlbnQuX2FjY2Vzc29yTmFtZX1cXGAgaXMgYSBjb2xsZWN0aW9uIGZpZWxkLmBcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgY29uc3QgeyB0b01vZGVsTmFtZSB9ID0gZmllbGQ7XG4gICAgY29uc3QgdG9Nb2RlbCA9IG9ybS5nZXQoXG4gICAgICAgIHRvTW9kZWxOYW1lID09PSBcInRoaXNcIiA/IG1vZGVsLm1vZGVsTmFtZSA6IHRvTW9kZWxOYW1lXG4gICAgKTtcbiAgICBPYmplY3QuZW50cmllcyh0b01vZGVsLmZpZWxkcykuZm9yRWFjaChcbiAgICAgICAgKFtyZWxhdGVkRmllbGROYW1lLCByZWxhdGVkRmllbGRdKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBmaWVsZEFjY2Vzc29yTmFtZSA9IHJlbGF0ZWRGaWVsZC5hcyB8fCByZWxhdGVkRmllbGROYW1lO1xuICAgICAgICAgICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KGZpZWxkU2VsZWN0b3JTcGVjLCBmaWVsZEFjY2Vzc29yTmFtZSwge1xuICAgICAgICAgICAgICAgIGdldDogKCkgPT5cbiAgICAgICAgICAgICAgICAgICAgY3JlYXRlRmllbGRTZWxlY3RvclNwZWMoe1xuICAgICAgICAgICAgICAgICAgICAgICAgcGFyZW50OiBmaWVsZFNlbGVjdG9yU3BlYyxcbiAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLFxuICAgICAgICAgICAgICAgICAgICAgICAgZmllbGRNb2RlbDogdG9Nb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgICAgIGZpZWxkOiByZWxhdGVkRmllbGQsXG4gICAgICAgICAgICAgICAgICAgICAgICBhY2Nlc3Nvck5hbWU6IGZpZWxkQWNjZXNzb3JOYW1lLFxuICAgICAgICAgICAgICAgICAgICAgICAgb3JtLFxuICAgICAgICAgICAgICAgICAgICAgICAgaXNWaXJ0dWFsOiBmYWxzZSxcbiAgICAgICAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICk7XG4gICAgT2JqZWN0LmVudHJpZXModG9Nb2RlbC52aXJ0dWFsRmllbGRzKS5mb3JFYWNoKFxuICAgICAgICAoW3JlbGF0ZWRGaWVsZE5hbWUsIHJlbGF0ZWRGaWVsZF0pID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGZpZWxkQWNjZXNzb3JOYW1lID0gcmVsYXRlZEZpZWxkLmFzIHx8IHJlbGF0ZWRGaWVsZE5hbWU7XG4gICAgICAgICAgICBpZiAoZmllbGRTZWxlY3RvclNwZWMuaGFzT3duUHJvcGVydHkoZmllbGRBY2Nlc3Nvck5hbWUpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KGZpZWxkU2VsZWN0b3JTcGVjLCBmaWVsZEFjY2Vzc29yTmFtZSwge1xuICAgICAgICAgICAgICAgIGdldDogKCkgPT5cbiAgICAgICAgICAgICAgICAgICAgY3JlYXRlRmllbGRTZWxlY3RvclNwZWMoe1xuICAgICAgICAgICAgICAgICAgICAgICAgcGFyZW50OiBmaWVsZFNlbGVjdG9yU3BlYyxcbiAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLFxuICAgICAgICAgICAgICAgICAgICAgICAgZmllbGRNb2RlbDogdG9Nb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgICAgIGZpZWxkOiByZWxhdGVkRmllbGQsXG4gICAgICAgICAgICAgICAgICAgICAgICBhY2Nlc3Nvck5hbWU6IGZpZWxkQWNjZXNzb3JOYW1lLFxuICAgICAgICAgICAgICAgICAgICAgICAgb3JtLFxuICAgICAgICAgICAgICAgICAgICAgICAgaXNWaXJ0dWFsOiB0cnVlLFxuICAgICAgICAgICAgICAgICAgICB9KSxcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgKTtcbiAgICByZXR1cm4gZmllbGRTZWxlY3RvclNwZWM7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVNb2RlbFNlbGVjdG9yU3BlYyh7IG1vZGVsLCBvcm0gfSkge1xuICAgIGNvbnN0IG1vZGVsU2VsZWN0b3JTcGVjID0gbmV3IE1vZGVsU2VsZWN0b3JTcGVjKHtcbiAgICAgICAgcGFyZW50OiBudWxsLFxuICAgICAgICBvcm0sXG4gICAgICAgIG1vZGVsLFxuICAgIH0pO1xuXG4gICAgT2JqZWN0LmVudHJpZXMobW9kZWwuZmllbGRzKS5mb3JFYWNoKChbZmllbGROYW1lLCBmaWVsZF0pID0+IHtcbiAgICAgICAgY29uc3QgZmllbGRBY2Nlc3Nvck5hbWUgPSBmaWVsZC5hcyB8fCBmaWVsZE5hbWU7XG4gICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShtb2RlbFNlbGVjdG9yU3BlYywgZmllbGRBY2Nlc3Nvck5hbWUsIHtcbiAgICAgICAgICAgIGdldDogKCkgPT5cbiAgICAgICAgICAgICAgICBjcmVhdGVGaWVsZFNlbGVjdG9yU3BlYyh7XG4gICAgICAgICAgICAgICAgICAgIHBhcmVudDogbW9kZWxTZWxlY3RvclNwZWMsXG4gICAgICAgICAgICAgICAgICAgIG1vZGVsLFxuICAgICAgICAgICAgICAgICAgICBmaWVsZE1vZGVsOiBtb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgZmllbGQsXG4gICAgICAgICAgICAgICAgICAgIGFjY2Vzc29yTmFtZTogZmllbGRBY2Nlc3Nvck5hbWUsXG4gICAgICAgICAgICAgICAgICAgIG9ybSxcbiAgICAgICAgICAgICAgICAgICAgaXNWaXJ0dWFsOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB9KSxcbiAgICAgICAgfSk7XG4gICAgfSk7XG5cbiAgICBPYmplY3QuZW50cmllcyhtb2RlbC52aXJ0dWFsRmllbGRzKS5mb3JFYWNoKChbZmllbGROYW1lLCBmaWVsZF0pID0+IHtcbiAgICAgICAgY29uc3QgZmllbGRBY2Nlc3Nvck5hbWUgPSBmaWVsZC5hcyB8fCBmaWVsZE5hbWU7XG4gICAgICAgIGlmIChtb2RlbFNlbGVjdG9yU3BlYy5oYXNPd25Qcm9wZXJ0eShmaWVsZEFjY2Vzc29yTmFtZSkpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkobW9kZWxTZWxlY3RvclNwZWMsIGZpZWxkQWNjZXNzb3JOYW1lLCB7XG4gICAgICAgICAgICBnZXQ6ICgpID0+XG4gICAgICAgICAgICAgICAgY3JlYXRlRmllbGRTZWxlY3RvclNwZWMoe1xuICAgICAgICAgICAgICAgICAgICBwYXJlbnQ6IG1vZGVsU2VsZWN0b3JTcGVjLFxuICAgICAgICAgICAgICAgICAgICBtb2RlbCxcbiAgICAgICAgICAgICAgICAgICAgZmllbGRNb2RlbDogbW9kZWwsXG4gICAgICAgICAgICAgICAgICAgIGZpZWxkLFxuICAgICAgICAgICAgICAgICAgICBhY2Nlc3Nvck5hbWU6IGZpZWxkQWNjZXNzb3JOYW1lLFxuICAgICAgICAgICAgICAgICAgICBvcm0sXG4gICAgICAgICAgICAgICAgICAgIGlzVmlydHVhbDogdHJ1ZSxcbiAgICAgICAgICAgICAgICB9KSxcbiAgICAgICAgfSk7XG4gICAgfSk7XG5cbiAgICByZXR1cm4gbW9kZWxTZWxlY3RvclNwZWM7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9\\n//# sourceURL=webpack-internal:///./src/selectors/index.js\\n\");\n \n /***/ }),\n \n@@ -4786,7 +4808,7 @@ eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) *\n /***/ (function(module, __webpack_exports__, __webpack_require__) {\n \n \"use strict\";\n-eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"attachQuerySetMethods\\\", function() { return attachQuerySetMethods; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"m2mName\\\", function() { return m2mName; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"m2mFromFieldName\\\", function() { return m2mFromFieldName; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"m2mToFieldName\\\", function() { return m2mToFieldName; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"reverseFieldName\\\", function() { return reverseFieldName; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"normalizeEntity\\\", function() { return normalizeEntity; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"reverseFieldErrorMessage\\\", function() { return reverseFieldErrorMessage; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"objectShallowEquals\\\", function() { return objectShallowEquals; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"arrayDiffActions\\\", function() { return arrayDiffActions; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"getBatchToken\\\", function() { return getBatchToken; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"clauseFiltersByAttribute\\\", function() { return clauseFiltersByAttribute; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"clauseReducesResultSetSize\\\", function() { return clauseReducesResultSetSize; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"warnDeprecated\\\", function() { return warnDeprecated; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"mapValues\\\", function() { return mapValues; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"normalizeModelReference\\\", function() { return normalizeModelReference; });\\n/* harmony import */ var immutable_ops__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! immutable-ops */ \\\"./node_modules/immutable-ops/es/index.js\\\");\\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \\\"ops\\\", function() { return immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"]; });\\n\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./constants */ \\\"./src/constants.js\\\");\\n\\n\\n/**\\n * @module utils\\n * @private\\n */\\n\\n/** @private */\\n\\nfunction warnDeprecated(msg) {\\n  const logger = typeof console.warn === \\\"function\\\" ? console.warn.bind(console) : console.log.bind(console);\\n  return logger(msg);\\n}\\n/** @private */\\n\\n\\nfunction capitalize(string) {\\n  return string.charAt(0).toUpperCase() + string.slice(1);\\n}\\n/**\\n * Returns the branch name for a many-to-many relation.\\n * The name is the combination of the model name and the field name the relation\\n * was declared. The field name's first letter is capitalized.\\n *\\n * Example: model `Author` has a many-to-many relation to the model `Book`, defined\\n * in the `Author` field `books`. The many-to-many branch name will be `AuthorBooks`.\\n *\\n * @param  {string} declarationModelName - the name of the model the many-to-many relation was declared on\\n * @param  {string} fieldName            - the field name where the many-to-many relation was declared on\\n * @return {string} The branch name for the many-to-many relation.\\n */\\n\\n\\nfunction m2mName(declarationModelName, fieldName) {\\n  return declarationModelName + capitalize(fieldName);\\n}\\n/**\\n * Returns the fieldname that saves a foreign key to the\\n * model id where the many-to-many relation was declared.\\n *\\n * Example: `Author` => `fromAuthorId`\\n *\\n * @param  {string} declarationModelName - the name of the model where the relation was declared\\n * @return {string} the field name in the through model for `declarationModelName`'s foreign key.\\n */\\n\\n\\nfunction m2mFromFieldName(declarationModelName) {\\n  return `from${declarationModelName}Id`;\\n}\\n/**\\n * Returns the fieldname that saves a foreign key in a many-to-many through model to the\\n * model where the many-to-many relation was declared.\\n *\\n * Example: `Book` => `toBookId`\\n *\\n * @param  {string} otherModelName - the name of the model that was the target of the many-to-many\\n *                                   declaration.\\n * @return {string} the field name in the through model for `otherModelName`'s foreign key..\\n */\\n\\n\\nfunction m2mToFieldName(otherModelName) {\\n  return `to${otherModelName}Id`;\\n}\\n/** */\\n\\n\\nfunction reverseFieldName(modelName) {\\n  return modelName.toLowerCase() + \\\"Set\\\"; // eslint-disable-line prefer-template\\n}\\n/** @private */\\n\\n\\nfunction querySetDelegatorFactory(methodName) {\\n  return function querySetDelegator(...args) {\\n    return this.getQuerySet()[methodName](...args);\\n  };\\n}\\n/** @private */\\n\\n\\nfunction querySetGetterDelegatorFactory(getterName) {\\n  return function querySetGetterDelegator() {\\n    const qs = this.getQuerySet();\\n    return qs[getterName];\\n  };\\n}\\n/** @private */\\n\\n\\nfunction forEachSuperClass(subClass, func) {\\n  let currClass = subClass;\\n\\n  while (currClass !== Function.prototype) {\\n    func(currClass);\\n    currClass = Object.getPrototypeOf(currClass);\\n  }\\n}\\n/** */\\n\\n\\nfunction attachQuerySetMethods(modelClass, querySetClass) {\\n  const leftToDefine = querySetClass.sharedMethods.slice(); // There is no way to get a property descriptor for the whole prototype chain;\\n  // only from an objects own properties. Therefore we traverse the whole prototype\\n  // chain for querySet.\\n\\n  forEachSuperClass(querySetClass, cls => {\\n    for (let i = 0; i < leftToDefine.length; i++) {\\n      let defined = false;\\n      const methodName = leftToDefine[i];\\n      const descriptor = Object.getOwnPropertyDescriptor(cls.prototype, methodName);\\n\\n      if (typeof descriptor !== \\\"undefined\\\") {\\n        if (typeof descriptor.get !== \\\"undefined\\\") {\\n          descriptor.get = querySetGetterDelegatorFactory(methodName);\\n          Object.defineProperty(modelClass, methodName, descriptor);\\n        } else {\\n          modelClass[methodName] = querySetDelegatorFactory(methodName);\\n        }\\n\\n        defined = true;\\n      }\\n\\n      if (defined) {\\n        leftToDefine.splice(i--, 1);\\n      }\\n    }\\n  });\\n}\\n/**\\n * Normalizes `entity` to an id, where `entity` can be an id\\n * or a Model instance.\\n *\\n * @param  {*} entity - either a Model instance or an id value\\n * @return {*} the id value of `entity`\\n */\\n\\n\\nfunction normalizeEntity(entity) {\\n  if (entity !== null && typeof entity !== \\\"undefined\\\" && typeof entity.getId === \\\"function\\\") {\\n    return entity.getId();\\n  }\\n\\n  return entity;\\n}\\n/** */\\n\\n\\nfunction reverseFieldErrorMessage(modelName, fieldName, toModelName, backwardsFieldName) {\\n  return [`Reverse field ${backwardsFieldName} already defined`, ` on model ${toModelName}. To fix, set a custom related`, ` name on ${modelName}.${fieldName}.`].join(\\\"\\\");\\n}\\n/**\\n * Fastest way to check if two objects are equal.\\n * Object and array values have to be referentially equal.\\n */\\n\\n\\nfunction objectShallowEquals(a, b) {\\n  const entriesInA = Object.entries(Object(a));\\n\\n  if (entriesInA.length !== Object.keys(b).length) {\\n    return false;\\n  }\\n\\n  return entriesInA.every(([key, value]) => b.hasOwnProperty(key) && b[key] === value);\\n}\\n/** */\\n\\n\\nfunction arrayDiffActions(sourceArr, targetArr) {\\n  const itemsInBoth = sourceArr.filter(item => targetArr.includes(item));\\n  const deleteItems = sourceArr.filter(item => !itemsInBoth.includes(item));\\n  const addItems = targetArr.filter(item => !itemsInBoth.includes(item));\\n\\n  if (deleteItems.length || addItems.length) {\\n    return {\\n      delete: deleteItems,\\n      add: addItems\\n    };\\n  }\\n\\n  return null;\\n}\\n\\nconst {\\n  getBatchToken\\n} = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"];\\n/**\\n * @return boolean\\n */\\n\\nfunction clauseFiltersByAttribute({\\n  type,\\n  payload\\n}, attribute) {\\n  if (type !== _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"FILTER\\\"]) return false;\\n\\n  if (typeof payload !== \\\"object\\\") {\\n    /**\\n     * payload could also be a function in which case\\n     * we would have no way of knowing what it does,\\n     * so we default to false for non-objects\\n     */\\n    return false;\\n  }\\n\\n  if (!payload.hasOwnProperty(attribute)) return false;\\n  const attributeValue = payload[attribute];\\n  if (attributeValue === null) return false;\\n  if (attributeValue === undefined) return false;\\n  return true;\\n}\\n/**\\n * @return boolean\\n */\\n\\n\\nfunction clauseReducesResultSetSize({\\n  type\\n}) {\\n  return [_constants__WEBPACK_IMPORTED_MODULE_1__[\\\"FILTER\\\"], _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"EXCLUDE\\\"]].includes(type);\\n}\\n/**\\n * @param {Object} object\\n * @return Object\\n */\\n\\n\\nfunction mapValues(object, func) {\\n  return Object.entries(object).reduce((newObject, [key, value]) => {\\n    newObject[key] = func(value);\\n    return newObject;\\n  }, {});\\n}\\n/** */\\n\\n\\nfunction normalizeModelReference(modelNameOrClass) {\\n  if (!modelNameOrClass || typeof modelNameOrClass === \\\"string\\\") {\\n    return modelNameOrClass;\\n  }\\n\\n  return modelNameOrClass.modelName;\\n}\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy91dGlscy5qcz8wMjVlIl0sIm5hbWVzIjpbIndhcm5EZXByZWNhdGVkIiwibXNnIiwibG9nZ2VyIiwiY29uc29sZSIsIndhcm4iLCJiaW5kIiwibG9nIiwiY2FwaXRhbGl6ZSIsInN0cmluZyIsImNoYXJBdCIsInRvVXBwZXJDYXNlIiwic2xpY2UiLCJtMm1OYW1lIiwiZGVjbGFyYXRpb25Nb2RlbE5hbWUiLCJmaWVsZE5hbWUiLCJtMm1Gcm9tRmllbGROYW1lIiwibTJtVG9GaWVsZE5hbWUiLCJvdGhlck1vZGVsTmFtZSIsInJldmVyc2VGaWVsZE5hbWUiLCJtb2RlbE5hbWUiLCJ0b0xvd2VyQ2FzZSIsInF1ZXJ5U2V0RGVsZWdhdG9yRmFjdG9yeSIsIm1ldGhvZE5hbWUiLCJxdWVyeVNldERlbGVnYXRvciIsImFyZ3MiLCJnZXRRdWVyeVNldCIsInF1ZXJ5U2V0R2V0dGVyRGVsZWdhdG9yRmFjdG9yeSIsImdldHRlck5hbWUiLCJxdWVyeVNldEdldHRlckRlbGVnYXRvciIsInFzIiwiZm9yRWFjaFN1cGVyQ2xhc3MiLCJzdWJDbGFzcyIsImZ1bmMiLCJjdXJyQ2xhc3MiLCJGdW5jdGlvbiIsInByb3RvdHlwZSIsIk9iamVjdCIsImdldFByb3RvdHlwZU9mIiwiYXR0YWNoUXVlcnlTZXRNZXRob2RzIiwibW9kZWxDbGFzcyIsInF1ZXJ5U2V0Q2xhc3MiLCJsZWZ0VG9EZWZpbmUiLCJzaGFyZWRNZXRob2RzIiwiY2xzIiwiaSIsImxlbmd0aCIsImRlZmluZWQiLCJkZXNjcmlwdG9yIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwiZ2V0IiwiZGVmaW5lUHJvcGVydHkiLCJzcGxpY2UiLCJub3JtYWxpemVFbnRpdHkiLCJlbnRpdHkiLCJnZXRJZCIsInJldmVyc2VGaWVsZEVycm9yTWVzc2FnZSIsInRvTW9kZWxOYW1lIiwiYmFja3dhcmRzRmllbGROYW1lIiwiam9pbiIsIm9iamVjdFNoYWxsb3dFcXVhbHMiLCJhIiwiYiIsImVudHJpZXNJbkEiLCJlbnRyaWVzIiwia2V5cyIsImV2ZXJ5Iiwia2V5IiwidmFsdWUiLCJoYXNPd25Qcm9wZXJ0eSIsImFycmF5RGlmZkFjdGlvbnMiLCJzb3VyY2VBcnIiLCJ0YXJnZXRBcnIiLCJpdGVtc0luQm90aCIsImZpbHRlciIsIml0ZW0iLCJpbmNsdWRlcyIsImRlbGV0ZUl0ZW1zIiwiYWRkSXRlbXMiLCJkZWxldGUiLCJhZGQiLCJnZXRCYXRjaFRva2VuIiwib3BzIiwiY2xhdXNlRmlsdGVyc0J5QXR0cmlidXRlIiwidHlwZSIsInBheWxvYWQiLCJhdHRyaWJ1dGUiLCJGSUxURVIiLCJhdHRyaWJ1dGVWYWx1ZSIsInVuZGVmaW5lZCIsImNsYXVzZVJlZHVjZXNSZXN1bHRTZXRTaXplIiwiRVhDTFVERSIsIm1hcFZhbHVlcyIsIm9iamVjdCIsInJlZHVjZSIsIm5ld09iamVjdCIsIm5vcm1hbGl6ZU1vZGVsUmVmZXJlbmNlIiwibW9kZWxOYW1lT3JDbGFzcyJdLCJtYXBwaW5ncyI6IkFBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQ0E7QUFFQTs7Ozs7QUFLQTs7QUFDQSxTQUFTQSxjQUFULENBQXdCQyxHQUF4QixFQUE2QjtBQUN6QixRQUFNQyxNQUFNLEdBQ1IsT0FBT0MsT0FBTyxDQUFDQyxJQUFmLEtBQXdCLFVBQXhCLEdBQ01ELE9BQU8sQ0FBQ0MsSUFBUixDQUFhQyxJQUFiLENBQWtCRixPQUFsQixDQUROLEdBRU1BLE9BQU8sQ0FBQ0csR0FBUixDQUFZRCxJQUFaLENBQWlCRixPQUFqQixDQUhWO0FBSUEsU0FBT0QsTUFBTSxDQUFDRCxHQUFELENBQWI7QUFDSDtBQUVEOzs7QUFDQSxTQUFTTSxVQUFULENBQW9CQyxNQUFwQixFQUE0QjtBQUN4QixTQUFPQSxNQUFNLENBQUNDLE1BQVAsQ0FBYyxDQUFkLEVBQWlCQyxX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O0FBR0EsU0FBU0MsMEJBQVQsQ0FBb0M7QUFBRU47QUFBRixDQUFwQyxFQUE4QztBQUMxQyxTQUFPLENBQUNHLGlEQUFELEVBQVNJLGtEQUFULEVBQWtCZixRQUFsQixDQUEyQlEsSUFBM0IsQ0FBUDtBQUNIO0FBRUQ7Ozs7OztBQUlBLFNBQVNRLFNBQVQsQ0FBbUJDLE1BQW5CLEVBQTJCNUQsSUFBM0IsRUFBaUM7QUFDN0IsU0FBT0ksTUFBTSxDQUFDMkIsT0FBUCxDQUFlNkIsTUFBZixFQUF1QkMsTUFBdkIsQ0FBOEIsQ0FBQ0MsU0FBRCxFQUFZLENBQUM1QixHQUFELEVBQU1DLEtBQU4sQ0FBWixLQUE2QjtBQUM5RDJCLGFBQVMsQ0FBQzVCLEdBQUQsQ0FBVCxHQUFpQmxDLElBQUksQ0FBQ21DLEtBQUQsQ0FBckI7QUFDQSxXQUFPMkIsU0FBUDtBQUNILEdBSE0sRUFHSixFQUhJLENBQVA7QUFJSDtBQUVEOzs7QUFDQSxTQUFTQyx1QkFBVCxDQUFpQ0MsZ0JBQWpDLEVBQW1EO0FBQy9DLE1BQUksQ0FBQ0EsZ0JBQUQsSUFBcUIsT0FBT0EsZ0JBQVAsS0FBNEIsUUFBckQsRUFBK0Q7QUFDM0QsV0FBT0EsZ0JBQVA7QUFDSDs7QUFDRCxTQUFPQSxnQkFBZ0IsQ0FBQzdFLFNBQXhCO0FBQ0giLCJmaWxlIjoiLi9zcmMvdXRpbHMuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgb3BzIGZyb20gXCJpbW11dGFibGUtb3BzXCI7XG5pbXBvcnQgeyBGSUxURVIsIEVYQ0xVREUgfSBmcm9tIFwiLi9jb25zdGFudHNcIjtcblxuLyoqXG4gKiBAbW9kdWxlIHV0aWxzXG4gKiBAcHJpdmF0ZVxuICovXG5cbi8qKiBAcHJpdmF0ZSAqL1xuZnVuY3Rpb24gd2FybkRlcHJlY2F0ZWQobXNnKSB7XG4gICAgY29uc3QgbG9nZ2VyID1cbiAgICAgICAgdHlwZW9mIGNvbnNvbGUud2FybiA9PT0gXCJmdW5jdGlvblwiXG4gICAgICAgICAgICA/IGNvbnNvbGUud2Fybi5iaW5kKGNvbnNvbGUpXG4gICAgICAgICAgICA6IGNvbnNvbGUubG9nLmJpbmQoY29uc29sZSk7XG4gICAgcmV0dXJuIGxvZ2dlcihtc2cpO1xufVxuXG4vKiogQHByaXZhdGUgKi9cbmZ1bmN0aW9uIGNhcGl0YWxpemUoc3RyaW5nKSB7XG4gICAgcmV0dXJuIHN0cmluZy5jaGFyQXQoMCkudG9VcHBlckNhc2UoKSArIHN0cmluZy5zbGljZSgxKTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBicmFuY2ggbmFtZSBmb3IgYSBtYW55LXRvLW1hbnkgcmVsYXRpb24uXG4gKiBUaGUgbmFtZSBpcyB0aGUgY29tYmluYXRpb24gb2YgdGhlIG1vZGVsIG5hbWUgYW5kIHRoZSBmaWVsZCBuYW1lIHRoZSByZWxhdGlvblxuICogd2FzIGRlY2xhcmVkLiBUaGUgZmllbGQgbmFtZSdzIGZpcnN0IGxldHRlciBpcyBjYXBpdGFsaXplZC5cbiAqXG4gKiBFeGFtcGxlOiBtb2RlbCBgQXV0aG9yYCBoYXMgYSBtYW55LXRvLW1hbnkgcmVsYXRpb24gdG8gdGhlIG1vZGVsIGBCb29rYCwgZGVmaW5lZFxuICogaW4gdGhlIGBBdXRob3JgIGZpZWxkIGBib29rc2AuIFRoZSBtYW55LXRvLW1hbnkgYnJhbmNoIG5hbWUgd2lsbCBiZSBgQXV0aG9yQm9va3NgLlxuICpcbiAqIEBwYXJhbSAge3N0cmluZ30gZGVjbGFyYXRpb25Nb2RlbE5hbWUgLSB0aGUgbmFtZSBvZiB0aGUgbW9kZWwgdGhlIG1hbnktdG8tbWFueSByZWxhdGlvbiB3YXMgZGVjbGFyZWQgb25cbiAqIEBwYXJhbSAge3N0cmluZ30gZmllbGROYW1lICAgICAgICAgICAgLSB0aGUgZmllbGQgbmFtZSB3aGVyZSB0aGUgbWFueS10by1tYW55IHJlbGF0aW9uIHdhcyBkZWNsYXJlZCBvblxuICogQHJldHVybiB7c3RyaW5nfSBUaGUgYnJhbmNoIG5hbWUgZm9yIHRoZSBtYW55LXRvLW1hbnkgcmVsYXRpb24uXG4gKi9cbmZ1bmN0aW9uIG0ybU5hbWUoZGVjbGFyYXRpb25Nb2RlbE5hbWUsIGZpZWxkTmFtZSkge1xuICAgIHJldHVybiBkZWNsYXJhdGlvbk1vZGVsTmFtZSArIGNhcGl0YWxpemUoZmllbGROYW1lKTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBmaWVsZG5hbWUgdGhhdCBzYXZlcyBhIGZvcmVpZ24ga2V5IHRvIHRoZVxuICogbW9kZWwgaWQgd2hlcmUgdGhlIG1hbnktdG8tbWFueSByZWxhdGlvbiB3YXMgZGVjbGFyZWQuXG4gKlxuICogRXhhbXBsZTogYEF1dGhvcmAgPT4gYGZyb21BdXRob3JJZGBcbiAqXG4gKiBAcGFyYW0gIHtzdHJpbmd9IGRlY2xhcmF0aW9uTW9kZWxOYW1lIC0gdGhlIG5hbWUgb2YgdGhlIG1vZGVsIHdoZXJlIHRoZSByZWxhdGlvbiB3YXMgZGVjbGFyZWRcbiAqIEByZXR1cm4ge3N0cmluZ30gdGhlIGZpZWxkIG5hbWUgaW4gdGhlIHRocm91Z2ggbW9kZWwgZm9yIGBkZWNsYXJhdGlvbk1vZGVsTmFtZWAncyBmb3JlaWduIGtleS5cbiAqL1xuZnVuY3Rpb24gbTJtRnJvbUZpZWxkTmFtZShkZWNsYXJhdGlvbk1vZGVsTmFtZSkge1xuICAgIHJldHVybiBgZnJvbSR7ZGVjbGFyYXRpb25Nb2RlbE5hbWV9SWRgO1xufVxuXG4vKipcbiAqIFJldHVybnMgdGhlIGZpZWxkbmFtZSB0aGF0IHNhdmVzIGEgZm9yZWlnbiBrZXkgaW4gYSBtYW55LXRvLW1hbnkgdGhyb3VnaCBtb2RlbCB0byB0aGVcbiAqIG1vZGVsIHdoZXJlIHRoZSBtYW55LXRvLW1hbnkgcmVsYXRpb24gd2FzIGRlY2xhcmVkLlxuICpcbiAqIEV4YW1wbGU6IGBCb29rYCA9PiBgdG9Cb29rSWRgXG4gKlxuICogQHBhcmFtICB7c3RyaW5nfSBvdGhlck1vZGVsTmFtZSAtIHRoZSBuYW1lIG9mIHRoZSBtb2RlbCB0aGF0IHdhcyB0aGUgdGFyZ2V0IG9mIHRoZSBtYW55LXRvLW1hbnlcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWNsYXJhdGlvbi5cbiAqIEByZXR1cm4ge3N0cmluZ30gdGhlIGZpZWxkIG5hbWUgaW4gdGhlIHRocm91Z2ggbW9kZWwgZm9yIGBvdGhlck1vZGVsTmFtZWAncyBmb3JlaWduIGtleS4uXG4gKi9cbmZ1bmN0aW9uIG0ybVRvRmllbGROYW1lKG90aGVyTW9kZWxOYW1lKSB7XG4gICAgcmV0dXJuIGB0byR7b3RoZXJNb2RlbE5hbWV9SWRgO1xufVxuXG4vKiogKi9cbmZ1bmN0aW9uIHJldmVyc2VGaWVsZE5hbWUobW9kZWxOYW1lKSB7XG4gICAgcmV0dXJuIG1vZGVsTmFtZS50b0xvd2VyQ2FzZSgpICsgXCJTZXRcIjsgLy8gZXNsaW50LWRpc2FibGUtbGluZSBwcmVmZXItdGVtcGxhdGVcbn1cblxuLyoqIEBwcml2YXRlICovXG5mdW5jdGlvbiBxdWVyeVNldERlbGVnYXRvckZhY3RvcnkobWV0aG9kTmFtZSkge1xuICAgIHJldHVybiBmdW5jdGlvbiBxdWVyeVNldERlbGVnYXRvciguLi5hcmdzKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldFF1ZXJ5U2V0KClbbWV0aG9kTmFtZV0oLi4uYXJncyk7XG4gICAgfTtcbn1cblxuLyoqIEBwcml2YXRlICovXG5mdW5jdGlvbiBxdWVyeVNldEdldHRlckRlbGVnYXRvckZhY3RvcnkoZ2V0dGVyTmFtZSkge1xuICAgIHJldHVybiBmdW5jdGlvbiBxdWVyeVNldEdldHRlckRlbGVnYXRvcigpIHtcbiAgICAgICAgY29uc3QgcXMgPSB0aGlzLmdldFF1ZXJ5U2V0KCk7XG4gICAgICAgIHJldHVybiBxc1tnZXR0ZXJOYW1lXTtcbiAgICB9O1xufVxuXG4vKiogQHByaXZhdGUgKi9cbmZ1bmN0aW9uIGZvckVhY2hTdXBlckNsYXNzKHN1YkNsYXNzLCBmdW5jKSB7XG4gICAgbGV0IGN1cnJDbGFzcyA9IHN1YkNsYXNzO1xuICAgIHdoaWxlIChjdXJyQ2xhc3MgIT09IEZ1bmN0aW9uLnByb3RvdHlwZSkge1xuICAgICAgICBmdW5jKGN1cnJDbGFzcyk7XG4gICAgICAgIGN1cnJDbGFzcyA9IE9iamVjdC5nZXRQcm90b3R5cGVPZihjdXJyQ2xhc3MpO1xuICAgIH1cbn1cblxuLyoqICovXG5mdW5jdGlvbiBhdHRhY2hRdWVyeVNldE1ldGhvZHMobW9kZWxDbGFzcywgcXVlcnlTZXRDbGFzcykge1xuICAgIGNvbnN0IGxlZnRUb0RlZmluZSA9IHF1ZXJ5U2V0Q2xhc3Muc2hhcmVkTWV0aG9kcy5zbGljZSgpO1xuXG4gICAgLy8gVGhlcmUgaXMgbm8gd2F5IHRvIGdldCBhIHByb3BlcnR5IGRlc2NyaXB0b3IgZm9yIHRoZSB3aG9sZSBwcm90b3R5cGUgY2hhaW47XG4gICAgLy8gb25seSBmcm9tIGFuIG9iamVjdHMgb3duIHByb3BlcnRpZXMuIFRoZXJlZm9yZSB3ZSB0cmF2ZXJzZSB0aGUgd2hvbGUgcHJvdG90eXBlXG4gICAgLy8gY2hhaW4gZm9yIHF1ZXJ5U2V0LlxuICAgIGZvckVhY2hTdXBlckNsYXNzKHF1ZXJ5U2V0Q2xhc3MsIGNscyA9PiB7XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbGVmdFRvRGVmaW5lLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBsZXQgZGVmaW5lZCA9IGZhbHNlO1xuICAgICAgICAgICAgY29uc3QgbWV0aG9kTmFtZSA9IGxlZnRUb0RlZmluZVtpXTtcbiAgICAgICAgICAgIGNvbnN0IGRlc2NyaXB0b3IgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKFxuICAgICAgICAgICAgICAgIGNscy5wcm90b3R5cGUsXG4gICAgICAgICAgICAgICAgbWV0aG9kTmFtZVxuICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIGlmICh0eXBlb2YgZGVzY3JpcHRvciAhPT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgICAgIGlmICh0eXBlb2YgZGVzY3JpcHRvci5nZXQgIT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgICAgICAgICAgZGVzY3JpcHRvci5nZXQgPSBxdWVyeVNldEdldHRlckRlbGVnYXRvckZhY3RvcnkobWV0aG9kTmFtZSk7XG4gICAgICAgICAgICAgICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShtb2RlbENsYXNzLCBtZXRob2ROYW1lLCBkZXNjcmlwdG9yKTtcbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICBtb2RlbENsYXNzW21ldGhvZE5hbWVdID0gcXVlcnlTZXREZWxlZ2F0b3JGYWN0b3J5KFxuICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kTmFtZVxuICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBkZWZpbmVkID0gdHJ1ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmIChkZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgbGVmdFRvRGVmaW5lLnNwbGljZShpLS0sIDEpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfSk7XG59XG5cbi8qKlxuICogTm9ybWFsaXplcyBgZW50aXR5YCB0byBhbiBpZCwgd2hlcmUgYGVudGl0eWAgY2FuIGJlIGFuIGlkXG4gKiBvciBhIE1vZGVsIGluc3RhbmNlLlxuICpcbiAqIEBwYXJhbSAgeyp9IGVudGl0eSAtIGVpdGhlciBhIE1vZGVsIGluc3RhbmNlIG9yIGFuIGlkIHZhbHVlXG4gKiBAcmV0dXJuIHsqfSB0aGUgaWQgdmFsdWUgb2YgYGVudGl0eWBcbiAqL1xuZnVuY3Rpb24gbm9ybWFsaXplRW50aXR5KGVudGl0eSkge1xuICAgIGlmIChcbiAgICAgICAgZW50aXR5ICE9PSBudWxsICYmXG4gICAgICAgIHR5cGVvZiBlbnRpdHkgIT09IFwidW5kZWZpbmVkXCIgJiZcbiAgICAgICAgdHlwZW9mIGVudGl0eS5nZXRJZCA9PT0gXCJmdW5jdGlvblwiXG4gICAgKSB7XG4gICAgICAgIHJldHVybiBlbnRpdHkuZ2V0SWQoKTtcbiAgICB9XG4gICAgcmV0dXJuIGVudGl0eTtcbn1cblxuLyoqICovXG5mdW5jdGlvbiByZXZlcnNlRmllbGRFcnJvck1lc3NhZ2UoXG4gICAgbW9kZWxOYW1lLFxuICAgIGZpZWxkTmFtZSxcbiAgICB0b01vZGVsTmFtZSxcbiAgICBiYWNrd2FyZHNGaWVsZE5hbWVcbikge1xuICAgIHJldHVybiBbXG4gICAgICAgIGBSZXZlcnNlIGZpZWxkICR7YmFja3dhcmRzRmllbGROYW1lfSBhbHJlYWR5IGRlZmluZWRgLFxuICAgICAgICBgIG9uIG1vZGVsICR7dG9Nb2RlbE5hbWV9LiBUbyBmaXgsIHNldCBhIGN1c3RvbSByZWxhdGVkYCxcbiAgICAgICAgYCBuYW1lIG9uICR7bW9kZWxOYW1lfS4ke2ZpZWxkTmFtZX0uYCxcbiAgICBdLmpvaW4oXCJcIik7XG59XG5cbi8qKlxuICogRmFzdGVzdCB3YXkgdG8gY2hlY2sgaWYgdHdvIG9iamVjdHMgYXJlIGVxdWFsLlxuICogT2JqZWN0IGFuZCBhcnJheSB2YWx1ZXMgaGF2ZSB0byBiZSByZWZlcmVudGlhbGx5IGVxdWFsLlxuICovXG5mdW5jdGlvbiBvYmplY3RTaGFsbG93RXF1YWxzKGEsIGIpIHtcbiAgICBjb25zdCBlbnRyaWVzSW5BID0gT2JqZWN0LmVudHJpZXMoT2JqZWN0KGEpKTtcblxuICAgIGlmIChlbnRyaWVzSW5BLmxlbmd0aCAhPT0gT2JqZWN0LmtleXMoYikubGVuZ3RoKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICByZXR1cm4gZW50cmllc0luQS5ldmVyeShcbiAgICAgICAgKFtrZXksIHZhbHVlXSkgPT4gYi5oYXNPd25Qcm9wZXJ0eShrZXkpICYmIGJba2V5XSA9PT0gdmFsdWVcbiAgICApO1xufVxuXG4vKiogKi9cbmZ1bmN0aW9uIGFycmF5RGlmZkFjdGlvbnMoc291cmNlQXJyLCB0YXJnZXRBcnIpIHtcbiAgICBjb25zdCBpdGVtc0luQm90aCA9IHNvdXJjZUFyci5maWx0ZXIoaXRlbSA9PiB0YXJnZXRBcnIuaW5jbHVkZXMoaXRlbSkpO1xuICAgIGNvbnN0IGRlbGV0ZUl0ZW1zID0gc291cmNlQXJyLmZpbHRlcihpdGVtID0+ICFpdGVtc0luQm90aC5pbmNsdWRlcyhpdGVtKSk7XG4gICAgY29uc3QgYWRkSXRlbXMgPSB0YXJnZXRBcnIuZmlsdGVyKGl0ZW0gPT4gIWl0ZW1zSW5Cb3RoLmluY2x1ZGVzKGl0ZW0pKTtcblxuICAgIGlmIChkZWxldGVJdGVtcy5sZW5ndGggfHwgYWRkSXRlbXMubGVuZ3RoKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBkZWxldGU6IGRlbGV0ZUl0ZW1zLFxuICAgICAgICAgICAgYWRkOiBhZGRJdGVtcyxcbiAgICAgICAgfTtcbiAgICB9XG4gICAgcmV0dXJuIG51bGw7XG59XG5cbmNvbnN0IHsgZ2V0QmF0Y2hUb2tlbiB9ID0gb3BzO1xuXG4vKipcbiAqIEByZXR1cm4gYm9vbGVhblxuICovXG5mdW5jdGlvbiBjbGF1c2VGaWx0ZXJzQnlBdHRyaWJ1dGUoeyB0eXBlLCBwYXlsb2FkIH0sIGF0dHJpYnV0ZSkge1xuICAgIGlmICh0eXBlICE9PSBGSUxURVIpIHJldHVybiBmYWxzZTtcblxuICAgIGlmICh0eXBlb2YgcGF5bG9hZCAhPT0gXCJvYmplY3RcIikge1xuICAgICAgICAvKipcbiAgICAgICAgICogcGF5bG9hZCBjb3VsZCBhbHNvIGJlIGEgZnVuY3Rpb24gaW4gd2hpY2ggY2FzZVxuICAgICAgICAgKiB3ZSB3b3VsZCBoYXZlIG5vIHdheSBvZiBrbm93aW5nIHdoYXQgaXQgZG9lcyxcbiAgICAgICAgICogc28gd2UgZGVmYXVsdCB0byBmYWxzZSBmb3Igbm9uLW9iamVjdHNcbiAgICAgICAgICovXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICBpZiAoIXBheWxvYWQuaGFzT3duUHJvcGVydHkoYXR0cmlidXRlKSkgcmV0dXJuIGZhbHNlO1xuICAgIGNvbnN0IGF0dHJpYnV0ZVZhbHVlID0gcGF5bG9hZFthdHRyaWJ1dGVdO1xuICAgIGlmIChhdHRyaWJ1dGVWYWx1ZSA9PT0gbnVsbCkgcmV0dXJuIGZhbHNlO1xuICAgIGlmIChhdHRyaWJ1dGVWYWx1ZSA9PT0gdW5kZWZpbmVkKSByZXR1cm4gZmFsc2U7XG5cbiAgICByZXR1cm4gdHJ1ZTtcbn1cblxuLyoqXG4gKiBAcmV0dXJuIGJvb2xlYW5cbiAqL1xuZnVuY3Rpb24gY2xhdXNlUmVkdWNlc1Jlc3VsdFNldFNpemUoeyB0eXBlIH0pIHtcbiAgICByZXR1cm4gW0ZJTFRFUiwgRVhDTFVERV0uaW5jbHVkZXModHlwZSk7XG59XG5cbi8qKlxuICogQHBhcmFtIHtPYmplY3R9IG9iamVjdFxuICogQHJldHVybiBPYmplY3RcbiAqL1xuZnVuY3Rpb24gbWFwVmFsdWVzKG9iamVjdCwgZnVuYykge1xuICAgIHJldHVybiBPYmplY3QuZW50cmllcyhvYmplY3QpLnJlZHVjZSgobmV3T2JqZWN0LCBba2V5LCB2YWx1ZV0pID0+IHtcbiAgICAgICAgbmV3T2JqZWN0W2tleV0gPSBmdW5jKHZhbHVlKTtcbiAgICAgICAgcmV0dXJuIG5ld09iamVjdDtcbiAgICB9LCB7fSk7XG59XG5cbi8qKiAqL1xuZnVuY3Rpb24gbm9ybWFsaXplTW9kZWxSZWZlcmVuY2UobW9kZWxOYW1lT3JDbGFzcykge1xuICAgIGlmICghbW9kZWxOYW1lT3JDbGFzcyB8fCB0eXBlb2YgbW9kZWxOYW1lT3JDbGFzcyA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgICByZXR1cm4gbW9kZWxOYW1lT3JDbGFzcztcbiAgICB9XG4gICAgcmV0dXJuIG1vZGVsTmFtZU9yQ2xhc3MubW9kZWxOYW1lO1xufVxuXG5leHBvcnQge1xuICAgIGF0dGFjaFF1ZXJ5U2V0TWV0aG9kcyxcbiAgICBtMm1OYW1lLFxuICAgIG0ybUZyb21GaWVsZE5hbWUsXG4gICAgbTJtVG9GaWVsZE5hbWUsXG4gICAgcmV2ZXJzZUZpZWxkTmFtZSxcbiAgICBub3JtYWxpemVFbnRpdHksXG4gICAgcmV2ZXJzZUZpZWxkRXJyb3JNZXNzYWdlLFxuICAgIG9iamVjdFNoYWxsb3dFcXVhbHMsXG4gICAgb3BzLFxuICAgIGFycmF5RGlmZkFjdGlvbnMsXG4gICAgZ2V0QmF0Y2hUb2tlbixcbiAgICBjbGF1c2VGaWx0ZXJzQnlBdHRyaWJ1dGUsXG4gICAgY2xhdXNlUmVkdWNlc1Jlc3VsdFNldFNpemUsXG4gICAgd2FybkRlcHJlY2F0ZWQsXG4gICAgbWFwVmFsdWVzLFxuICAgIG5vcm1hbGl6ZU1vZGVsUmVmZXJlbmNlLFxufTtcbiJdLCJzb3VyY2VSb290IjoiIn0=\\n//# sourceURL=webpack-internal:///./src/utils.js\\n\");\n+eval(\"__webpack_require__.r(__webpack_exports__);\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"attachQuerySetMethods\\\", function() { return attachQuerySetMethods; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"m2mName\\\", function() { return m2mName; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"m2mFromFieldName\\\", function() { return m2mFromFieldName; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"m2mToFieldName\\\", function() { return m2mToFieldName; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"reverseFieldName\\\", function() { return reverseFieldName; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"normalizeEntity\\\", function() { return normalizeEntity; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"reverseFieldErrorMessage\\\", function() { return reverseFieldErrorMessage; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"objectShallowEquals\\\", function() { return objectShallowEquals; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"arrayDiffActions\\\", function() { return arrayDiffActions; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"getBatchToken\\\", function() { return getBatchToken; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"clauseFiltersByAttribute\\\", function() { return clauseFiltersByAttribute; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"clauseReducesResultSetSize\\\", function() { return clauseReducesResultSetSize; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"warnDeprecated\\\", function() { return warnDeprecated; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"mapValues\\\", function() { return mapValues; });\\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\\"normalizeModelReference\\\", function() { return normalizeModelReference; });\\n/* harmony import */ var immutable_ops__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! immutable-ops */ \\\"./node_modules/immutable-ops/es/index.js\\\");\\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \\\"ops\\\", function() { return immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"]; });\\n\\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./constants */ \\\"./src/constants.js\\\");\\n\\n\\n/**\\n * @module utils\\n * @private\\n */\\n\\n/** @private */\\n\\nfunction warnDeprecated(msg) {\\n  const logger = typeof console.warn === \\\"function\\\" ? console.warn.bind(console) : console.log.bind(console);\\n  return logger(msg);\\n}\\n/** @private */\\n\\n\\nfunction capitalize(string) {\\n  return string.charAt(0).toUpperCase() + string.slice(1);\\n}\\n/**\\n * Returns the branch name for a many-to-many relation.\\n * The name is the combination of the model name and the field name the relation\\n * was declared. The field name's first letter is capitalized.\\n *\\n * Example: model `Author` has a many-to-many relation to the model `Book`, defined\\n * in the `Author` field `books`. The many-to-many branch name will be `AuthorBooks`.\\n *\\n * @param  {string} declarationModelName - the name of the model the many-to-many relation was declared on\\n * @param  {string} fieldName            - the field name where the many-to-many relation was declared on\\n * @return {string} The branch name for the many-to-many relation.\\n */\\n\\n\\nfunction m2mName(declarationModelName, fieldName) {\\n  return declarationModelName + capitalize(fieldName);\\n}\\n/**\\n * Returns the fieldname that saves a foreign key to the\\n * model id where the many-to-many relation was declared.\\n *\\n * Example: `Author` => `fromAuthorId`\\n *\\n * @param  {string} declarationModelName - the name of the model where the relation was declared\\n * @return {string} the field name in the through model for `declarationModelName`'s foreign key.\\n */\\n\\n\\nfunction m2mFromFieldName(declarationModelName) {\\n  return `from${declarationModelName}Id`;\\n}\\n/**\\n * Returns the fieldname that saves a foreign key in a many-to-many through model to the\\n * model where the many-to-many relation was declared.\\n *\\n * Example: `Book` => `toBookId`\\n *\\n * @param  {string} otherModelName - the name of the model that was the target of the many-to-many\\n *                                   declaration.\\n * @return {string} the field name in the through model for `otherModelName`'s foreign key..\\n */\\n\\n\\nfunction m2mToFieldName(otherModelName) {\\n  return `to${otherModelName}Id`;\\n}\\n/** */\\n\\n\\nfunction reverseFieldName(modelName) {\\n  return modelName.toLowerCase() + \\\"Set\\\"; // eslint-disable-line prefer-template\\n}\\n/** @private */\\n\\n\\nfunction querySetDelegatorFactory(methodName) {\\n  return function querySetDelegator(...args) {\\n    return this.getQuerySet()[methodName](...args);\\n  };\\n}\\n/** @private */\\n\\n\\nfunction querySetGetterDelegatorFactory(getterName) {\\n  return function querySetGetterDelegator() {\\n    const qs = this.getQuerySet();\\n    return qs[getterName];\\n  };\\n}\\n/** @private */\\n\\n\\nfunction forEachSuperClass(subClass, func) {\\n  let currClass = subClass;\\n\\n  while (currClass !== Function.prototype) {\\n    func(currClass);\\n    currClass = Object.getPrototypeOf(currClass);\\n  }\\n}\\n/** */\\n\\n\\nfunction attachQuerySetMethods(modelClass, querySetClass) {\\n  const leftToDefine = querySetClass.sharedMethods.slice(); // There is no way to get a property descriptor for the whole prototype chain;\\n  // only from an objects own properties. Therefore we traverse the whole prototype\\n  // chain for querySet.\\n\\n  forEachSuperClass(querySetClass, cls => {\\n    for (let i = 0; i < leftToDefine.length; i++) {\\n      let defined = false;\\n      const methodName = leftToDefine[i];\\n      const descriptor = Object.getOwnPropertyDescriptor(cls.prototype, methodName);\\n\\n      if (typeof descriptor !== \\\"undefined\\\") {\\n        if (typeof descriptor.get !== \\\"undefined\\\") {\\n          descriptor.get = querySetGetterDelegatorFactory(methodName);\\n          Object.defineProperty(modelClass, methodName, descriptor);\\n        } else {\\n          modelClass[methodName] = querySetDelegatorFactory(methodName);\\n        }\\n\\n        defined = true;\\n      }\\n\\n      if (defined) {\\n        leftToDefine.splice(i--, 1);\\n      }\\n    }\\n  });\\n}\\n/**\\n * Normalizes `entity` to an id, where `entity` can be an id\\n * or a Model instance.\\n *\\n * @param  {*} entity - either a Model instance or an id value\\n * @return {*} the id value of `entity`\\n */\\n\\n\\nfunction normalizeEntity(entity) {\\n  if (entity !== null && typeof entity !== \\\"undefined\\\" && typeof entity.getId === \\\"function\\\") {\\n    return entity.getId();\\n  }\\n\\n  return entity;\\n}\\n/** */\\n\\n\\nfunction reverseFieldErrorMessage(modelName, fieldName, toModelName, backwardsFieldName) {\\n  return [`Reverse field ${backwardsFieldName} already defined`, ` on model ${toModelName}. To fix, set a custom related`, ` name on ${modelName}.${fieldName}.`].join(\\\"\\\");\\n}\\n/**\\n * Fastest way to check if two objects are equal.\\n * Object and array values have to be referentially equal.\\n */\\n\\n\\nfunction objectShallowEquals(a, b) {\\n  const entriesInA = Object.entries(Object(a));\\n\\n  if (entriesInA.length !== Object.keys(b).length) {\\n    return false;\\n  }\\n\\n  return entriesInA.every(([key, value]) => b.hasOwnProperty(key) && b[key] === value);\\n}\\n/** */\\n\\n\\nfunction arrayDiffActions(sourceArr, targetArr) {\\n  const itemsInBoth = sourceArr.filter(item => targetArr.includes(item));\\n  const deleteItems = sourceArr.filter(item => !itemsInBoth.includes(item));\\n  const addItems = targetArr.filter(item => !itemsInBoth.includes(item));\\n\\n  if (deleteItems.length || addItems.length) {\\n    return {\\n      delete: deleteItems,\\n      add: addItems\\n    };\\n  }\\n\\n  return null;\\n}\\n\\nconst {\\n  getBatchToken\\n} = immutable_ops__WEBPACK_IMPORTED_MODULE_0__[\\\"default\\\"];\\n/**\\n * @return boolean\\n */\\n\\nfunction clauseFiltersByAttribute({\\n  type,\\n  payload\\n}, attribute) {\\n  if (type !== _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"FILTER\\\"]) return false;\\n\\n  if (typeof payload !== \\\"object\\\") {\\n    /**\\n     * payload could also be a function in which case\\n     * we would have no way of knowing what it does,\\n     * so we default to false for non-objects\\n     */\\n    return false;\\n  }\\n\\n  if (!payload.hasOwnProperty(attribute)) return false;\\n  const attributeValue = payload[attribute];\\n  if (attributeValue === null) return false;\\n  if (attributeValue === undefined) return false;\\n  return true;\\n}\\n/**\\n * @return boolean\\n */\\n\\n\\nfunction clauseReducesResultSetSize({\\n  type\\n}) {\\n  return [_constants__WEBPACK_IMPORTED_MODULE_1__[\\\"FILTER\\\"], _constants__WEBPACK_IMPORTED_MODULE_1__[\\\"EXCLUDE\\\"]].includes(type);\\n}\\n/**\\n * @param {Object} object\\n * @return Object\\n */\\n\\n\\nfunction mapValues(object, func) {\\n  return Object.entries(object).reduce((newObject, [key, value]) => {\\n    newObject[key] = func(value);\\n    return newObject;\\n  }, {});\\n}\\n/** */\\n\\n\\nfunction normalizeModelReference(modelNameOrClass) {\\n  if (!modelNameOrClass || typeof modelNameOrClass === \\\"string\\\") {\\n    return modelNameOrClass;\\n  }\\n\\n  return modelNameOrClass.modelName;\\n}\\n\\n//# sourceURL=[module]\\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9SZWR1eE9ybS8uL3NyYy91dGlscy5qcz8wMjVlIl0sIm5hbWVzIjpbIndhcm5EZXByZWNhdGVkIiwibXNnIiwibG9nZ2VyIiwiY29uc29sZSIsIndhcm4iLCJiaW5kIiwibG9nIiwiY2FwaXRhbGl6ZSIsInN0cmluZyIsImNoYXJBdCIsInRvVXBwZXJDYXNlIiwic2xpY2UiLCJtMm1OYW1lIiwiZGVjbGFyYXRpb25Nb2RlbE5hbWUiLCJmaWVsZE5hbWUiLCJtMm1Gcm9tRmllbGROYW1lIiwibTJtVG9GaWVsZE5hbWUiLCJvdGhlck1vZGVsTmFtZSIsInJldmVyc2VGaWVsZE5hbWUiLCJtb2RlbE5hbWUiLCJ0b0xvd2VyQ2FzZSIsInF1ZXJ5U2V0RGVsZWdhdG9yRmFjdG9yeSIsIm1ldGhvZE5hbWUiLCJxdWVyeVNldERlbGVnYXRvciIsImFyZ3MiLCJnZXRRdWVyeVNldCIsInF1ZXJ5U2V0R2V0dGVyRGVsZWdhdG9yRmFjdG9yeSIsImdldHRlck5hbWUiLCJxdWVyeVNldEdldHRlckRlbGVnYXRvciIsInFzIiwiZm9yRWFjaFN1cGVyQ2xhc3MiLCJzdWJDbGFzcyIsImZ1bmMiLCJjdXJyQ2xhc3MiLCJGdW5jdGlvbiIsInByb3RvdHlwZSIsIk9iamVjdCIsImdldFByb3RvdHlwZU9mIiwiYXR0YWNoUXVlcnlTZXRNZXRob2RzIiwibW9kZWxDbGFzcyIsInF1ZXJ5U2V0Q2xhc3MiLCJsZWZ0VG9EZWZpbmUiLCJzaGFyZWRNZXRob2RzIiwiY2xzIiwiaSIsImxlbmd0aCIsImRlZmluZWQiLCJkZXNjcmlwdG9yIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwiZ2V0IiwiZGVmaW5lUHJvcGVydHkiLCJzcGxpY2UiLCJub3JtYWxpemVFbnRpdHkiLCJlbnRpdHkiLCJnZXRJZCIsInJldmVyc2VGaWVsZEVycm9yTWVzc2FnZSIsInRvTW9kZWxOYW1lIiwiYmFja3dhcmRzRmllbGROYW1lIiwiam9pbiIsIm9iamVjdFNoYWxsb3dFcXVhbHMiLCJhIiwiYiIsImVudHJpZXNJbkEiLCJlbnRyaWVzIiwia2V5cyIsImV2ZXJ5Iiwia2V5IiwidmFsdWUiLCJoYXNPd25Qcm9wZXJ0eSIsImFycmF5RGlmZkFjdGlvbnMiLCJzb3VyY2VBcnIiLCJ0YXJnZXRBcnIiLCJpdGVtc0luQm90aCIsImZpbHRlciIsIml0ZW0iLCJpbmNsdWRlcyIsImRlbGV0ZUl0ZW1zIiwiYWRkSXRlbXMiLCJkZWxldGUiLCJhZGQiLCJnZXRCYXRjaFRva2VuIiwib3BzIiwiY2xhdXNlRmlsdGVyc0J5QXR0cmlidXRlIiwidHlwZSIsInBheWxvYWQiLCJhdHRyaWJ1dGUiLCJGSUxURVIiLCJhdHRyaWJ1dGVWYWx1ZSIsInVuZGVmaW5lZCIsImNsYXVzZVJlZHVjZXNSZXN1bHRTZXRTaXplIiwiRVhDTFVERSIsIm1hcFZhbHVlcyIsIm9iamVjdCIsInJlZHVjZSIsIm5ld09iamVjdCIsIm5vcm1hbGl6ZU1vZGVsUmVmZXJlbmNlIiwibW9kZWxOYW1lT3JDbGFzcyJdLCJtYXBwaW5ncyI6IkFBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQ0E7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFDQSxTQUFTQSxjQUFULENBQXdCQyxHQUF4QixFQUE2QjtBQUN6QixRQUFNQyxNQUFNLEdBQ1IsT0FBT0MsT0FBTyxDQUFDQyxJQUFmLEtBQXdCLFVBQXhCLEdBQ01ELE9BQU8sQ0FBQ0MsSUFBUixDQUFhQyxJQUFiLENBQWtCRixPQUFsQixDQUROLEdBRU1BLE9BQU8sQ0FBQ0csR0FBUixDQUFZRCxJQUFaLENBQWlCRixPQUFqQixDQUhWO0FBSUEsU0FBT0QsTUFBTSxDQUFDRCxHQUFELENBQWI7QUFDSDtBQUVEOzs7QUFDQSxTQUFTTSxVQUFULENBQW9CQyxNQUFwQixFQUE0QjtBQUN4QixTQUFPQSxNQUFNLENBQUNDLE1BQVAsQ0FBYyxDQUFkLEVBQWlCQyxXQUFqQixLQUFpQ0YsTUFBTSxDQUFDRyxLQUFQLENBQWEsQ0FBYixDQUF4QztBQUNIO0FBRUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7QUFDQSxTQUFTQyxPQUFULENBQWlCQyxvQkFBakIsRUFBdUNDLFNBQXZDLEVBQWtEO0FBQzlDLFNBQU9ELG9CQUFvQixHQUFHTixVQUFVLENBQUNPLFNBQUQsQ0FBeEM7QUFDSDtBQUVEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7O0FBQ0EsU0FBU0MsZ0JBQVQsQ0FBMEJGLG9CQUExQixFQUFnRDtBQUM1QyxTQUFRLE9BQU1BLG9CQUFxQixJQUFuQztBQUNIO0FBRUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQUNBLFNBQVNHLGNBQVQsQ0FBd0JDLGNBQXhCLEVBQXdDO0FBQ3BDLFNBQVEsS0FBSUEsY0FBZSxJQUEzQjtBQUNIO0FBRUQ7OztBQUNBLFNBQVNDLGdCQUFULENBQTBCQyxTQUExQixFQUFxQztBQUNqQyxTQUFPQSxTQUFTLENBQUNDLFdBQVYsS0FBMEIsS0FBakMsQ0FEaUMsQ0FDTztBQUMzQztBQUVEOzs7QUFDQSxTQUFTQyx3QkFBVCxDQUFrQ0MsVUFBbEMsRUFBOEM7QUFDMUMsU0FBTyxTQUFTQyxpQkFBVCxDQUEyQixHQUFHQyxJQUE5QixFQUFvQztBQUN2QyxXQUFPLEtBQUtDLFdBQUwsR0FBbUJILFVBQW5CLEVBQStCLEdBQUdFLElBQWxDLENBQVA7QUFDSCxHQUZEO0FBR0g7QUFFRDs7O0FBQ0EsU0FBU0UsOEJBQVQsQ0FBd0NDLFVBQXhDLEVBQW9EO0FBQ2hELFNBQU8sU0FBU0MsdUJBQVQsR0FBbUM7QUFDdEMsVUFBTUMsRUFBRSxHQUFHLEtBQUtKLFdBQUwsRUFBWDtBQUNBLFdBQU9JLEVBQUUsQ0FBQ0YsVUFBRCxDQUFUO0FBQ0gsR0FIRDtBQUlIO0FBRUQ7OztBQUNBLFNBQVNHLGlCQUFULENBQTJCQyxRQUEzQixFQUFxQ0MsSUFBckMsRUFBMkM7QUFDdkMsTUFBSUMsU0FBUyxHQUFHRixRQUFoQjs7QUFDQSxTQUFPRSxTQUFTLEtBQUtDLFFBQVEsQ0FBQ0MsU0FBOUIsRUFBeUM7QUFDckNILFFBQUksQ0FBQ0MsU0FBRCxDQUFKO0FBQ0FBLGFBQVMsR0FBR0csTUFBTSxDQUFDQyxjQUFQLENBQXNCSixTQUF0QixDQUFaO0FBQ0g7QUFDSjtBQUVEOzs7QUFDQSxTQUFTSyxxQkFBVCxDQUErQkMsVUFBL0IsRUFBMkNDLGFBQTNDLEVBQTBEO0FBQ3RELFFBQU1DLFlBQVksR0FBR0QsYUFBYSxDQUFDRSxhQUFkLENBQTRCL0IsS0FBNUIsRUFBckIsQ0FEc0QsQ0FHdEQ7QUFDQTtBQUNBOztBQUNBbUIsbUJBQWlCLENBQUNVLGFBQUQsRUFBaUJHLEdBQUQsSUFBUztBQUN0QyxTQUFLLElBQUlDLENBQUMsR0FBRyxDQUFiLEVBQWdCQSxDQUFDLEdBQUdILFlBQVksQ0FBQ0ksTUFBakMsRUFBeUNELENBQUMsRUFBMUMsRUFBOEM7QUFDMUMsVUFBSUUsT0FBTyxHQUFHLEtBQWQ7QUFDQSxZQUFNeEIsVUFBVSxHQUFHbUIsWUFBWSxDQUFDRyxDQUFELENBQS9CO0FBQ0EsWUFBTUcsVUFBVSxHQUFHWCxNQUFNLENBQUNZLHdCQUFQLENBQ2ZMLEdBQUcsQ0FBQ1IsU0FEVyxFQUVmYixVQUZlLENBQW5COztBQUlBLFVBQUksT0FBT3lCLFVBQVAsS0FBc0IsV0FBMUIsRUFBdUM7QUFDbkMsWUFBSSxPQUFPQSxVQUFVLENBQUNFLEdBQWxCLEtBQTBCLFdBQTlCLEVBQTJDO0FBQ3ZDRixvQkFBVSxDQUFDRSxHQUFYLEdBQWlCdkIsOEJBQThCLENBQUNKLFVBQUQsQ0FBL0M7QUFDQWMsZ0JBQU0sQ0FBQ2MsY0FBUCxDQUFzQlgsVUFBdEIsRUFBa0NqQixVQUFsQyxFQUE4Q3lCLFVBQTlDO0FBQ0gsU0FIRCxNQUdPO0FBQ0hSLG9CQUFVLENBQUNqQixVQUFELENBQVYsR0FBeUJELHdCQUF3QixDQUM3Q0MsVUFENkMsQ0FBakQ7QUFHSDs7QUFDRHdCLGVBQU8sR0FBRyxJQUFWO0FBQ0g7O0FBQ0QsVUFBSUEsT0FBSixFQUFhO0FBQ1RMLG9CQUFZLENBQUNVLE1BQWIsQ0FBb0JQLENBQUMsRUFBckIsRUFBeUIsQ0FBekI7QUFDSDtBQUNKO0FBQ0osR0F2QmdCLENBQWpCO0FBd0JIO0FBRUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQUNBLFNBQVNRLGVBQVQsQ0FBeUJDLE1BQXpCLEVBQWlDO0FBQzdCLE1BQ0lBLE1BQU0sS0FBSyxJQUFYLElBQ0EsT0FBT0EsTUFBUCxLQUFrQixXQURsQixJQUVBLE9BQU9BLE1BQU0sQ0FBQ0MsS0FBZCxLQUF3QixVQUg1QixFQUlFO0FBQ0UsV0FBT0QsTUFBTSxDQUFDQyxLQUFQLEVBQVA7QUFDSDs7QUFDRCxTQUFPRCxNQUFQO0FBQ0g7QUFFRDs7O0FBQ0EsU0FBU0Usd0JBQVQsQ0FDSXBDLFNBREosRUFFSUwsU0FGSixFQUdJMEMsV0FISixFQUlJQyxrQkFKSixFQUtFO0FBQ0UsU0FBTyxDQUNGLGlCQUFnQkEsa0JBQW1CLGtCQURqQyxFQUVGLGFBQVlELFdBQVksZ0NBRnRCLEVBR0YsWUFBV3JDLFNBQVUsSUFBR0wsU0FBVSxHQUhoQyxFQUlMNEMsSUFKSyxDQUlBLEVBSkEsQ0FBUDtBQUtIO0FBRUQ7QUFDQTtBQUNBO0FBQ0E7OztBQUNBLFNBQVNDLG1CQUFULENBQTZCQyxDQUE3QixFQUFnQ0MsQ0FBaEMsRUFBbUM7QUFDL0IsUUFBTUMsVUFBVSxHQUFHMUIsTUFBTSxDQUFDMkIsT0FBUCxDQUFlM0IsTUFBTSxDQUFDd0IsQ0FBRCxDQUFyQixDQUFuQjs7QUFFQSxNQUFJRSxVQUFVLENBQUNqQixNQUFYLEtBQXNCVCxNQUFNLENBQUM0QixJQUFQLENBQVlILENBQVosRUFBZWhCLE1BQXpDLEVBQWlEO0FBQzdDLFdBQU8sS0FBUDtBQUNIOztBQUVELFNBQU9pQixVQUFVLENBQUNHLEtBQVgsQ0FDSCxDQUFDLENBQUNDLEdBQUQsRUFBTUMsS0FBTixDQUFELEtBQWtCTixDQUFDLENBQUNPLGNBQUYsQ0FBaUJGLEdBQWpCLEtBQXlCTCxDQUFDLENBQUNLLEdBQUQsQ0FBRCxLQUFXQyxLQURuRCxDQUFQO0FBR0g7QUFFRDs7O0FBQ0EsU0FBU0UsZ0JBQVQsQ0FBMEJDLFNBQTFCLEVBQXFDQyxTQUFyQyxFQUFnRDtBQUM1QyxRQUFNQyxXQUFXLEdBQUdGLFNBQVMsQ0FBQ0csTUFBVixDQUFrQkMsSUFBRCxJQUFVSCxTQUFTLENBQUNJLFFBQVYsQ0FBbUJELElBQW5CLENBQTNCLENBQXBCO0FBQ0EsUUFBTUUsV0FBVyxHQUFHTixTQUFTLENBQUNHLE1BQVYsQ0FBa0JDLElBQUQsSUFBVSxDQUFDRixXQUFXLENBQUNHLFFBQVosQ0FBcUJELElBQXJCLENBQTVCLENBQXBCO0FBQ0EsUUFBTUcsUUFBUSxHQUFHTixTQUFTLENBQUNFLE1BQVYsQ0FBa0JDLElBQUQsSUFBVSxDQUFDRixXQUFXLENBQUNHLFFBQVosQ0FBcUJELElBQXJCLENBQTVCLENBQWpCOztBQUVBLE1BQUlFLFdBQVcsQ0FBQy9CLE1BQVosSUFBc0JnQyxRQUFRLENBQUNoQyxNQUFuQyxFQUEyQztBQUN2QyxXQUFPO0FBQ0hpQyxZQUFNLEVBQUVGLFdBREw7QUFFSEcsU0FBRyxFQUFFRjtBQUZGLEtBQVA7QUFJSDs7QUFDRCxTQUFPLElBQVA7QUFDSDs7QUFFRCxNQUFNO0FBQUVHO0FBQUYsSUFBb0JDLHFEQUExQjtBQUVBO0FBQ0E7QUFDQTs7QUFDQSxTQUFTQyx3QkFBVCxDQUFrQztBQUFFQyxNQUFGO0FBQVFDO0FBQVIsQ0FBbEMsRUFBcURDLFNBQXJELEVBQWdFO0FBQzVELE1BQUlGLElBQUksS0FBS0csaURBQWIsRUFBcUIsT0FBTyxLQUFQOztBQUVyQixNQUFJLE9BQU9GLE9BQVAsS0FBbUIsUUFBdkIsRUFBaUM7QUFDN0I7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNRLFdBQU8sS0FBUDtBQUNIOztBQUVELE1BQUksQ0FBQ0EsT0FBTyxDQUFDaEIsY0FBUixDQUF1QmlCLFNBQXZCLENBQUwsRUFBd0MsT0FBTyxLQUFQO0FBQ3hDLFFBQU1FLGNBQWMsR0FBR0gsT0FBTyxDQUFDQyxTQUFELENBQTlCO0FBQ0EsTUFBSUUsY0FBYyxLQUFLLElBQXZCLEVBQTZCLE9BQU8sS0FBUDtBQUM3QixNQUFJQSxjQUFjLEtBQUtDLFNBQXZCLEVBQWtDLE9BQU8sS0FBUDtBQUVsQyxTQUFPLElBQVA7QUFDSDtBQUVEO0FBQ0E7QUFDQTs7O0FBQ0EsU0FBU0MsMEJBQVQsQ0FBb0M7QUFBRU47QUFBRixDQUFwQyxFQUE4QztBQUMxQyxTQUFPLENBQUNHLGlEQUFELEVBQVNJLGtEQUFULEVBQWtCZixRQUFsQixDQUEyQlEsSUFBM0IsQ0FBUDtBQUNIO0FBRUQ7QUFDQTtBQUNBO0FBQ0E7OztBQUNBLFNBQVNRLFNBQVQsQ0FBbUJDLE1BQW5CLEVBQTJCNUQsSUFBM0IsRUFBaUM7QUFDN0IsU0FBT0ksTUFBTSxDQUFDMkIsT0FBUCxDQUFlNkIsTUFBZixFQUF1QkMsTUFBdkIsQ0FBOEIsQ0FBQ0MsU0FBRCxFQUFZLENBQUM1QixHQUFELEVBQU1DLEtBQU4sQ0FBWixLQUE2QjtBQUM5RDJCLGFBQVMsQ0FBQzVCLEdBQUQsQ0FBVCxHQUFpQmxDLElBQUksQ0FBQ21DLEtBQUQsQ0FBckI7QUFDQSxXQUFPMkIsU0FBUDtBQUNILEdBSE0sRUFHSixFQUhJLENBQVA7QUFJSDtBQUVEOzs7QUFDQSxTQUFTQyx1QkFBVCxDQUFpQ0MsZ0JBQWpDLEVBQW1EO0FBQy9DLE1BQUksQ0FBQ0EsZ0JBQUQsSUFBcUIsT0FBT0EsZ0JBQVAsS0FBNEIsUUFBckQsRUFBK0Q7QUFDM0QsV0FBT0EsZ0JBQVA7QUFDSDs7QUFDRCxTQUFPQSxnQkFBZ0IsQ0FBQzdFLFNBQXhCO0FBQ0giLCJmaWxlIjoiLi9zcmMvdXRpbHMuanMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgb3BzIGZyb20gXCJpbW11dGFibGUtb3BzXCI7XG5pbXBvcnQgeyBGSUxURVIsIEVYQ0xVREUgfSBmcm9tIFwiLi9jb25zdGFudHNcIjtcblxuLyoqXG4gKiBAbW9kdWxlIHV0aWxzXG4gKiBAcHJpdmF0ZVxuICovXG5cbi8qKiBAcHJpdmF0ZSAqL1xuZnVuY3Rpb24gd2FybkRlcHJlY2F0ZWQobXNnKSB7XG4gICAgY29uc3QgbG9nZ2VyID1cbiAgICAgICAgdHlwZW9mIGNvbnNvbGUud2FybiA9PT0gXCJmdW5jdGlvblwiXG4gICAgICAgICAgICA/IGNvbnNvbGUud2Fybi5iaW5kKGNvbnNvbGUpXG4gICAgICAgICAgICA6IGNvbnNvbGUubG9nLmJpbmQoY29uc29sZSk7XG4gICAgcmV0dXJuIGxvZ2dlcihtc2cpO1xufVxuXG4vKiogQHByaXZhdGUgKi9cbmZ1bmN0aW9uIGNhcGl0YWxpemUoc3RyaW5nKSB7XG4gICAgcmV0dXJuIHN0cmluZy5jaGFyQXQoMCkudG9VcHBlckNhc2UoKSArIHN0cmluZy5zbGljZSgxKTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBicmFuY2ggbmFtZSBmb3IgYSBtYW55LXRvLW1hbnkgcmVsYXRpb24uXG4gKiBUaGUgbmFtZSBpcyB0aGUgY29tYmluYXRpb24gb2YgdGhlIG1vZGVsIG5hbWUgYW5kIHRoZSBmaWVsZCBuYW1lIHRoZSByZWxhdGlvblxuICogd2FzIGRlY2xhcmVkLiBUaGUgZmllbGQgbmFtZSdzIGZpcnN0IGxldHRlciBpcyBjYXBpdGFsaXplZC5cbiAqXG4gKiBFeGFtcGxlOiBtb2RlbCBgQXV0aG9yYCBoYXMgYSBtYW55LXRvLW1hbnkgcmVsYXRpb24gdG8gdGhlIG1vZGVsIGBCb29rYCwgZGVmaW5lZFxuICogaW4gdGhlIGBBdXRob3JgIGZpZWxkIGBib29rc2AuIFRoZSBtYW55LXRvLW1hbnkgYnJhbmNoIG5hbWUgd2lsbCBiZSBgQXV0aG9yQm9va3NgLlxuICpcbiAqIEBwYXJhbSAge3N0cmluZ30gZGVjbGFyYXRpb25Nb2RlbE5hbWUgLSB0aGUgbmFtZSBvZiB0aGUgbW9kZWwgdGhlIG1hbnktdG8tbWFueSByZWxhdGlvbiB3YXMgZGVjbGFyZWQgb25cbiAqIEBwYXJhbSAge3N0cmluZ30gZmllbGROYW1lICAgICAgICAgICAgLSB0aGUgZmllbGQgbmFtZSB3aGVyZSB0aGUgbWFueS10by1tYW55IHJlbGF0aW9uIHdhcyBkZWNsYXJlZCBvblxuICogQHJldHVybiB7c3RyaW5nfSBUaGUgYnJhbmNoIG5hbWUgZm9yIHRoZSBtYW55LXRvLW1hbnkgcmVsYXRpb24uXG4gKi9cbmZ1bmN0aW9uIG0ybU5hbWUoZGVjbGFyYXRpb25Nb2RlbE5hbWUsIGZpZWxkTmFtZSkge1xuICAgIHJldHVybiBkZWNsYXJhdGlvbk1vZGVsTmFtZSArIGNhcGl0YWxpemUoZmllbGROYW1lKTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBmaWVsZG5hbWUgdGhhdCBzYXZlcyBhIGZvcmVpZ24ga2V5IHRvIHRoZVxuICogbW9kZWwgaWQgd2hlcmUgdGhlIG1hbnktdG8tbWFueSByZWxhdGlvbiB3YXMgZGVjbGFyZWQuXG4gKlxuICogRXhhbXBsZTogYEF1dGhvcmAgPT4gYGZyb21BdXRob3JJZGBcbiAqXG4gKiBAcGFyYW0gIHtzdHJpbmd9IGRlY2xhcmF0aW9uTW9kZWxOYW1lIC0gdGhlIG5hbWUgb2YgdGhlIG1vZGVsIHdoZXJlIHRoZSByZWxhdGlvbiB3YXMgZGVjbGFyZWRcbiAqIEByZXR1cm4ge3N0cmluZ30gdGhlIGZpZWxkIG5hbWUgaW4gdGhlIHRocm91Z2ggbW9kZWwgZm9yIGBkZWNsYXJhdGlvbk1vZGVsTmFtZWAncyBmb3JlaWduIGtleS5cbiAqL1xuZnVuY3Rpb24gbTJtRnJvbUZpZWxkTmFtZShkZWNsYXJhdGlvbk1vZGVsTmFtZSkge1xuICAgIHJldHVybiBgZnJvbSR7ZGVjbGFyYXRpb25Nb2RlbE5hbWV9SWRgO1xufVxuXG4vKipcbiAqIFJldHVybnMgdGhlIGZpZWxkbmFtZSB0aGF0IHNhdmVzIGEgZm9yZWlnbiBrZXkgaW4gYSBtYW55LXRvLW1hbnkgdGhyb3VnaCBtb2RlbCB0byB0aGVcbiAqIG1vZGVsIHdoZXJlIHRoZSBtYW55LXRvLW1hbnkgcmVsYXRpb24gd2FzIGRlY2xhcmVkLlxuICpcbiAqIEV4YW1wbGU6IGBCb29rYCA9PiBgdG9Cb29rSWRgXG4gKlxuICogQHBhcmFtICB7c3RyaW5nfSBvdGhlck1vZGVsTmFtZSAtIHRoZSBuYW1lIG9mIHRoZSBtb2RlbCB0aGF0IHdhcyB0aGUgdGFyZ2V0IG9mIHRoZSBtYW55LXRvLW1hbnlcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWNsYXJhdGlvbi5cbiAqIEByZXR1cm4ge3N0cmluZ30gdGhlIGZpZWxkIG5hbWUgaW4gdGhlIHRocm91Z2ggbW9kZWwgZm9yIGBvdGhlck1vZGVsTmFtZWAncyBmb3JlaWduIGtleS4uXG4gKi9cbmZ1bmN0aW9uIG0ybVRvRmllbGROYW1lKG90aGVyTW9kZWxOYW1lKSB7XG4gICAgcmV0dXJuIGB0byR7b3RoZXJNb2RlbE5hbWV9SWRgO1xufVxuXG4vKiogKi9cbmZ1bmN0aW9uIHJldmVyc2VGaWVsZE5hbWUobW9kZWxOYW1lKSB7XG4gICAgcmV0dXJuIG1vZGVsTmFtZS50b0xvd2VyQ2FzZSgpICsgXCJTZXRcIjsgLy8gZXNsaW50LWRpc2FibGUtbGluZSBwcmVmZXItdGVtcGxhdGVcbn1cblxuLyoqIEBwcml2YXRlICovXG5mdW5jdGlvbiBxdWVyeVNldERlbGVnYXRvckZhY3RvcnkobWV0aG9kTmFtZSkge1xuICAgIHJldHVybiBmdW5jdGlvbiBxdWVyeVNldERlbGVnYXRvciguLi5hcmdzKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldFF1ZXJ5U2V0KClbbWV0aG9kTmFtZV0oLi4uYXJncyk7XG4gICAgfTtcbn1cblxuLyoqIEBwcml2YXRlICovXG5mdW5jdGlvbiBxdWVyeVNldEdldHRlckRlbGVnYXRvckZhY3RvcnkoZ2V0dGVyTmFtZSkge1xuICAgIHJldHVybiBmdW5jdGlvbiBxdWVyeVNldEdldHRlckRlbGVnYXRvcigpIHtcbiAgICAgICAgY29uc3QgcXMgPSB0aGlzLmdldFF1ZXJ5U2V0KCk7XG4gICAgICAgIHJldHVybiBxc1tnZXR0ZXJOYW1lXTtcbiAgICB9O1xufVxuXG4vKiogQHByaXZhdGUgKi9cbmZ1bmN0aW9uIGZvckVhY2hTdXBlckNsYXNzKHN1YkNsYXNzLCBmdW5jKSB7XG4gICAgbGV0IGN1cnJDbGFzcyA9IHN1YkNsYXNzO1xuICAgIHdoaWxlIChjdXJyQ2xhc3MgIT09IEZ1bmN0aW9uLnByb3RvdHlwZSkge1xuICAgICAgICBmdW5jKGN1cnJDbGFzcyk7XG4gICAgICAgIGN1cnJDbGFzcyA9IE9iamVjdC5nZXRQcm90b3R5cGVPZihjdXJyQ2xhc3MpO1xuICAgIH1cbn1cblxuLyoqICovXG5mdW5jdGlvbiBhdHRhY2hRdWVyeVNldE1ldGhvZHMobW9kZWxDbGFzcywgcXVlcnlTZXRDbGFzcykge1xuICAgIGNvbnN0IGxlZnRUb0RlZmluZSA9IHF1ZXJ5U2V0Q2xhc3Muc2hhcmVkTWV0aG9kcy5zbGljZSgpO1xuXG4gICAgLy8gVGhlcmUgaXMgbm8gd2F5IHRvIGdldCBhIHByb3BlcnR5IGRlc2NyaXB0b3IgZm9yIHRoZSB3aG9sZSBwcm90b3R5cGUgY2hhaW47XG4gICAgLy8gb25seSBmcm9tIGFuIG9iamVjdHMgb3duIHByb3BlcnRpZXMuIFRoZXJlZm9yZSB3ZSB0cmF2ZXJzZSB0aGUgd2hvbGUgcHJvdG90eXBlXG4gICAgLy8gY2hhaW4gZm9yIHF1ZXJ5U2V0LlxuICAgIGZvckVhY2hTdXBlckNsYXNzKHF1ZXJ5U2V0Q2xhc3MsIChjbHMpID0+IHtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBsZWZ0VG9EZWZpbmUubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGxldCBkZWZpbmVkID0gZmFsc2U7XG4gICAgICAgICAgICBjb25zdCBtZXRob2ROYW1lID0gbGVmdFRvRGVmaW5lW2ldO1xuICAgICAgICAgICAgY29uc3QgZGVzY3JpcHRvciA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IoXG4gICAgICAgICAgICAgICAgY2xzLnByb3RvdHlwZSxcbiAgICAgICAgICAgICAgICBtZXRob2ROYW1lXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgaWYgKHR5cGVvZiBkZXNjcmlwdG9yICE9PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgICAgICAgICAgaWYgKHR5cGVvZiBkZXNjcmlwdG9yLmdldCAhPT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgICAgICAgICBkZXNjcmlwdG9yLmdldCA9IHF1ZXJ5U2V0R2V0dGVyRGVsZWdhdG9yRmFjdG9yeShtZXRob2ROYW1lKTtcbiAgICAgICAgICAgICAgICAgICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KG1vZGVsQ2xhc3MsIG1ldGhvZE5hbWUsIGRlc2NyaXB0b3IpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIG1vZGVsQ2xhc3NbbWV0aG9kTmFtZV0gPSBxdWVyeVNldERlbGVnYXRvckZhY3RvcnkoXG4gICAgICAgICAgICAgICAgICAgICAgICBtZXRob2ROYW1lXG4gICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGRlZmluZWQgPSB0cnVlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKGRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICBsZWZ0VG9EZWZpbmUuc3BsaWNlKGktLSwgMSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9KTtcbn1cblxuLyoqXG4gKiBOb3JtYWxpemVzIGBlbnRpdHlgIHRvIGFuIGlkLCB3aGVyZSBgZW50aXR5YCBjYW4gYmUgYW4gaWRcbiAqIG9yIGEgTW9kZWwgaW5zdGFuY2UuXG4gKlxuICogQHBhcmFtICB7Kn0gZW50aXR5IC0gZWl0aGVyIGEgTW9kZWwgaW5zdGFuY2Ugb3IgYW4gaWQgdmFsdWVcbiAqIEByZXR1cm4geyp9IHRoZSBpZCB2YWx1ZSBvZiBgZW50aXR5YFxuICovXG5mdW5jdGlvbiBub3JtYWxpemVFbnRpdHkoZW50aXR5KSB7XG4gICAgaWYgKFxuICAgICAgICBlbnRpdHkgIT09IG51bGwgJiZcbiAgICAgICAgdHlwZW9mIGVudGl0eSAhPT0gXCJ1bmRlZmluZWRcIiAmJlxuICAgICAgICB0eXBlb2YgZW50aXR5LmdldElkID09PSBcImZ1bmN0aW9uXCJcbiAgICApIHtcbiAgICAgICAgcmV0dXJuIGVudGl0eS5nZXRJZCgpO1xuICAgIH1cbiAgICByZXR1cm4gZW50aXR5O1xufVxuXG4vKiogKi9cbmZ1bmN0aW9uIHJldmVyc2VGaWVsZEVycm9yTWVzc2FnZShcbiAgICBtb2RlbE5hbWUsXG4gICAgZmllbGROYW1lLFxuICAgIHRvTW9kZWxOYW1lLFxuICAgIGJhY2t3YXJkc0ZpZWxkTmFtZVxuKSB7XG4gICAgcmV0dXJuIFtcbiAgICAgICAgYFJldmVyc2UgZmllbGQgJHtiYWNrd2FyZHNGaWVsZE5hbWV9IGFscmVhZHkgZGVmaW5lZGAsXG4gICAgICAgIGAgb24gbW9kZWwgJHt0b01vZGVsTmFtZX0uIFRvIGZpeCwgc2V0IGEgY3VzdG9tIHJlbGF0ZWRgLFxuICAgICAgICBgIG5hbWUgb24gJHttb2RlbE5hbWV9LiR7ZmllbGROYW1lfS5gLFxuICAgIF0uam9pbihcIlwiKTtcbn1cblxuLyoqXG4gKiBGYXN0ZXN0IHdheSB0byBjaGVjayBpZiB0d28gb2JqZWN0cyBhcmUgZXF1YWwuXG4gKiBPYmplY3QgYW5kIGFycmF5IHZhbHVlcyBoYXZlIHRvIGJlIHJlZmVyZW50aWFsbHkgZXF1YWwuXG4gKi9cbmZ1bmN0aW9uIG9iamVjdFNoYWxsb3dFcXVhbHMoYSwgYikge1xuICAgIGNvbnN0IGVudHJpZXNJbkEgPSBPYmplY3QuZW50cmllcyhPYmplY3QoYSkpO1xuXG4gICAgaWYgKGVudHJpZXNJbkEubGVuZ3RoICE9PSBPYmplY3Qua2V5cyhiKS5sZW5ndGgpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIHJldHVybiBlbnRyaWVzSW5BLmV2ZXJ5KFxuICAgICAgICAoW2tleSwgdmFsdWVdKSA9PiBiLmhhc093blByb3BlcnR5KGtleSkgJiYgYltrZXldID09PSB2YWx1ZVxuICAgICk7XG59XG5cbi8qKiAqL1xuZnVuY3Rpb24gYXJyYXlEaWZmQWN0aW9ucyhzb3VyY2VBcnIsIHRhcmdldEFycikge1xuICAgIGNvbnN0IGl0ZW1zSW5Cb3RoID0gc291cmNlQXJyLmZpbHRlcigoaXRlbSkgPT4gdGFyZ2V0QXJyLmluY2x1ZGVzKGl0ZW0pKTtcbiAgICBjb25zdCBkZWxldGVJdGVtcyA9IHNvdXJjZUFyci5maWx0ZXIoKGl0ZW0pID0+ICFpdGVtc0luQm90aC5pbmNsdWRlcyhpdGVtKSk7XG4gICAgY29uc3QgYWRkSXRlbXMgPSB0YXJnZXRBcnIuZmlsdGVyKChpdGVtKSA9PiAhaXRlbXNJbkJvdGguaW5jbHVkZXMoaXRlbSkpO1xuXG4gICAgaWYgKGRlbGV0ZUl0ZW1zLmxlbmd0aCB8fCBhZGRJdGVtcy5sZW5ndGgpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGRlbGV0ZTogZGVsZXRlSXRlbXMsXG4gICAgICAgICAgICBhZGQ6IGFkZEl0ZW1zLFxuICAgICAgICB9O1xuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbn1cblxuY29uc3QgeyBnZXRCYXRjaFRva2VuIH0gPSBvcHM7XG5cbi8qKlxuICogQHJldHVybiBib29sZWFuXG4gKi9cbmZ1bmN0aW9uIGNsYXVzZUZpbHRlcnNCeUF0dHJpYnV0ZSh7IHR5cGUsIHBheWxvYWQgfSwgYXR0cmlidXRlKSB7XG4gICAgaWYgKHR5cGUgIT09IEZJTFRFUikgcmV0dXJuIGZhbHNlO1xuXG4gICAgaWYgKHR5cGVvZiBwYXlsb2FkICE9PSBcIm9iamVjdFwiKSB7XG4gICAgICAgIC8qKlxuICAgICAgICAgKiBwYXlsb2FkIGNvdWxkIGFsc28gYmUgYSBmdW5jdGlvbiBpbiB3aGljaCBjYXNlXG4gICAgICAgICAqIHdlIHdvdWxkIGhhdmUgbm8gd2F5IG9mIGtub3dpbmcgd2hhdCBpdCBkb2VzLFxuICAgICAgICAgKiBzbyB3ZSBkZWZhdWx0IHRvIGZhbHNlIGZvciBub24tb2JqZWN0c1xuICAgICAgICAgKi9cbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIGlmICghcGF5bG9hZC5oYXNPd25Qcm9wZXJ0eShhdHRyaWJ1dGUpKSByZXR1cm4gZmFsc2U7XG4gICAgY29uc3QgYXR0cmlidXRlVmFsdWUgPSBwYXlsb2FkW2F0dHJpYnV0ZV07XG4gICAgaWYgKGF0dHJpYnV0ZVZhbHVlID09PSBudWxsKSByZXR1cm4gZmFsc2U7XG4gICAgaWYgKGF0dHJpYnV0ZVZhbHVlID09PSB1bmRlZmluZWQpIHJldHVybiBmYWxzZTtcblxuICAgIHJldHVybiB0cnVlO1xufVxuXG4vKipcbiAqIEByZXR1cm4gYm9vbGVhblxuICovXG5mdW5jdGlvbiBjbGF1c2VSZWR1Y2VzUmVzdWx0U2V0U2l6ZSh7IHR5cGUgfSkge1xuICAgIHJldHVybiBbRklMVEVSLCBFWENMVURFXS5pbmNsdWRlcyh0eXBlKTtcbn1cblxuLyoqXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0XG4gKiBAcmV0dXJuIE9iamVjdFxuICovXG5mdW5jdGlvbiBtYXBWYWx1ZXMob2JqZWN0LCBmdW5jKSB7XG4gICAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKG9iamVjdCkucmVkdWNlKChuZXdPYmplY3QsIFtrZXksIHZhbHVlXSkgPT4ge1xuICAgICAgICBuZXdPYmplY3Rba2V5XSA9IGZ1bmModmFsdWUpO1xuICAgICAgICByZXR1cm4gbmV3T2JqZWN0O1xuICAgIH0sIHt9KTtcbn1cblxuLyoqICovXG5mdW5jdGlvbiBub3JtYWxpemVNb2RlbFJlZmVyZW5jZShtb2RlbE5hbWVPckNsYXNzKSB7XG4gICAgaWYgKCFtb2RlbE5hbWVPckNsYXNzIHx8IHR5cGVvZiBtb2RlbE5hbWVPckNsYXNzID09PSBcInN0cmluZ1wiKSB7XG4gICAgICAgIHJldHVybiBtb2RlbE5hbWVPckNsYXNzO1xuICAgIH1cbiAgICByZXR1cm4gbW9kZWxOYW1lT3JDbGFzcy5tb2RlbE5hbWU7XG59XG5cbmV4cG9ydCB7XG4gICAgYXR0YWNoUXVlcnlTZXRNZXRob2RzLFxuICAgIG0ybU5hbWUsXG4gICAgbTJtRnJvbUZpZWxkTmFtZSxcbiAgICBtMm1Ub0ZpZWxkTmFtZSxcbiAgICByZXZlcnNlRmllbGROYW1lLFxuICAgIG5vcm1hbGl6ZUVudGl0eSxcbiAgICByZXZlcnNlRmllbGRFcnJvck1lc3NhZ2UsXG4gICAgb2JqZWN0U2hhbGxvd0VxdWFscyxcbiAgICBvcHMsXG4gICAgYXJyYXlEaWZmQWN0aW9ucyxcbiAgICBnZXRCYXRjaFRva2VuLFxuICAgIGNsYXVzZUZpbHRlcnNCeUF0dHJpYnV0ZSxcbiAgICBjbGF1c2VSZWR1Y2VzUmVzdWx0U2V0U2l6ZSxcbiAgICB3YXJuRGVwcmVjYXRlZCxcbiAgICBtYXBWYWx1ZXMsXG4gICAgbm9ybWFsaXplTW9kZWxSZWZlcmVuY2UsXG59O1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\\n//# sourceURL=webpack-internal:///./src/utils.js\\n\");\n \n /***/ })\n \ndiff --git a/node_modules/redux-orm/dist/redux-orm.min.js b/node_modules/redux-orm/dist/redux-orm.min.js\nindex f76f1b4..3b207b3 100644\n--- a/node_modules/redux-orm/dist/redux-orm.min.js\n+++ b/node_modules/redux-orm/dist/redux-orm.min.js\n@@ -1,2 +1,2 @@\n-!function(e,t){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=t():\"function\"==typeof define&&define.amd?define(\"ReduxOrm\",[],t):\"object\"==typeof exports?exports.ReduxOrm=t():e.ReduxOrm=t()}(window,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){\"undefined\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:\"Module\"}),Object.defineProperty(e,\"__esModule\",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&\"object\"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,\"default\",{enumerable:!0,value:e}),2&t&&\"string\"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,\"a\",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p=\"\",n(n.s=35)}([function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}e.exports=function(e,t,r){return t&&n(e.prototype,t),r&&n(e,r),e}},function(e,t){e.exports=function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.__proto__=t}},function(e,t){function n(t){return\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?e.exports=n=function(e){return typeof e}:e.exports=n=function(e){return e&&\"function\"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e},n(t)}e.exports=n},function(e,t,n){var r=n(15),o=n(16),s=n(17),i=n(18);e.exports=function(e){return r(e)||o(e)||s(e)||i()}},function(e,t,n){\"use strict\";function r(e,t){return e===t}function o(e,t,n){if(null===t||null===n||t.length!==n.length)return!1;for(var r=t.length,o=0;o<r;o++)if(!e(t[o],n[o]))return!1;return!0}function s(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:r,n=null,s=null;return function(){return o(t,n,arguments)||(s=e.apply(null,arguments)),n=arguments,s}}function i(e){var t=Array.isArray(e[0])?e[0]:e;if(!t.every((function(e){return\"function\"==typeof e}))){var n=t.map((function(e){return typeof e})).join(\", \");throw new Error(\"Selector creators expect all input-selectors to be functions, instead received the following types: [\"+n+\"]\")}return t}function a(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];return function(){for(var t=arguments.length,r=Array(t),o=0;o<t;o++)r[o]=arguments[o];var a=0,c=r.pop(),u=i(r),l=e.apply(void 0,[function(){return a++,c.apply(null,arguments)}].concat(n)),d=s((function(){for(var e=[],t=u.length,n=0;n<t;n++)e.push(u[n].apply(null,arguments));return l.apply(null,e)}));return d.resultFunc=c,d.recomputations=function(){return a},d.resetRecomputations=function(){return a=0},d}}t.__esModule=!0,t.defaultMemoize=s,t.createSelectorCreator=a,t.createStructuredSelector=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:c;if(\"object\"!=typeof e)throw new Error(\"createStructuredSelector expects first argument to be an object where each property is a selector, instead received a \"+typeof e);var n=Object.keys(e);return t(n.map((function(t){return e[t]})),(function(){for(var e=arguments.length,t=Array(e),r=0;r<e;r++)t[r]=arguments[r];return t.reduce((function(e,t,r){return e[n[r]]=t,e}),{})}))};var c=t.createSelector=a(s)},function(e,t,n){!function(e,t){\"use strict\";function n(e){return\"string\"==typeof e||\"number\"==typeof e}var r=function(){function e(){this._cache={}}var t=e.prototype;return t.set=function(e,t){this._cache[e]=t},t.get=function(e){return this._cache[e]},t.remove=function(e){delete this._cache[e]},t.clear=function(){this._cache={}},t.isValidCacheKey=function(e){return n(e)},e}(),o=r,s=function(){return!0};function i(){for(var e=arguments.length,n=new Array(e),r=0;r<e;r++)n[r]=arguments[r];return function(e,r){if(\"function\"==typeof r)throw new Error('[re-reselect] Second argument \"options\" must be an object. Please use \"options.selectorCreator\" to provide a custom selectorCreator.');var i={};\"function\"==typeof e?Object.assign(i,r,{keySelector:e}):Object.assign(i,e);var a=0,c=n.pop(),u=Array.isArray(n[0])?n[0]:[].concat(n);n.push((function(){return a++,c.apply(void 0,arguments)}));var l=i.cacheObject||new o,d=i.selectorCreator||t.createSelector,h=l.isValidCacheKey||s;i.keySelectorCreator&&(i.keySelector=i.keySelectorCreator({keySelector:i.keySelector,inputSelectors:u,resultFunc:c}));var f=function(){var e=i.keySelector.apply(i,arguments);if(h(e)){var t=l.get(e);return void 0===t&&(t=d.apply(void 0,n),l.set(e,t)),t.apply(void 0,arguments)}console.warn('[re-reselect] Invalid cache key \"'+e+'\" has been returned by keySelector function.')};return f.getMatchingSelector=function(){var e=i.keySelector.apply(i,arguments);return l.get(e)},f.removeMatchingSelector=function(){var e=i.keySelector.apply(i,arguments);l.remove(e)},f.clearCache=function(){l.clear()},f.resultFunc=c,f.dependencies=u,f.cache=l,f.recomputations=function(){return a},f.resetRecomputations=function(){return a=0},f.keySelector=i.keySelector,f}}function a(e){if(void 0===e)throw new Error('Missing the required property \"cacheSize\".');if(!Number.isInteger(e)||e<=0)throw new Error('The \"cacheSize\" property must be a positive integer value.')}var c=function(){function e(e){var t=(void 0===e?{}:e).cacheSize;a(t),this._cache={},this._cacheOrdering=[],this._cacheSize=t}var t=e.prototype;return t.set=function(e,t){if(this._cache[e]=t,this._cacheOrdering.push(e),this._cacheOrdering.length>this._cacheSize){var n=this._cacheOrdering[0];this.remove(n)}},t.get=function(e){return this._cache[e]},t.remove=function(e){var t=this._cacheOrdering.indexOf(e);t>-1&&this._cacheOrdering.splice(t,1),delete this._cache[e]},t.clear=function(){this._cache={},this._cacheOrdering=[]},t.isValidCacheKey=function(e){return n(e)},e}(),u=function(){function e(e){var t=(void 0===e?{}:e).cacheSize;a(t),this._cache={},this._cacheOrdering=[],this._cacheSize=t}var t=e.prototype;return t.set=function(e,t){if(this._cache[e]=t,this._registerCacheHit(e),this._cacheOrdering.length>this._cacheSize){var n=this._cacheOrdering[0];this.remove(n)}},t.get=function(e){return this._registerCacheHit(e),this._cache[e]},t.remove=function(e){this._deleteCacheHit(e),delete this._cache[e]},t.clear=function(){this._cache={},this._cacheOrdering=[]},t._registerCacheHit=function(e){this._deleteCacheHit(e),this._cacheOrdering.push(e)},t._deleteCacheHit=function(e){var t=this._cacheOrdering.indexOf(e);t>-1&&this._cacheOrdering.splice(t,1)},t.isValidCacheKey=function(e){return n(e)},e}(),l=function(){function e(){this._cache=new Map}var t=e.prototype;return t.set=function(e,t){this._cache.set(e,t)},t.get=function(e){return this._cache.get(e)},t.remove=function(e){this._cache.delete(e)},t.clear=function(){this._cache.clear()},e}(),d=function(){function e(e){var t=(void 0===e?{}:e).cacheSize;a(t),this._cache=new Map,this._cacheSize=t}var t=e.prototype;return t.set=function(e,t){if(this._cache.set(e,t),this._cache.size>this._cacheSize){var n=this._cache.keys().next().value;this.remove(n)}},t.get=function(e){return this._cache.get(e)},t.remove=function(e){this._cache.delete(e)},t.clear=function(){this._cache.clear()},e}(),h=function(){function e(e){var t=(void 0===e?{}:e).cacheSize;a(t),this._cache=new Map,this._cacheSize=t}var t=e.prototype;return t.set=function(e,t){if(this._cache.set(e,t),this._cache.size>this._cacheSize){var n=this._cache.keys().next().value;this.remove(n)}},t.get=function(e){var t=this._cache.get(e);return this._cache.has(e)&&(this.remove(e),this._cache.set(e,t)),t},t.remove=function(e){this._cache.delete(e)},t.clear=function(){this._cache.clear()},e}();e.FifoCacheObject=c,e.FifoMapCache=d,e.FifoObjectCache=c,e.FlatCacheObject=r,e.FlatMapCache=l,e.FlatObjectCache=r,e.LruCacheObject=h,e.LruMapCache=h,e.LruObjectCache=u,e.createStructuredCachedSelector=function(e){return t.createStructuredSelector(e,i)},e.default=i,Object.defineProperty(e,\"__esModule\",{value:!0})}(t,n(4))},function(e,t){e.exports=function(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}},function(e,t,n){var r=n(19),o=n(8),s=n(20),i=n(21),a=n(22),c=n(23),u=n(9);e.exports=function(e,t,n){var l=-1;t=r(t.length?t:[u],a(o));var d=s(e,(function(e,n,o){return{criteria:r(t,(function(t){return t(e)})),index:++l,value:e}}));return i(d,(function(e,t){return c(e,t,n)}))}},function(e,t){e.exports=function(e){return e}},function(e,t){e.exports=function(e){return e}},function(e,t){var n=Array.isArray;e.exports=n},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=0,s=[];++n<r;){var i=e[n];t(i,n,e)&&(s[o++]=i)}return s}},function(e,t,n){var r=n(7),o=n(10);e.exports=function(e,t,n,s){return null==e?[]:(o(t)||(t=null==t?[]:[t]),o(n=s?void 0:n)||(n=null==n?[]:[n]),r(e,t,n))}},function(e,t,n){var r=n(26),o=n(27),s=n(8),i=n(10),a=n(28);e.exports=function(e,t){return(i(e)?r:o)(e,a(s(t,3)))}},function(e,t,n){var r=n(29),o=n(7),s=n(30),i=n(34),a=s((function(e,t){if(null==e)return[];var n=t.length;return n>1&&i(e,t[0],t[1])?t=[]:n>2&&i(t[0],t[1],t[2])&&(t=[t[0]]),o(e,r(t,1),[])}));e.exports=a},function(e,t,n){var r=n(6);e.exports=function(e){if(Array.isArray(e))return r(e)}},function(e,t){e.exports=function(e){if(\"undefined\"!=typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}},function(e,t,n){var r=n(6);e.exports=function(e,t){if(e){if(\"string\"==typeof e)return r(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return\"Object\"===n&&e.constructor&&(n=e.constructor.name),\"Map\"===n||\"Set\"===n?Array.from(e):\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?r(e,t):void 0}}},function(e,t){e.exports=function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++n<r;)o[n]=t(e[n],n,e);return o}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++n<r;)o[n]=t(e[n],n,e);return o}},function(e,t){e.exports=function(e,t){var n=e.length;for(e.sort(t);n--;)e[n]=e[n].value;return e}},function(e,t){e.exports=function(e){return function(t){return e(t)}}},function(e,t,n){var r=n(24);e.exports=function(e,t,n){for(var o=-1,s=e.criteria,i=t.criteria,a=s.length,c=n.length;++o<a;){var u=r(s[o],i[o]);if(u)return o>=c?u:u*(\"desc\"==n[o]?-1:1)}return e.index-t.index}},function(e,t,n){var r=n(25);e.exports=function(e,t){if(e!==t){var n=void 0!==e,o=null===e,s=e==e,i=r(e),a=void 0!==t,c=null===t,u=t==t,l=r(t);if(!c&&!l&&!i&&e>t||i&&a&&u&&!c&&!l||o&&a&&u||!n&&u||!s)return 1;if(!o&&!i&&!l&&e<t||l&&n&&s&&!o&&!i||c&&n&&s||!a&&s||!u)return-1}return 0}},function(e,t){e.exports=function(){return!1}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=0,s=[];++n<r;){var i=e[n];t(i,n,e)&&(s[o++]=i)}return s}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=0,s=[];++n<r;){var i=e[n];t(i,n,e)&&(s[o++]=i)}return s}},function(e,t){var n=\"Expected a function\";e.exports=function(e){if(\"function\"!=typeof e)throw new TypeError(n);return function(){var t=arguments;switch(t.length){case 0:return!e.call(this);case 1:return!e.call(this,t[0]);case 2:return!e.call(this,t[0],t[1]);case 3:return!e.call(this,t[0],t[1],t[2])}return!e.apply(this,t)}}},function(e,t){e.exports=function(e){return e&&e.length?e[0]:void 0}},function(e,t,n){var r=n(9),o=n(31),s=n(33);e.exports=function(e,t){return s(o(e,t,r),e+\"\")}},function(e,t,n){var r=n(32),o=Math.max;e.exports=function(e,t,n){return t=o(void 0===t?e.length-1:t,0),function(){for(var s=arguments,i=-1,a=o(s.length-t,0),c=Array(a);++i<a;)c[i]=s[t+i];i=-1;for(var u=Array(t+1);++i<t;)u[i]=s[i];return u[t]=n(c),r(e,this,u)}}},function(e,t){e.exports=function(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}},function(e,t){e.exports=function(e){return e}},function(e,t){e.exports=function(){return!1}},function(e,t,n){\"use strict\";n.r(t);var r=n(0),o=n.n(r),s=n(3),i=n.n(s),a=n(2),c=n.n(a);function u(e){return null!=e&&\"object\"==typeof e&&!0===e[\"@@functional/placeholder\"]}function l(e){return function t(n){return 0===arguments.length||u(n)?t:e.apply(this,arguments)}}function d(e,t){switch(e){case 0:return function(){return t.apply(this,arguments)};case 1:return function(e){return t.apply(this,arguments)};case 2:return function(e,n){return t.apply(this,arguments)};case 3:return function(e,n,r){return t.apply(this,arguments)};case 4:return function(e,n,r,o){return t.apply(this,arguments)};case 5:return function(e,n,r,o,s){return t.apply(this,arguments)};case 6:return function(e,n,r,o,s,i){return t.apply(this,arguments)};case 7:return function(e,n,r,o,s,i,a){return t.apply(this,arguments)};case 8:return function(e,n,r,o,s,i,a,c){return t.apply(this,arguments)};case 9:return function(e,n,r,o,s,i,a,c,u){return t.apply(this,arguments)};case 10:return function(e,n,r,o,s,i,a,c,u,l){return t.apply(this,arguments)};default:throw new Error(\"First argument to _arity must be a non-negative integer no greater than ten\")}}function h(e){return function t(n,r){switch(arguments.length){case 0:return t;case 1:return u(n)?t:l((function(t){return e(n,t)}));default:return u(n)&&u(r)?t:u(n)?l((function(t){return e(t,r)})):u(r)?l((function(t){return e(n,t)})):e(n,r)}}}var f=h((function(e,t){return 1===e?l(t):d(e,function e(t,n,r){return function(){for(var o=[],s=0,i=t,a=0;a<n.length||s<arguments.length;){var c;a<n.length&&(!u(n[a])||s>=arguments.length)?c=n[a]:(c=arguments[s],s+=1),o[a]=c,u(c)||(i-=1),a+=1}return i<=0?r.apply(this,o):d(i,e(t,o,r))}}(e,[],t))})),p=l((function(e){return f(e.length,e)})),m={\"@@functional/placeholder\":!0};function y(e,t){for(var n in e)e.hasOwnProperty(n)&&t(e[n],n)}var g=\"@@_______immutableOpsOwnerID\";function b(e,t){return!!t&&e[g]===t}var w=\"function\"==typeof Symbol?function(){return Symbol(\"ownerID\")}:function(){return{}};function v(e,t){return t&&function(e,t){Object.defineProperty(e,g,{value:t,configurable:!0,enumerable:!1})}(e,t),e}function _(e){return e instanceof Array?e:[e]}var M=\".\";function O(e){return\"string\"==typeof e?-1===e.indexOf(M)?[e]:e.split(M):e}function N(e,t,n){return n[e]=t,n}function S(e,t,n){var r=_(t);return e?r.forEach((function(t){y(t,(function(t,r){var o;e&&n.hasOwnProperty(r)?(o=\"object\"===c()(t)?S(e,[t],n[r]):t,n[r]=o):n[r]=t}))})):Object.assign.apply(Object,[n].concat(i()(r))),n}var k=S.bind(null,!1),E=S.bind(null,!0);function j(e,t){return _(e).forEach((function(e){delete t[e]})),t}function x(e,t,n){return e[n]!==t[n]}function F(e,t,n,r){if(b(r,t))return S(e,n,r);var o=_(n),s=!1,i=r,a=function(){s||(s=!0,v(i=Object.assign({},r),t))};return o.forEach((function(n){y(n,(function(o,s){if(e&&r.hasOwnProperty(s)){var u=i[s];if(\"object\"===c()(o)&&!(o instanceof Array)){if(x(i,n,s)){var l=F(e,t,o,u);l!==u&&(a(),i[s]=l)}return!0}}x(i,n,s)&&(a(),i[s]=o)}))})),i}var A=F.bind(null,!0);function C(e,t,n,r){if(b(r,e))return N(t,n,r);if(r[t]===n)return r;var o=function(e){for(var t=new Array(e.length),n=0;n<e.length;n++)t[n]=e[n];return t}(r);return o[t]=n,v(o,e),o}function I(e,t){for(var n=0,r=0;n<t.length;){e(t[n],r)?n++:t.splice(n,1),r++}return t}function R(e,t,n,r){var o=_(n);return r.splice.apply(r,[e,t].concat(i()(o))),r}function D(e,t,n){return R(e,0,t,n)}function $(e,t,n,r,o){if(b(o,e))return R(t,n,r,o);var s=_(r),a=o.slice();return v(a,e),a.splice.apply(a,[t,n].concat(i()(s))),a}function T(e,t,n,r){return b(r,e)?D(t,n,r):$(e,t,0,n,r)}var P={merge:F.bind(null,!1),deepMerge:A,omit:function(e,t,n){if(b(n,e))return j(t,n);var r=_(t).filter((function(e){return n.hasOwnProperty(e)}));if(0===r.length)return n;var o=Object.assign({},n);return r.forEach((function(e){delete o[e]})),v(o,e),o},setIn:function(e,t,n,r){var o=O(t),s=function(e,t){for(var n=O(e),r=t,o=0;o<n.length;o++){var s=r[n[o]];if(o===n.length-1)return s;if(\"object\"!==c()(s))return;r=s}}(o,r);if(n===s)return r;var i,a=o.length,u=i=b(r,e)?r:Object.assign(v({},e),r);return o.forEach((function(t,r){if(r!==a-1){var s=i[t],u=c()(s);if(\"object\"!==u){if(\"undefined\"===u){var l=v({},e);return i[t]=l,void(i=l)}var d=\"\".concat(o[r-1],\".\").concat(t);throw new Error(\"A non-object value was encountered when traversing setIn path at \".concat(d,\".\"))}if(b(s,e))i=s;else{var h=v({},e);i[t]=Object.assign(h,s),i=h}}else i[t]=n})),u},insert:T,push:function(e,t,n){return T(e,n.length,t,n)},filter:function(e,t,n){if(b(n,e))return I(t,n);var r=n.filter(t);return r.length===n.length?n:(v(r,e),r)},splice:$,set:function(e,t,n,r){if(function(e){return e&&\"object\"===c()(e)&&\"number\"==typeof e.length&&e.length>=0&&e.length%1==0}(r))return C(e,t,n,r);if(b(r,e))return N(t,n,r);if(r[t]===n)return r;var o=Object.assign({},r);return v(o,e),o[t]=n,o}},B={merge:k,deepMerge:E,omit:j,setIn:function(e,t,n){for(var r=O(e),o=r.length,s=!1,i=0,a=n,u=r[i];!s;)if(i===o-1)a[u]=t,s=!0;else{var l=c()(a[u]);if(\"undefined\"===l){var d={};v(d,null),a[u]=d}else if(\"object\"!==l){var h=\"\".concat(r[i-1],\".\").concat(u);throw new Error(\"A non-object value was encountered when traversing setIn path at \".concat(h,\".\"))}a=a[u],u=r[++i]}return n},insert:D,push:function(e,t){var n=_(e);return t.push.apply(t,i()(n)),t},filter:I,splice:R,set:N};var U=function(){var e=Object.assign({},P);y(e,(function(t,n){e[n]=p(t.bind(null,null))}));var t=Object.assign({},B);y(t,(function(e,n){t[n]=p(e)}));var n=Object.assign({},P);return y(n,(function(e,t){n[t]=p(e)})),Object.assign(e,{mutable:t,batch:n,batched:function(e,t){var n,r;\"function\"==typeof e?(r=e,n=w()):(n=e,r=t);var o=Object.assign({},P);return y(o,(function(e,t){o[t]=p(e.bind(null,n))})),r(o)},__:m,getBatchToken:w})}();const V=\"REDUX_ORM_UPDATE\",q=\"REDUX_ORM_DELETE\",z=\"REDUX_ORM_CREATE\",L=\"REDUX_ORM_FILTER\",Q=\"REDUX_ORM_EXCLUDE\",X=\"SUCCESS\",H=Symbol(\"REDUX_ORM_ALL_INSTANCES\"),Y=(e,t)=>void 0===t?H:t;function K(e){return(\"function\"==typeof console.warn?console.warn.bind(console):console.log.bind(console))(e)}function G(e,t){return e+((n=t).charAt(0).toUpperCase()+n.slice(1));var n}function J(e){return`from${e}Id`}function W(e){return`to${e}Id`}function Z(e){return function(...t){return this.getQuerySet()[e](...t)}}function ee(e){return function(){return this.getQuerySet()[e]}}function te(e,t){const n=t.sharedMethods.slice();!function(e,t){let n=e;for(;n!==Function.prototype;)t(n),n=Object.getPrototypeOf(n)}(t,t=>{for(let r=0;r<n.length;r++){let o=!1;const s=n[r],i=Object.getOwnPropertyDescriptor(t.prototype,s);void 0!==i&&(void 0!==i.get?(i.get=ee(s),Object.defineProperty(e,s,i)):e[s]=Z(s),o=!0),o&&n.splice(r--,1)}})}function ne(e){return null!=e&&\"function\"==typeof e.getId?e.getId():e}const{getBatchToken:re}=U;function oe({type:e,payload:t},n){if(e!==L)return!1;if(\"object\"!=typeof t)return!1;if(!t.hasOwnProperty(n))return!1;const r=t[n];return null!==r&&void 0!==r}function se(e,t){return Object.entries(e).reduce((e,[n,r])=>(e[n]=t(r),e),{})}function ie(e){return e&&\"string\"!=typeof e?e.modelName:e}const ae=function(){function e(e,t,n){Object.assign(this,{modelClass:e,clauses:t||[]}),this._opts=n}e.addSharedMethod=function(e){this.sharedMethods=this.sharedMethods.concat(e)};var t=e.prototype;return t._new=function(e,t){const n={...this._opts,...t};return new this.constructor(this.modelClass,e,n)},t.toString=function(){return this._evaluate(),`QuerySet contents:\\n    - ${this.rows.map(({id:e})=>this.modelClass.withId(e).toString()).join(\"\\n    - \")}`},t.toRefArray=function(){return this._evaluate()},t.toModelArray=function(){const{modelClass:e}=this;return this._evaluate().map(t=>new e(t))},t.count=function(){return this._evaluate(),this.rows.length},t.exists=function(){return Boolean(this.count())},t.at=function(e){const{modelClass:t}=this,n=this._evaluate();if(e>=0&&e<n.length)return new t(n[e])},t.first=function(){return this.at(0)},t.last=function(){const e=this._evaluate();return this.at(e.length-1)},t.all=function(){return this._new(this.clauses)},t.filter=function(e){const t=\"object\"==typeof e?se(e,ne):e,n={type:L,payload:t};return this._new(this.clauses.concat(n))},t.exclude=function(e){const t=\"object\"==typeof e?se(e,ne):e,n={type:Q,payload:t};return this._new(this.clauses.concat(n))},t._evaluate=function(){if(void 0===this.modelClass.session)throw new Error([`Tried to query the ${this.modelClass.modelName} model's table without a session. `,\"Create a session using `session = orm.session()` and use \",`\\`session[\"${this.modelClass.modelName}\"]\\` for querying instead.`].join(\"\"));if(!this._evaluated){const{session:e,modelName:t}=this.modelClass,n={table:t,clauses:this.clauses};this.rows=e.query(n).rows,this._evaluated=!0}return this.rows},t.orderBy=function(e,t){const n={type:\"REDUX_ORM_ORDER_BY\",payload:[e,t]};return this._new(this.clauses.concat(n))},t.update=function(e){const{session:t,modelName:n}=this.modelClass;t.applyUpdate({action:V,query:{table:n,clauses:this.clauses},payload:e}),this._evaluated=!1},t.delete=function(){const{session:e,modelName:t}=this.modelClass;this.toModelArray().forEach(e=>e._onDelete()),e.applyUpdate({action:q,query:{table:t,clauses:this.clauses}}),this._evaluated=!1},t.map=function(){throw new Error(\"`QuerySet.prototype.map` has been removed. Call `.toModelArray()` or `.toRefArray()` first to map.\")},t.forEach=function(){throw new Error(\"`QuerySet.prototype.forEach` has been removed. Call `.toModelArray()` or `.toRefArray()` first to iterate.\")},o()(e,[{key:\"withModels\",get:function(){throw new Error(\"`QuerySet.prototype.withModels` has been removed. Use `.toModelArray()` or predicate functions that instantiate Models from refs, e.g. `new Model(ref)`.\")}},{key:\"withRefs\",get:function(){K(\"`QuerySet.prototype.withRefs` has been deprecated. Query building operates on refs only now.\")}}]),e}();ae.sharedMethods=[\"count\",\"at\",\"all\",\"last\",\"first\",\"filter\",\"exclude\",\"orderBy\",\"update\",\"delete\"];var ce=ae;var ue=function(){function e(e,t,n,r,o){this.schema=e,this.db=t,this.state=n||t.getEmptyState(),this.initialState=this.state,this.withMutations=Boolean(r),this.batchToken=o||w(),this.modelData={},this.models=e.getModelClasses(),this.sessionBoundModels=this.models.map(e=>{function t(){return Reflect.construct(e,arguments,t)}return Reflect.setPrototypeOf(t.prototype,e.prototype),Reflect.setPrototypeOf(t,e),Object.defineProperty(this,e.modelName,{get:()=>t}),t.connect(this),t})}var t=e.prototype;return t.getDataForModel=function(e){return this.modelData[e]||(this.modelData[e]={}),this.modelData[e]},t.getModelData=function(){return this.modelData},t.markAccessed=function(e,t){const n=this.getDataForModel(e);n.accessedInstances||(n.accessedInstances={}),t.forEach(e=>{n.accessedInstances[e]=!0})},t.markFullTableScanned=function(e){this.getDataForModel(e).fullTableScanned=!0},t.markAccessedIndexes=function(e){e.forEach(([e,t,n])=>{const r=this.getDataForModel(e);r.accessedIndexes||(r.accessedIndexes={}),r.accessedIndexes[t]=[...r.accessedIndexes[t]||[],n]})},t.applyUpdate=function(e){const t=this._getTransaction(e),n=this.db.update(e,t,this.state),{status:r,state:o,payload:s}=n;if(r!==X)throw new Error(`Applying update failed with status ${r}. Payload: ${s}`);return this.state=o,s},t.query=function(e){const t=this.db.query(e,this.state);return this._markAccessedByQuery(e,t),t},t._getTransaction=function(e){const{withMutations:t}=this,{action:n}=e;let{batchToken:r}=this;return[V,q].includes(n)&&(r=w()),{batchToken:r,withMutations:t}},t._markAccessedByQuery=function(e,t){const{table:n,clauses:r}=e,{rows:o}=t,{idAttribute:s}=this[n],i=new Set(o.map(e=>e[s])),a=r.some(e=>!!oe(e,s)&&(i.add(e.payload[s]),!0)),c=[],{indexes:u}=this.state[n];r.forEach(e=>{Object.keys(u).forEach(t=>{if(!oe(e,t))return;const r=e.payload[t];c.push([n,t,r])})}),a?this.markAccessed(n,i):c.length?(this.markAccessed(n,i),this.markAccessedIndexes(c)):this.markFullTableScanned(n)},t.getNextState=function(){return K(\"`Session.prototype.getNextState` has been deprecated. Access the `Session.prototype.state` property instead.\"),this.state},t.reduce=function(){throw new Error(\"`Session.prototype.reduce` has been removed. The Redux integration API is now decoupled from ORM and Session - see the 0.9 migration guide in the GitHub repo.\")},o()(e,[{key:\"accessedModelInstances\",get:function(){return Object.entries(this.getModelData()).reduce((e,[t,n])=>(n.accessedInstances&&(e[t]=n.accessedInstances),e),{})}},{key:\"fullTableScannedModels\",get:function(){return Object.entries(this.getModelData()).reduce((e,[t,n])=>(n.fullTableScanned&&e.push(t),e),[])}},{key:\"accessedIndexes\",get:function(){return Object.entries(this.getModelData()).reduce((e,[t,n])=>(n.accessedIndexes&&(e[t]=n.accessedIndexes),e),{})}}]),e}(),le=n(1),de=n.n(le);var he=function(e){function t(){return e.apply(this,arguments)||this}de()(t,e);var n=t.prototype;return n.installForwardsDescriptor=function(){Object.defineProperty(this.model.prototype,this.fieldName,this.field.createForwardsDescriptor(this.fieldName,this.model,this.toModel,this.throughModel))},n.installForwardsVirtualField=function(){this.model.virtualFields[this.fieldName]=this.field.createForwardsVirtualField(this.fieldName,this.model,this.toModel,this.throughModel)},n.installBackwardsDescriptor=function(){if(Object.getOwnPropertyDescriptor(this.toModel.prototype,this.backwardsFieldName))throw new Error((e=this.model.modelName,t=this.fieldName,n=this.toModel.modelName,[`Reverse field ${this.backwardsFieldName} already defined`,` on model ${n}. To fix, set a custom related`,` name on ${e}.${t}.`].join(\"\")));var e,t,n;Object.defineProperty(this.toModel.prototype,this.backwardsFieldName,this.field.createBackwardsDescriptor(this.fieldName,this.model,this.toModel,this.throughModel))},n.installBackwardsVirtualField=function(){this.toModel.virtualFields[this.backwardsFieldName]=this.field.createBackwardsVirtualField(this.fieldName,this.model,this.toModel,this.throughModel)},t}(function(){function e(e){this.field=e.field,this.fieldName=e.fieldName,this.model=e.model,this.orm=e.orm,this.field.references(this.model)&&(this.field.toModelName=\"this\")}return e.prototype.run=function(){this.installForwardsDescriptor(),this.field.installsForwardsVirtualField&&this.installForwardsVirtualField(),this.field.installsBackwardsDescriptor&&this.installBackwardsDescriptor(),this.field.installsBackwardsVirtualField&&this.installBackwardsVirtualField()},o()(e,[{key:\"toModel\",get:function(){if(void 0===this._toModel){const{toModelName:e}=this.field;this._toModel=e?\"this\"===e?this.model:this.orm.get(e):null}return this._toModel}},{key:\"throughModel\",get:function(){if(void 0===this._throughModel){const e=this.field.getThroughModelName(this.fieldName,this.model);this._throughModel=e?this.orm.get(e):null}return this._throughModel}},{key:\"backwardsFieldName\",get:function(){return this.field.getBackwardsFieldName(this.model)}}]),e}());var fe=function(){function e(){}var t=e.prototype;return t.getClass=function(){return this.constructor},t.references=function(e){return!1},t.getThroughModelName=function(e,t){return null},o()(e,[{key:\"installerClass\",get:function(){return he}},{key:\"installsForwardsVirtualField\",get:function(){return!1}},{key:\"installsBackwardsDescriptor\",get:function(){return!1}},{key:\"installsBackwardsVirtualField\",get:function(){return!1}},{key:\"index\",get:function(){return!1}}]),e}();function pe(e,t){return{get(){const{session:{[t]:n}}=this.getClass(),{[e]:r}=this._fields;return n.withId(r)},set(t){this.update({[e]:ne(t)})}}}function me(e,t,n,r,o){return{get(){const{session:{[e]:s,[t]:i,[n]:a}}=this.getClass(),c=o?i:s,u=o?s:i,l=o?r.to:r.from,d=o?r.from:r.to,h=this.getId(),f=a.filter({[l]:h}),p=new Set(f.toRefArray().map(e=>e[d])),m=u.filter(e=>p.has(e[u.idAttribute]));return m.add=function(...e){const t=new Set(e.map(ne)),n=f.filter(e=>t.has(e[d]));if(n.exists()){const e=n.toRefArray().map(e=>e[d]);throw new Error(`Tried to add already existing ${u.modelName} id(s) ${e} to the ${c.modelName} instance with id ${h}`)}t.forEach(e=>{a.create({[d]:e,[l]:h})})},m.clear=function(){f.delete()},m.remove=function(...e){const t=new Set(e.map(ne)),n=f.filter(e=>t.has(e[d]));if(n.count()!==t.size){const e=n.toRefArray().map(e=>e[d]),r=[...t].filter(t=>!e.includes(t));throw new Error(`Tried to delete non-existing ${u.modelName} id(s) ${r} from the ${c.modelName} instance with id ${h}`)}n.delete()},m},set(){throw new Error(\"Tried setting a M2M field. Please use the related QuerySet methods add, remove and clear.\")}}}var ye=function(e){function t(t){var n;return(n=e.call(this)||this).opts=t||{},n.opts.hasOwnProperty(\"getDefault\")&&(n.getDefault=n.opts.getDefault),n}return de()(t,e),t.prototype.createForwardsDescriptor=function(e,t){return function(e){return{get(){return this._fields[e]},set(t){return this.set(e,t)},enumerable:!0,configurable:!0}}(e)},t}(fe);var ge=function(e){function t(...t){var n;if(n=e.call(this)||this,1===t.length&&\"object\"==typeof t[0]){const e=t[0];n.toModelName=ie(e.to),n.relatedName=e.relatedName,n.through=ie(e.through),n.throughFields=e.throughFields,n.as=e.as}else[n.toModelName,n.relatedName]=[ie(t[0]),t[1]];return n}de()(t,e);var n=t.prototype;return n.getBackwardsFieldName=function(e){return this.relatedName||e.modelName.toLowerCase()+\"Set\"},n.createBackwardsVirtualField=function(e,t,n,r){return new(this.getClass())(t.modelName,e)},n.references=function(e){return this.toModelName===e.modelName},o()(t,[{key:\"installsBackwardsVirtualField\",get:function(){return!0}},{key:\"installsBackwardsDescriptor\",get:function(){return!0}},{key:\"installerClass\",get:function(){return function(e){function t(){return e.apply(this,arguments)||this}return de()(t,e),t.prototype.installForwardsDescriptor=function(){Object.defineProperty(this.model.prototype,this.field.as||this.fieldName,this.field.createForwardsDescriptor(this.fieldName,this.model,this.toModel,this.throughModel))},t}(he)}}]),t}(fe);var be=function(e){function t(){return e.apply(this,arguments)||this}de()(t,e);var n=t.prototype;return n.createForwardsDescriptor=function(e,t,n,r){return pe(e,n.modelName)},n.createBackwardsDescriptor=function(e,t,n,r){return o=e,s=t.modelName,{get(){const{session:{[s]:e}}=this.getClass();return e.filter({[o]:this.getId()})},set(){throw new Error(\"Can't mutate a reverse many-to-one relation.\")}};var o,s},o()(t,[{key:\"index\",get:function(){return!0}}]),t}(ge);var we=function(e){function t(){return e.apply(this,arguments)||this}de()(t,e);var n=t.prototype;return n.getDefault=function(){return[]},n.getThroughModelName=function(e,t){return this.through||G(t.modelName,e)},n.createForwardsDescriptor=function(e,t,n,r){return me(t.modelName,n.modelName,r.modelName,this.getThroughFields(e,t,n,r),!1)},n.createBackwardsDescriptor=function(e,t,n,r){return me(t.modelName,n.modelName,r.modelName,this.getThroughFields(e,t,n,r),!0)},n.createBackwardsVirtualField=function(e,t,n,r){return new(this.getClass())({to:t.modelName,relatedName:e,through:r.modelName,throughFields:this.getThroughFields(e,t,n,r)})},n.createForwardsVirtualField=function(e,t,n,r){return new(this.getClass())({to:n.modelName,relatedName:e,through:this.through,throughFields:this.getThroughFields(e,t,n,r),as:this.as})},n.getThroughFields=function(e,t,n,r){if(this.throughFields){const[e,t]=this.throughFields,o=r.fields[e];return{to:o.references(n)?e:t,from:o.references(n)?t:e}}if(t.modelName===n.modelName)return{to:W(n.modelName),from:J(t.modelName)};const o=e=>Object.keys(r.fields).find(t=>r.fields[t].references(e));return{to:o(n),from:o(t)}},o()(t,[{key:\"installsForwardsVirtualField\",get:function(){return!0}}]),t}(ge);var ve=function(e){function t(){return e.apply(this,arguments)||this}de()(t,e);var n=t.prototype;return n.getBackwardsFieldName=function(e){return this.relatedName||e.modelName.toLowerCase()},n.createForwardsDescriptor=function(e,t,n,r){return function(...e){return pe(...e)}(e,n.modelName)},n.createBackwardsDescriptor=function(e,t,n,r){return o=e,s=t.modelName,{get(){const{session:{[s]:e}}=this.getClass();return e.get({[o]:this.getId()})},set(){throw new Error(\"Can't mutate a reverse one-to-one relation.\")}};var o,s},t}(ge);function _e(e){return new ye(e)}function Me(...e){return new be(...e)}function Oe(...e){return new we(...e)}function Ne(...e){return new ve(...e)}function Se(e){const t=e.getClass(),{idAttribute:n,modelName:r}=t;return{table:r,clauses:[{type:L,payload:{[n]:e.getId()}}]}}const ke=function(){function e(e){this._initFields(e)}var t=e.prototype;return t._initFields=function(e){const t=Object(e);this._fields={...t},Object.keys(t).forEach(e=>{e in this||Object.defineProperty(this,e,{get:()=>this._fields[e],set:t=>this.set(e,t),configurable:!0,enumerable:!0})})},e.toString=function(){return`ModelClass: ${this.modelName}`},e.options=function(){return{}},e.markAccessed=function(e){if(void 0===this._session)throw new Error([`Tried to mark rows of the ${this.modelName} model as accessed without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].markAccessed\\` instead.`].join(\"\"));this.session.markAccessed(this.modelName,e)},e.markFullTableScanned=function(){if(void 0===this._session)throw new Error([`Tried to mark the ${this.modelName} model as full table scanned without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].markFullTableScanned\\` instead.`].join(\"\"));this.session.markFullTableScanned(this.modelName)},e.markAccessedIndexes=function(e){if(void 0===this._session)throw new Error([`Tried to mark indexes for the ${this.modelName} model as accessed without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].markAccessedIndexes\\` instead.`].join(\"\"));this.session.markAccessedIndexes(e.map(([e,t])=>[this.modelName,e,t]))},e.connect=function(e){if(!(e instanceof ue))throw new Error(\"A model can only be connected to instances of Session.\");this._session=e},e.getQuerySet=function(){const{querySetClass:e}=this;return new e(this)},e.invalidateClassCache=function(){this.isSetUp=void 0,this.virtualFields={}},e.tableOptions=function(){return\"function\"==typeof this.backend?(K(\"`Model.backend` has been deprecated. Please rename to `.options`.\"),this.backend()):this.backend?(K(\"`Model.backend` has been deprecated. Please rename to `.options`.\"),this.backend):\"function\"==typeof this.options?this.options():this.options},e.create=function(e){if(void 0===this._session)throw new Error([`Tried to create a ${this.modelName} model instance without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].create\\` instead.`].join(\"\"));const t={...e},n={},r=Object.keys(this.fields),o=Object.keys(this.virtualFields);r.forEach(r=>{const o=this.fields[r],s=e.hasOwnProperty(r);if(o instanceof we)s&&(n[r]=e[r],o.as||delete t[r]);else if(s){const n=e[r];t[r]=ne(n)}else o.getDefault&&(t[r]=o.getDefault())}),o.forEach(r=>{if(!n.hasOwnProperty(r)){const o=this.virtualFields[r];e.hasOwnProperty(r)&&o instanceof we&&(n[r]=e[r],delete t[r])}});const s=new this(this.session.applyUpdate({action:z,table:this.modelName,payload:t}));return s._refreshMany2Many(n),s},e.upsert=function(e){if(void 0===this.session)throw new Error([`Tried to upsert a ${this.modelName} model instance without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].upsert\\` instead.`].join(\"\"));const{idAttribute:t}=this;if(e.hasOwnProperty(t)){const n=e[t];if(this.idExists(n)){const t=this.withId(n);return t.update(e),t}}return this.create(e)},e.withId=function(e){return this.get({[this.idAttribute]:e})},e.idExists=function(e){return this.exists({[this.idAttribute]:e})},e.exists=function(e){if(void 0===this.session)throw new Error([`Tried to check if a ${this.modelName} model instance exists without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].exists\\` instead.`].join(\"\"));return Boolean(this._findDatabaseRows(e).length)},e.get=function(e){const t=this._findDatabaseRows(e);if(0===t.length)return null;if(t.length>1)throw new Error(`Expected to find a single row in \\`${this.modelName}.get\\`. Found ${t.length}.`);return new this(t[0])},t.getClass=function(){return this.constructor},t.getId=function(){return this._fields[this.getClass().idAttribute]},e._findDatabaseRows=function(e){const t={table:this.modelName};return e&&(t.clauses=[{type:L,payload:e}]),this.session.query(t).rows},t.toString=function(){const e=this.getClass();return`${e.modelName}: {${Object.keys(e.fields).map(t=>{if(e.fields[t]instanceof we){return`${t}: [${this[t].toModelArray().map(e=>e.getId()).join(\", \")}]`}return`${t}: ${this._fields[t]}`}).join(\", \")}}`},t.equals=function(e){return function(e,t){const n=Object.entries(Object(e));return n.length===Object.keys(t).length&&n.every(([e,n])=>t.hasOwnProperty(e)&&t[e]===n)}(this._fields,e._fields)},t.set=function(e,t){this.update({[e]:t})},t.update=function(e){const t=this.getClass();if(void 0===t.session)throw new Error([`Tried to update a ${t.modelName} model instance without a session. `,\"You cannot call `.update` on an instance that you did not receive from the database.\"].join(\"\"));const n={...e},{fields:r,virtualFields:o}=t,s={};for(const e in n){if(r.hasOwnProperty(e)){const t=r[e];t instanceof be||t instanceof ve?n[e]=ne(n[e]):t instanceof we&&(s[e]=n[e],t.as||delete n[e])}else if(o.hasOwnProperty(e)){o[e]instanceof we&&(s[e]=n[e],delete n[e])}}const i={...this._fields,...n},a=new t(i);this.equals(a)||(this._initFields(i),t.session.applyUpdate({action:V,query:Se(this),payload:n})),this._refreshMany2Many(s)},t.refreshFromState=function(){this._initFields(this.ref)},t.delete=function(){const e=this.getClass();if(void 0===e.session)throw new Error([`Tried to delete a ${e.modelName} model instance without a session. `,\"You cannot call `.delete` on an instance that you did not receive from the database.\"].join(\"\"));this._onDelete(),e.session.applyUpdate({action:q,query:Se(this)})},t._refreshMany2Many=function(e){const t=this.getClass(),{fields:n,virtualFields:r,modelName:o}=t;Object.keys(e).forEach(s=>{const i=!n.hasOwnProperty(s),a=r[s],c=e[s];if(!Array.isArray(c))throw new TypeError(`Failed to resolve many-to-many relationship: ${o}[${s}] must be an array (passed: ${c})`);const u=c.map(ne),l=[...new Set(u)];if(u.length!==l.length)throw new Error(`Found duplicate id(s) when passing \"${u}\" to ${t.modelName}.${s} value`);const d=a.through||G(t.modelName,s),h=t.session[d];let f,p;i?({from:p,to:f}=a.throughFields):({from:f,to:p}=a.throughFields);const m=function(e,t){const n=e.filter(e=>t.includes(e)),r=e.filter(e=>!n.includes(e)),o=t.filter(e=>!n.includes(e));return r.length||o.length?{delete:r,add:o}:null}(h.filter(e=>e[f]===this[t.idAttribute]).toRefArray().map(e=>e[p]),u);if(m){const{delete:e,add:t}=m;e.length>0&&this[a.as||s].remove(...e),t.length>0&&this[a.as||s].add(...t)}})},t._onDelete=function(){const{virtualFields:e}=this.getClass();for(const t in e){const n=e[t];if(n instanceof we){this[n.as||t].clear()}else if(n instanceof be){const e=this[t];e.exists()&&e.update({[n.relatedName]:null})}else n instanceof ve&&null!==this[t]&&(this[t][n.relatedName]=null)}},e.hasId=function(e){return console.warn(\"`Model.hasId` has been deprecated. Please use `Model.idExists` instead.\"),this.idExists(e)},t.getNextState=function(){throw new Error(\"`Model.prototype.getNextState` has been removed. See the 0.9 migration guide on the GitHub repo.\")},o()(e,[{key:\"ref\",get:function(){const e=this.getClass();return e._findDatabaseRows({[e.idAttribute]:this.getId()})[0]}}],[{key:\"idAttribute\",get:function(){if(void 0===this._session)throw new Error([`Tried to get the ${this.modelName} model's id attribute without a session. `,\"Create a session using `session = orm.session()` and access \",`\\`session[\"${this.modelName}\"].idAttribute\\` instead.`].join(\"\"));return this.session.db.describe(this.modelName).idAttribute}},{key:\"session\",get:function(){return this._session}},{key:\"query\",get:function(){return this.getQuerySet()}}]),e}();ke.fields={id:_e()},ke.virtualFields={},ke.querySetClass=ce;var Ee=ke,je=n(11),xe=n.n(je),Fe=n(12),Ae=n.n(Fe),Ce=n(13),Ie=n.n(Ce),Re=n(14),De=n.n(Re);const $e={idAttribute:\"id\",arrName:\"items\",mapName:\"itemsById\",fields:{}};var Te=function(){function e(e){Object.assign(this,$e,e)}var t=e.prototype;return t.accessId=function(e,t){return e[this.mapName][t]},t.accessIds=function(e,t){const n=e[this.mapName];return t.map(e=>n[e])},t.idExists=function(e,t){return e[this.mapName].hasOwnProperty(t)},t.accessIdList=function(e){return e[this.arrName]},t.accessList=function(e){return this.accessIds(e,this.accessIdList(e))},t.getMaxId=function(e){return this.getMeta(e,\"maxId\")},t.setMaxId=function(e,t,n){return this.setMeta(e,t,\"maxId\",n)},t.nextId=function(e){return e+1},t.getEmptyState=function(){return{...{[this.arrName]:[],[this.mapName]:{}},indexes:Object.keys(this.fields).filter(e=>e!==this.idAttribute).filter(e=>this.fields[e].index).reduce((e,t)=>({...e,[t]:{}}),{}),meta:{}}},t.setMeta=function(e,t,n,r){const{batchToken:o,withMutations:s}=e;if(s){return U.mutable.setIn([\"meta\",n],r,t)}return U.batch.setIn(o,[\"meta\",n],r,t)},t.getMeta=function(e,t){return e.meta[t]},t.query=function(e,t){if(0===t.length)return this.accessList(e);const{idAttribute:n}=this,r=De()(t,e=>oe(e,n)?1:function({type:e}){return[L,Q].includes(e)}(e)?2:3),o=(t,r)=>{const{type:s,payload:i}=r;if(!t){if(oe(r,n)){const t=i[n],s=Object.keys(i).reduce((e,t)=>(t!==n&&(e[t]=i[t]),e),{}),a=this.idExists(e,t)?[t]:[];return Object.keys(s).length?o(this.accessIds(e,a),{...r,payload:s}):this.accessIds(e,a)}if(s===L&&\"object\"==typeof i){const t=Object.entries(e.indexes),n=[],s=[];if(t.forEach(([e,t])=>{oe(r,e)&&t.hasOwnProperty(i[e])&&(n.push(t[i[e]]),s.push(e))}),n.length){const t=n.pop(),a=n.reduce((e,t)=>{const n=new Set(t);return e.filter(Set.prototype.has,n)},t),c=Object.keys(i).reduce((e,t)=>(s.includes(t)||(e[t]=i[t]),e),{});return Object.keys(c).length?o(this.accessIds(e,a),{...r,payload:c}):this.accessIds(e,a)}}return o(this.accessList(e),r)}switch(s){case L:return xe()(t,i);case Q:return Ie()(t,i);case\"REDUX_ORM_ORDER_BY\":{const[e,n]=i;return Ae()(t,e,function(e){if(void 0===e)return;const t=e=>[\"desc\",!1].includes(e)?\"desc\":\"asc\";return Array.isArray(e)?e.map(t):t(e)}(n))}default:return t}};return r.reduce(o,void 0)},t.insert=function(e,t,n){const{batchToken:r,withMutations:o}=e,s=n.hasOwnProperty(this.idAttribute);let i=t;const[a,c]=function(e,t){let n,r,o=e;return void 0===o&&(o=-1),void 0===t?(n=o+1,r=n):(n=Math.max(o+1,t),r=t),[n,r]}(this.getMaxId(t),n[this.idAttribute]);i=this.setMaxId(e,t,a);const u=s?n:U.batch.set(r,this.idAttribute,c,n),l=Object.keys(i.indexes).filter(e=>n.hasOwnProperty(e)&&null!==n[e]).map(e=>[e,n[e]]);if(o)return U.mutable.push(c,i[this.arrName]),U.mutable.set(c,u,i[this.mapName]),l.forEach(([e,t])=>{const n=i.indexes[e];n.hasOwnProperty(t)?U.mutable.push(c,n[t]):U.mutable.set(t,[c],n)}),{state:i,created:u};const d=U.batch.merge(r,l.reduce((e,[t,n])=>(e[t]=U.batch.merge(r,{[n]:U.batch.push(r,c,e[t][n]||[])},e[t]),e),{...i.indexes}),i.indexes);return{state:U.batch.merge(r,{[this.arrName]:U.batch.push(r,c,i[this.arrName]),[this.mapName]:U.batch.merge(r,{[c]:u},i[this.mapName]),indexes:d},i),created:u}},t.update=function(e,t,n,r){const{batchToken:o,withMutations:s}=e,i=s?U.mutable.set:U.batch.set(o),a=Object.keys(t.indexes).filter(e=>r.hasOwnProperty(e)),c=[],u=[],l=n.reduce((e,t)=>{const n=a.reduce((e,n)=>({...e,[n]:t[n]}),{}),l=(e=>{return(s?U.mutable.merge:U.batch.merge(o))(r,e)})(t),d=a.reduce((e,t)=>({...e,[t]:l[t]}),{}),h=l[this.idAttribute],f=i(h,l,e);return a.forEach(e=>{const{[e]:t}=n,{[e]:r}=d;t!==r&&(null!=t&&u.push([e,t,h]),null!==r&&c.push([e,r,h]))}),f},t[this.mapName]);let d=t.indexes;return s?(u.forEach(([e,t,n])=>{const r=d[e][t],o=r.indexOf(n);U.mutable.splice(o,1,[],r)}),c.forEach(([e,t,n])=>{U.mutable.push(n,d[e][t])})):(c.length&&(d=U.batch.merge(o,c.reduce((e,[t,n,r])=>(e[t]=U.batch.merge(o,{[n]:U.batch.push(o,r,e[t][n]||[])},e[t]),e),{...d}),d)),u.length&&(d=U.batch.merge(o,u.reduce((e,[t,n,r])=>(e[t]=U.batch.merge(o,{[n]:U.batch.filter(o,e=>e!==r,e[t][n])},e[t]),e),{...d}),d))),U.batch.merge(o,{[this.mapName]:l,indexes:d},t)},t.delete=function(e,t,n){const{batchToken:r,withMutations:o}=e,{arrName:s,mapName:i}=this,a=t[s],c=n.map(e=>e[this.idAttribute]);if(o)return c.forEach(e=>{const n=a.indexOf(e);U.mutable.splice(n,1,[],a),U.mutable.omit(e,t[i])}),Object.values(t.indexes).forEach(e=>Object.values(e).forEach(e=>c.forEach(t=>{const n=e.indexOf(t);-1!==n&&U.mutable.splice(n,1,[],e)}))),t;const u=U.batch.merge(r,Object.entries(t.indexes).reduce((e,[t,n])=>(e[t]=U.batch.merge(r,Object.entries(n).reduce((e,[t,n])=>(e[t]=U.batch.filter(r,e=>!c.includes(e),n),e),{...e[t]}),e[t]),e),{...t.indexes}),t.indexes);return U.batch.merge(r,{[s]:U.batch.filter(r,e=>!c.includes(e),t[s]),[i]:U.batch.omit(r,c,t[i]),indexes:U.batch.merge(r,u,t.indexes)},t)},e}();const Pe={};function Be(e,t,n){const{table:r,clauses:o}=t;return{rows:e[r].query(n[r],o)}}function Ue(e,t,n,r){const{action:o,payload:s}=t;let i,a,c;if(o===z){({table:i}=t);const o=e[i],u=r[i],l=o.insert(n,u,s);a=l.state,c=l.created}else{const{query:u}=t;({table:i}=u);const{rows:l}=Be(e,u,r),d=e[i],h=r[i];if(o===V)a=d.update(n,h,l,s),c=Be(e,u,r).rows;else{if(o!==q)throw new Error(`Database received unknown update type: ${o}`);a=d.delete(n,h,l),c=l}}const u=function(e,t,n,r){const{batchToken:o,withMutations:s}=n;return s?(r[e]=t,r):U.batch.set(o,e,t,r)}(i,a,n,r);return{status:X,state:u,payload:c}}Object.defineProperty(Pe,\"@@_______REDUX_ORM_STATE_FLAG\",{enumerable:!0,value:!0});var Ve=function(e){const{tables:t}=e,n=Object.entries(t).reduce((e,[t,n])=>({...e,[t]:new Te(n)}),{});return{getEmptyState:()=>Object.entries(n).reduce((e,[t,n])=>({...e,[t]:n.getEmptyState()}),Pe),query:Be.bind(null,n),update:Ue.bind(null,n),describe:e=>n[e]}};let qe=function(){function e({parent:e,orm:t}){this._parent=e,this._orm=t,this.keySelector=Y}return o()(e,[{key:\"cachePath\",get:function(){return[...this._parent?this._parent.cachePath:[],this.key]}},{key:\"orm\",get:function(){return this._orm}},{key:\"parent\",get:function(){return this._parent}}]),e}(),ze=function(e){function t({model:t,...n}){var r;return(r=e.call(this,n)||this)._model=t,r}return de()(t,e),o()(t,[{key:\"resultFunc\",get:function(){return(e,t,...n)=>{const{[this._model.modelName]:r}=e;return void 0===t?r.all().toModelArray().map(t=>this.valueForInstance(t,e,...n)):Array.isArray(t)?t.map(t=>this.valueForInstance(r.withId(t),e,...n)):this.valueForInstance(r.withId(t),e,...n)}}},{key:\"model\",get:function(){return this._model}}]),t}(qe);function Le(e,t){return t}let Qe=function(e){function t({field:t,selector:n,...r}){var o;return(o=e.call(this,r)||this)._field=t,o._selector=n,o}return de()(t,e),t.prototype.createResultFunc=function(e){const{idAttribute:t}=this._parent.toModel;return(n,...r)=>{const o=e(n,...r),s=Le(n,...r),i=e=>null===e?null:e.map(e=>this._selector(n,e[t]));return void 0===s||Array.isArray(s)?o.map(i):i(o)}},o()(t,[{key:\"selector\",get:function(){return this._selector},set:function(e){this._selector=e}},{key:\"key\",get:function(){return this._selector}}]),t}(ze),Xe=function(e){function t({model:t,...n}){var r;return(r=e.call(this,n)||this)._model=t,r}return de()(t,e),o()(t,[{key:\"key\",get:function(){return this._model.modelName}},{key:\"dependencies\",get:function(){return[this._orm,Le]}},{key:\"resultFunc\",get:function(){return({[this._model.modelName]:e},t)=>{if(void 0===t)return e.all().toRefArray();if(Array.isArray(t))return t.map(t=>{const n=e.withId(t);return n?n.ref:null});const n=e.withId(t);return n?n.ref:null}}},{key:\"model\",get:function(){return this._model}}]),t}(qe),He=function(e){function t({field:t,fieldModel:n,accessorName:r,isVirtual:o,...s}){var i;return(i=e.call(this,s)||this)._field=t,i._fieldModel=n,i._accessorName=r,i._isVirtual=o,i}de()(t,e);var n=t.prototype;return n.valueForInstance=function(e,t){if(!e)return null;let n;if(this._parent instanceof Xe)n=e[this._accessorName];else{const{[this._parent.toModelName]:r}=t,o=this._parent.valueForInstance(e,t),s=o?new r(o):null;n=s?s[this._accessorName]:null}return n instanceof Ee?n.ref:n instanceof ce?n.toRefArray():n},n.map=function(e){if(e instanceof Xe)throw this.toModelName===e.model.modelName?new Error(`Cannot select models in a \\`map()\\` call. If you just want the \\`${this._accessorName}\\` as a ref array then you can simply drop the \\`map()\\`. Otherwise make sure you're passing a field selector of the form \\`${this.toModelName}.<field>\\` or a custom selector instead.`):new Error(`Cannot select \\`${e.model.modelName}\\` models in this \\`map()\\` call. Make sure you're passing a field selector of the form \\`${this.toModelName}.<field>\\` or a custom selector instead.`);if(e instanceof t||e instanceof Qe){if(this.toModelName!==e.model.modelName)throw new Error(`Cannot select fields of the \\`${e.model.modelName}\\` model in this \\`map()\\` call. Make sure you're passing a field selector of the form \\`${this.toModelName}.<field>\\` or a custom selector instead.`)}else if(!e||\"function\"!=typeof e||!e.recomputations)throw new Error(`\\`map()\\` requires a selector as an input. Received: ${JSON.stringify(e)} of type ${typeof e}`);if(!(this._field instanceof be||this._field instanceof we))throw new Error(\"Cannot map selectors for non-collection fields\");return new Qe({parent:this,model:this._model,orm:this._orm,field:this._field,selector:e})},o()(t,[{key:\"key\",get:function(){return this._accessorName}},{key:\"dependencies\",get:function(){return[this._orm,Le]}},{key:\"toModelName\",get:function(){return\"this\"===this._field.toModelName?this._fieldModel.modelName:this._field.toModelName}},{key:\"toModel\",get:function(){return this._orm.getDatabase().describe(this.toModelName)}}]),t}(ze);function Ye({parent:e,model:t,field:n,fieldModel:r,accessorName:o,orm:s,isVirtual:i}){const a=new He({parent:e,model:t,field:n,fieldModel:r,accessorName:o,orm:s,isVirtual:i});if(!(n instanceof ge))return a;if(e instanceof He&&(e._field instanceof be&&e._isVirtual||e._field instanceof we))throw new Error(`Cannot create a selector for \\`${e._accessorName}.${o}\\` because \\`${e._accessorName}\\` is a collection field.`);const{toModelName:c}=n,u=s.get(\"this\"===c?t.modelName:c);return Object.entries(u.fields).forEach(([e,n])=>{const r=n.as||e;Object.defineProperty(a,r,{get:()=>Ye({parent:a,model:t,fieldModel:u,field:n,accessorName:r,orm:s,isVirtual:!1})})}),Object.entries(u.virtualFields).forEach(([e,n])=>{const r=n.as||e;a.hasOwnProperty(r)||Object.defineProperty(a,r,{get:()=>Ye({parent:a,model:t,fieldModel:u,field:n,accessorName:r,orm:s,isVirtual:!0})})}),a}const Ke={createDatabase:Ve},Ge=[\"indexes\",\"meta\"],Je=e=>Ge.includes(e);let We=function(){function e(e){const{createDatabase:t}={...Ke,...e||{}};this.createDatabase=t,this.registry=[],this.implicitThroughModels=[],this.installedFields={},this.stateSelector=e?e.stateSelector:null}var t=e.prototype;return t.register=function(...e){e.forEach(e=>{if(void 0===e.modelName)throw new Error(\"A model was passed that doesn't have a modelName set\");e.invalidateClassCache(),this.registerManyToManyModelsFor(e),this.registry.push(e),Object.defineProperty(this,e.modelName,{get:()=>(this._setupModelPrototypes(this.registry),function({model:e,orm:t}){const n=new Xe({parent:null,orm:t,model:e});return Object.entries(e.fields).forEach(([r,o])=>{const s=o.as||r;Object.defineProperty(n,s,{get:()=>Ye({parent:n,model:e,fieldModel:e,field:o,accessorName:s,orm:t,isVirtual:!1})})}),Object.entries(e.virtualFields).forEach(([r,o])=>{const s=o.as||r;n.hasOwnProperty(s)||Object.defineProperty(n,s,{get:()=>Ye({parent:n,model:e,fieldModel:e,field:o,accessorName:s,orm:t,isVirtual:!0})})}),n}({model:e,orm:this}))})})},t.registerManyToManyModelsFor=function(e){const{fields:t}=e,n=e.modelName;Object.entries(t).forEach(([e,t])=>{if(!(t instanceof we))return;let r;r=\"this\"===t.toModelName?n:t.toModelName;const s=n===r,i=J(n),a=W(r);if(t.through){if(s&&!t.throughFields)throw new Error(\"Self-referencing many-to-many relationship at \"+`\"${n}.${e}\" using custom `+`model \"${t.through}\" has no `+\"throughFields key. Cannot determine which fields reference the instances partaking in the relationship.\")}else{const t=function(e){function t(){return e.apply(this,arguments)||this}return de()(t,e),t}(Ee);t.modelName=G(n,e);const c=function(e){function t(){return e.apply(this,arguments)||this}return de()(t,e),o()(t,[{key:\"installsBackwardsVirtualField\",get:function(){return!1}},{key:\"installsBackwardsDescriptor\",get:function(){return!1}}]),t}(be),u=s?c:be;t.fields={id:_e(),[i]:new u(n),[a]:new u(r)},t.invalidateClassCache(),this.implicitThroughModels.push(t)}})},t.get=function(e){const t=this.registry.concat(this.implicitThroughModels),n=Object.values(t).find(t=>t.modelName===e);if(void 0===n)throw new Error(`Did not find model ${e} from registry.`);return n},t.getModelClasses=function(){return this._setupModelPrototypes(this.registry),this._setupModelPrototypes(this.implicitThroughModels),this.registry.concat(this.implicitThroughModels)},t.generateSchemaSpec=function(){return{tables:this.getModelClasses().reduce((e,t)=>{const n=t.modelName,r=t.tableOptions();return Object.keys(r).filter(Je).forEach(e=>{throw new Error(`Reserved keyword \\`${e}\\` used in ${n}.options.`)}),e[n]={fields:{...t.fields},...r},e},{})}},t.getDatabase=function(){return this.db||(this.db=this.createDatabase(this.generateSchemaSpec())),this.db},t.getEmptyState=function(){return this.getDatabase().getEmptyState()},t.session=function(e){return new ue(this,this.getDatabase(),e)},t.mutableSession=function(e){return new ue(this,this.getDatabase(),e,!0)},t._setupModelPrototypes=function(e){e.filter(e=>!e.isSetUp).forEach(e=>{const{fields:t,modelName:n,querySetClass:r}=e;Object.entries(t).forEach(([t,r])=>{if(!(r instanceof fe))throw new Error(`${n}.${t} is of type \"${typeof r}\" `+\"but must be an instance of Field. Please use the `attr`, `fk`, `oneToOne` and `many` functions to define fields.\");this._isFieldInstalled(n,t)||(this._installField(r,t,e),this._setFieldInstalled(n,t))}),te(e,r),e.isSetUp=!0})},t._isFieldInstalled=function(e,t){return!!this.installedFields.hasOwnProperty(e)&&!!this.installedFields[e][t]},t._setFieldInstalled=function(e,t){this.installedFields.hasOwnProperty(e)||(this.installedFields[e]={}),this.installedFields[e][t]=!0},t._installField=function(e,t,n){new(0,e.installerClass)({field:e,fieldName:t,model:n,orm:this}).run()},t.withMutations=function(e){return K(\"`ORM.prototype.withMutations` has been deprecated. Use `ORM.prototype.mutableSession` instead.\"),this.mutableSession(e)},t.from=function(e){return K(\"`ORM.prototype.from` has been deprecated. Use `ORM.prototype.session` instead.\"),this.session(e)},t.getDefaultState=function(){return K(\"`ORM.prototype.getDefaultState` has been deprecated. Use `ORM.prototype.getEmptyState` instead.\"),this.getEmptyState()},t.define=function(){throw new Error(\"`ORM.prototype.define` has been removed. Please define a Model class.\")},e}();var Ze=n(4),et=n(5),tt=n.n(et);const nt=(e,t)=>e===t,rt=e=>e&&\"object\"==typeof e&&e.hasOwnProperty(\"@@_______REDUX_ORM_STATE_FLAG\"),ot=(e,t,n)=>t.every((t,r)=>rt(t)&&rt(e[r])||n(t,e[r])),st=(e,t,n)=>{const{accessedInstances:r}=e;return Object.entries(r).every(([r,o])=>{if(e.ormState[r]===t[r])return!0;const{mapName:s}=n.getDatabase().describe(r),{[s]:i}=e.ormState[r],{[s]:a}=t[r];return((e,t,n)=>e.every(e=>t[e]===n[e]))(Object.keys(o),i,a)})},it=(e,t)=>{const{accessedIndexes:n}=e;return Object.entries(n).every(([n,r])=>Object.entries(r).every(([r,o])=>o.every(o=>e.ormState[n].indexes[r][o]===t[n].indexes[r][o])))},at=(e,t)=>e.fullTableScannedModels.every(n=>e.ormState[n]===t[n]);function ct(e,t=nt,n){let r={result:null,args:null,ormState:null,fullTableScannedModels:[],accessedInstances:{},accessedIndexes:{}};return(...o)=>{const[s,...i]=o;if(Boolean(r.args)&&ot(r.args,i,t)&&at(r,s)&&it(r,s)&&st(r,s,n))return r.result;const a=n.session(s),c=i.map(e=>rt(e)?a:e),u=e.apply(null,c);return r={args:i,result:u,ormState:s,accessedInstances:a.accessedModelInstances,accessedIndexes:a.accessedIndexes,fullTableScannedModels:a.fullTableScannedModels},u}}function ut(e,t){e.sessionBoundModels.forEach(n=>{\"function\"==typeof n.reducer&&n.reducer(t,n,e)})}function lt(e,t=ut){return(n,r)=>{const o=e.session(n||e.getEmptyState());return t(o,r),o.state}}function dt(e){return e instanceof We?e:e instanceof qe&&e._orm}const ht=new Map,ft=Symbol.for(\"REDUX_ORM_SELECTOR\");function pt(e){if(\"function\"==typeof e)return e;if(e instanceof We)return e.stateSelector;if(e instanceof Qe&&(e.selector=pt(e.selector)),e instanceof qe){const{orm:t,cachePath:n}=e;let r;ht.has(t)||ht.set(t,new Map),r=ht.get(t);for(let e=0;e<n.length;++e){const t=n[e];r.has(t)||r.set(t,new Map),r=r.get(t)}if(r&&r.has(ft))return r.get(ft);const o=function e(t){if(t instanceof Qe){const n=e(t.parent);return t.createResultFunc(n)}return tt()(t.dependencies,t.resultFunc)({keySelector:t.keySelector,cacheObject:new et.FlatMapCache,selectorCreator:mt})}(e);return r.set(ft,o),o}throw new Error(`Failed to interpret selector argument: ${JSON.stringify(e)} of type ${typeof e}`)}function mt(...e){if(!e.length)throw new Error(\"Cannot create a selector without arguments.\");const t=e.pop(),n=Array.isArray(e[0])?e[0]:e,r=n.map(dt).find(Boolean),o=n.map(pt);if(\"function\"==typeof t){if(!r)throw new Error(\"Failed to resolve the current ORM database state. Please pass an ORM instance or an ORM selector as an argument to `createSelector()`.\");if(!r.stateSelector)throw new Error(\"Failed to resolve the current ORM database state. Please pass an object to the ORM constructor that specifies a `stateSelector` function.\");if(\"function\"!=typeof r.stateSelector)throw new Error(`Failed to resolve the current ORM database state. Please pass a function when specifying the ORM's \\`stateSelector\\`. Received: ${JSON.stringify(r.stateSelector)} of type ${typeof r.stateSelector}`);return Object(Ze.createSelectorCreator)(ct,void 0,r)([r.stateSelector,...o],t)}if(t instanceof We)throw new Error(\"ORM instances cannot be the result function of selectors. You can access your models in the last function that you pass to `createSelector()`.\");return o.length&&console.warn(\"Your input selectors will be ignored: the passed result function does not require any input.\"),pt(t)}n.d(t,\"Schema\",(function(){return yt})),n.d(t,\"Backend\",(function(){return gt})),n.d(t,\"Attribute\",(function(){return ye})),n.d(t,\"QuerySet\",(function(){return ce})),n.d(t,\"Model\",(function(){return Ee})),n.d(t,\"ORM\",(function(){return We})),n.d(t,\"Session\",(function(){return ue})),n.d(t,\"ForeignKey\",(function(){return be})),n.d(t,\"ManyToMany\",(function(){return we})),n.d(t,\"OneToOne\",(function(){return ve})),n.d(t,\"fk\",(function(){return Me})),n.d(t,\"many\",(function(){return Oe})),n.d(t,\"attr\",(function(){return _e})),n.d(t,\"oneToOne\",(function(){return Ne})),n.d(t,\"createReducer\",(function(){return lt})),n.d(t,\"createSelector\",(function(){return mt}));const yt=function(){throw new Error(\"Schema has been renamed to ORM. Please import ORM instead of Schema from Redux-ORM.\")},gt=function(){throw new Error(\"Having a custom Backend instance is now unsupported. Documentation for database customization is upcoming, for now please look at the db folder in the source.\")};t.default=Ee}])}));\n+!function(e,t){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=t():\"function\"==typeof define&&define.amd?define(\"ReduxOrm\",[],t):\"object\"==typeof exports?exports.ReduxOrm=t():e.ReduxOrm=t()}(window,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){\"undefined\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:\"Module\"}),Object.defineProperty(e,\"__esModule\",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&\"object\"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,\"default\",{enumerable:!0,value:e}),2&t&&\"string\"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,\"a\",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p=\"\",n(n.s=37)}([function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}e.exports=function(e,t,r){return t&&n(e.prototype,t),r&&n(e,r),e},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){var r=n(15);e.exports=function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,r(e,t)},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){function n(t){return\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?(e.exports=n=function(e){return typeof e},e.exports.default=e.exports,e.exports.__esModule=!0):(e.exports=n=function(e){return e&&\"function\"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e},e.exports.default=e.exports,e.exports.__esModule=!0),n(t)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){var r=n(16),o=n(17),s=n(18),i=n(19);e.exports=function(e){return r(e)||o(e)||s(e)||i()},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){var n=Array.isArray;e.exports=n},function(e,t,n){\"use strict\";function r(e,t){return e===t}function o(e,t,n){if(null===t||null===n||t.length!==n.length)return!1;for(var r=t.length,o=0;o<r;o++)if(!e(t[o],n[o]))return!1;return!0}function s(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:r,n=null,s=null;return function(){return o(t,n,arguments)||(s=e.apply(null,arguments)),n=arguments,s}}function i(e){var t=Array.isArray(e[0])?e[0]:e;if(!t.every((function(e){return\"function\"==typeof e}))){var n=t.map((function(e){return typeof e})).join(\", \");throw new Error(\"Selector creators expect all input-selectors to be functions, instead received the following types: [\"+n+\"]\")}return t}function a(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];return function(){for(var t=arguments.length,r=Array(t),o=0;o<t;o++)r[o]=arguments[o];var a=0,c=r.pop(),u=i(r),l=e.apply(void 0,[function(){return a++,c.apply(null,arguments)}].concat(n)),d=s((function(){for(var e=[],t=u.length,n=0;n<t;n++)e.push(u[n].apply(null,arguments));return l.apply(null,e)}));return d.resultFunc=c,d.recomputations=function(){return a},d.resetRecomputations=function(){return a=0},d}}t.__esModule=!0,t.defaultMemoize=s,t.createSelectorCreator=a,t.createStructuredSelector=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:c;if(\"object\"!=typeof e)throw new Error(\"createStructuredSelector expects first argument to be an object where each property is a selector, instead received a \"+typeof e);var n=Object.keys(e);return t(n.map((function(t){return e[t]})),(function(){for(var e=arguments.length,t=Array(e),r=0;r<e;r++)t[r]=arguments[r];return t.reduce((function(e,t,r){return e[n[r]]=t,e}),{})}))};var c=t.createSelector=a(s)},function(e,t,n){!function(e,t){\"use strict\";function n(e){return\"string\"==typeof e||\"number\"==typeof e}var r=function(){function e(){this._cache={}}var t=e.prototype;return t.set=function(e,t){this._cache[e]=t},t.get=function(e){return this._cache[e]},t.remove=function(e){delete this._cache[e]},t.clear=function(){this._cache={}},t.isValidCacheKey=function(e){return n(e)},e}(),o=r,s=function(){return!0};function i(){for(var e=arguments.length,n=new Array(e),r=0;r<e;r++)n[r]=arguments[r];return function(e,r){if(r)throw new Error('[re-reselect] \"options\" as second argument is not supported anymore. Please provide an option object as single argument.');var i=\"function\"==typeof e?{keySelector:e}:Object.assign({},e),a=0,c=n.pop(),u=Array.isArray(n[0])?n[0]:[].concat(n);n.push((function(){return a++,c.apply(void 0,arguments)}));var l=i.cacheObject||new o,d=i.selectorCreator||t.createSelector,h=l.isValidCacheKey||s;i.keySelectorCreator&&(i.keySelector=i.keySelectorCreator({keySelector:i.keySelector,inputSelectors:u,resultFunc:c}));var f=function(){var e=i.keySelector.apply(i,arguments);if(h(e)){var t=l.get(e);return void 0===t&&(t=d.apply(void 0,n),l.set(e,t)),t.apply(void 0,arguments)}console.warn('[re-reselect] Invalid cache key \"'+e+'\" has been returned by keySelector function.')};return f.getMatchingSelector=function(){var e=i.keySelector.apply(i,arguments);return l.get(e)},f.removeMatchingSelector=function(){var e=i.keySelector.apply(i,arguments);l.remove(e)},f.clearCache=function(){l.clear()},f.resultFunc=c,f.dependencies=u,f.cache=l,f.recomputations=function(){return a},f.resetRecomputations=function(){return a=0},f.keySelector=i.keySelector,f}}function a(e){if(void 0===e)throw new Error('Missing the required property \"cacheSize\".');if(!Number.isInteger(e)||e<=0)throw new Error('The \"cacheSize\" property must be a positive integer value.')}var c=function(){function e(e){var t=(void 0===e?{}:e).cacheSize;a(t),this._cache={},this._cacheOrdering=[],this._cacheSize=t}var t=e.prototype;return t.set=function(e,t){if(this._cache[e]=t,this._cacheOrdering.push(e),this._cacheOrdering.length>this._cacheSize){var n=this._cacheOrdering[0];this.remove(n)}},t.get=function(e){return this._cache[e]},t.remove=function(e){var t=this._cacheOrdering.indexOf(e);t>-1&&this._cacheOrdering.splice(t,1),delete this._cache[e]},t.clear=function(){this._cache={},this._cacheOrdering=[]},t.isValidCacheKey=function(e){return n(e)},e}(),u=function(){function e(e){var t=(void 0===e?{}:e).cacheSize;a(t),this._cache={},this._cacheOrdering=[],this._cacheSize=t}var t=e.prototype;return t.set=function(e,t){if(this._cache[e]=t,this._registerCacheHit(e),this._cacheOrdering.length>this._cacheSize){var n=this._cacheOrdering[0];this.remove(n)}},t.get=function(e){return this._registerCacheHit(e),this._cache[e]},t.remove=function(e){this._deleteCacheHit(e),delete this._cache[e]},t.clear=function(){this._cache={},this._cacheOrdering=[]},t._registerCacheHit=function(e){this._deleteCacheHit(e),this._cacheOrdering.push(e)},t._deleteCacheHit=function(e){var t=this._cacheOrdering.indexOf(e);t>-1&&this._cacheOrdering.splice(t,1)},t.isValidCacheKey=function(e){return n(e)},e}(),l=function(){function e(){this._cache=new Map}var t=e.prototype;return t.set=function(e,t){this._cache.set(e,t)},t.get=function(e){return this._cache.get(e)},t.remove=function(e){this._cache.delete(e)},t.clear=function(){this._cache.clear()},e}(),d=function(){function e(e){var t=(void 0===e?{}:e).cacheSize;a(t),this._cache=new Map,this._cacheSize=t}var t=e.prototype;return t.set=function(e,t){if(this._cache.set(e,t),this._cache.size>this._cacheSize){var n=this._cache.keys().next().value;this.remove(n)}},t.get=function(e){return this._cache.get(e)},t.remove=function(e){this._cache.delete(e)},t.clear=function(){this._cache.clear()},e}(),h=function(){function e(e){var t=(void 0===e?{}:e).cacheSize;a(t),this._cache=new Map,this._cacheSize=t}var t=e.prototype;return t.set=function(e,t){if(this._cache.set(e,t),this._cache.size>this._cacheSize){var n=this._cache.keys().next().value;this.remove(n)}},t.get=function(e){var t=this._cache.get(e);return this._cache.has(e)&&(this.remove(e),this._cache.set(e,t)),t},t.remove=function(e){this._cache.delete(e)},t.clear=function(){this._cache.clear()},e}();e.FifoMapCache=d,e.FifoObjectCache=c,e.FlatMapCache=l,e.FlatObjectCache=r,e.LruMapCache=h,e.LruObjectCache=u,e.createCachedSelector=i,e.createStructuredCachedSelector=function(e){return t.createStructuredSelector(e,i)},e.default=i,Object.defineProperty(e,\"__esModule\",{value:!0})}(t,n(5))},function(e,t){e.exports=function(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){var r=n(20),o=n(21),s=n(9),i=n(22),a=n(23),c=n(24),u=n(25),l=n(10),d=n(4);e.exports=function(e,t,n){t=t.length?r(t,(function(e){return d(e)?function(t){return o(t,1===e.length?e[0]:e)}:e})):[l];var h=-1;t=r(t,c(s));var f=i(e,(function(e,n,o){return{criteria:r(t,(function(t){return t(e)})),index:++h,value:e}}));return a(f,(function(e,t){return u(e,t,n)}))}},function(e,t){e.exports=function(e){return e}},function(e,t){e.exports=function(e){return e}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=0,s=[];++n<r;){var i=e[n];t(i,n,e)&&(s[o++]=i)}return s}},function(e,t,n){var r=n(8),o=n(4);e.exports=function(e,t,n,s){return null==e?[]:(o(t)||(t=null==t?[]:[t]),o(n=s?void 0:n)||(n=null==n?[]:[n]),r(e,t,n))}},function(e,t,n){var r=n(28),o=n(29),s=n(9),i=n(4),a=n(30);e.exports=function(e,t){return(i(e)?r:o)(e,a(s(t,3)))}},function(e,t,n){var r=n(31),o=n(8),s=n(32),i=n(36),a=s((function(e,t){if(null==e)return[];var n=t.length;return n>1&&i(e,t[0],t[1])?t=[]:n>2&&i(t[0],t[1],t[2])&&(t=[t[0]]),o(e,r(t,1),[])}));e.exports=a},function(e,t){function n(t,r){return e.exports=n=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},e.exports.default=e.exports,e.exports.__esModule=!0,n(t,r)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){var r=n(7);e.exports=function(e){if(Array.isArray(e))return r(e)},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){e.exports=function(e){if(\"undefined\"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e[\"@@iterator\"])return Array.from(e)},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){var r=n(7);e.exports=function(e,t){if(e){if(\"string\"==typeof e)return r(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return\"Object\"===n&&e.constructor&&(n=e.constructor.name),\"Map\"===n||\"Set\"===n?Array.from(e):\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?r(e,t):void 0}},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){e.exports=function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++n<r;)o[n]=t(e[n],n,e);return o}},function(e,t){e.exports=function(e,t){return null==e?void 0:e[t]}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++n<r;)o[n]=t(e[n],n,e);return o}},function(e,t){e.exports=function(e,t){var n=e.length;for(e.sort(t);n--;)e[n]=e[n].value;return e}},function(e,t){e.exports=function(e){return function(t){return e(t)}}},function(e,t,n){var r=n(26);e.exports=function(e,t,n){for(var o=-1,s=e.criteria,i=t.criteria,a=s.length,c=n.length;++o<a;){var u=r(s[o],i[o]);if(u)return o>=c?u:u*(\"desc\"==n[o]?-1:1)}return e.index-t.index}},function(e,t,n){var r=n(27);e.exports=function(e,t){if(e!==t){var n=void 0!==e,o=null===e,s=e==e,i=r(e),a=void 0!==t,c=null===t,u=t==t,l=r(t);if(!c&&!l&&!i&&e>t||i&&a&&u&&!c&&!l||o&&a&&u||!n&&u||!s)return 1;if(!o&&!i&&!l&&e<t||l&&n&&s&&!o&&!i||c&&n&&s||!a&&s||!u)return-1}return 0}},function(e,t){e.exports=function(){return!1}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=0,s=[];++n<r;){var i=e[n];t(i,n,e)&&(s[o++]=i)}return s}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=0,s=[];++n<r;){var i=e[n];t(i,n,e)&&(s[o++]=i)}return s}},function(e,t){e.exports=function(e){if(\"function\"!=typeof e)throw new TypeError(\"Expected a function\");return function(){var t=arguments;switch(t.length){case 0:return!e.call(this);case 1:return!e.call(this,t[0]);case 2:return!e.call(this,t[0],t[1]);case 3:return!e.call(this,t[0],t[1],t[2])}return!e.apply(this,t)}}},function(e,t){e.exports=function(e){return e&&e.length?e[0]:void 0}},function(e,t,n){var r=n(10),o=n(33),s=n(35);e.exports=function(e,t){return s(o(e,t,r),e+\"\")}},function(e,t,n){var r=n(34),o=Math.max;e.exports=function(e,t,n){return t=o(void 0===t?e.length-1:t,0),function(){for(var s=arguments,i=-1,a=o(s.length-t,0),c=Array(a);++i<a;)c[i]=s[t+i];i=-1;for(var u=Array(t+1);++i<t;)u[i]=s[i];return u[t]=n(c),r(e,this,u)}}},function(e,t){e.exports=function(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}},function(e,t){e.exports=function(e){return e}},function(e,t){e.exports=function(){return!1}},function(e,t,n){\"use strict\";n.r(t),n.d(t,\"Attribute\",(function(){return he})),n.d(t,\"QuerySet\",(function(){return oe})),n.d(t,\"Model\",(function(){return Oe})),n.d(t,\"ORM\",(function(){return Ye})),n.d(t,\"Schema\",(function(){return ct})),n.d(t,\"Backend\",(function(){return ut})),n.d(t,\"Session\",(function(){return se})),n.d(t,\"ForeignKey\",(function(){return pe})),n.d(t,\"ManyToMany\",(function(){return me})),n.d(t,\"OneToOne\",(function(){return ye})),n.d(t,\"fk\",(function(){return be})),n.d(t,\"many\",(function(){return we})),n.d(t,\"attr\",(function(){return ge})),n.d(t,\"oneToOne\",(function(){return ve})),n.d(t,\"createReducer\",(function(){return nt})),n.d(t,\"createSelector\",(function(){return at}));var r=n(0),o=n.n(r),s=n(3),i=n.n(s),a=n(2),c=n.n(a);function u(e){return null!=e&&\"object\"==typeof e&&!0===e[\"@@functional/placeholder\"]}function l(e){return function t(n){return 0===arguments.length||u(n)?t:e.apply(this,arguments)}}function d(e,t){switch(e){case 0:return function(){return t.apply(this,arguments)};case 1:return function(e){return t.apply(this,arguments)};case 2:return function(e,n){return t.apply(this,arguments)};case 3:return function(e,n,r){return t.apply(this,arguments)};case 4:return function(e,n,r,o){return t.apply(this,arguments)};case 5:return function(e,n,r,o,s){return t.apply(this,arguments)};case 6:return function(e,n,r,o,s,i){return t.apply(this,arguments)};case 7:return function(e,n,r,o,s,i,a){return t.apply(this,arguments)};case 8:return function(e,n,r,o,s,i,a,c){return t.apply(this,arguments)};case 9:return function(e,n,r,o,s,i,a,c,u){return t.apply(this,arguments)};case 10:return function(e,n,r,o,s,i,a,c,u,l){return t.apply(this,arguments)};default:throw new Error(\"First argument to _arity must be a non-negative integer no greater than ten\")}}function h(e){return function t(n,r){switch(arguments.length){case 0:return t;case 1:return u(n)?t:l((function(t){return e(n,t)}));default:return u(n)&&u(r)?t:u(n)?l((function(t){return e(t,r)})):u(r)?l((function(t){return e(n,t)})):e(n,r)}}}var f=h((function(e,t){return 1===e?l(t):d(e,function e(t,n,r){return function(){for(var o=[],s=0,i=t,a=0;a<n.length||s<arguments.length;){var c;a<n.length&&(!u(n[a])||s>=arguments.length)?c=n[a]:(c=arguments[s],s+=1),o[a]=c,u(c)||(i-=1),a+=1}return i<=0?r.apply(this,o):d(i,e(t,o,r))}}(e,[],t))})),p=l((function(e){return f(e.length,e)})),m={\"@@functional/placeholder\":!0};function y(e,t){for(var n in e)e.hasOwnProperty(n)&&t(e[n],n)}function g(e,t){return!!t&&e[\"@@_______immutableOpsOwnerID\"]===t}var b=\"function\"==typeof Symbol?function(){return Symbol(\"ownerID\")}:function(){return{}};function w(e,t){return t&&function(e,t){Object.defineProperty(e,\"@@_______immutableOpsOwnerID\",{value:t,configurable:!0,enumerable:!1})}(e,t),e}function v(e){return e instanceof Array?e:[e]}function _(e){return\"string\"==typeof e?-1===e.indexOf(\".\")?[e]:e.split(\".\"):e}function M(e,t,n){return n[e]=t,n}function O(e,t,n){var r=v(t);return e?r.forEach((function(t){y(t,(function(t,r){var o;e&&n.hasOwnProperty(r)?(o=\"object\"===c()(t)?O(e,[t],n[r]):t,n[r]=o):n[r]=t}))})):Object.assign.apply(Object,[n].concat(i()(r))),n}var x=O.bind(null,!1),N=O.bind(null,!0);function S(e,t){return v(e).forEach((function(e){delete t[e]})),t}function k(e,t,n){return e[n]!==t[n]}function E(e,t,n,r){if(g(r,t))return O(e,n,r);var o=v(n),s=!1,i=r,a=function(){s||(s=!0,w(i=Object.assign({},r),t))};return o.forEach((function(n){y(n,(function(o,s){if(e&&r.hasOwnProperty(s)){var u=i[s];if(\"object\"===c()(o)&&!(o instanceof Array)){if(k(i,n,s)){var l=E(e,t,o,u);l!==u&&(a(),i[s]=l)}return!0}}k(i,n,s)&&(a(),i[s]=o)}))})),i}var j=E.bind(null,!0);function F(e,t,n,r){if(g(r,e))return M(t,n,r);if(r[t]===n)return r;var o=function(e){for(var t=new Array(e.length),n=0;n<e.length;n++)t[n]=e[n];return t}(r);return o[t]=n,w(o,e),o}function A(e,t){for(var n=0,r=0;n<t.length;){e(t[n],r)?n++:t.splice(n,1),r++}return t}function C(e,t,n,r){var o=v(n);return r.splice.apply(r,[e,t].concat(i()(o))),r}function I(e,t,n){return C(e,0,t,n)}function R(e,t,n,r,o){if(g(o,e))return C(t,n,r,o);var s=v(r),a=o.slice();return w(a,e),a.splice.apply(a,[t,n].concat(i()(s))),a}function D(e,t,n,r){return g(r,e)?I(t,n,r):R(e,t,0,n,r)}var T={merge:E.bind(null,!1),deepMerge:j,omit:function(e,t,n){if(g(n,e))return S(t,n);var r=v(t).filter((function(e){return n.hasOwnProperty(e)}));if(0===r.length)return n;var o=Object.assign({},n);return r.forEach((function(e){delete o[e]})),w(o,e),o},setIn:function(e,t,n,r){var o=_(t),s=function(e,t){for(var n=_(e),r=t,o=0;o<n.length;o++){var s=r[n[o]];if(o===n.length-1)return s;if(\"object\"!==c()(s))return;r=s}}(o,r);if(n===s)return r;var i,a=o.length,u=i=g(r,e)?r:Object.assign(w({},e),r);return o.forEach((function(t,r){if(r!==a-1){var s=i[t],u=c()(s);if(\"object\"!==u){if(\"undefined\"===u){var l=w({},e);return i[t]=l,void(i=l)}var d=\"\".concat(o[r-1],\".\").concat(t);throw new Error(\"A non-object value was encountered when traversing setIn path at \".concat(d,\".\"))}if(g(s,e))i=s;else{var h=w({},e);i[t]=Object.assign(h,s),i=h}}else i[t]=n})),u},insert:D,push:function(e,t,n){return D(e,n.length,t,n)},filter:function(e,t,n){if(g(n,e))return A(t,n);var r=n.filter(t);return r.length===n.length?n:(w(r,e),r)},splice:R,set:function(e,t,n,r){if(function(e){return e&&\"object\"===c()(e)&&\"number\"==typeof e.length&&e.length>=0&&e.length%1==0}(r))return F(e,t,n,r);if(g(r,e))return M(t,n,r);if(r[t]===n)return r;var o=Object.assign({},r);return w(o,e),o[t]=n,o}},$={merge:x,deepMerge:N,omit:S,setIn:function(e,t,n){for(var r=_(e),o=r.length,s=!1,i=0,a=n,u=r[i];!s;)if(i===o-1)a[u]=t,s=!0;else{var l=c()(a[u]);if(\"undefined\"===l){var d={};w(d,null),a[u]=d}else if(\"object\"!==l){var h=\"\".concat(r[i-1],\".\").concat(u);throw new Error(\"A non-object value was encountered when traversing setIn path at \".concat(h,\".\"))}a=a[u],u=r[++i]}return n},insert:I,push:function(e,t){var n=v(e);return t.push.apply(t,i()(n)),t},filter:A,splice:C,set:M};var P=function(){var e=Object.assign({},T);y(e,(function(t,n){e[n]=p(t.bind(null,null))}));var t=Object.assign({},$);y(t,(function(e,n){t[n]=p(e)}));var n=Object.assign({},T);return y(n,(function(e,t){n[t]=p(e)})),Object.assign(e,{mutable:t,batch:n,batched:function(e,t){var n,r;\"function\"==typeof e?(r=e,n=b()):(n=e,r=t);var o=Object.assign({},T);return y(o,(function(e,t){o[t]=p(e.bind(null,n))})),r(o)},__:m,getBatchToken:b})}();const B=\"REDUX_ORM_UPDATE\",U=\"REDUX_ORM_DELETE\",V=\"REDUX_ORM_FILTER\",q=\"REDUX_ORM_EXCLUDE\",z=Symbol(\"REDUX_ORM_ALL_INSTANCES\"),L=(e,t)=>void 0===t?z:t;function Q(e){return(\"function\"==typeof console.warn?console.warn.bind(console):console.log.bind(console))(e)}function X(e,t){return e+((n=t).charAt(0).toUpperCase()+n.slice(1));var n}function H(e){return`from${e}Id`}function Y(e){return`to${e}Id`}function K(e){return function(...t){return this.getQuerySet()[e](...t)}}function G(e){return function(){return this.getQuerySet()[e]}}function J(e,t){const n=t.sharedMethods.slice();!function(e,t){let n=e;for(;n!==Function.prototype;)t(n),n=Object.getPrototypeOf(n)}(t,t=>{for(let r=0;r<n.length;r++){let o=!1;const s=n[r],i=Object.getOwnPropertyDescriptor(t.prototype,s);void 0!==i&&(void 0!==i.get?(i.get=G(s),Object.defineProperty(e,s,i)):e[s]=K(s),o=!0),o&&n.splice(r--,1)}})}function W(e){return null!=e&&\"function\"==typeof e.getId?e.getId():e}const{getBatchToken:Z}=P;function ee({type:e,payload:t},n){if(e!==V)return!1;if(\"object\"!=typeof t)return!1;if(!t.hasOwnProperty(n))return!1;const r=t[n];return null!==r&&void 0!==r}function te(e,t){return Object.entries(e).reduce((e,[n,r])=>(e[n]=t(r),e),{})}function ne(e){return e&&\"string\"!=typeof e?e.modelName:e}const re=function(){function e(e,t,n){Object.assign(this,{modelClass:e,clauses:t||[]}),this._opts=n}e.addSharedMethod=function(e){this.sharedMethods=this.sharedMethods.concat(e)};var t=e.prototype;return t._new=function(e,t){const n={...this._opts,...t};return new this.constructor(this.modelClass,e,n)},t.toString=function(){this._evaluate();return\"QuerySet contents:\\n    - \"+this.rows.map(({id:e})=>this.modelClass.withId(e).toString()).join(\"\\n    - \")},t.toRefArray=function(){return this._evaluate()},t.toModelArray=function(){const{modelClass:e}=this;return this._evaluate().map(t=>new e(t))},t.count=function(){return this._evaluate(),this.rows.length},t.exists=function(){return Boolean(this.count())},t.at=function(e){const{modelClass:t}=this,n=this._evaluate();if(e>=0&&e<n.length)return new t(n[e])},t.first=function(){return this.at(0)},t.last=function(){const e=this._evaluate();return this.at(e.length-1)},t.all=function(){return this._new(this.clauses)},t.filter=function(e){const t=\"object\"==typeof e?te(e,W):e,n={type:V,payload:t};return this._new(this.clauses.concat(n))},t.exclude=function(e){const t=\"object\"==typeof e?te(e,W):e,n={type:q,payload:t};return this._new(this.clauses.concat(n))},t._evaluate=function(){if(void 0===this.modelClass.session)throw new Error([`Tried to query the ${this.modelClass.modelName} model's table without a session. `,\"Create a session using `session = orm.session()` and use \",`\\`session[\"${this.modelClass.modelName}\"]\\` for querying instead.`].join(\"\"));if(!this._evaluated){const{session:e,modelName:t}=this.modelClass,n={table:t,clauses:this.clauses};this.rows=e.query(n).rows,this._evaluated=!0}return this.rows},t.orderBy=function(e,t){const n={type:\"REDUX_ORM_ORDER_BY\",payload:[e,t]};return this._new(this.clauses.concat(n))},t.update=function(e){const{session:t,modelName:n}=this.modelClass;t.applyUpdate({action:B,query:{table:n,clauses:this.clauses},payload:e}),this._evaluated=!1},t.delete=function(){const{session:e,modelName:t}=this.modelClass;this.toModelArray().forEach(e=>e._onDelete()),e.applyUpdate({action:U,query:{table:t,clauses:this.clauses}}),this._evaluated=!1},t.map=function(){throw new Error(\"`QuerySet.prototype.map` has been removed. Call `.toModelArray()` or `.toRefArray()` first to map.\")},t.forEach=function(){throw new Error(\"`QuerySet.prototype.forEach` has been removed. Call `.toModelArray()` or `.toRefArray()` first to iterate.\")},o()(e,[{key:\"withModels\",get:function(){throw new Error(\"`QuerySet.prototype.withModels` has been removed. Use `.toModelArray()` or predicate functions that instantiate Models from refs, e.g. `new Model(ref)`.\")}},{key:\"withRefs\",get:function(){Q(\"`QuerySet.prototype.withRefs` has been deprecated. Query building operates on refs only now.\")}}]),e}();re.sharedMethods=[\"count\",\"at\",\"all\",\"last\",\"first\",\"filter\",\"exclude\",\"orderBy\",\"update\",\"delete\"];var oe=re;var se=function(){function e(e,t,n,r,o){this.schema=e,this.db=t,this.state=n||t.getEmptyState(),this.initialState=this.state,this.withMutations=Boolean(r),this.batchToken=o||b(),this.modelData={},this.models=e.getModelClasses(),this.sessionBoundModels=this.models.map(e=>{function t(){return Reflect.construct(e,arguments,t)}return Reflect.setPrototypeOf(t.prototype,e.prototype),Reflect.setPrototypeOf(t,e),Object.defineProperty(this,e.modelName,{get:()=>t}),t.connect(this),t})}var t=e.prototype;return t.getDataForModel=function(e){return this.modelData[e]||(this.modelData[e]={}),this.modelData[e]},t.getModelData=function(){return this.modelData},t.markAccessed=function(e,t){const n=this.getDataForModel(e);n.accessedInstances||(n.accessedInstances={}),t.forEach(e=>{n.accessedInstances[e]=!0})},t.markFullTableScanned=function(e){this.getDataForModel(e).fullTableScanned=!0},t.markAccessedIndexes=function(e){e.forEach(([e,t,n])=>{const r=this.getDataForModel(e);r.accessedIndexes||(r.accessedIndexes={}),r.accessedIndexes[t]=[...r.accessedIndexes[t]||[],n]})},t.applyUpdate=function(e){const t=this._getTransaction(e),n=this.db.update(e,t,this.state),{status:r,state:o,payload:s}=n;if(\"SUCCESS\"!==r)throw new Error(`Applying update failed with status ${r}. Payload: ${s}`);return this.state=o,s},t.query=function(e){const t=this.db.query(e,this.state);return this._markAccessedByQuery(e,t),t},t._getTransaction=function(e){const{withMutations:t}=this,{action:n}=e;let{batchToken:r}=this;return[B,U].includes(n)&&(r=b()),{batchToken:r,withMutations:t}},t._markAccessedByQuery=function(e,t){const{table:n,clauses:r}=e,{rows:o}=t,{idAttribute:s}=this[n],i=new Set(o.map(e=>e[s])),a=r.some(e=>!!ee(e,s)&&(i.add(e.payload[s]),!0)),c=[],{indexes:u}=this.state[n];r.forEach(e=>{Object.keys(u).forEach(t=>{if(!ee(e,t))return;const r=e.payload[t];c.push([n,t,r])})}),a?this.markAccessed(n,i):c.length?(this.markAccessed(n,i),this.markAccessedIndexes(c)):this.markFullTableScanned(n)},t.getNextState=function(){return Q(\"`Session.prototype.getNextState` has been deprecated. Access the `Session.prototype.state` property instead.\"),this.state},t.reduce=function(){throw new Error(\"`Session.prototype.reduce` has been removed. The Redux integration API is now decoupled from ORM and Session - see the 0.9 migration guide in the GitHub repo.\")},o()(e,[{key:\"accessedModelInstances\",get:function(){return Object.entries(this.getModelData()).reduce((e,[t,n])=>(n.accessedInstances&&(e[t]=n.accessedInstances),e),{})}},{key:\"fullTableScannedModels\",get:function(){return Object.entries(this.getModelData()).reduce((e,[t,n])=>(n.fullTableScanned&&e.push(t),e),[])}},{key:\"accessedIndexes\",get:function(){return Object.entries(this.getModelData()).reduce((e,[t,n])=>(n.accessedIndexes&&(e[t]=n.accessedIndexes),e),{})}}]),e}(),ie=n(1),ae=n.n(ie);var ce=function(e){function t(){return e.apply(this,arguments)||this}ae()(t,e);var n=t.prototype;return n.installForwardsDescriptor=function(){Object.defineProperty(this.model.prototype,this.fieldName,this.field.createForwardsDescriptor(this.fieldName,this.model,this.toModel,this.throughModel))},n.installForwardsVirtualField=function(){this.model.virtualFields[this.fieldName]=this.field.createForwardsVirtualField(this.fieldName,this.model,this.toModel,this.throughModel)},n.installBackwardsDescriptor=function(){if(Object.getOwnPropertyDescriptor(this.toModel.prototype,this.backwardsFieldName))throw new Error((e=this.model.modelName,t=this.fieldName,n=this.toModel.modelName,[`Reverse field ${this.backwardsFieldName} already defined`,` on model ${n}. To fix, set a custom related`,` name on ${e}.${t}.`].join(\"\")));var e,t,n;Object.defineProperty(this.toModel.prototype,this.backwardsFieldName,this.field.createBackwardsDescriptor(this.fieldName,this.model,this.toModel,this.throughModel))},n.installBackwardsVirtualField=function(){this.toModel.virtualFields[this.backwardsFieldName]=this.field.createBackwardsVirtualField(this.fieldName,this.model,this.toModel,this.throughModel)},t}(function(){function e(e){this.field=e.field,this.fieldName=e.fieldName,this.model=e.model,this.orm=e.orm,this.field.references(this.model)&&(this.field.toModelName=\"this\")}return e.prototype.run=function(){this.installForwardsDescriptor(),this.field.installsForwardsVirtualField&&this.installForwardsVirtualField(),this.field.installsBackwardsDescriptor&&this.installBackwardsDescriptor(),this.field.installsBackwardsVirtualField&&this.installBackwardsVirtualField()},o()(e,[{key:\"toModel\",get:function(){if(void 0===this._toModel){const{toModelName:e}=this.field;this._toModel=e?\"this\"===e?this.model:this.orm.get(e):null}return this._toModel}},{key:\"throughModel\",get:function(){if(void 0===this._throughModel){const e=this.field.getThroughModelName(this.fieldName,this.model);this._throughModel=e?this.orm.get(e):null}return this._throughModel}},{key:\"backwardsFieldName\",get:function(){return this.field.getBackwardsFieldName(this.model)}}]),e}());var ue=function(){function e(){}var t=e.prototype;return t.getClass=function(){return this.constructor},t.references=function(e){return!1},t.getThroughModelName=function(e,t){return null},o()(e,[{key:\"installerClass\",get:function(){return ce}},{key:\"installsForwardsVirtualField\",get:function(){return!1}},{key:\"installsBackwardsDescriptor\",get:function(){return!1}},{key:\"installsBackwardsVirtualField\",get:function(){return!1}},{key:\"index\",get:function(){return!1}}]),e}();function le(e,t){return{get(){const{session:{[t]:n}}=this.getClass(),{[e]:r}=this._fields;return n.withId(r)},set(t){this.update({[e]:W(t)})}}}function de(e,t,n,r,o){return{get(){const{session:{[e]:s,[t]:i,[n]:a}}=this.getClass(),c=o?i:s,u=o?s:i,l=o?r.to:r.from,d=o?r.from:r.to,h=this.getId(),f=a.filter({[l]:h}),p=new Set(f.toRefArray().map(e=>e[d])),m=u.filter(e=>p.has(e[u.idAttribute]));return m.add=function(...e){const t=new Set(e.map(W)),n=f.filter(e=>t.has(e[d]));if(n.exists()){const e=n.toRefArray().map(e=>e[d]);throw new Error(`Tried to add already existing ${u.modelName} id(s) ${e} to the ${c.modelName} instance with id ${h}`)}t.forEach(e=>{a.create({[d]:e,[l]:h})})},m.clear=function(){f.delete()},m.remove=function(...e){const t=new Set(e.map(W)),n=f.filter(e=>t.has(e[d]));if(n.count()!==t.size){const e=n.toRefArray().map(e=>e[d]),r=[...t].filter(t=>!e.includes(t));throw new Error(`Tried to delete non-existing ${u.modelName} id(s) ${r} from the ${c.modelName} instance with id ${h}`)}n.delete()},m},set(){throw new Error(\"Tried setting a M2M field. Please use the related QuerySet methods add, remove and clear.\")}}}var he=function(e){function t(t){var n;return(n=e.call(this)||this).opts=t||{},n.opts.hasOwnProperty(\"getDefault\")&&(n.getDefault=n.opts.getDefault),n}return ae()(t,e),t.prototype.createForwardsDescriptor=function(e,t){return function(e){return{get(){return this._fields[e]},set(t){return this.set(e,t)},enumerable:!0,configurable:!0}}(e)},t}(ue);var fe=function(e){function t(...t){var n;if(n=e.call(this)||this,1===t.length&&\"object\"==typeof t[0]){const e=t[0];n.toModelName=ne(e.to),n.relatedName=e.relatedName,n.through=ne(e.through),n.throughFields=e.throughFields,n.as=e.as}else[n.toModelName,n.relatedName]=[ne(t[0]),t[1]];return n}ae()(t,e);var n=t.prototype;return n.getBackwardsFieldName=function(e){return this.relatedName||e.modelName.toLowerCase()+\"Set\"},n.createBackwardsVirtualField=function(e,t,n,r){return new(this.getClass())(t.modelName,e)},n.references=function(e){return this.toModelName===e.modelName},o()(t,[{key:\"installsBackwardsVirtualField\",get:function(){return!0}},{key:\"installsBackwardsDescriptor\",get:function(){return!0}},{key:\"installerClass\",get:function(){return function(e){function t(){return e.apply(this,arguments)||this}return ae()(t,e),t.prototype.installForwardsDescriptor=function(){Object.defineProperty(this.model.prototype,this.field.as||this.fieldName,this.field.createForwardsDescriptor(this.fieldName,this.model,this.toModel,this.throughModel))},t}(ce)}}]),t}(ue);var pe=function(e){function t(){return e.apply(this,arguments)||this}ae()(t,e);var n=t.prototype;return n.createForwardsDescriptor=function(e,t,n,r){return le(e,n.modelName)},n.createBackwardsDescriptor=function(e,t,n,r){return o=e,s=t.modelName,{get(){const{session:{[s]:e}}=this.getClass();return e.filter({[o]:this.getId()})},set(){throw new Error(\"Can't mutate a reverse many-to-one relation.\")}};var o,s},o()(t,[{key:\"index\",get:function(){return!0}}]),t}(fe);var me=function(e){function t(){return e.apply(this,arguments)||this}ae()(t,e);var n=t.prototype;return n.getDefault=function(){return[]},n.getThroughModelName=function(e,t){return this.through||X(t.modelName,e)},n.createForwardsDescriptor=function(e,t,n,r){return de(t.modelName,n.modelName,r.modelName,this.getThroughFields(e,t,n,r),!1)},n.createBackwardsDescriptor=function(e,t,n,r){return de(t.modelName,n.modelName,r.modelName,this.getThroughFields(e,t,n,r),!0)},n.createBackwardsVirtualField=function(e,t,n,r){return new(this.getClass())({to:t.modelName,relatedName:e,through:r.modelName,throughFields:this.getThroughFields(e,t,n,r)})},n.createForwardsVirtualField=function(e,t,n,r){return new(this.getClass())({to:n.modelName,relatedName:e,through:this.through,throughFields:this.getThroughFields(e,t,n,r),as:this.as})},n.getThroughFields=function(e,t,n,r){if(this.throughFields){const[e,t]=this.throughFields,o=r.fields[e];return{to:o.references(n)?e:t,from:o.references(n)?t:e}}if(t.modelName===n.modelName)return{to:Y(n.modelName),from:H(t.modelName)};const o=e=>Object.keys(r.fields).find(t=>r.fields[t].references(e));return{to:o(n),from:o(t)}},o()(t,[{key:\"installsForwardsVirtualField\",get:function(){return!0}}]),t}(fe);var ye=function(e){function t(){return e.apply(this,arguments)||this}ae()(t,e);var n=t.prototype;return n.getBackwardsFieldName=function(e){return this.relatedName||e.modelName.toLowerCase()},n.createForwardsDescriptor=function(e,t,n,r){return function(...e){return le(...e)}(e,n.modelName)},n.createBackwardsDescriptor=function(e,t,n,r){return o=e,s=t.modelName,{get(){const{session:{[s]:e}}=this.getClass();return e.get({[o]:this.getId()})},set(){throw new Error(\"Can't mutate a reverse one-to-one relation.\")}};var o,s},t}(fe);function ge(e){return new he(e)}function be(...e){return new pe(...e)}function we(...e){return new me(...e)}function ve(...e){return new ye(...e)}function _e(e){const t=e.getClass(),{idAttribute:n,modelName:r}=t;return{table:r,clauses:[{type:V,payload:{[n]:e.getId()}}]}}const Me=function(){function e(e){this._initFields(e)}var t=e.prototype;return t._initFields=function(e){const t=Object(e);this._fields={...t},Object.keys(t).forEach(e=>{e in this||Object.defineProperty(this,e,{get:()=>this._fields[e],set:t=>this.set(e,t),configurable:!0,enumerable:!0})})},e.toString=function(){return\"ModelClass: \"+this.modelName},e.options=function(){return{}},e.markAccessed=function(e){if(void 0===this._session)throw new Error([`Tried to mark rows of the ${this.modelName} model as accessed without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].markAccessed\\` instead.`].join(\"\"));this.session.markAccessed(this.modelName,e)},e.markFullTableScanned=function(){if(void 0===this._session)throw new Error([`Tried to mark the ${this.modelName} model as full table scanned without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].markFullTableScanned\\` instead.`].join(\"\"));this.session.markFullTableScanned(this.modelName)},e.markAccessedIndexes=function(e){if(void 0===this._session)throw new Error([`Tried to mark indexes for the ${this.modelName} model as accessed without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].markAccessedIndexes\\` instead.`].join(\"\"));this.session.markAccessedIndexes(e.map(([e,t])=>[this.modelName,e,t]))},e.connect=function(e){if(!(e instanceof se))throw new Error(\"A model can only be connected to instances of Session.\");this._session=e},e.getQuerySet=function(){const{querySetClass:e}=this;return new e(this)},e.invalidateClassCache=function(){this.isSetUp=void 0,this.virtualFields={}},e.tableOptions=function(){return\"function\"==typeof this.backend?(Q(\"`Model.backend` has been deprecated. Please rename to `.options`.\"),this.backend()):this.backend?(Q(\"`Model.backend` has been deprecated. Please rename to `.options`.\"),this.backend):\"function\"==typeof this.options?this.options():this.options},e.create=function(e){if(void 0===this._session)throw new Error([`Tried to create a ${this.modelName} model instance without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].create\\` instead.`].join(\"\"));const t={...e},n={},r=Object.keys(this.fields),o=Object.keys(this.virtualFields);r.forEach(r=>{const o=this.fields[r],s=e.hasOwnProperty(r);if(o instanceof me)s&&(n[r]=e[r],o.as||delete t[r]);else if(s){const n=e[r];t[r]=W(n)}else o.getDefault&&(t[r]=o.getDefault(e))}),o.forEach(r=>{if(!n.hasOwnProperty(r)){const o=this.virtualFields[r];e.hasOwnProperty(r)&&o instanceof me&&(n[r]=e[r],delete t[r])}});const s=new this(this.session.applyUpdate({action:\"REDUX_ORM_CREATE\",table:this.modelName,payload:t}));return s._refreshMany2Many(n),s},e.upsert=function(e){if(void 0===this.session)throw new Error([`Tried to upsert a ${this.modelName} model instance without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].upsert\\` instead.`].join(\"\"));const{idAttribute:t}=this;if(e.hasOwnProperty(t)){const n=e[t];if(this.idExists(n)){const t=this.withId(n);return t.update(e),t}}return this.create(e)},e.withId=function(e){return this.get({[this.idAttribute]:e})},e.idExists=function(e){return this.exists({[this.idAttribute]:e})},e.exists=function(e){if(void 0===this.session)throw new Error([`Tried to check if a ${this.modelName} model instance exists without a session. `,\"Create a session using `session = orm.session()` and call \",`\\`session[\"${this.modelName}\"].exists\\` instead.`].join(\"\"));return Boolean(this._findDatabaseRows(e).length)},e.get=function(e){const t=this._findDatabaseRows(e);if(0===t.length)return null;if(t.length>1)throw new Error(`Expected to find a single row in \\`${this.modelName}.get\\`. Found ${t.length}.`);return new this(t[0])},t.getClass=function(){return this.constructor},t.getId=function(){return this._fields[this.getClass().idAttribute]},e._findDatabaseRows=function(e){const t={table:this.modelName};return e&&(t.clauses=[{type:V,payload:e}]),this.session.query(t).rows},t.toString=function(){const e=this.getClass();return`${e.modelName}: {${Object.keys(e.fields).map(t=>{if(e.fields[t]instanceof me){return`${t}: [${this[t].toModelArray().map(e=>e.getId()).join(\", \")}]`}return`${t}: ${this._fields[t]}`}).join(\", \")}}`},t.equals=function(e){return function(e,t){const n=Object.entries(Object(e));return n.length===Object.keys(t).length&&n.every(([e,n])=>t.hasOwnProperty(e)&&t[e]===n)}(this._fields,e._fields)},t.set=function(e,t){this.update({[e]:t})},t.update=function(e){const t=this.getClass();if(void 0===t.session)throw new Error([`Tried to update a ${t.modelName} model instance without a session. `,\"You cannot call `.update` on an instance that you did not receive from the database.\"].join(\"\"));const n={...e},{fields:r,virtualFields:o}=t,s={};for(const e in n){if(r.hasOwnProperty(e)){const t=r[e];t instanceof pe||t instanceof ye?n[e]=W(n[e]):t instanceof me&&(s[e]=n[e],t.as||delete n[e])}else if(o.hasOwnProperty(e)){o[e]instanceof me&&(s[e]=n[e],delete n[e])}}const i={...this._fields,...n},a=new t(i);this.equals(a)||(this._initFields(i),t.session.applyUpdate({action:B,query:_e(this),payload:n})),this._refreshMany2Many(s)},t.refreshFromState=function(){this._initFields(this.ref)},t.delete=function(){const e=this.getClass();if(void 0===e.session)throw new Error([`Tried to delete a ${e.modelName} model instance without a session. `,\"You cannot call `.delete` on an instance that you did not receive from the database.\"].join(\"\"));this._onDelete(),e.session.applyUpdate({action:U,query:_e(this)})},t._refreshMany2Many=function(e){const t=this.getClass(),{fields:n,virtualFields:r,modelName:o}=t;Object.keys(e).forEach(s=>{const i=!n.hasOwnProperty(s),a=r[s],c=e[s];if(!Array.isArray(c))throw new TypeError(`Failed to resolve many-to-many relationship: ${o}[${s}] must be an array (passed: ${c})`);const u=c.map(W),l=[...new Set(u)];if(u.length!==l.length)throw new Error(`Found duplicate id(s) when passing \"${u}\" to ${t.modelName}.${s} value`);const d=a.through||X(t.modelName,s),h=t.session[d];let f,p;i?({from:p,to:f}=a.throughFields):({from:f,to:p}=a.throughFields);const m=function(e,t){const n=e.filter(e=>t.includes(e)),r=e.filter(e=>!n.includes(e)),o=t.filter(e=>!n.includes(e));return r.length||o.length?{delete:r,add:o}:null}(h.filter(e=>e[f]===this[t.idAttribute]).toRefArray().map(e=>e[p]),u);if(m){const{delete:e,add:t}=m;e.length>0&&this[a.as||s].remove(...e),t.length>0&&this[a.as||s].add(...t)}})},t._onDelete=function(){const{virtualFields:e}=this.getClass();for(const t in e){const n=e[t];if(n instanceof me){this[n.as||t].clear()}else if(n instanceof pe){const e=this[t];e.exists()&&e.update({[n.relatedName]:null})}else n instanceof ye&&null!==this[t]&&(this[t][n.relatedName]=null)}},e.hasId=function(e){return console.warn(\"`Model.hasId` has been deprecated. Please use `Model.idExists` instead.\"),this.idExists(e)},t.getNextState=function(){throw new Error(\"`Model.prototype.getNextState` has been removed. See the 0.9 migration guide on the GitHub repo.\")},o()(e,[{key:\"ref\",get:function(){const e=this.getClass();return e._findDatabaseRows({[e.idAttribute]:this.getId()})[0]}}],[{key:\"idAttribute\",get:function(){if(void 0===this._session)throw new Error([`Tried to get the ${this.modelName} model's id attribute without a session. `,\"Create a session using `session = orm.session()` and access \",`\\`session[\"${this.modelName}\"].idAttribute\\` instead.`].join(\"\"));return this.session.db.describe(this.modelName).idAttribute}},{key:\"session\",get:function(){return this._session}},{key:\"query\",get:function(){return this.getQuerySet()}}]),e}();Me.fields={id:ge()},Me.virtualFields={},Me.querySetClass=oe;var Oe=Me,xe=n(11),Ne=n.n(xe),Se=n(12),ke=n.n(Se),Ee=n(13),je=n.n(Ee),Fe=n(14),Ae=n.n(Fe);const Ce={idAttribute:\"id\",arrName:\"items\",mapName:\"itemsById\",fields:{}};var Ie=function(){function e(e){Object.assign(this,Ce,e)}var t=e.prototype;return t.accessId=function(e,t){return e[this.mapName][t]},t.accessIds=function(e,t){const n=e[this.mapName];return t.map(e=>n[e])},t.idExists=function(e,t){return e[this.mapName].hasOwnProperty(t)},t.accessIdList=function(e){return e[this.arrName]},t.accessList=function(e){return this.accessIds(e,this.accessIdList(e))},t.getMaxId=function(e){return this.getMeta(e,\"maxId\")},t.setMaxId=function(e,t,n){return this.setMeta(e,t,\"maxId\",n)},t.nextId=function(e){return e+1},t.getEmptyState=function(){return{...{[this.arrName]:[],[this.mapName]:{}},indexes:Object.keys(this.fields).filter(e=>e!==this.idAttribute).filter(e=>this.fields[e].index).reduce((e,t)=>({...e,[t]:{}}),{}),meta:{}}},t.setMeta=function(e,t,n,r){const{batchToken:o,withMutations:s}=e;if(s){return P.mutable.setIn([\"meta\",n],r,t)}return P.batch.setIn(o,[\"meta\",n],r,t)},t.getMeta=function(e,t){return e.meta[t]},t.query=function(e,t){if(0===t.length)return this.accessList(e);const{idAttribute:n}=this,r=Ae()(t,e=>ee(e,n)?1:function({type:e}){return[V,q].includes(e)}(e)?2:3),o=(t,r)=>{const{type:s,payload:i}=r;if(!t){if(ee(r,n)){const t=i[n],s=Object.keys(i).reduce((e,t)=>(t!==n&&(e[t]=i[t]),e),{}),a=this.idExists(e,t)?[t]:[];return Object.keys(s).length?o(this.accessIds(e,a),{...r,payload:s}):this.accessIds(e,a)}if(s===V&&\"object\"==typeof i){const t=Object.entries(e.indexes),n=[],s=[];if(t.forEach(([e,t])=>{ee(r,e)&&t.hasOwnProperty(i[e])&&(n.push(t[i[e]]),s.push(e))}),n.length){const t=n.pop(),a=n.reduce((e,t)=>{const n=new Set(t);return e.filter(Set.prototype.has,n)},t),c=Object.keys(i).reduce((e,t)=>(s.includes(t)||(e[t]=i[t]),e),{});return Object.keys(c).length?o(this.accessIds(e,a),{...r,payload:c}):this.accessIds(e,a)}}return o(this.accessList(e),r)}switch(s){case V:return Ne()(t,i);case q:return je()(t,i);case\"REDUX_ORM_ORDER_BY\":{const[e,n]=i;return ke()(t,e,function(e){if(void 0===e)return;const t=e=>[\"desc\",!1].includes(e)?\"desc\":\"asc\";return Array.isArray(e)?e.map(t):t(e)}(n))}default:return t}};return r.reduce(o,void 0)},t.insert=function(e,t,n){const{batchToken:r,withMutations:o}=e,s=n.hasOwnProperty(this.idAttribute);let i=t;const[a,c]=function(e,t){let n,r,o=e;return void 0===o&&(o=-1),void 0===t?(n=o+1,r=n):(n=Math.max(o+1,t),r=t),[n,r]}(this.getMaxId(t),n[this.idAttribute]);i=this.setMaxId(e,t,a);const u=s?n:P.batch.set(r,this.idAttribute,c,n),l=Object.keys(i.indexes).filter(e=>n.hasOwnProperty(e)&&null!==n[e]).map(e=>[e,n[e]]);if(o)return P.mutable.push(c,i[this.arrName]),P.mutable.set(c,u,i[this.mapName]),l.forEach(([e,t])=>{const n=i.indexes[e];n.hasOwnProperty(t)?P.mutable.push(c,n[t]):P.mutable.set(t,[c],n)}),{state:i,created:u};const d=P.batch.merge(r,l.reduce((e,[t,n])=>(e[t]=P.batch.merge(r,{[n]:P.batch.push(r,c,e[t][n]||[])},e[t]),e),{...i.indexes}),i.indexes);return{state:P.batch.merge(r,{[this.arrName]:P.batch.push(r,c,i[this.arrName]),[this.mapName]:P.batch.merge(r,{[c]:u},i[this.mapName]),indexes:d},i),created:u}},t.update=function(e,t,n,r){const{batchToken:o,withMutations:s}=e,i=s?P.mutable.set:P.batch.set(o),a=Object.keys(t.indexes).filter(e=>r.hasOwnProperty(e)),c=[],u=[],l=n.reduce((e,t)=>{const n=a.reduce((e,n)=>({...e,[n]:t[n]}),{}),l=(e=>(s?P.mutable.merge:P.batch.merge(o))(r,e))(t),d=a.reduce((e,t)=>({...e,[t]:l[t]}),{}),h=l[this.idAttribute],f=i(h,l,e);return a.forEach(e=>{const{[e]:t}=n,{[e]:r}=d;t!==r&&(null!=t&&u.push([e,t,h]),null!==r&&c.push([e,r,h]))}),f},t[this.mapName]);let d=t.indexes;return s?(u.forEach(([e,t,n])=>{const r=d[e][t],o=r.indexOf(n);P.mutable.splice(o,1,[],r)}),c.forEach(([e,t,n])=>{P.mutable.push(n,d[e][t])})):(c.length&&(d=P.batch.merge(o,c.reduce((e,[t,n,r])=>(e[t]=P.batch.merge(o,{[n]:P.batch.push(o,r,e[t][n]||[])},e[t]),e),{...d}),d)),u.length&&(d=P.batch.merge(o,u.reduce((e,[t,n,r])=>(e[t]=P.batch.merge(o,{[n]:P.batch.filter(o,e=>e!==r,e[t][n])},e[t]),e),{...d}),d))),P.batch.merge(o,{[this.mapName]:l,indexes:d},t)},t.delete=function(e,t,n){const{batchToken:r,withMutations:o}=e,{arrName:s,mapName:i}=this,a=t[s],c=n.map(e=>e[this.idAttribute]);if(o)return c.forEach(e=>{const n=a.indexOf(e);P.mutable.splice(n,1,[],a),P.mutable.omit(e,t[i])}),Object.values(t.indexes).forEach(e=>Object.values(e).forEach(e=>c.forEach(t=>{const n=e.indexOf(t);-1!==n&&P.mutable.splice(n,1,[],e)}))),t;const u=P.batch.merge(r,Object.entries(t.indexes).reduce((e,[t,n])=>(e[t]=P.batch.merge(r,Object.entries(n).reduce((e,[t,n])=>(e[t]=P.batch.filter(r,e=>!c.includes(e),n),e),{...e[t]}),e[t]),e),{...t.indexes}),t.indexes);return P.batch.merge(r,{[s]:P.batch.filter(r,e=>!c.includes(e),t[s]),[i]:P.batch.omit(r,c,t[i]),indexes:P.batch.merge(r,u,t.indexes)},t)},e}();const Re={};function De(e,t,n){const{table:r,clauses:o}=t;return{rows:e[r].query(n[r],o)}}function Te(e,t,n,r){const{action:o,payload:s}=t;let i,a,c;if(\"REDUX_ORM_CREATE\"===o){({table:i}=t);const o=e[i],u=r[i],l=o.insert(n,u,s);a=l.state,c=l.created}else{const{query:u}=t;({table:i}=u);const{rows:l}=De(e,u,r),d=e[i],h=r[i];if(o===B)a=d.update(n,h,l,s),c=De(e,u,r).rows;else{if(o!==U)throw new Error(\"Database received unknown update type: \"+o);a=d.delete(n,h,l),c=l}}return{status:\"SUCCESS\",state:function(e,t,n,r){const{batchToken:o,withMutations:s}=n;return s?(r[e]=t,r):P.batch.set(o,e,t,r)}(i,a,n,r),payload:c}}Object.defineProperty(Re,\"@@_______REDUX_ORM_STATE_FLAG\",{enumerable:!0,value:!0});var $e=function(e){const{tables:t}=e,n=Object.entries(t).reduce((e,[t,n])=>({...e,[t]:new Ie(n)}),{});return{getEmptyState:()=>Object.entries(n).reduce((e,[t,n])=>({...e,[t]:n.getEmptyState()}),Re),query:De.bind(null,n),update:Te.bind(null,n),describe:e=>n[e]}};let Pe=function(){function e({parent:e,orm:t}){this._parent=e,this._orm=t,this.keySelector=L}return o()(e,[{key:\"cachePath\",get:function(){return[...this._parent?this._parent.cachePath:[],this.key]}},{key:\"orm\",get:function(){return this._orm}},{key:\"parent\",get:function(){return this._parent}}]),e}(),Be=function(e){function t({model:t,...n}){var r;return(r=e.call(this,n)||this)._model=t,r}return ae()(t,e),o()(t,[{key:\"resultFunc\",get:function(){return(e,t,...n)=>{const{[this._model.modelName]:r}=e;return void 0===t?r.all().toModelArray().map(t=>this.valueForInstance(t,e,...n)):Array.isArray(t)?t.map(t=>this.valueForInstance(r.withId(t),e,...n)):this.valueForInstance(r.withId(t),e,...n)}}},{key:\"model\",get:function(){return this._model}}]),t}(Pe);function Ue(e,t){return t}let Ve=function(e){function t({field:t,selector:n,...r}){var o;return(o=e.call(this,r)||this)._field=t,o._selector=n,o}return ae()(t,e),t.prototype.createResultFunc=function(e){const{idAttribute:t}=this._parent.toModel;return(n,...r)=>{const o=e(n,...r),s=Ue(n,...r),i=e=>null===e?null:e.map(e=>this._selector(n,e[t]));return void 0===s||Array.isArray(s)?o.map(i):i(o)}},o()(t,[{key:\"selector\",get:function(){return this._selector},set:function(e){this._selector=e}},{key:\"key\",get:function(){return this._selector}}]),t}(Be),qe=function(e){function t({model:t,...n}){var r;return(r=e.call(this,n)||this)._model=t,r}return ae()(t,e),o()(t,[{key:\"key\",get:function(){return this._model.modelName}},{key:\"dependencies\",get:function(){return[this._orm,Ue]}},{key:\"resultFunc\",get:function(){return({[this._model.modelName]:e},t)=>{if(void 0===t)return e.all().toRefArray();if(Array.isArray(t))return t.map(t=>{const n=e.withId(t);return n?n.ref:null});const n=e.withId(t);return n?n.ref:null}}},{key:\"model\",get:function(){return this._model}}]),t}(Pe),ze=function(e){function t({field:t,fieldModel:n,accessorName:r,isVirtual:o,...s}){var i;return(i=e.call(this,s)||this)._field=t,i._fieldModel=n,i._accessorName=r,i._isVirtual=o,i}ae()(t,e);var n=t.prototype;return n.valueForInstance=function(e,t){if(!e)return null;let n;if(this._parent instanceof qe)n=e[this._accessorName];else{const{[this._parent.toModelName]:r}=t,o=this._parent.valueForInstance(e,t),s=o?new r(o):null;n=s?s[this._accessorName]:null}return n instanceof Oe?n.ref:n instanceof oe?n.toRefArray():n},n.map=function(e){if(e instanceof qe)throw this.toModelName===e.model.modelName?new Error(`Cannot select models in a \\`map()\\` call. If you just want the \\`${this._accessorName}\\` as a ref array then you can simply drop the \\`map()\\`. Otherwise make sure you're passing a field selector of the form \\`${this.toModelName}.<field>\\` or a custom selector instead.`):new Error(`Cannot select \\`${e.model.modelName}\\` models in this \\`map()\\` call. Make sure you're passing a field selector of the form \\`${this.toModelName}.<field>\\` or a custom selector instead.`);if(e instanceof t||e instanceof Ve){if(this.toModelName!==e.model.modelName)throw new Error(`Cannot select fields of the \\`${e.model.modelName}\\` model in this \\`map()\\` call. Make sure you're passing a field selector of the form \\`${this.toModelName}.<field>\\` or a custom selector instead.`)}else if(!e||\"function\"!=typeof e||!e.recomputations)throw new Error(`\\`map()\\` requires a selector as an input. Received: ${JSON.stringify(e)} of type ${typeof e}`);if(!(this._field instanceof pe||this._field instanceof me))throw new Error(\"Cannot map selectors for non-collection fields\");return new Ve({parent:this,model:this._model,orm:this._orm,field:this._field,selector:e})},o()(t,[{key:\"key\",get:function(){return this._accessorName}},{key:\"dependencies\",get:function(){return[this._orm,Ue]}},{key:\"toModelName\",get:function(){return\"this\"===this._field.toModelName?this._fieldModel.modelName:this._field.toModelName}},{key:\"toModel\",get:function(){return this._orm.getDatabase().describe(this.toModelName)}}]),t}(Be);function Le({parent:e,model:t,field:n,fieldModel:r,accessorName:o,orm:s,isVirtual:i}){const a=new ze({parent:e,model:t,field:n,fieldModel:r,accessorName:o,orm:s,isVirtual:i});if(!(n instanceof fe))return a;if(e instanceof ze&&(e._field instanceof pe&&e._isVirtual||e._field instanceof me))throw new Error(`Cannot create a selector for \\`${e._accessorName}.${o}\\` because \\`${e._accessorName}\\` is a collection field.`);const{toModelName:c}=n,u=s.get(\"this\"===c?t.modelName:c);return Object.entries(u.fields).forEach(([e,n])=>{const r=n.as||e;Object.defineProperty(a,r,{get:()=>Le({parent:a,model:t,fieldModel:u,field:n,accessorName:r,orm:s,isVirtual:!1})})}),Object.entries(u.virtualFields).forEach(([e,n])=>{const r=n.as||e;a.hasOwnProperty(r)||Object.defineProperty(a,r,{get:()=>Le({parent:a,model:t,fieldModel:u,field:n,accessorName:r,orm:s,isVirtual:!0})})}),a}const Qe={createDatabase:$e},Xe=[\"indexes\",\"meta\"],He=e=>Xe.includes(e);let Ye=function(){function e(e){const{createDatabase:t}={...Qe,...e||{}};this.createDatabase=t,this.registry=[],this.implicitThroughModels=[],this.installedFields={},this.stateSelector=e?e.stateSelector:null}var t=e.prototype;return t.register=function(...e){e.forEach(e=>{if(void 0===e.modelName)throw new Error(\"A model was passed that doesn't have a modelName set\");e.invalidateClassCache(),this.registerManyToManyModelsFor(e),this.registry.push(e),Object.defineProperty(this,e.modelName,{get:()=>(this._setupModelPrototypes(this.registry),function({model:e,orm:t}){const n=new qe({parent:null,orm:t,model:e});return Object.entries(e.fields).forEach(([r,o])=>{const s=o.as||r;Object.defineProperty(n,s,{get:()=>Le({parent:n,model:e,fieldModel:e,field:o,accessorName:s,orm:t,isVirtual:!1})})}),Object.entries(e.virtualFields).forEach(([r,o])=>{const s=o.as||r;n.hasOwnProperty(s)||Object.defineProperty(n,s,{get:()=>Le({parent:n,model:e,fieldModel:e,field:o,accessorName:s,orm:t,isVirtual:!0})})}),n}({model:e,orm:this}))})})},t.registerManyToManyModelsFor=function(e){const{fields:t}=e,n=e.modelName;Object.entries(t).forEach(([e,t])=>{if(!(t instanceof me))return;let r;r=\"this\"===t.toModelName?n:t.toModelName;const s=n===r,i=H(n),a=Y(r);if(t.through){if(s&&!t.throughFields)throw new Error(`Self-referencing many-to-many relationship at \"${n}.${e}\" using custom model \"${t.through}\" has no throughFields key. Cannot determine which fields reference the instances partaking in the relationship.`)}else{const t=function(e){function t(){return e.apply(this,arguments)||this}return ae()(t,e),t}(Oe);t.modelName=X(n,e);const c=function(e){function t(){return e.apply(this,arguments)||this}return ae()(t,e),o()(t,[{key:\"installsBackwardsVirtualField\",get:function(){return!1}},{key:\"installsBackwardsDescriptor\",get:function(){return!1}}]),t}(pe),u=s?c:pe;t.fields={id:ge(),[i]:new u(n),[a]:new u(r)},t.invalidateClassCache(),this.implicitThroughModels.push(t)}})},t.get=function(e){const t=this.registry.concat(this.implicitThroughModels),n=Object.values(t).find(t=>t.modelName===e);if(void 0===n)throw new Error(`Did not find model ${e} from registry.`);return n},t.getModelClasses=function(){return this._setupModelPrototypes(this.registry),this._setupModelPrototypes(this.implicitThroughModels),this.registry.concat(this.implicitThroughModels)},t.generateSchemaSpec=function(){return{tables:this.getModelClasses().reduce((e,t)=>{const n=t.modelName,r=t.tableOptions();return Object.keys(r).filter(He).forEach(e=>{throw new Error(`Reserved keyword \\`${e}\\` used in ${n}.options.`)}),e[n]={fields:{...t.fields},...r},e},{})}},t.getDatabase=function(){return this.db||(this.db=this.createDatabase(this.generateSchemaSpec())),this.db},t.getEmptyState=function(){return this.getDatabase().getEmptyState()},t.session=function(e){return new se(this,this.getDatabase(),e)},t.mutableSession=function(e){return new se(this,this.getDatabase(),e,!0)},t._setupModelPrototypes=function(e){e.filter(e=>!e.isSetUp).forEach(e=>{const{fields:t,modelName:n,querySetClass:r}=e;Object.entries(t).forEach(([t,r])=>{if(!(r instanceof ue))throw new Error(`${n}.${t} is of type \"${typeof r}\" but must be an instance of Field. Please use the \\`attr\\`, \\`fk\\`, \\`oneToOne\\` and \\`many\\` functions to define fields.`);this._isFieldInstalled(n,t)||(this._installField(r,t,e),this._setFieldInstalled(n,t))}),J(e,r),e.isSetUp=!0})},t._isFieldInstalled=function(e,t){return!!this.installedFields.hasOwnProperty(e)&&!!this.installedFields[e][t]},t._setFieldInstalled=function(e,t){this.installedFields.hasOwnProperty(e)||(this.installedFields[e]={}),this.installedFields[e][t]=!0},t._installField=function(e,t,n){new(0,e.installerClass)({field:e,fieldName:t,model:n,orm:this}).run()},t.withMutations=function(e){return Q(\"`ORM.prototype.withMutations` has been deprecated. Use `ORM.prototype.mutableSession` instead.\"),this.mutableSession(e)},t.from=function(e){return Q(\"`ORM.prototype.from` has been deprecated. Use `ORM.prototype.session` instead.\"),this.session(e)},t.getDefaultState=function(){return Q(\"`ORM.prototype.getDefaultState` has been deprecated. Use `ORM.prototype.getEmptyState` instead.\"),this.getEmptyState()},t.define=function(){throw new Error(\"`ORM.prototype.define` has been removed. Please define a Model class.\")},e}();var Ke=n(5),Ge=n(6),Je=n.n(Ge);const We=(e,t)=>e===t,Ze=e=>e&&\"object\"==typeof e&&e.hasOwnProperty(\"@@_______REDUX_ORM_STATE_FLAG\");function et(e,t=We,n){let r={result:null,args:null,ormState:null,fullTableScannedModels:[],accessedInstances:{},accessedIndexes:{}};return(...o)=>{const[s,...i]=o;if(Boolean(r.args)&&(a=r.args,c=t,i.every((e,t)=>Ze(e)&&Ze(a[t])||c(e,a[t])))&&((e,t)=>e.fullTableScannedModels.every(n=>e.ormState[n]===t[n]))(r,s)&&((e,t)=>{const{accessedIndexes:n}=e;return Object.entries(n).every(([n,r])=>Object.entries(r).every(([r,o])=>o.every(o=>e.ormState[n].indexes[r][o]===t[n].indexes[r][o])))})(r,s)&&((e,t,n)=>{const{accessedInstances:r}=e;return Object.entries(r).every(([r,o])=>{if(e.ormState[r]===t[r])return!0;const{mapName:s}=n.getDatabase().describe(r),{[s]:i}=e.ormState[r],{[s]:a}=t[r],c=Object.keys(o);return u=i,l=a,c.every(e=>u[e]===l[e]);var u,l})})(r,s,n))return r.result;var a,c;const u=n.session(s),l=i.map(e=>Ze(e)?u:e),d=e.apply(null,l);return r={args:i,result:d,ormState:s,accessedInstances:u.accessedModelInstances,accessedIndexes:u.accessedIndexes,fullTableScannedModels:u.fullTableScannedModels},d}}function tt(e,t){e.sessionBoundModels.forEach(n=>{\"function\"==typeof n.reducer&&n.reducer(t,n,e)})}function nt(e,t=tt){return(n,r)=>{const o=e.session(n||e.getEmptyState());return t(o,r),o.state}}function rt(e){return e instanceof Ye?e:e instanceof Pe&&e._orm}const ot=new Map,st=Symbol.for(\"REDUX_ORM_SELECTOR\");function it(e){if(\"function\"==typeof e)return e;if(e instanceof Ye)return e.stateSelector;if(e instanceof Ve&&(e.selector=it(e.selector)),e instanceof Pe){const{orm:t,cachePath:n}=e;let r;ot.has(t)||ot.set(t,new Map);r=ot.get(t);for(let e=0;e<n.length;++e){const t=n[e];r.has(t)||r.set(t,new Map),r=r.get(t)}if(r&&r.has(st))return r.get(st);const o=function e(t){if(t instanceof Ve){const n=e(t.parent);return t.createResultFunc(n)}return Je()(t.dependencies,t.resultFunc)({keySelector:t.keySelector,cacheObject:new Ge.FlatMapCache,selectorCreator:at})}(e);return r.set(st,o),o}throw new Error(`Failed to interpret selector argument: ${JSON.stringify(e)} of type ${typeof e}`)}function at(...e){if(!e.length)throw new Error(\"Cannot create a selector without arguments.\");const t=e.pop(),n=Array.isArray(e[0])?e[0]:e,r=n.map(rt).find(Boolean),o=n.map(it);if(\"function\"==typeof t){if(!r)throw new Error(\"Failed to resolve the current ORM database state. Please pass an ORM instance or an ORM selector as an argument to `createSelector()`.\");if(!r.stateSelector)throw new Error(\"Failed to resolve the current ORM database state. Please pass an object to the ORM constructor that specifies a `stateSelector` function.\");if(\"function\"!=typeof r.stateSelector)throw new Error(`Failed to resolve the current ORM database state. Please pass a function when specifying the ORM's \\`stateSelector\\`. Received: ${JSON.stringify(r.stateSelector)} of type ${typeof r.stateSelector}`);return Object(Ke.createSelectorCreator)(et,void 0,r)([r.stateSelector,...o],t)}if(t instanceof Ye)throw new Error(\"ORM instances cannot be the result function of selectors. You can access your models in the last function that you pass to `createSelector()`.\");return o.length&&console.warn(\"Your input selectors will be ignored: the passed result function does not require any input.\"),it(t)}const ct=function(){throw new Error(\"Schema has been renamed to ORM. Please import ORM instead of Schema from Redux-ORM.\")},ut=function(){throw new Error(\"Having a custom Backend instance is now unsupported. Documentation for database customization is upcoming, for now please look at the db folder in the source.\")};t.default=Oe}])}));\n //# sourceMappingURL=redux-orm.min.js.map\n\\ No newline at end of file\ndiff --git a/node_modules/redux-orm/dist/redux-orm.min.js.map b/node_modules/redux-orm/dist/redux-orm.min.js.map\nindex d45294a..8fa3298 100644\n--- a/node_modules/redux-orm/dist/redux-orm.min.js.map\n+++ b/node_modules/redux-orm/dist/redux-orm.min.js.map\n@@ -1 +1 @@\n-{\"version\":3,\"sources\":[\"webpack://ReduxOrm/webpack/universalModuleDefinition\",\"webpack://ReduxOrm/webpack/bootstrap\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/createClass.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/inheritsLoose.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/typeof.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/toConsumableArray.js\",\"webpack://ReduxOrm/./node_modules/reselect/lib/index.js\",\"webpack://ReduxOrm/./node_modules/re-reselect/dist/index.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/arrayLikeToArray.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseOrderBy.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseIteratee.js\",\"webpack://ReduxOrm/./node_modules/lodash/identity.js\",\"webpack://ReduxOrm/./node_modules/lodash/isArray.js\",\"webpack://ReduxOrm/./node_modules/lodash/filter.js\",\"webpack://ReduxOrm/./node_modules/lodash/orderBy.js\",\"webpack://ReduxOrm/./node_modules/lodash/reject.js\",\"webpack://ReduxOrm/./node_modules/lodash/sortBy.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/arrayWithoutHoles.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/iterableToArray.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/unsupportedIterableToArray.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/nonIterableSpread.js\",\"webpack://ReduxOrm/./node_modules/lodash/_arrayMap.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseMap.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseSortBy.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseUnary.js\",\"webpack://ReduxOrm/./node_modules/lodash/_compareMultiple.js\",\"webpack://ReduxOrm/./node_modules/lodash/_compareAscending.js\",\"webpack://ReduxOrm/./node_modules/lodash/isSymbol.js\",\"webpack://ReduxOrm/./node_modules/lodash/_arrayFilter.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseFilter.js\",\"webpack://ReduxOrm/./node_modules/lodash/negate.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseFlatten.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseRest.js\",\"webpack://ReduxOrm/./node_modules/lodash/_overRest.js\",\"webpack://ReduxOrm/./node_modules/lodash/_apply.js\",\"webpack://ReduxOrm/./node_modules/lodash/_setToString.js\",\"webpack://ReduxOrm/./node_modules/lodash/_isIterateeCall.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/internal/_isPlaceholder.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/internal/_curry1.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/internal/_arity.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/internal/_curry2.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/curryN.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/internal/_curryN.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/curry.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/__.js\",\"webpack://ReduxOrm/./node_modules/immutable-ops/es/index.js\",\"webpack://ReduxOrm/./src/constants.js\",\"webpack://ReduxOrm/./src/utils.js\",\"webpack://ReduxOrm/./src/QuerySet.js\",\"webpack://ReduxOrm/./src/Session.js\",\"webpack://ReduxOrm/./src/fields/DefaultFieldInstaller.js\",\"webpack://ReduxOrm/./src/fields/FieldInstallerTemplate.js\",\"webpack://ReduxOrm/./src/fields/Field.js\",\"webpack://ReduxOrm/./src/descriptors.js\",\"webpack://ReduxOrm/./src/fields/Attribute.js\",\"webpack://ReduxOrm/./src/fields/RelationalField.js\",\"webpack://ReduxOrm/./src/fields/ForeignKey.js\",\"webpack://ReduxOrm/./src/fields/ManyToMany.js\",\"webpack://ReduxOrm/./src/fields/OneToOne.js\",\"webpack://ReduxOrm/./src/fields/index.js\",\"webpack://ReduxOrm/./src/Model.js\",\"webpack://ReduxOrm/./src/db/Table.js\",\"webpack://ReduxOrm/./src/db/Database.js\",\"webpack://ReduxOrm/./src/selectors/SelectorSpec.js\",\"webpack://ReduxOrm/./src/selectors/ModelBasedSelectorSpec.js\",\"webpack://ReduxOrm/./src/selectors/idArgSelector.js\",\"webpack://ReduxOrm/./src/selectors/MapSelectorSpec.js\",\"webpack://ReduxOrm/./src/selectors/ModelSelectorSpec.js\",\"webpack://ReduxOrm/./src/selectors/FieldSelectorSpec.js\",\"webpack://ReduxOrm/./src/selectors/index.js\",\"webpack://ReduxOrm/./src/ORM.js\",\"webpack://ReduxOrm/./src/memoize.js\",\"webpack://ReduxOrm/./src/redux.js\",\"webpack://ReduxOrm/./src/index.js\"],\"names\":[\"root\",\"factory\",\"exports\",\"module\",\"define\",\"amd\",\"window\",\"installedModules\",\"__webpack_require__\",\"moduleId\",\"i\",\"l\",\"modules\",\"call\",\"m\",\"c\",\"d\",\"name\",\"getter\",\"o\",\"Object\",\"defineProperty\",\"enumerable\",\"get\",\"r\",\"Symbol\",\"toStringTag\",\"value\",\"t\",\"mode\",\"__esModule\",\"ns\",\"create\",\"key\",\"bind\",\"n\",\"object\",\"property\",\"prototype\",\"hasOwnProperty\",\"p\",\"s\",\"_defineProperties\",\"target\",\"props\",\"length\",\"descriptor\",\"configurable\",\"writable\",\"Constructor\",\"protoProps\",\"staticProps\",\"subClass\",\"superClass\",\"constructor\",\"__proto__\",\"_typeof\",\"obj\",\"iterator\",\"arrayWithoutHoles\",\"iterableToArray\",\"unsupportedIterableToArray\",\"nonIterableSpread\",\"arr\",\"defaultEqualityCheck\",\"a\",\"b\",\"areArgumentsShallowlyEqual\",\"equalityCheck\",\"prev\",\"next\",\"defaultMemoize\",\"func\",\"arguments\",\"undefined\",\"lastArgs\",\"lastResult\",\"apply\",\"getDependencies\",\"funcs\",\"dependencies\",\"Array\",\"isArray\",\"every\",\"dep\",\"dependencyTypes\",\"map\",\"join\",\"Error\",\"createSelectorCreator\",\"memoize\",\"_len\",\"memoizeOptions\",\"_key\",\"_len2\",\"_key2\",\"recomputations\",\"resultFunc\",\"pop\",\"memoizedResultFunc\",\"concat\",\"selector\",\"params\",\"push\",\"resetRecomputations\",\"createStructuredSelector\",\"selectors\",\"selectorCreator\",\"createSelector\",\"objectKeys\",\"keys\",\"_len3\",\"values\",\"_key3\",\"reduce\",\"composition\",\"index\",\"reselect\",\"isStringOrNumber\",\"FlatObjectCache\",\"this\",\"_cache\",\"_proto\",\"set\",\"selectorFn\",\"remove\",\"clear\",\"isValidCacheKey\",\"cacheKey\",\"defaultCacheCreator\",\"defaultCacheKeyValidator\",\"createCachedSelector\",\"polymorphicOptions\",\"legacyOptions\",\"options\",\"assign\",\"keySelector\",\"cache\",\"cacheObject\",\"keySelectorCreator\",\"inputSelectors\",\"cacheResponse\",\"console\",\"warn\",\"getMatchingSelector\",\"removeMatchingSelector\",\"clearCache\",\"validateCacheSize\",\"cacheSize\",\"Number\",\"isInteger\",\"FifoObjectCache\",\"_temp\",\"_cacheOrdering\",\"_cacheSize\",\"earliest\",\"indexOf\",\"splice\",\"LruObjectCache\",\"_registerCacheHit\",\"_deleteCacheHit\",\"FlatMapCache\",\"Map\",\"FifoMapCache\",\"size\",\"LruMapCache\",\"has\",\"FifoCacheObject\",\"FlatCacheObject\",\"LruCacheObject\",\"createStructuredCachedSelector\",\"default\",\"len\",\"arr2\",\"arrayMap\",\"baseIteratee\",\"baseMap\",\"baseSortBy\",\"baseUnary\",\"compareMultiple\",\"identity\",\"collection\",\"iteratees\",\"orders\",\"result\",\"iteratee\",\"other\",\"array\",\"predicate\",\"resIndex\",\"baseOrderBy\",\"guard\",\"arrayFilter\",\"baseFilter\",\"negate\",\"baseFlatten\",\"baseRest\",\"isIterateeCall\",\"sortBy\",\"arrayLikeToArray\",\"iter\",\"from\",\"minLen\",\"toString\",\"slice\",\"test\",\"TypeError\",\"comparer\",\"sort\",\"compareAscending\",\"objCriteria\",\"criteria\",\"othCriteria\",\"ordersLength\",\"isSymbol\",\"valIsDefined\",\"valIsNull\",\"valIsReflexive\",\"valIsSymbol\",\"othIsDefined\",\"othIsNull\",\"othIsReflexive\",\"othIsSymbol\",\"FUNC_ERROR_TEXT\",\"args\",\"overRest\",\"setToString\",\"start\",\"nativeMax\",\"Math\",\"max\",\"transform\",\"otherArgs\",\"thisArg\",\"_isPlaceholder\",\"_curry1\",\"fn\",\"f1\",\"_arity\",\"a0\",\"a1\",\"a2\",\"a3\",\"a4\",\"a5\",\"a6\",\"a7\",\"a8\",\"a9\",\"_curry2\",\"f2\",\"_b\",\"_a\",\"_curryN\",\"received\",\"combined\",\"argsIdx\",\"left\",\"combinedIdx\",\"forOwn\",\"OWNER_ID_TAG\",\"canMutate\",\"ownerID\",\"getBatchToken\",\"prepareNewObject\",\"instance\",\"addOwnerID\",\"forceArray\",\"arg\",\"PATH_SEPARATOR\",\"normalizePath\",\"pathArg\",\"split\",\"mutableSet\",\"mutableMerge\",\"isDeep\",\"_mergeObjs\",\"baseObj\",\"mergeObjs\",\"forEach\",\"mergeObj\",\"assignValue\",\"mutableShallowMerge\",\"mutableDeepMerge\",\"mutableOmit\",\"_keys\",\"shouldMergeKey\",\"immutableMerge\",\"hasChanges\",\"nextObject\",\"willChange\",\"mergeValue\",\"currentValue\",\"recursiveMergeResult\",\"immutableDeepMerge\",\"immutableArrSet\",\"newArr\",\"copied\",\"fastArrayCopy\",\"mutableArrFilter\",\"currIndex\",\"originalIndex\",\"mutableArrSplice\",\"deleteCount\",\"_vals\",\"vals\",\"mutableArrInsert\",\"immutableArrSplice\",\"immutableArrInsert\",\"immutableOperations\",\"merge\",\"deepMerge\",\"omit\",\"keysInObj\",\"filter\",\"newObj\",\"setIn\",\"_pathArg\",\"acc\",\"currRef\",\"valueInPath\",\"pathLen\",\"rootObj\",\"curr\",\"idx\",\"currType\",\"_newObj\",\"pathRepr\",\"insert\",\"isArrayLike\",\"mutableOperations\",\"originalPathArg\",\"done\",\"immutableOps\",\"mutableOps\",\"batchOps\",\"mutable\",\"batch\",\"batched\",\"_token\",\"_fn\",\"token\",\"immutableOpsBoundToToken\",\"__\",\"getImmutableOps\",\"UPDATE\",\"DELETE\",\"CREATE\",\"FILTER\",\"EXCLUDE\",\"SUCCESS\",\"ALL_INSTANCES\",\"ID_ARG_KEY_SELECTOR\",\"_state\",\"idArg\",\"warnDeprecated\",\"msg\",\"log\",\"m2mName\",\"declarationModelName\",\"fieldName\",\"string\",\"charAt\",\"toUpperCase\",\"m2mFromFieldName\",\"m2mToFieldName\",\"otherModelName\",\"querySetDelegatorFactory\",\"methodName\",\"getQuerySet\",\"querySetGetterDelegatorFactory\",\"getterName\",\"attachQuerySetMethods\",\"modelClass\",\"querySetClass\",\"leftToDefine\",\"sharedMethods\",\"currClass\",\"Function\",\"getPrototypeOf\",\"forEachSuperClass\",\"cls\",\"defined\",\"getOwnPropertyDescriptor\",\"normalizeEntity\",\"entity\",\"getId\",\"ops\",\"clauseFiltersByAttribute\",\"type\",\"payload\",\"attribute\",\"attributeValue\",\"mapValues\",\"entries\",\"newObject\",\"normalizeModelReference\",\"modelNameOrClass\",\"modelName\",\"QuerySet\",\"clauses\",\"opts\",\"_opts\",\"addSharedMethod\",\"_new\",\"userOpts\",\"_evaluate\",\"rows\",\"id\",\"withId\",\"toRefArray\",\"toModelArray\",\"ModelClass\",\"count\",\"exists\",\"Boolean\",\"at\",\"first\",\"last\",\"all\",\"lookupObj\",\"normalizedLookupObj\",\"filterDescriptor\",\"exclude\",\"excludeDescriptor\",\"session\",\"_evaluated\",\"table\",\"querySpec\",\"query\",\"orderBy\",\"orderByDescriptor\",\"update\",\"applyUpdate\",\"action\",\"delete\",\"model\",\"_onDelete\",\"Session\",\"schema\",\"db\",\"state\",\"withMutations\",\"batchToken\",\"getEmptyState\",\"initialState\",\"modelData\",\"models\",\"getModelClasses\",\"sessionBoundModels\",\"SessionBoundModel\",\"Reflect\",\"construct\",\"setPrototypeOf\",\"connect\",\"getDataForModel\",\"getModelData\",\"markAccessed\",\"modelIds\",\"data\",\"accessedInstances\",\"markFullTableScanned\",\"fullTableScanned\",\"markAccessedIndexes\",\"indexes\",\"attr\",\"accessedIndexes\",\"updateSpec\",\"tx\",\"_getTransaction\",\"status\",\"_markAccessedByQuery\",\"includes\",\"idAttribute\",\"accessedIds\",\"Set\",\"row\",\"anyClauseFilteredByPk\",\"some\",\"clause\",\"add\",\"getNextState\",\"DefaultFieldInstaller\",\"installForwardsDescriptor\",\"field\",\"createForwardsDescriptor\",\"toModel\",\"throughModel\",\"installForwardsVirtualField\",\"virtualFields\",\"createForwardsVirtualField\",\"installBackwardsDescriptor\",\"backwardsFieldName\",\"toModelName\",\"createBackwardsDescriptor\",\"installBackwardsVirtualField\",\"createBackwardsVirtualField\",\"orm\",\"references\",\"run\",\"installsForwardsVirtualField\",\"installsBackwardsDescriptor\",\"installsBackwardsVirtualField\",\"_toModel\",\"_throughModel\",\"throughModelName\",\"getThroughModelName\",\"getBackwardsFieldName\",\"Field\",\"getClass\",\"forwardsManyToOneDescriptor\",\"declaredToModelName\",\"DeclaredToModel\",\"toId\",\"_fields\",\"manyToManyDescriptor\",\"declaredFromModelName\",\"throughFields\",\"reverse\",\"DeclaredFromModel\",\"ThroughModel\",\"ThisModel\",\"OtherModel\",\"thisReferencingField\",\"to\",\"otherReferencingField\",\"thisId\",\"throughQs\",\"referencedOtherIds\",\"qs\",\"otherModelInstance\",\"entities\",\"idsToAdd\",\"existingQs\",\"through\",\"existingIds\",\"idsToRemove\",\"entitiesToDelete\",\"entitiesToDeleteIds\",\"unexistingIds\",\"Attribute\",\"getDefault\",\"attrDescriptor\",\"RelationalField\",\"relatedName\",\"as\",\"toLowerCase\",\"ForeignKey\",\"declaredFieldName\",\"ManyToMany\",\"getThroughFields\",\"fieldAName\",\"fieldBName\",\"fieldA\",\"fields\",\"throughModelFieldReferencing\",\"otherModel\",\"find\",\"someFieldName\",\"OneToOne\",\"forwardsOneToOneDescriptor\",\"fk\",\"many\",\"oneToOne\",\"getByIdQuery\",\"modelInstance\",\"Model\",\"_initFields\",\"propsObj\",\"ids\",\"_session\",\"QuerySetClass\",\"invalidateClassCache\",\"isSetUp\",\"tableOptions\",\"backend\",\"userProps\",\"m2mRelations\",\"declaredFieldNames\",\"declaredVirtualFieldNames\",\"valuePassed\",\"_refreshMany2Many\",\"upsert\",\"idExists\",\"_findDatabaseRows\",\"equals\",\"entriesInA\",\"objectShallowEquals\",\"propertyName\",\"userMergeObj\",\"mergeKey\",\"mergedFields\",\"updatedModel\",\"refreshFromState\",\"ref\",\"relations\",\"normalizedNewIds\",\"uniqueIds\",\"fromField\",\"toField\",\"diffActions\",\"sourceArr\",\"targetArr\",\"itemsInBoth\",\"item\",\"deleteItems\",\"addItems\",\"arrayDiffActions\",\"idsToDelete\",\"relatedQs\",\"hasId\",\"describe\",\"DEFAULT_TABLE_OPTIONS\",\"arrName\",\"mapName\",\"Table\",\"accessId\",\"branch\",\"accessIds\",\"accessIdList\",\"accessList\",\"getMaxId\",\"getMeta\",\"setMaxId\",\"newMaxId\",\"setMeta\",\"nextId\",\"meta\",\"optimallyOrderedClauses\",\"clauseReducesResultSetSize\",\"reducer\",\"remainingPayload\",\"withoutPkAttr\",\"filterAttr\",\"indexAttrs\",\"lastIndex\",\"indexedIds\",\"indexSet\",\"withoutIndexAttrs\",\"reject\",\"convert\",\"order\",\"normalizeOrders\",\"entry\",\"workingState\",\"_currMax\",\"userPassedId\",\"newMax\",\"newId\",\"currMax\",\"idSequencer\",\"finalEntry\",\"indexesToAppendTo\",\"fkAttr\",\"attrIndex\",\"created\",\"nextIndexes\",\"indexMap\",\"indexedAttrs\",\"indexIdsToAdd\",\"indexIdsToDelete\",\"nextMap\",\"prevAttrValues\",\"valueMap\",\"mergeObjInto\",\"nextAttrValues\",\"nextRow\",\"prevValue\",\"nextValue\",\"rowId\",\"valueIndex\",\"attrIndexMap\",\"BASE_EMPTY_STATE\",\"tables\",\"tableName\",\"nextTableState\",\"resultPayload\",\"currTableState\",\"nextDBState\",\"newTableState\",\"replaceTableState\",\"createDatabase\",\"schemaSpec\",\"tableSpecs\",\"tableSpec\",\"SelectorSpec\",\"parent\",\"_parent\",\"_orm\",\"cachePath\",\"ModelBasedSelectorSpec\",\"_model\",\"valueForInstance\",\"idArgSelector\",\"MapSelectorSpec\",\"_field\",\"_selector\",\"createResultFunc\",\"parentSelector\",\"parentResult\",\"single\",\"refArray\",\"ModelSelectorSpec\",\"FieldSelectorSpec\",\"fieldModel\",\"accessorName\",\"isVirtual\",\"_fieldModel\",\"_accessorName\",\"_isVirtual\",\"ParentToModel\",\"parentRef\",\"parentInstance\",\"JSON\",\"stringify\",\"getDatabase\",\"createFieldSelectorSpec\",\"fieldSelectorSpec\",\"relatedFieldName\",\"relatedField\",\"fieldAccessorName\",\"ORM_DEFAULTS\",\"defaultCreateDatabase\",\"RESERVED_TABLE_OPTIONS\",\"isReservedTableOption\",\"word\",\"ORM\",\"registry\",\"implicitThroughModels\",\"installedFields\",\"stateSelector\",\"register\",\"registerManyToManyModelsFor\",\"_setupModelPrototypes\",\"modelSelectorSpec\",\"createModelSelectorSpec\",\"thisModelName\",\"fieldInstance\",\"selfReferencing\",\"fromFieldName\",\"toFieldName\",\"Through\",\"PlainForeignKey\",\"ForeignKeyClass\",\"allModels\",\"found\",\"generateSchemaSpec\",\"spec\",\"mutableSession\",\"_isFieldInstalled\",\"_installField\",\"_setFieldInstalled\",\"FieldInstaller\",\"installerClass\",\"getDefaultState\",\"isOrmState\",\"argsAreEqual\",\"nextArgs\",\"accessedModelInstancesAreEqual\",\"previous\",\"ormState\",\"instances\",\"previousRows\",\"rowsA\",\"rowsB\",\"rowsAreEqual\",\"accessedIndexesAreEqual\",\"column\",\"fullTableScannedModelsAreEqual\",\"fullTableScannedModels\",\"argEqualityCheck\",\"stateAndArgs\",\"argsWithSession\",\"accessedModelInstances\",\"defaultUpdater\",\"createReducer\",\"updater\",\"toORM\",\"selectorCache\",\"SELECTOR_KEY\",\"for\",\"toSelector\",\"level\",\"storageKey\",\"createSelectorFromSpec\",\"resultArg\",\"inputFuncs\",\"Schema\",\"Backend\"],\"mappings\":\"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\",\"file\":\"redux-orm.min.js\",\"sourcesContent\":[\"(function webpackUniversalModuleDefinition(root, factory) {\\n\\tif(typeof exports === 'object' && typeof module === 'object')\\n\\t\\tmodule.exports = factory();\\n\\telse if(typeof define === 'function' && define.amd)\\n\\t\\tdefine(\\\"ReduxOrm\\\", [], factory);\\n\\telse if(typeof exports === 'object')\\n\\t\\texports[\\\"ReduxOrm\\\"] = factory();\\n\\telse\\n\\t\\troot[\\\"ReduxOrm\\\"] = factory();\\n})(window, function() {\\nreturn \",\" \\t// The module cache\\n \\tvar installedModules = {};\\n\\n \\t// The require function\\n \\tfunction __webpack_require__(moduleId) {\\n\\n \\t\\t// Check if module is in cache\\n \\t\\tif(installedModules[moduleId]) {\\n \\t\\t\\treturn installedModules[moduleId].exports;\\n \\t\\t}\\n \\t\\t// Create a new module (and put it into the cache)\\n \\t\\tvar module = installedModules[moduleId] = {\\n \\t\\t\\ti: moduleId,\\n \\t\\t\\tl: false,\\n \\t\\t\\texports: {}\\n \\t\\t};\\n\\n \\t\\t// Execute the module function\\n \\t\\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\\n\\n \\t\\t// Flag the module as loaded\\n \\t\\tmodule.l = true;\\n\\n \\t\\t// Return the exports of the module\\n \\t\\treturn module.exports;\\n \\t}\\n\\n\\n \\t// expose the modules object (__webpack_modules__)\\n \\t__webpack_require__.m = modules;\\n\\n \\t// expose the module cache\\n \\t__webpack_require__.c = installedModules;\\n\\n \\t// define getter function for harmony exports\\n \\t__webpack_require__.d = function(exports, name, getter) {\\n \\t\\tif(!__webpack_require__.o(exports, name)) {\\n \\t\\t\\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\\n \\t\\t}\\n \\t};\\n\\n \\t// define __esModule on exports\\n \\t__webpack_require__.r = function(exports) {\\n \\t\\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\\n \\t\\t\\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\\n \\t\\t}\\n \\t\\tObject.defineProperty(exports, '__esModule', { value: true });\\n \\t};\\n\\n \\t// create a fake namespace object\\n \\t// mode & 1: value is a module id, require it\\n \\t// mode & 2: merge all properties of value into the ns\\n \\t// mode & 4: return value when already ns object\\n \\t// mode & 8|1: behave like require\\n \\t__webpack_require__.t = function(value, mode) {\\n \\t\\tif(mode & 1) value = __webpack_require__(value);\\n \\t\\tif(mode & 8) return value;\\n \\t\\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\\n \\t\\tvar ns = Object.create(null);\\n \\t\\t__webpack_require__.r(ns);\\n \\t\\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\\n \\t\\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\\n \\t\\treturn ns;\\n \\t};\\n\\n \\t// getDefaultExport function for compatibility with non-harmony modules\\n \\t__webpack_require__.n = function(module) {\\n \\t\\tvar getter = module && module.__esModule ?\\n \\t\\t\\tfunction getDefault() { return module['default']; } :\\n \\t\\t\\tfunction getModuleExports() { return module; };\\n \\t\\t__webpack_require__.d(getter, 'a', getter);\\n \\t\\treturn getter;\\n \\t};\\n\\n \\t// Object.prototype.hasOwnProperty.call\\n \\t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\\n\\n \\t// __webpack_public_path__\\n \\t__webpack_require__.p = \\\"\\\";\\n\\n\\n \\t// Load entry module and return exports\\n \\treturn __webpack_require__(__webpack_require__.s = 35);\\n\",\"function _defineProperties(target, props) {\\n  for (var i = 0; i < props.length; i++) {\\n    var descriptor = props[i];\\n    descriptor.enumerable = descriptor.enumerable || false;\\n    descriptor.configurable = true;\\n    if (\\\"value\\\" in descriptor) descriptor.writable = true;\\n    Object.defineProperty(target, descriptor.key, descriptor);\\n  }\\n}\\n\\nfunction _createClass(Constructor, protoProps, staticProps) {\\n  if (protoProps) _defineProperties(Constructor.prototype, protoProps);\\n  if (staticProps) _defineProperties(Constructor, staticProps);\\n  return Constructor;\\n}\\n\\nmodule.exports = _createClass;\",\"function _inheritsLoose(subClass, superClass) {\\n  subClass.prototype = Object.create(superClass.prototype);\\n  subClass.prototype.constructor = subClass;\\n  subClass.__proto__ = superClass;\\n}\\n\\nmodule.exports = _inheritsLoose;\",\"function _typeof(obj) {\\n  \\\"@babel/helpers - typeof\\\";\\n\\n  if (typeof Symbol === \\\"function\\\" && typeof Symbol.iterator === \\\"symbol\\\") {\\n    module.exports = _typeof = function _typeof(obj) {\\n      return typeof obj;\\n    };\\n  } else {\\n    module.exports = _typeof = function _typeof(obj) {\\n      return obj && typeof Symbol === \\\"function\\\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \\\"symbol\\\" : typeof obj;\\n    };\\n  }\\n\\n  return _typeof(obj);\\n}\\n\\nmodule.exports = _typeof;\",\"var arrayWithoutHoles = require(\\\"./arrayWithoutHoles\\\");\\n\\nvar iterableToArray = require(\\\"./iterableToArray\\\");\\n\\nvar unsupportedIterableToArray = require(\\\"./unsupportedIterableToArray\\\");\\n\\nvar nonIterableSpread = require(\\\"./nonIterableSpread\\\");\\n\\nfunction _toConsumableArray(arr) {\\n  return arrayWithoutHoles(arr) || iterableToArray(arr) || unsupportedIterableToArray(arr) || nonIterableSpread();\\n}\\n\\nmodule.exports = _toConsumableArray;\",\"'use strict';\\n\\nexports.__esModule = true;\\nexports.defaultMemoize = defaultMemoize;\\nexports.createSelectorCreator = createSelectorCreator;\\nexports.createStructuredSelector = createStructuredSelector;\\nfunction defaultEqualityCheck(a, b) {\\n  return a === b;\\n}\\n\\nfunction areArgumentsShallowlyEqual(equalityCheck, prev, next) {\\n  if (prev === null || next === null || prev.length !== next.length) {\\n    return false;\\n  }\\n\\n  // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.\\n  var length = prev.length;\\n  for (var i = 0; i < length; i++) {\\n    if (!equalityCheck(prev[i], next[i])) {\\n      return false;\\n    }\\n  }\\n\\n  return true;\\n}\\n\\nfunction defaultMemoize(func) {\\n  var equalityCheck = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultEqualityCheck;\\n\\n  var lastArgs = null;\\n  var lastResult = null;\\n  // we reference arguments instead of spreading them for performance reasons\\n  return function () {\\n    if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {\\n      // apply arguments instead of spreading for performance.\\n      lastResult = func.apply(null, arguments);\\n    }\\n\\n    lastArgs = arguments;\\n    return lastResult;\\n  };\\n}\\n\\nfunction getDependencies(funcs) {\\n  var dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs;\\n\\n  if (!dependencies.every(function (dep) {\\n    return typeof dep === 'function';\\n  })) {\\n    var dependencyTypes = dependencies.map(function (dep) {\\n      return typeof dep;\\n    }).join(', ');\\n    throw new Error('Selector creators expect all input-selectors to be functions, ' + ('instead received the following types: [' + dependencyTypes + ']'));\\n  }\\n\\n  return dependencies;\\n}\\n\\nfunction createSelectorCreator(memoize) {\\n  for (var _len = arguments.length, memoizeOptions = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\\n    memoizeOptions[_key - 1] = arguments[_key];\\n  }\\n\\n  return function () {\\n    for (var _len2 = arguments.length, funcs = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\\n      funcs[_key2] = arguments[_key2];\\n    }\\n\\n    var recomputations = 0;\\n    var resultFunc = funcs.pop();\\n    var dependencies = getDependencies(funcs);\\n\\n    var memoizedResultFunc = memoize.apply(undefined, [function () {\\n      recomputations++;\\n      // apply arguments instead of spreading for performance.\\n      return resultFunc.apply(null, arguments);\\n    }].concat(memoizeOptions));\\n\\n    // If a selector is called with the exact same arguments we don't need to traverse our dependencies again.\\n    var selector = defaultMemoize(function () {\\n      var params = [];\\n      var length = dependencies.length;\\n\\n      for (var i = 0; i < length; i++) {\\n        // apply arguments instead of spreading and mutate a local list of params for performance.\\n        params.push(dependencies[i].apply(null, arguments));\\n      }\\n\\n      // apply arguments instead of spreading for performance.\\n      return memoizedResultFunc.apply(null, params);\\n    });\\n\\n    selector.resultFunc = resultFunc;\\n    selector.recomputations = function () {\\n      return recomputations;\\n    };\\n    selector.resetRecomputations = function () {\\n      return recomputations = 0;\\n    };\\n    return selector;\\n  };\\n}\\n\\nvar createSelector = exports.createSelector = createSelectorCreator(defaultMemoize);\\n\\nfunction createStructuredSelector(selectors) {\\n  var selectorCreator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : createSelector;\\n\\n  if (typeof selectors !== 'object') {\\n    throw new Error('createStructuredSelector expects first argument to be an object ' + ('where each property is a selector, instead received a ' + typeof selectors));\\n  }\\n  var objectKeys = Object.keys(selectors);\\n  return selectorCreator(objectKeys.map(function (key) {\\n    return selectors[key];\\n  }), function () {\\n    for (var _len3 = arguments.length, values = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {\\n      values[_key3] = arguments[_key3];\\n    }\\n\\n    return values.reduce(function (composition, value, index) {\\n      composition[objectKeys[index]] = value;\\n      return composition;\\n    }, {});\\n  });\\n}\",\"(function (global, factory) {\\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('reselect')) :\\n  typeof define === 'function' && define.amd ? define(['exports', 'reselect'], factory) :\\n  (global = global || self, factory(global['Re-reselect'] = {}, global.Reselect));\\n}(this, function (exports, reselect) { 'use strict';\\n\\n  function isStringOrNumber(value) {\\n    return typeof value === 'string' || typeof value === 'number';\\n  }\\n\\n  var FlatObjectCache =\\n  /*#__PURE__*/\\n  function () {\\n    function FlatObjectCache() {\\n      this._cache = {};\\n    }\\n\\n    var _proto = FlatObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return FlatObjectCache;\\n  }();\\n\\n  var defaultCacheCreator = FlatObjectCache;\\n\\n  var defaultCacheKeyValidator = function defaultCacheKeyValidator() {\\n    return true;\\n  };\\n\\n  function createCachedSelector() {\\n    for (var _len = arguments.length, funcs = new Array(_len), _key = 0; _key < _len; _key++) {\\n      funcs[_key] = arguments[_key];\\n    }\\n\\n    return function (polymorphicOptions, legacyOptions) {\\n      // @NOTE Versions 0.x/1.x accepted \\\"options\\\" as a function\\n      if (typeof legacyOptions === 'function') {\\n        throw new Error('[re-reselect] Second argument \\\"options\\\" must be an object. Please use \\\"options.selectorCreator\\\" to provide a custom selectorCreator.');\\n      }\\n\\n      var options = {};\\n\\n      if (typeof polymorphicOptions === 'function') {\\n        Object.assign(options, legacyOptions, {\\n          keySelector: polymorphicOptions\\n        }); // @TODO add legacyOptions deprecation notice in next major release\\n      } else {\\n        Object.assign(options, polymorphicOptions);\\n      } // https://github.com/reduxjs/reselect/blob/v4.0.0/src/index.js#L54\\n\\n\\n      var recomputations = 0;\\n      var resultFunc = funcs.pop();\\n      var dependencies = Array.isArray(funcs[0]) ? funcs[0] : [].concat(funcs);\\n\\n      var resultFuncWithRecomputations = function resultFuncWithRecomputations() {\\n        recomputations++;\\n        return resultFunc.apply(void 0, arguments);\\n      };\\n\\n      funcs.push(resultFuncWithRecomputations);\\n      var cache = options.cacheObject || new defaultCacheCreator();\\n      var selectorCreator = options.selectorCreator || reselect.createSelector;\\n      var isValidCacheKey = cache.isValidCacheKey || defaultCacheKeyValidator;\\n\\n      if (options.keySelectorCreator) {\\n        options.keySelector = options.keySelectorCreator({\\n          keySelector: options.keySelector,\\n          inputSelectors: dependencies,\\n          resultFunc: resultFunc\\n        });\\n      } // Application receives this function\\n\\n\\n      var selector = function selector() {\\n        var cacheKey = options.keySelector.apply(options, arguments);\\n\\n        if (isValidCacheKey(cacheKey)) {\\n          var cacheResponse = cache.get(cacheKey);\\n\\n          if (cacheResponse === undefined) {\\n            cacheResponse = selectorCreator.apply(void 0, funcs);\\n            cache.set(cacheKey, cacheResponse);\\n          }\\n\\n          return cacheResponse.apply(void 0, arguments);\\n        }\\n\\n        console.warn(\\\"[re-reselect] Invalid cache key \\\\\\\"\\\" + cacheKey + \\\"\\\\\\\" has been returned by keySelector function.\\\");\\n        return undefined;\\n      }; // Further selector methods\\n\\n\\n      selector.getMatchingSelector = function () {\\n        var cacheKey = options.keySelector.apply(options, arguments); // @NOTE It might update cache hit count in LRU-like caches\\n\\n        return cache.get(cacheKey);\\n      };\\n\\n      selector.removeMatchingSelector = function () {\\n        var cacheKey = options.keySelector.apply(options, arguments);\\n        cache.remove(cacheKey);\\n      };\\n\\n      selector.clearCache = function () {\\n        cache.clear();\\n      };\\n\\n      selector.resultFunc = resultFunc;\\n      selector.dependencies = dependencies;\\n      selector.cache = cache;\\n\\n      selector.recomputations = function () {\\n        return recomputations;\\n      };\\n\\n      selector.resetRecomputations = function () {\\n        return recomputations = 0;\\n      };\\n\\n      selector.keySelector = options.keySelector;\\n      return selector;\\n    };\\n  }\\n\\n  function createStructuredCachedSelector(selectors) {\\n    return reselect.createStructuredSelector(selectors, createCachedSelector);\\n  }\\n\\n  function validateCacheSize(cacheSize) {\\n    if (cacheSize === undefined) {\\n      throw new Error('Missing the required property \\\"cacheSize\\\".');\\n    }\\n\\n    if (!Number.isInteger(cacheSize) || cacheSize <= 0) {\\n      throw new Error('The \\\"cacheSize\\\" property must be a positive integer value.');\\n    }\\n  }\\n\\n  var FifoObjectCache =\\n  /*#__PURE__*/\\n  function () {\\n    function FifoObjectCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = FifoObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n\\n      this._cacheOrdering.push(key);\\n\\n      if (this._cacheOrdering.length > this._cacheSize) {\\n        var earliest = this._cacheOrdering[0];\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      var index = this._cacheOrdering.indexOf(key);\\n\\n      if (index > -1) {\\n        this._cacheOrdering.splice(index, 1);\\n      }\\n\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return FifoObjectCache;\\n  }();\\n\\n  var LruObjectCache =\\n  /*#__PURE__*/\\n  function () {\\n    function LruObjectCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = LruObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n\\n      this._registerCacheHit(key);\\n\\n      if (this._cacheOrdering.length > this._cacheSize) {\\n        var earliest = this._cacheOrdering[0];\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      this._registerCacheHit(key);\\n\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._deleteCacheHit(key);\\n\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n    };\\n\\n    _proto._registerCacheHit = function _registerCacheHit(key) {\\n      this._deleteCacheHit(key);\\n\\n      this._cacheOrdering.push(key);\\n    };\\n\\n    _proto._deleteCacheHit = function _deleteCacheHit(key) {\\n      var index = this._cacheOrdering.indexOf(key);\\n\\n      if (index > -1) {\\n        this._cacheOrdering.splice(index, 1);\\n      }\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return LruObjectCache;\\n  }();\\n\\n  var FlatMapCache =\\n  /*#__PURE__*/\\n  function () {\\n    function FlatMapCache() {\\n      this._cache = new Map();\\n    }\\n\\n    var _proto = FlatMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache.get(key);\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return FlatMapCache;\\n  }();\\n\\n  var FifoMapCache =\\n  /*#__PURE__*/\\n  function () {\\n    function FifoMapCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = new Map();\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = FifoMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n\\n      if (this._cache.size > this._cacheSize) {\\n        var earliest = this._cache.keys().next().value;\\n\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache.get(key);\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return FifoMapCache;\\n  }();\\n\\n  var LruMapCache =\\n  /*#__PURE__*/\\n  function () {\\n    function LruMapCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = new Map();\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = LruMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n\\n      if (this._cache.size > this._cacheSize) {\\n        var earliest = this._cache.keys().next().value;\\n\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      var value = this._cache.get(key); // Register cache hit\\n\\n\\n      if (this._cache.has(key)) {\\n        this.remove(key);\\n\\n        this._cache.set(key, value);\\n      }\\n\\n      return value;\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return LruMapCache;\\n  }();\\n\\n  exports.FifoCacheObject = FifoObjectCache;\\n  exports.FifoMapCache = FifoMapCache;\\n  exports.FifoObjectCache = FifoObjectCache;\\n  exports.FlatCacheObject = FlatObjectCache;\\n  exports.FlatMapCache = FlatMapCache;\\n  exports.FlatObjectCache = FlatObjectCache;\\n  exports.LruCacheObject = LruMapCache;\\n  exports.LruMapCache = LruMapCache;\\n  exports.LruObjectCache = LruObjectCache;\\n  exports.createStructuredCachedSelector = createStructuredCachedSelector;\\n  exports.default = createCachedSelector;\\n\\n  Object.defineProperty(exports, '__esModule', { value: true });\\n\\n}));\\n//# sourceMappingURL=index.js.map\\n\",\"function _arrayLikeToArray(arr, len) {\\n  if (len == null || len > arr.length) len = arr.length;\\n\\n  for (var i = 0, arr2 = new Array(len); i < len; i++) {\\n    arr2[i] = arr[i];\\n  }\\n\\n  return arr2;\\n}\\n\\nmodule.exports = _arrayLikeToArray;\",\"var arrayMap = require('./_arrayMap'),\\n    baseIteratee = require('./_baseIteratee'),\\n    baseMap = require('./_baseMap'),\\n    baseSortBy = require('./_baseSortBy'),\\n    baseUnary = require('./_baseUnary'),\\n    compareMultiple = require('./_compareMultiple'),\\n    identity = require('./identity');\\n\\n/**\\n * The base implementation of `_.orderBy` without param guards.\\n *\\n * @private\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.\\n * @param {string[]} orders The sort orders of `iteratees`.\\n * @returns {Array} Returns the new sorted array.\\n */\\nfunction baseOrderBy(collection, iteratees, orders) {\\n  var index = -1;\\n  iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(baseIteratee));\\n\\n  var result = baseMap(collection, function(value, key, collection) {\\n    var criteria = arrayMap(iteratees, function(iteratee) {\\n      return iteratee(value);\\n    });\\n    return { 'criteria': criteria, 'index': ++index, 'value': value };\\n  });\\n\\n  return baseSortBy(result, function(object, other) {\\n    return compareMultiple(object, other, orders);\\n  });\\n}\\n\\nmodule.exports = baseOrderBy;\\n\",\"/**\\n * This method returns the first argument it receives.\\n *\\n * @static\\n * @since 0.1.0\\n * @memberOf _\\n * @category Util\\n * @param {*} value Any value.\\n * @returns {*} Returns `value`.\\n * @example\\n *\\n * var object = { 'a': 1 };\\n *\\n * console.log(_.identity(object) === object);\\n * // => true\\n */\\nfunction identity(value) {\\n  return value;\\n}\\n\\nmodule.exports = identity;\\n\",\"/**\\n * This method returns the first argument it receives.\\n *\\n * @static\\n * @since 0.1.0\\n * @memberOf _\\n * @category Util\\n * @param {*} value Any value.\\n * @returns {*} Returns `value`.\\n * @example\\n *\\n * var object = { 'a': 1 };\\n *\\n * console.log(_.identity(object) === object);\\n * // => true\\n */\\nfunction identity(value) {\\n  return value;\\n}\\n\\nmodule.exports = identity;\\n\",\"/**\\n * Checks if `value` is classified as an `Array` object.\\n *\\n * @static\\n * @memberOf _\\n * @since 0.1.0\\n * @category Lang\\n * @param {*} value The value to check.\\n * @returns {boolean} Returns `true` if `value` is an array, else `false`.\\n * @example\\n *\\n * _.isArray([1, 2, 3]);\\n * // => true\\n *\\n * _.isArray(document.body.children);\\n * // => false\\n *\\n * _.isArray('abc');\\n * // => false\\n *\\n * _.isArray(_.noop);\\n * // => false\\n */\\nvar isArray = Array.isArray;\\n\\nmodule.exports = isArray;\\n\",\"/**\\n * A specialized version of `_.filter` for arrays without support for\\n * iteratee shorthands.\\n *\\n * @private\\n * @param {Array} [array] The array to iterate over.\\n * @param {Function} predicate The function invoked per iteration.\\n * @returns {Array} Returns the new filtered array.\\n */\\nfunction arrayFilter(array, predicate) {\\n  var index = -1,\\n      length = array == null ? 0 : array.length,\\n      resIndex = 0,\\n      result = [];\\n\\n  while (++index < length) {\\n    var value = array[index];\\n    if (predicate(value, index, array)) {\\n      result[resIndex++] = value;\\n    }\\n  }\\n  return result;\\n}\\n\\nmodule.exports = arrayFilter;\\n\",\"var baseOrderBy = require('./_baseOrderBy'),\\n    isArray = require('./isArray');\\n\\n/**\\n * This method is like `_.sortBy` except that it allows specifying the sort\\n * orders of the iteratees to sort by. If `orders` is unspecified, all values\\n * are sorted in ascending order. Otherwise, specify an order of \\\"desc\\\" for\\n * descending or \\\"asc\\\" for ascending sort order of corresponding values.\\n *\\n * @static\\n * @memberOf _\\n * @since 4.0.0\\n * @category Collection\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]\\n *  The iteratees to sort by.\\n * @param {string[]} [orders] The sort orders of `iteratees`.\\n * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.\\n * @returns {Array} Returns the new sorted array.\\n * @example\\n *\\n * var users = [\\n *   { 'user': 'fred',   'age': 48 },\\n *   { 'user': 'barney', 'age': 34 },\\n *   { 'user': 'fred',   'age': 40 },\\n *   { 'user': 'barney', 'age': 36 }\\n * ];\\n *\\n * // Sort by `user` in ascending order and by `age` in descending order.\\n * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);\\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]\\n */\\nfunction orderBy(collection, iteratees, orders, guard) {\\n  if (collection == null) {\\n    return [];\\n  }\\n  if (!isArray(iteratees)) {\\n    iteratees = iteratees == null ? [] : [iteratees];\\n  }\\n  orders = guard ? undefined : orders;\\n  if (!isArray(orders)) {\\n    orders = orders == null ? [] : [orders];\\n  }\\n  return baseOrderBy(collection, iteratees, orders);\\n}\\n\\nmodule.exports = orderBy;\\n\",\"var arrayFilter = require('./_arrayFilter'),\\n    baseFilter = require('./_baseFilter'),\\n    baseIteratee = require('./_baseIteratee'),\\n    isArray = require('./isArray'),\\n    negate = require('./negate');\\n\\n/**\\n * The opposite of `_.filter`; this method returns the elements of `collection`\\n * that `predicate` does **not** return truthy for.\\n *\\n * @static\\n * @memberOf _\\n * @since 0.1.0\\n * @category Collection\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {Function} [predicate=_.identity] The function invoked per iteration.\\n * @returns {Array} Returns the new filtered array.\\n * @see _.filter\\n * @example\\n *\\n * var users = [\\n *   { 'user': 'barney', 'age': 36, 'active': false },\\n *   { 'user': 'fred',   'age': 40, 'active': true }\\n * ];\\n *\\n * _.reject(users, function(o) { return !o.active; });\\n * // => objects for ['fred']\\n *\\n * // The `_.matches` iteratee shorthand.\\n * _.reject(users, { 'age': 40, 'active': true });\\n * // => objects for ['barney']\\n *\\n * // The `_.matchesProperty` iteratee shorthand.\\n * _.reject(users, ['active', false]);\\n * // => objects for ['fred']\\n *\\n * // The `_.property` iteratee shorthand.\\n * _.reject(users, 'active');\\n * // => objects for ['barney']\\n */\\nfunction reject(collection, predicate) {\\n  var func = isArray(collection) ? arrayFilter : baseFilter;\\n  return func(collection, negate(baseIteratee(predicate, 3)));\\n}\\n\\nmodule.exports = reject;\\n\",\"var baseFlatten = require('./_baseFlatten'),\\n    baseOrderBy = require('./_baseOrderBy'),\\n    baseRest = require('./_baseRest'),\\n    isIterateeCall = require('./_isIterateeCall');\\n\\n/**\\n * Creates an array of elements, sorted in ascending order by the results of\\n * running each element in a collection thru each iteratee. This method\\n * performs a stable sort, that is, it preserves the original sort order of\\n * equal elements. The iteratees are invoked with one argument: (value).\\n *\\n * @static\\n * @memberOf _\\n * @since 0.1.0\\n * @category Collection\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {...(Function|Function[])} [iteratees=[_.identity]]\\n *  The iteratees to sort by.\\n * @returns {Array} Returns the new sorted array.\\n * @example\\n *\\n * var users = [\\n *   { 'user': 'fred',   'age': 48 },\\n *   { 'user': 'barney', 'age': 36 },\\n *   { 'user': 'fred',   'age': 40 },\\n *   { 'user': 'barney', 'age': 34 }\\n * ];\\n *\\n * _.sortBy(users, [function(o) { return o.user; }]);\\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]\\n *\\n * _.sortBy(users, ['user', 'age']);\\n * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]\\n */\\nvar sortBy = baseRest(function(collection, iteratees) {\\n  if (collection == null) {\\n    return [];\\n  }\\n  var length = iteratees.length;\\n  if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {\\n    iteratees = [];\\n  } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {\\n    iteratees = [iteratees[0]];\\n  }\\n  return baseOrderBy(collection, baseFlatten(iteratees, 1), []);\\n});\\n\\nmodule.exports = sortBy;\\n\",\"var arrayLikeToArray = require(\\\"./arrayLikeToArray\\\");\\n\\nfunction _arrayWithoutHoles(arr) {\\n  if (Array.isArray(arr)) return arrayLikeToArray(arr);\\n}\\n\\nmodule.exports = _arrayWithoutHoles;\",\"function _iterableToArray(iter) {\\n  if (typeof Symbol !== \\\"undefined\\\" && Symbol.iterator in Object(iter)) return Array.from(iter);\\n}\\n\\nmodule.exports = _iterableToArray;\",\"var arrayLikeToArray = require(\\\"./arrayLikeToArray\\\");\\n\\nfunction _unsupportedIterableToArray(o, minLen) {\\n  if (!o) return;\\n  if (typeof o === \\\"string\\\") return arrayLikeToArray(o, minLen);\\n  var n = Object.prototype.toString.call(o).slice(8, -1);\\n  if (n === \\\"Object\\\" && o.constructor) n = o.constructor.name;\\n  if (n === \\\"Map\\\" || n === \\\"Set\\\") return Array.from(o);\\n  if (n === \\\"Arguments\\\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return arrayLikeToArray(o, minLen);\\n}\\n\\nmodule.exports = _unsupportedIterableToArray;\",\"function _nonIterableSpread() {\\n  throw new TypeError(\\\"Invalid attempt to spread non-iterable instance.\\\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\\\");\\n}\\n\\nmodule.exports = _nonIterableSpread;\",\"/**\\n * A specialized version of `_.map` for arrays without support for iteratee\\n * shorthands.\\n *\\n * @private\\n * @param {Array} [array] The array to iterate over.\\n * @param {Function} iteratee The function invoked per iteration.\\n * @returns {Array} Returns the new mapped array.\\n */\\nfunction arrayMap(array, iteratee) {\\n  var index = -1,\\n      length = array == null ? 0 : array.length,\\n      result = Array(length);\\n\\n  while (++index < length) {\\n    result[index] = iteratee(array[index], index, array);\\n  }\\n  return result;\\n}\\n\\nmodule.exports = arrayMap;\\n\",\"/**\\n * A specialized version of `_.map` for arrays without support for iteratee\\n * shorthands.\\n *\\n * @private\\n * @param {Array} [array] The array to iterate over.\\n * @param {Function} iteratee The function invoked per iteration.\\n * @returns {Array} Returns the new mapped array.\\n */\\nfunction arrayMap(array, iteratee) {\\n  var index = -1,\\n      length = array == null ? 0 : array.length,\\n      result = Array(length);\\n\\n  while (++index < length) {\\n    result[index] = iteratee(array[index], index, array);\\n  }\\n  return result;\\n}\\n\\nmodule.exports = arrayMap;\\n\",\"/**\\n * The base implementation of `_.sortBy` which uses `comparer` to define the\\n * sort order of `array` and replaces criteria objects with their corresponding\\n * values.\\n *\\n * @private\\n * @param {Array} array The array to sort.\\n * @param {Function} comparer The function to define sort order.\\n * @returns {Array} Returns `array`.\\n */\\nfunction baseSortBy(array, comparer) {\\n  var length = array.length;\\n\\n  array.sort(comparer);\\n  while (length--) {\\n    array[length] = array[length].value;\\n  }\\n  return array;\\n}\\n\\nmodule.exports = baseSortBy;\\n\",\"/**\\n * The base implementation of `_.unary` without support for storing metadata.\\n *\\n * @private\\n * @param {Function} func The function to cap arguments for.\\n * @returns {Function} Returns the new capped function.\\n */\\nfunction baseUnary(func) {\\n  return function(value) {\\n    return func(value);\\n  };\\n}\\n\\nmodule.exports = baseUnary;\\n\",\"var compareAscending = require('./_compareAscending');\\n\\n/**\\n * Used by `_.orderBy` to compare multiple properties of a value to another\\n * and stable sort them.\\n *\\n * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,\\n * specify an order of \\\"desc\\\" for descending or \\\"asc\\\" for ascending sort order\\n * of corresponding values.\\n *\\n * @private\\n * @param {Object} object The object to compare.\\n * @param {Object} other The other object to compare.\\n * @param {boolean[]|string[]} orders The order to sort by for each property.\\n * @returns {number} Returns the sort order indicator for `object`.\\n */\\nfunction compareMultiple(object, other, orders) {\\n  var index = -1,\\n      objCriteria = object.criteria,\\n      othCriteria = other.criteria,\\n      length = objCriteria.length,\\n      ordersLength = orders.length;\\n\\n  while (++index < length) {\\n    var result = compareAscending(objCriteria[index], othCriteria[index]);\\n    if (result) {\\n      if (index >= ordersLength) {\\n        return result;\\n      }\\n      var order = orders[index];\\n      return result * (order == 'desc' ? -1 : 1);\\n    }\\n  }\\n  // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications\\n  // that causes it, under certain circumstances, to provide the same value for\\n  // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247\\n  // for more details.\\n  //\\n  // This also ensures a stable sort in V8 and other engines.\\n  // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.\\n  return object.index - other.index;\\n}\\n\\nmodule.exports = compareMultiple;\\n\",\"var isSymbol = require('./isSymbol');\\n\\n/**\\n * Compares values to sort them in ascending order.\\n *\\n * @private\\n * @param {*} value The value to compare.\\n * @param {*} other The other value to compare.\\n * @returns {number} Returns the sort order indicator for `value`.\\n */\\nfunction compareAscending(value, other) {\\n  if (value !== other) {\\n    var valIsDefined = value !== undefined,\\n        valIsNull = value === null,\\n        valIsReflexive = value === value,\\n        valIsSymbol = isSymbol(value);\\n\\n    var othIsDefined = other !== undefined,\\n        othIsNull = other === null,\\n        othIsReflexive = other === other,\\n        othIsSymbol = isSymbol(other);\\n\\n    if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||\\n        (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) ||\\n        (valIsNull && othIsDefined && othIsReflexive) ||\\n        (!valIsDefined && othIsReflexive) ||\\n        !valIsReflexive) {\\n      return 1;\\n    }\\n    if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||\\n        (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) ||\\n        (othIsNull && valIsDefined && valIsReflexive) ||\\n        (!othIsDefined && valIsReflexive) ||\\n        !othIsReflexive) {\\n      return -1;\\n    }\\n  }\\n  return 0;\\n}\\n\\nmodule.exports = compareAscending;\\n\",\"/**\\n * This method returns `false`.\\n *\\n * @static\\n * @memberOf _\\n * @since 4.13.0\\n * @category Util\\n * @returns {boolean} Returns `false`.\\n * @example\\n *\\n * _.times(2, _.stubFalse);\\n * // => [false, false]\\n */\\nfunction stubFalse() {\\n  return false;\\n}\\n\\nmodule.exports = stubFalse;\\n\",\"/**\\n * A specialized version of `_.filter` for arrays without support for\\n * iteratee shorthands.\\n *\\n * @private\\n * @param {Array} [array] The array to iterate over.\\n * @param {Function} predicate The function invoked per iteration.\\n * @returns {Array} Returns the new filtered array.\\n */\\nfunction arrayFilter(array, predicate) {\\n  var index = -1,\\n      length = array == null ? 0 : array.length,\\n      resIndex = 0,\\n      result = [];\\n\\n  while (++index < length) {\\n    var value = array[index];\\n    if (predicate(value, index, array)) {\\n      result[resIndex++] = value;\\n    }\\n  }\\n  return result;\\n}\\n\\nmodule.exports = arrayFilter;\\n\",\"/**\\n * A specialized version of `_.filter` for arrays without support for\\n * iteratee shorthands.\\n *\\n * @private\\n * @param {Array} [array] The array to iterate over.\\n * @param {Function} predicate The function invoked per iteration.\\n * @returns {Array} Returns the new filtered array.\\n */\\nfunction arrayFilter(array, predicate) {\\n  var index = -1,\\n      length = array == null ? 0 : array.length,\\n      resIndex = 0,\\n      result = [];\\n\\n  while (++index < length) {\\n    var value = array[index];\\n    if (predicate(value, index, array)) {\\n      result[resIndex++] = value;\\n    }\\n  }\\n  return result;\\n}\\n\\nmodule.exports = arrayFilter;\\n\",\"/** Error message constants. */\\nvar FUNC_ERROR_TEXT = 'Expected a function';\\n\\n/**\\n * Creates a function that negates the result of the predicate `func`. The\\n * `func` predicate is invoked with the `this` binding and arguments of the\\n * created function.\\n *\\n * @static\\n * @memberOf _\\n * @since 3.0.0\\n * @category Function\\n * @param {Function} predicate The predicate to negate.\\n * @returns {Function} Returns the new negated function.\\n * @example\\n *\\n * function isEven(n) {\\n *   return n % 2 == 0;\\n * }\\n *\\n * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));\\n * // => [1, 3, 5]\\n */\\nfunction negate(predicate) {\\n  if (typeof predicate != 'function') {\\n    throw new TypeError(FUNC_ERROR_TEXT);\\n  }\\n  return function() {\\n    var args = arguments;\\n    switch (args.length) {\\n      case 0: return !predicate.call(this);\\n      case 1: return !predicate.call(this, args[0]);\\n      case 2: return !predicate.call(this, args[0], args[1]);\\n      case 3: return !predicate.call(this, args[0], args[1], args[2]);\\n    }\\n    return !predicate.apply(this, args);\\n  };\\n}\\n\\nmodule.exports = negate;\\n\",\"/**\\n * Gets the first element of `array`.\\n *\\n * @static\\n * @memberOf _\\n * @since 0.1.0\\n * @alias first\\n * @category Array\\n * @param {Array} array The array to query.\\n * @returns {*} Returns the first element of `array`.\\n * @example\\n *\\n * _.head([1, 2, 3]);\\n * // => 1\\n *\\n * _.head([]);\\n * // => undefined\\n */\\nfunction head(array) {\\n  return (array && array.length) ? array[0] : undefined;\\n}\\n\\nmodule.exports = head;\\n\",\"var identity = require('./identity'),\\n    overRest = require('./_overRest'),\\n    setToString = require('./_setToString');\\n\\n/**\\n * The base implementation of `_.rest` which doesn't validate or coerce arguments.\\n *\\n * @private\\n * @param {Function} func The function to apply a rest parameter to.\\n * @param {number} [start=func.length-1] The start position of the rest parameter.\\n * @returns {Function} Returns the new function.\\n */\\nfunction baseRest(func, start) {\\n  return setToString(overRest(func, start, identity), func + '');\\n}\\n\\nmodule.exports = baseRest;\\n\",\"var apply = require('./_apply');\\n\\n/* Built-in method references for those with the same name as other `lodash` methods. */\\nvar nativeMax = Math.max;\\n\\n/**\\n * A specialized version of `baseRest` which transforms the rest array.\\n *\\n * @private\\n * @param {Function} func The function to apply a rest parameter to.\\n * @param {number} [start=func.length-1] The start position of the rest parameter.\\n * @param {Function} transform The rest array transform.\\n * @returns {Function} Returns the new function.\\n */\\nfunction overRest(func, start, transform) {\\n  start = nativeMax(start === undefined ? (func.length - 1) : start, 0);\\n  return function() {\\n    var args = arguments,\\n        index = -1,\\n        length = nativeMax(args.length - start, 0),\\n        array = Array(length);\\n\\n    while (++index < length) {\\n      array[index] = args[start + index];\\n    }\\n    index = -1;\\n    var otherArgs = Array(start + 1);\\n    while (++index < start) {\\n      otherArgs[index] = args[index];\\n    }\\n    otherArgs[start] = transform(array);\\n    return apply(func, this, otherArgs);\\n  };\\n}\\n\\nmodule.exports = overRest;\\n\",\"/**\\n * A faster alternative to `Function#apply`, this function invokes `func`\\n * with the `this` binding of `thisArg` and the arguments of `args`.\\n *\\n * @private\\n * @param {Function} func The function to invoke.\\n * @param {*} thisArg The `this` binding of `func`.\\n * @param {Array} args The arguments to invoke `func` with.\\n * @returns {*} Returns the result of `func`.\\n */\\nfunction apply(func, thisArg, args) {\\n  switch (args.length) {\\n    case 0: return func.call(thisArg);\\n    case 1: return func.call(thisArg, args[0]);\\n    case 2: return func.call(thisArg, args[0], args[1]);\\n    case 3: return func.call(thisArg, args[0], args[1], args[2]);\\n  }\\n  return func.apply(thisArg, args);\\n}\\n\\nmodule.exports = apply;\\n\",\"/**\\n * This method returns the first argument it receives.\\n *\\n * @static\\n * @since 0.1.0\\n * @memberOf _\\n * @category Util\\n * @param {*} value Any value.\\n * @returns {*} Returns `value`.\\n * @example\\n *\\n * var object = { 'a': 1 };\\n *\\n * console.log(_.identity(object) === object);\\n * // => true\\n */\\nfunction identity(value) {\\n  return value;\\n}\\n\\nmodule.exports = identity;\\n\",\"/**\\n * This method returns `false`.\\n *\\n * @static\\n * @memberOf _\\n * @since 4.13.0\\n * @category Util\\n * @returns {boolean} Returns `false`.\\n * @example\\n *\\n * _.times(2, _.stubFalse);\\n * // => [false, false]\\n */\\nfunction stubFalse() {\\n  return false;\\n}\\n\\nmodule.exports = stubFalse;\\n\",\"export default function _isPlaceholder(a) {\\n       return a != null && typeof a === 'object' && a['@@functional/placeholder'] === true;\\n}\",\"import _isPlaceholder from './_isPlaceholder.js';\\n\\n/**\\n * Optimized internal one-arity curry function.\\n *\\n * @private\\n * @category Function\\n * @param {Function} fn The function to curry.\\n * @return {Function} The curried function.\\n */\\nexport default function _curry1(fn) {\\n  return function f1(a) {\\n    if (arguments.length === 0 || _isPlaceholder(a)) {\\n      return f1;\\n    } else {\\n      return fn.apply(this, arguments);\\n    }\\n  };\\n}\",\"export default function _arity(n, fn) {\\n  /* eslint-disable no-unused-vars */\\n  switch (n) {\\n    case 0:\\n      return function () {\\n        return fn.apply(this, arguments);\\n      };\\n    case 1:\\n      return function (a0) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 2:\\n      return function (a0, a1) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 3:\\n      return function (a0, a1, a2) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 4:\\n      return function (a0, a1, a2, a3) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 5:\\n      return function (a0, a1, a2, a3, a4) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 6:\\n      return function (a0, a1, a2, a3, a4, a5) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 7:\\n      return function (a0, a1, a2, a3, a4, a5, a6) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 8:\\n      return function (a0, a1, a2, a3, a4, a5, a6, a7) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 9:\\n      return function (a0, a1, a2, a3, a4, a5, a6, a7, a8) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 10:\\n      return function (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) {\\n        return fn.apply(this, arguments);\\n      };\\n    default:\\n      throw new Error('First argument to _arity must be a non-negative integer no greater than ten');\\n  }\\n}\",\"import _curry1 from './_curry1.js';\\nimport _isPlaceholder from './_isPlaceholder.js';\\n\\n/**\\n * Optimized internal two-arity curry function.\\n *\\n * @private\\n * @category Function\\n * @param {Function} fn The function to curry.\\n * @return {Function} The curried function.\\n */\\nexport default function _curry2(fn) {\\n  return function f2(a, b) {\\n    switch (arguments.length) {\\n      case 0:\\n        return f2;\\n      case 1:\\n        return _isPlaceholder(a) ? f2 : _curry1(function (_b) {\\n          return fn(a, _b);\\n        });\\n      default:\\n        return _isPlaceholder(a) && _isPlaceholder(b) ? f2 : _isPlaceholder(a) ? _curry1(function (_a) {\\n          return fn(_a, b);\\n        }) : _isPlaceholder(b) ? _curry1(function (_b) {\\n          return fn(a, _b);\\n        }) : fn(a, b);\\n    }\\n  };\\n}\",\"import _arity from './internal/_arity.js';\\nimport _curry1 from './internal/_curry1.js';\\nimport _curry2 from './internal/_curry2.js';\\nimport _curryN from './internal/_curryN.js';\\n\\n/**\\n * Returns a curried equivalent of the provided function, with the specified\\n * arity. The curried function has two unusual capabilities. First, its\\n * arguments needn't be provided one at a time. If `g` is `R.curryN(3, f)`, the\\n * following are equivalent:\\n *\\n *   - `g(1)(2)(3)`\\n *   - `g(1)(2, 3)`\\n *   - `g(1, 2)(3)`\\n *   - `g(1, 2, 3)`\\n *\\n * Secondly, the special placeholder value [`R.__`](#__) may be used to specify\\n * \\\"gaps\\\", allowing partial application of any combination of arguments,\\n * regardless of their positions. If `g` is as above and `_` is [`R.__`](#__),\\n * the following are equivalent:\\n *\\n *   - `g(1, 2, 3)`\\n *   - `g(_, 2, 3)(1)`\\n *   - `g(_, _, 3)(1)(2)`\\n *   - `g(_, _, 3)(1, 2)`\\n *   - `g(_, 2)(1)(3)`\\n *   - `g(_, 2)(1, 3)`\\n *   - `g(_, 2)(_, 3)(1)`\\n *\\n * @func\\n * @memberOf R\\n * @since v0.5.0\\n * @category Function\\n * @sig Number -> (* -> a) -> (* -> a)\\n * @param {Number} length The arity for the returned function.\\n * @param {Function} fn The function to curry.\\n * @return {Function} A new, curried function.\\n * @see R.curry\\n * @example\\n *\\n *      const sumArgs = (...args) => R.sum(args);\\n *\\n *      const curriedAddFourNumbers = R.curryN(4, sumArgs);\\n *      const f = curriedAddFourNumbers(1, 2);\\n *      const g = f(3);\\n *      g(4); //=> 10\\n */\\nvar curryN = /*#__PURE__*/_curry2(function curryN(length, fn) {\\n  if (length === 1) {\\n    return _curry1(fn);\\n  }\\n  return _arity(length, _curryN(length, [], fn));\\n});\\nexport default curryN;\",\"import _arity from './_arity.js';\\nimport _isPlaceholder from './_isPlaceholder.js';\\n\\n/**\\n * Internal curryN function.\\n *\\n * @private\\n * @category Function\\n * @param {Number} length The arity of the curried function.\\n * @param {Array} received An array of arguments received thus far.\\n * @param {Function} fn The function to curry.\\n * @return {Function} The curried function.\\n */\\nexport default function _curryN(length, received, fn) {\\n  return function () {\\n    var combined = [];\\n    var argsIdx = 0;\\n    var left = length;\\n    var combinedIdx = 0;\\n    while (combinedIdx < received.length || argsIdx < arguments.length) {\\n      var result;\\n      if (combinedIdx < received.length && (!_isPlaceholder(received[combinedIdx]) || argsIdx >= arguments.length)) {\\n        result = received[combinedIdx];\\n      } else {\\n        result = arguments[argsIdx];\\n        argsIdx += 1;\\n      }\\n      combined[combinedIdx] = result;\\n      if (!_isPlaceholder(result)) {\\n        left -= 1;\\n      }\\n      combinedIdx += 1;\\n    }\\n    return left <= 0 ? fn.apply(this, combined) : _arity(left, _curryN(length, combined, fn));\\n  };\\n}\",\"import _curry1 from './internal/_curry1.js';\\nimport curryN from './curryN.js';\\n\\n/**\\n * Returns a curried equivalent of the provided function. The curried function\\n * has two unusual capabilities. First, its arguments needn't be provided one\\n * at a time. If `f` is a ternary function and `g` is `R.curry(f)`, the\\n * following are equivalent:\\n *\\n *   - `g(1)(2)(3)`\\n *   - `g(1)(2, 3)`\\n *   - `g(1, 2)(3)`\\n *   - `g(1, 2, 3)`\\n *\\n * Secondly, the special placeholder value [`R.__`](#__) may be used to specify\\n * \\\"gaps\\\", allowing partial application of any combination of arguments,\\n * regardless of their positions. If `g` is as above and `_` is [`R.__`](#__),\\n * the following are equivalent:\\n *\\n *   - `g(1, 2, 3)`\\n *   - `g(_, 2, 3)(1)`\\n *   - `g(_, _, 3)(1)(2)`\\n *   - `g(_, _, 3)(1, 2)`\\n *   - `g(_, 2)(1)(3)`\\n *   - `g(_, 2)(1, 3)`\\n *   - `g(_, 2)(_, 3)(1)`\\n *\\n * @func\\n * @memberOf R\\n * @since v0.1.0\\n * @category Function\\n * @sig (* -> a) -> (* -> a)\\n * @param {Function} fn The function to curry.\\n * @return {Function} A new, curried function.\\n * @see R.curryN, R.partial\\n * @example\\n *\\n *      const addFourNumbers = (a, b, c, d) => a + b + c + d;\\n *\\n *      const curriedAddFourNumbers = R.curry(addFourNumbers);\\n *      const f = curriedAddFourNumbers(1, 2);\\n *      const g = f(3);\\n *      g(4); //=> 10\\n */\\nvar curry = /*#__PURE__*/_curry1(function curry(fn) {\\n  return curryN(fn.length, fn);\\n});\\nexport default curry;\",\"/**\\n * A special placeholder value used to specify \\\"gaps\\\" within curried functions,\\n * allowing partial application of any combination of arguments, regardless of\\n * their positions.\\n *\\n * If `g` is a curried ternary function and `_` is `R.__`, the following are\\n * equivalent:\\n *\\n *   - `g(1, 2, 3)`\\n *   - `g(_, 2, 3)(1)`\\n *   - `g(_, _, 3)(1)(2)`\\n *   - `g(_, _, 3)(1, 2)`\\n *   - `g(_, 2, _)(1, 3)`\\n *   - `g(_, 2)(1)(3)`\\n *   - `g(_, 2)(1, 3)`\\n *   - `g(_, 2)(_, 3)(1)`\\n *\\n * @name __\\n * @constant\\n * @memberOf R\\n * @since v0.6.0\\n * @category Function\\n * @example\\n *\\n *      const greet = R.replace('{name}', R.__, 'Hello, {name}!');\\n *      greet('Alice'); //=> 'Hello, Alice!'\\n */\\nexport default { '@@functional/placeholder': true };\",\"import _toConsumableArray from \\\"@babel/runtime/helpers/toConsumableArray\\\";\\nimport _typeof from \\\"@babel/runtime/helpers/typeof\\\";\\nimport { curry, __ as placeholder } from 'ramda';\\n\\nfunction forOwn(obj, fn) {\\n  for (var key in obj) {\\n    if (obj.hasOwnProperty(key)) {\\n      fn(obj[key], key);\\n    }\\n  }\\n}\\n\\nfunction isArrayLike(value) {\\n  return value && _typeof(value) === 'object' && typeof value.length === 'number' && value.length >= 0 && value.length % 1 === 0;\\n}\\n\\nvar OWNER_ID_TAG = '@@_______immutableOpsOwnerID';\\n\\nfunction fastArrayCopy(arr) {\\n  var copied = new Array(arr.length);\\n\\n  for (var i = 0; i < arr.length; i++) {\\n    copied[i] = arr[i];\\n  }\\n\\n  return copied;\\n}\\n\\nexport function canMutate(obj, ownerID) {\\n  if (!ownerID) return false;\\n  return obj[OWNER_ID_TAG] === ownerID;\\n}\\nvar newOwnerID = typeof Symbol === 'function' ? function () {\\n  return Symbol('ownerID');\\n} : function () {\\n  return {};\\n};\\nexport var getBatchToken = newOwnerID;\\n\\nfunction addOwnerID(obj, ownerID) {\\n  Object.defineProperty(obj, OWNER_ID_TAG, {\\n    value: ownerID,\\n    configurable: true,\\n    enumerable: false\\n  });\\n  return obj;\\n}\\n\\nfunction prepareNewObject(instance, ownerID) {\\n  if (ownerID) {\\n    addOwnerID(instance, ownerID);\\n  }\\n\\n  return instance;\\n}\\n\\nfunction forceArray(arg) {\\n  if (!(arg instanceof Array)) {\\n    return [arg];\\n  }\\n\\n  return arg;\\n}\\n\\nvar PATH_SEPARATOR = '.';\\n\\nfunction normalizePath(pathArg) {\\n  if (typeof pathArg === 'string') {\\n    if (pathArg.indexOf(PATH_SEPARATOR) === -1) {\\n      return [pathArg];\\n    }\\n\\n    return pathArg.split(PATH_SEPARATOR);\\n  }\\n\\n  return pathArg;\\n}\\n\\nfunction mutableSet(key, value, obj) {\\n  obj[key] = value;\\n  return obj;\\n}\\n\\nfunction mutableSetIn(_pathArg, value, obj) {\\n  var originalPathArg = normalizePath(_pathArg);\\n  var pathLen = originalPathArg.length;\\n  var done = false;\\n  var idx = 0;\\n  var acc = obj;\\n  var curr = originalPathArg[idx];\\n\\n  while (!done) {\\n    if (idx === pathLen - 1) {\\n      acc[curr] = value;\\n      done = true;\\n    } else {\\n      var currType = _typeof(acc[curr]);\\n\\n      if (currType === 'undefined') {\\n        var newObj = {};\\n        prepareNewObject(newObj, null);\\n        acc[curr] = newObj;\\n      } else if (currType !== 'object') {\\n        var pathRepr = \\\"\\\".concat(originalPathArg[idx - 1], \\\".\\\").concat(curr);\\n        throw new Error(\\\"A non-object value was encountered when traversing setIn path at \\\".concat(pathRepr, \\\".\\\"));\\n      }\\n\\n      acc = acc[curr];\\n      idx++;\\n      curr = originalPathArg[idx];\\n    }\\n  }\\n\\n  return obj;\\n}\\n\\nfunction valueInPath(_pathArg, obj) {\\n  var pathArg = normalizePath(_pathArg);\\n  var acc = obj;\\n\\n  for (var i = 0; i < pathArg.length; i++) {\\n    var curr = pathArg[i];\\n    var currRef = acc[curr];\\n\\n    if (i === pathArg.length - 1) {\\n      return currRef;\\n    }\\n\\n    if (_typeof(currRef) === 'object') {\\n      acc = currRef;\\n    } else {\\n      return undefined;\\n    }\\n  }\\n\\n  return undefined;\\n}\\n\\nfunction immutableSetIn(ownerID, _pathArg, value, obj) {\\n  var pathArg = normalizePath(_pathArg);\\n  var currentValue = valueInPath(pathArg, obj);\\n  if (value === currentValue) return obj;\\n  var pathLen = pathArg.length;\\n  var acc;\\n\\n  if (canMutate(obj, ownerID)) {\\n    acc = obj;\\n  } else {\\n    acc = Object.assign(prepareNewObject({}, ownerID), obj);\\n  }\\n\\n  var rootObj = acc;\\n  pathArg.forEach(function (curr, idx) {\\n    if (idx === pathLen - 1) {\\n      acc[curr] = value;\\n      return;\\n    }\\n\\n    var currRef = acc[curr];\\n\\n    var currType = _typeof(currRef);\\n\\n    if (currType === 'object') {\\n      if (canMutate(currRef, ownerID)) {\\n        acc = currRef;\\n      } else {\\n        var newObj = prepareNewObject({}, ownerID);\\n        acc[curr] = Object.assign(newObj, currRef);\\n        acc = newObj;\\n      }\\n\\n      return;\\n    }\\n\\n    if (currType === 'undefined') {\\n      var _newObj = prepareNewObject({}, ownerID);\\n\\n      acc[curr] = _newObj;\\n      acc = _newObj;\\n      return;\\n    }\\n\\n    var pathRepr = \\\"\\\".concat(pathArg[idx - 1], \\\".\\\").concat(curr);\\n    throw new Error(\\\"A non-object value was encountered when traversing setIn path at \\\".concat(pathRepr, \\\".\\\"));\\n  });\\n  return rootObj;\\n}\\n\\nfunction mutableMerge(isDeep, _mergeObjs, baseObj) {\\n  var mergeObjs = forceArray(_mergeObjs);\\n\\n  if (isDeep) {\\n    mergeObjs.forEach(function (mergeObj) {\\n      forOwn(mergeObj, function (value, key) {\\n        if (isDeep && baseObj.hasOwnProperty(key)) {\\n          var assignValue;\\n\\n          if (_typeof(value) === 'object') {\\n            assignValue = mutableMerge(isDeep, [value], baseObj[key]);\\n          } else {\\n            assignValue = value;\\n          }\\n\\n          baseObj[key] = assignValue;\\n        } else {\\n          baseObj[key] = value;\\n        }\\n      });\\n    });\\n  } else {\\n    Object.assign.apply(Object, [baseObj].concat(_toConsumableArray(mergeObjs)));\\n  }\\n\\n  return baseObj;\\n}\\n\\nvar mutableShallowMerge = mutableMerge.bind(null, false);\\nvar mutableDeepMerge = mutableMerge.bind(null, true);\\n\\nfunction mutableOmit(_keys, obj) {\\n  var keys = forceArray(_keys);\\n  keys.forEach(function (key) {\\n    delete obj[key];\\n  });\\n  return obj;\\n}\\n\\nfunction shouldMergeKey(obj, other, key) {\\n  return obj[key] !== other[key];\\n}\\n\\nfunction immutableMerge(isDeep, ownerID, _mergeObjs, obj) {\\n  if (canMutate(obj, ownerID)) return mutableMerge(isDeep, _mergeObjs, obj);\\n  var mergeObjs = forceArray(_mergeObjs);\\n  var hasChanges = false;\\n  var nextObject = obj;\\n\\n  var willChange = function willChange() {\\n    if (!hasChanges) {\\n      hasChanges = true;\\n      nextObject = Object.assign({}, obj);\\n      prepareNewObject(nextObject, ownerID);\\n    }\\n  };\\n\\n  mergeObjs.forEach(function (mergeObj) {\\n    forOwn(mergeObj, function (mergeValue, key) {\\n      if (isDeep && obj.hasOwnProperty(key)) {\\n        var currentValue = nextObject[key];\\n\\n        if (_typeof(mergeValue) === 'object' && !(mergeValue instanceof Array)) {\\n          if (shouldMergeKey(nextObject, mergeObj, key)) {\\n            var recursiveMergeResult = immutableMerge(isDeep, ownerID, mergeValue, currentValue);\\n\\n            if (recursiveMergeResult !== currentValue) {\\n              willChange();\\n              nextObject[key] = recursiveMergeResult;\\n            }\\n          }\\n\\n          return true; // continue forOwn\\n        }\\n      }\\n\\n      if (shouldMergeKey(nextObject, mergeObj, key)) {\\n        willChange();\\n        nextObject[key] = mergeValue;\\n      }\\n\\n      return undefined;\\n    });\\n  });\\n  return nextObject;\\n}\\n\\nvar immutableDeepMerge = immutableMerge.bind(null, true);\\nvar immutableShallowMerge = immutableMerge.bind(null, false);\\n\\nfunction immutableArrSet(ownerID, index, value, arr) {\\n  if (canMutate(arr, ownerID)) return mutableSet(index, value, arr);\\n  if (arr[index] === value) return arr;\\n  var newArr = fastArrayCopy(arr);\\n  newArr[index] = value;\\n  prepareNewObject(newArr, ownerID);\\n  return newArr;\\n}\\n\\nfunction immutableSet(ownerID, key, value, obj) {\\n  if (isArrayLike(obj)) return immutableArrSet(ownerID, key, value, obj);\\n  if (canMutate(obj, ownerID)) return mutableSet(key, value, obj);\\n  if (obj[key] === value) return obj;\\n  var newObj = Object.assign({}, obj);\\n  prepareNewObject(newObj, ownerID);\\n  newObj[key] = value;\\n  return newObj;\\n}\\n\\nfunction immutableOmit(ownerID, _keys, obj) {\\n  if (canMutate(obj, ownerID)) return mutableOmit(_keys, obj);\\n  var keys = forceArray(_keys);\\n  var keysInObj = keys.filter(function (key) {\\n    return obj.hasOwnProperty(key);\\n  }); // None of the keys were in the object, so we can return `obj`.\\n\\n  if (keysInObj.length === 0) return obj;\\n  var newObj = Object.assign({}, obj);\\n  keysInObj.forEach(function (key) {\\n    delete newObj[key];\\n  });\\n  prepareNewObject(newObj, ownerID);\\n  return newObj;\\n}\\n\\nfunction mutableArrPush(_vals, arr) {\\n  var vals = forceArray(_vals);\\n  arr.push.apply(arr, _toConsumableArray(vals));\\n  return arr;\\n}\\n\\nfunction mutableArrFilter(func, arr) {\\n  var currIndex = 0;\\n  var originalIndex = 0;\\n\\n  while (currIndex < arr.length) {\\n    var item = arr[currIndex];\\n\\n    if (!func(item, originalIndex)) {\\n      arr.splice(currIndex, 1);\\n    } else {\\n      currIndex++;\\n    }\\n\\n    originalIndex++;\\n  }\\n\\n  return arr;\\n}\\n\\nfunction mutableArrSplice(index, deleteCount, _vals, arr) {\\n  var vals = forceArray(_vals);\\n  arr.splice.apply(arr, [index, deleteCount].concat(_toConsumableArray(vals)));\\n  return arr;\\n}\\n\\nfunction mutableArrInsert(index, _vals, arr) {\\n  return mutableArrSplice(index, 0, _vals, arr);\\n}\\n\\nfunction immutableArrSplice(ownerID, index, deleteCount, _vals, arr) {\\n  if (canMutate(arr, ownerID)) return mutableArrSplice(index, deleteCount, _vals, arr);\\n  var vals = forceArray(_vals);\\n  var newArr = arr.slice();\\n  prepareNewObject(newArr, ownerID);\\n  newArr.splice.apply(newArr, [index, deleteCount].concat(_toConsumableArray(vals)));\\n  return newArr;\\n}\\n\\nfunction immutableArrInsert(ownerID, index, _vals, arr) {\\n  if (canMutate(arr, ownerID)) return mutableArrInsert(index, _vals, arr);\\n  return immutableArrSplice(ownerID, index, 0, _vals, arr);\\n}\\n\\nfunction immutableArrPush(ownerID, vals, arr) {\\n  return immutableArrInsert(ownerID, arr.length, vals, arr);\\n}\\n\\nfunction immutableArrFilter(ownerID, func, arr) {\\n  if (canMutate(arr, ownerID)) return mutableArrFilter(func, arr);\\n  var newArr = arr.filter(func);\\n  if (newArr.length === arr.length) return arr;\\n  prepareNewObject(newArr, ownerID);\\n  return newArr;\\n}\\n\\nvar immutableOperations = {\\n  // object operations\\n  merge: immutableShallowMerge,\\n  deepMerge: immutableDeepMerge,\\n  omit: immutableOmit,\\n  setIn: immutableSetIn,\\n  // array operations\\n  insert: immutableArrInsert,\\n  push: immutableArrPush,\\n  filter: immutableArrFilter,\\n  splice: immutableArrSplice,\\n  // both\\n  set: immutableSet\\n};\\nvar mutableOperations = {\\n  // object operations\\n  merge: mutableShallowMerge,\\n  deepMerge: mutableDeepMerge,\\n  omit: mutableOmit,\\n  setIn: mutableSetIn,\\n  // array operations\\n  insert: mutableArrInsert,\\n  push: mutableArrPush,\\n  filter: mutableArrFilter,\\n  splice: mutableArrSplice,\\n  // both\\n  set: mutableSet\\n};\\nexport function getImmutableOps() {\\n  var immutableOps = Object.assign({}, immutableOperations);\\n  forOwn(immutableOps, function (value, key) {\\n    immutableOps[key] = curry(value.bind(null, null));\\n  });\\n  var mutableOps = Object.assign({}, mutableOperations);\\n  forOwn(mutableOps, function (value, key) {\\n    mutableOps[key] = curry(value);\\n  });\\n  var batchOps = Object.assign({}, immutableOperations);\\n  forOwn(batchOps, function (value, key) {\\n    batchOps[key] = curry(value);\\n  });\\n\\n  function batched(_token, _fn) {\\n    var token;\\n    var fn;\\n\\n    if (typeof _token === 'function') {\\n      fn = _token;\\n      token = getBatchToken();\\n    } else {\\n      token = _token;\\n      fn = _fn;\\n    }\\n\\n    var immutableOpsBoundToToken = Object.assign({}, immutableOperations);\\n    forOwn(immutableOpsBoundToToken, function (value, key) {\\n      immutableOpsBoundToToken[key] = curry(value.bind(null, token));\\n    });\\n    return fn(immutableOpsBoundToToken);\\n  }\\n\\n  return Object.assign(immutableOps, {\\n    mutable: mutableOps,\\n    batch: batchOps,\\n    batched: batched,\\n    __: placeholder,\\n    getBatchToken: getBatchToken\\n  });\\n}\\nexport var ops = getImmutableOps();\\nexport default ops;\",\"export const UPDATE = \\\"REDUX_ORM_UPDATE\\\";\\nexport const DELETE = \\\"REDUX_ORM_DELETE\\\";\\nexport const CREATE = \\\"REDUX_ORM_CREATE\\\";\\n\\nexport const FILTER = \\\"REDUX_ORM_FILTER\\\";\\nexport const EXCLUDE = \\\"REDUX_ORM_EXCLUDE\\\";\\nexport const ORDER_BY = \\\"REDUX_ORM_ORDER_BY\\\";\\n\\nexport const SUCCESS = \\\"SUCCESS\\\";\\nexport const FAILURE = \\\"FAILURE\\\";\\n\\n// for detecting ORM state objects\\nexport const STATE_FLAG = \\\"@@_______REDUX_ORM_STATE_FLAG\\\";\\n\\n// for caching selectors based on their ID argument\\nexport const ALL_INSTANCES = Symbol(\\\"REDUX_ORM_ALL_INSTANCES\\\");\\nexport const ID_ARG_KEY_SELECTOR = (_state, idArg) =>\\n    typeof idArg === \\\"undefined\\\" ? ALL_INSTANCES : idArg;\\n\",\"import ops from \\\"immutable-ops\\\";\\nimport { FILTER, EXCLUDE } from \\\"./constants\\\";\\n\\n/**\\n * @module utils\\n * @private\\n */\\n\\n/** @private */\\nfunction warnDeprecated(msg) {\\n    const logger =\\n        typeof console.warn === \\\"function\\\"\\n            ? console.warn.bind(console)\\n            : console.log.bind(console);\\n    return logger(msg);\\n}\\n\\n/** @private */\\nfunction capitalize(string) {\\n    return string.charAt(0).toUpperCase() + string.slice(1);\\n}\\n\\n/**\\n * Returns the branch name for a many-to-many relation.\\n * The name is the combination of the model name and the field name the relation\\n * was declared. The field name's first letter is capitalized.\\n *\\n * Example: model `Author` has a many-to-many relation to the model `Book`, defined\\n * in the `Author` field `books`. The many-to-many branch name will be `AuthorBooks`.\\n *\\n * @param  {string} declarationModelName - the name of the model the many-to-many relation was declared on\\n * @param  {string} fieldName            - the field name where the many-to-many relation was declared on\\n * @return {string} The branch name for the many-to-many relation.\\n */\\nfunction m2mName(declarationModelName, fieldName) {\\n    return declarationModelName + capitalize(fieldName);\\n}\\n\\n/**\\n * Returns the fieldname that saves a foreign key to the\\n * model id where the many-to-many relation was declared.\\n *\\n * Example: `Author` => `fromAuthorId`\\n *\\n * @param  {string} declarationModelName - the name of the model where the relation was declared\\n * @return {string} the field name in the through model for `declarationModelName`'s foreign key.\\n */\\nfunction m2mFromFieldName(declarationModelName) {\\n    return `from${declarationModelName}Id`;\\n}\\n\\n/**\\n * Returns the fieldname that saves a foreign key in a many-to-many through model to the\\n * model where the many-to-many relation was declared.\\n *\\n * Example: `Book` => `toBookId`\\n *\\n * @param  {string} otherModelName - the name of the model that was the target of the many-to-many\\n *                                   declaration.\\n * @return {string} the field name in the through model for `otherModelName`'s foreign key..\\n */\\nfunction m2mToFieldName(otherModelName) {\\n    return `to${otherModelName}Id`;\\n}\\n\\n/** */\\nfunction reverseFieldName(modelName) {\\n    return modelName.toLowerCase() + \\\"Set\\\"; // eslint-disable-line prefer-template\\n}\\n\\n/** @private */\\nfunction querySetDelegatorFactory(methodName) {\\n    return function querySetDelegator(...args) {\\n        return this.getQuerySet()[methodName](...args);\\n    };\\n}\\n\\n/** @private */\\nfunction querySetGetterDelegatorFactory(getterName) {\\n    return function querySetGetterDelegator() {\\n        const qs = this.getQuerySet();\\n        return qs[getterName];\\n    };\\n}\\n\\n/** @private */\\nfunction forEachSuperClass(subClass, func) {\\n    let currClass = subClass;\\n    while (currClass !== Function.prototype) {\\n        func(currClass);\\n        currClass = Object.getPrototypeOf(currClass);\\n    }\\n}\\n\\n/** */\\nfunction attachQuerySetMethods(modelClass, querySetClass) {\\n    const leftToDefine = querySetClass.sharedMethods.slice();\\n\\n    // There is no way to get a property descriptor for the whole prototype chain;\\n    // only from an objects own properties. Therefore we traverse the whole prototype\\n    // chain for querySet.\\n    forEachSuperClass(querySetClass, cls => {\\n        for (let i = 0; i < leftToDefine.length; i++) {\\n            let defined = false;\\n            const methodName = leftToDefine[i];\\n            const descriptor = Object.getOwnPropertyDescriptor(\\n                cls.prototype,\\n                methodName\\n            );\\n            if (typeof descriptor !== \\\"undefined\\\") {\\n                if (typeof descriptor.get !== \\\"undefined\\\") {\\n                    descriptor.get = querySetGetterDelegatorFactory(methodName);\\n                    Object.defineProperty(modelClass, methodName, descriptor);\\n                } else {\\n                    modelClass[methodName] = querySetDelegatorFactory(\\n                        methodName\\n                    );\\n                }\\n                defined = true;\\n            }\\n            if (defined) {\\n                leftToDefine.splice(i--, 1);\\n            }\\n        }\\n    });\\n}\\n\\n/**\\n * Normalizes `entity` to an id, where `entity` can be an id\\n * or a Model instance.\\n *\\n * @param  {*} entity - either a Model instance or an id value\\n * @return {*} the id value of `entity`\\n */\\nfunction normalizeEntity(entity) {\\n    if (\\n        entity !== null &&\\n        typeof entity !== \\\"undefined\\\" &&\\n        typeof entity.getId === \\\"function\\\"\\n    ) {\\n        return entity.getId();\\n    }\\n    return entity;\\n}\\n\\n/** */\\nfunction reverseFieldErrorMessage(\\n    modelName,\\n    fieldName,\\n    toModelName,\\n    backwardsFieldName\\n) {\\n    return [\\n        `Reverse field ${backwardsFieldName} already defined`,\\n        ` on model ${toModelName}. To fix, set a custom related`,\\n        ` name on ${modelName}.${fieldName}.`,\\n    ].join(\\\"\\\");\\n}\\n\\n/**\\n * Fastest way to check if two objects are equal.\\n * Object and array values have to be referentially equal.\\n */\\nfunction objectShallowEquals(a, b) {\\n    const entriesInA = Object.entries(Object(a));\\n\\n    if (entriesInA.length !== Object.keys(b).length) {\\n        return false;\\n    }\\n\\n    return entriesInA.every(\\n        ([key, value]) => b.hasOwnProperty(key) && b[key] === value\\n    );\\n}\\n\\n/** */\\nfunction arrayDiffActions(sourceArr, targetArr) {\\n    const itemsInBoth = sourceArr.filter(item => targetArr.includes(item));\\n    const deleteItems = sourceArr.filter(item => !itemsInBoth.includes(item));\\n    const addItems = targetArr.filter(item => !itemsInBoth.includes(item));\\n\\n    if (deleteItems.length || addItems.length) {\\n        return {\\n            delete: deleteItems,\\n            add: addItems,\\n        };\\n    }\\n    return null;\\n}\\n\\nconst { getBatchToken } = ops;\\n\\n/**\\n * @return boolean\\n */\\nfunction clauseFiltersByAttribute({ type, payload }, attribute) {\\n    if (type !== FILTER) return false;\\n\\n    if (typeof payload !== \\\"object\\\") {\\n        /**\\n         * payload could also be a function in which case\\n         * we would have no way of knowing what it does,\\n         * so we default to false for non-objects\\n         */\\n        return false;\\n    }\\n\\n    if (!payload.hasOwnProperty(attribute)) return false;\\n    const attributeValue = payload[attribute];\\n    if (attributeValue === null) return false;\\n    if (attributeValue === undefined) return false;\\n\\n    return true;\\n}\\n\\n/**\\n * @return boolean\\n */\\nfunction clauseReducesResultSetSize({ type }) {\\n    return [FILTER, EXCLUDE].includes(type);\\n}\\n\\n/**\\n * @param {Object} object\\n * @return Object\\n */\\nfunction mapValues(object, func) {\\n    return Object.entries(object).reduce((newObject, [key, value]) => {\\n        newObject[key] = func(value);\\n        return newObject;\\n    }, {});\\n}\\n\\n/** */\\nfunction normalizeModelReference(modelNameOrClass) {\\n    if (!modelNameOrClass || typeof modelNameOrClass === \\\"string\\\") {\\n        return modelNameOrClass;\\n    }\\n    return modelNameOrClass.modelName;\\n}\\n\\nexport {\\n    attachQuerySetMethods,\\n    m2mName,\\n    m2mFromFieldName,\\n    m2mToFieldName,\\n    reverseFieldName,\\n    normalizeEntity,\\n    reverseFieldErrorMessage,\\n    objectShallowEquals,\\n    ops,\\n    arrayDiffActions,\\n    getBatchToken,\\n    clauseFiltersByAttribute,\\n    clauseReducesResultSetSize,\\n    warnDeprecated,\\n    mapValues,\\n    normalizeModelReference,\\n};\\n\",\"import { normalizeEntity, warnDeprecated, mapValues } from \\\"./utils\\\";\\n\\nimport { UPDATE, DELETE, FILTER, EXCLUDE, ORDER_BY } from \\\"./constants\\\";\\n\\n/**\\n * This class is used to build and make queries to the database\\n * and operating the resulting set (such as updating attributes\\n * or deleting the records).\\n *\\n * The queries are built lazily. For example:\\n *\\n * ```javascript\\n * const qs = Book.all()\\n *     .filter(book => book.releaseYear > 1999)\\n *     .orderBy('name');\\n * ```\\n *\\n * Doesn't execute a query. The query is executed only when\\n * you need information from the query result, such as {@link QuerySet#count},\\n * {@link QuerySet#toRefArray}. After the query is executed, the resulting\\n * set is cached in the QuerySet instance.\\n *\\n * QuerySet instances also return copies, so chaining filters doesn't\\n * mutate the previous instances.\\n */\\nconst QuerySet = class QuerySet {\\n    /**\\n     * Creates a QuerySet. The constructor is mainly for internal use;\\n     * You should access QuerySet instances from {@link Model}.\\n     *\\n     * @param  {Model} modelClass - the model class of objects in this QuerySet.\\n     * @param  {any[]} clauses - query clauses needed to evaluate the set.\\n     * @param {Object} [opts] - additional options\\n     */\\n    constructor(modelClass, clauses, opts) {\\n        Object.assign(this, {\\n            modelClass,\\n            clauses: clauses || [],\\n        });\\n\\n        this._opts = opts;\\n    }\\n\\n    static addSharedMethod(methodName) {\\n        this.sharedMethods = this.sharedMethods.concat(methodName);\\n    }\\n\\n    _new(clauses, userOpts) {\\n        const opts = { ...this._opts, ...userOpts };\\n        return new this.constructor(this.modelClass, clauses, opts);\\n    }\\n\\n    toString() {\\n        this._evaluate();\\n        const contents = this.rows\\n            .map(({ id }) => this.modelClass.withId(id).toString())\\n            .join(\\\"\\\\n    - \\\");\\n        return `QuerySet contents:\\\\n    - ${contents}`;\\n    }\\n\\n    /**\\n     * Returns an array of the plain objects represented by the QuerySet.\\n     * The plain objects are direct references to the store.\\n     *\\n     * @return {Object[]} references to the plain JS objects represented by\\n     *                    the QuerySet\\n     */\\n    toRefArray() {\\n        return this._evaluate();\\n    }\\n\\n    /**\\n     * Returns an array of {@link Model} instances represented by the QuerySet.\\n     * @return {Model[]} model instances represented by the QuerySet\\n     */\\n    toModelArray() {\\n        const { modelClass: ModelClass } = this;\\n        return this._evaluate().map(props => new ModelClass(props));\\n    }\\n\\n    /**\\n     * Returns the number of {@link Model} instances represented by the QuerySet.\\n     *\\n     * @return {number} length of the QuerySet\\n     */\\n    count() {\\n        this._evaluate();\\n        return this.rows.length;\\n    }\\n\\n    /**\\n     * Checks if the {@link QuerySet} instance has any records matching the query\\n     * in the database.\\n     *\\n     * @return {Boolean} `true` if the {@link QuerySet} instance contains entities, else `false`.\\n     */\\n    exists() {\\n        return Boolean(this.count());\\n    }\\n\\n    /**\\n     * Returns the {@link Model} instance at index `index` in the {@link QuerySet} instance if\\n     * `withRefs` flag is set to `false`, or a reference to the plain JavaScript\\n     * object in the model state if `true`.\\n     *\\n     * @param  {number} index - index of the model instance to get\\n     * @return {Model|undefined} a {@link Model} instance at index\\n     *                           `index` in the {@link QuerySet} instance,\\n     *                           or undefined if the index is out of bounds.\\n     */\\n    at(index) {\\n        const { modelClass: ModelClass } = this;\\n\\n        const rows = this._evaluate();\\n        if (index >= 0 && index < rows.length) {\\n            return new ModelClass(rows[index]);\\n        }\\n\\n        return undefined;\\n    }\\n\\n    /**\\n     * Returns the {@link Model} instance at index 0 in the {@link QuerySet} instance.\\n     * @return {Model}\\n     */\\n    first() {\\n        return this.at(0);\\n    }\\n\\n    /**\\n     * Returns the {@link Model} instance at index `QuerySet.count() - 1`\\n     * @return {Model}\\n     */\\n    last() {\\n        const rows = this._evaluate();\\n        return this.at(rows.length - 1);\\n    }\\n\\n    /**\\n     * Returns a new {@link QuerySet} instance with the same entities.\\n     * @return {QuerySet} a new QuerySet with the same entities.\\n     */\\n    all() {\\n        return this._new(this.clauses);\\n    }\\n\\n    /**\\n     * Returns a new {@link QuerySet} instance with entities that match properties in `lookupObj`.\\n     *\\n     * @param  {Object} lookupObj - the properties to match objects with. Can also be a function.\\n     *                              It works the same as [Lodash filter](https://lodash.com/docs/#filter).\\n     * @return {QuerySet} a new {@link QuerySet} instance with objects that passed the filter.\\n     */\\n    filter(lookupObj) {\\n        /**\\n         * allow foreign keys to be specified as model instances,\\n         * transform model instances to their primary keys\\n         */\\n        const normalizedLookupObj =\\n            typeof lookupObj === \\\"object\\\"\\n                ? mapValues(lookupObj, normalizeEntity)\\n                : lookupObj;\\n\\n        const filterDescriptor = {\\n            type: FILTER,\\n            payload: normalizedLookupObj,\\n        };\\n        /**\\n         * create a new QuerySet\\n         * including only rows matching the lookupObj\\n         */\\n        return this._new(this.clauses.concat(filterDescriptor));\\n    }\\n\\n    /**\\n     * Returns a new {@link QuerySet} instance with entities that do not match\\n     * properties in `lookupObj`.\\n     *\\n     * @param  {Object} lookupObj - the properties to unmatch objects with. Can also be a function.\\n     *                              It works the same as [Lodash reject](https://lodash.com/docs/#reject).\\n     * @return {QuerySet} a new {@link QuerySet} instance with objects that did not pass the filter.\\n     */\\n    exclude(lookupObj) {\\n        /**\\n         * allow foreign keys to be specified as model instances,\\n         * transform model instances to their primary keys\\n         */\\n        const normalizedLookupObj =\\n            typeof lookupObj === \\\"object\\\"\\n                ? mapValues(lookupObj, normalizeEntity)\\n                : lookupObj;\\n        const excludeDescriptor = {\\n            type: EXCLUDE,\\n            payload: normalizedLookupObj,\\n        };\\n\\n        /**\\n         * create a new QuerySet\\n         * excluding all rows matching the lookupObj\\n         */\\n        return this._new(this.clauses.concat(excludeDescriptor));\\n    }\\n\\n    /**\\n     * Performs the actual database query.\\n     * @private\\n     * @return {Array} rows corresponding to the QuerySet's clauses\\n     */\\n    _evaluate() {\\n        if (typeof this.modelClass.session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to query the ${this.modelClass.modelName} model's table without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and use \\\",\\n                    `\\\\`session[\\\"${this.modelClass.modelName}\\\"]\\\\` for querying instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        if (!this._evaluated) {\\n            const { session, modelName: table } = this.modelClass;\\n            const querySpec = {\\n                table,\\n                clauses: this.clauses,\\n            };\\n            this.rows = session.query(querySpec).rows;\\n            this._evaluated = true;\\n        }\\n        return this.rows;\\n    }\\n\\n    /**\\n     * Returns a new {@link QuerySet} instance with entities ordered by `iteratees` in ascending\\n     * order, unless otherwise specified. Delegates to [Lodash orderBy](https://lodash.com/docs/#orderBy).\\n     *\\n     * @param  {string[]|Function[]} iteratees - an array where each item can be a string or a\\n     *                                           function. If a string is supplied, it should\\n     *                                           correspond to property on the entity that will\\n     *                                           determine the order. If a function is supplied,\\n     *                                           it should return the value to order by.\\n     * @param {Array<Boolean|'asc'|'desc'>} [orders] - the sort orders of `iteratees`. If unspecified, all iteratees\\n     *                               will be sorted in ascending order. `true` and `'asc'`\\n     *                               correspond to ascending order, and `false` and `'desc'`\\n     *                               to descending order.\\n     * @return {QuerySet} a new {@link QuerySet} with objects ordered by `iteratees`.\\n     */\\n    orderBy(iteratees, orders) {\\n        const orderByDescriptor = {\\n            type: ORDER_BY,\\n            payload: [iteratees, orders],\\n        };\\n\\n        /**\\n         * create a new QuerySet\\n         * sorting all rows according to the passed arguments\\n         */\\n        return this._new(this.clauses.concat(orderByDescriptor));\\n    }\\n\\n    /**\\n     * Records an update specified with `mergeObj` to all the objects\\n     * in the {@link QuerySet} instance.\\n     *\\n     * @param  {Object} mergeObj - an object to merge with all the objects in this\\n     *                             queryset.\\n     * @return {undefined}\\n     */\\n    update(mergeObj) {\\n        const { session, modelName: table } = this.modelClass;\\n\\n        session.applyUpdate({\\n            action: UPDATE,\\n            query: {\\n                table,\\n                clauses: this.clauses,\\n            },\\n            payload: mergeObj,\\n        });\\n\\n        this._evaluated = false;\\n    }\\n\\n    /**\\n     * Records a deletion of all the objects in this {@link QuerySet} instance.\\n     * @return {undefined}\\n     */\\n    delete() {\\n        const { session, modelName: table } = this.modelClass;\\n\\n        this.toModelArray().forEach(\\n            model => model._onDelete() // eslint-disable-line no-underscore-dangle\\n        );\\n\\n        session.applyUpdate({\\n            action: DELETE,\\n            query: {\\n                table,\\n                clauses: this.clauses,\\n            },\\n        });\\n\\n        this._evaluated = false;\\n    }\\n\\n    // DEPRECATED AND REMOVED METHODS\\n\\n    /**\\n     * @deprecated\\n     * Use {@link QuerySet#toModelArray} or predicate functions that\\n     * instantiate Models from refs, e.g. `new Model(ref)`.\\n     */\\n    get withModels() {\\n        throw new Error(\\n            \\\"`QuerySet.prototype.withModels` has been removed. \\\" +\\n                \\\"Use `.toModelArray()` or predicate functions that \\\" +\\n                \\\"instantiate Models from refs, e.g. `new Model(ref)`.\\\"\\n        );\\n    }\\n\\n    /**\\n     * @deprecated Query building operates on refs only now.\\n     */\\n    get withRefs() {\\n        warnDeprecated(\\n            \\\"`QuerySet.prototype.withRefs` has been deprecated. \\\" +\\n                \\\"Query building operates on refs only now.\\\"\\n        );\\n        return undefined;\\n    }\\n\\n    /**\\n     * @deprecated\\n     * Call {@link QuerySet#toModelArray} or {@link QuerySet#toRefArray} first to map.\\n     */\\n    map() {\\n        throw new Error(\\n            \\\"`QuerySet.prototype.map` has been removed. \\\" +\\n                \\\"Call `.toModelArray()` or `.toRefArray()` first to map.\\\"\\n        );\\n    }\\n\\n    /**\\n     * @deprecated\\n     * Call {@link QuerySet#toModelArray} or {@link QuerySet#toRefArray} first to iterate.\\n     */\\n    forEach() {\\n        throw new Error(\\n            \\\"`QuerySet.prototype.forEach` has been removed. \\\" +\\n                \\\"Call `.toModelArray()` or `.toRefArray()` first to iterate.\\\"\\n        );\\n    }\\n};\\n\\nQuerySet.sharedMethods = [\\n    \\\"count\\\",\\n    \\\"at\\\",\\n    \\\"all\\\",\\n    \\\"last\\\",\\n    \\\"first\\\",\\n    \\\"filter\\\",\\n    \\\"exclude\\\",\\n    \\\"orderBy\\\",\\n    \\\"update\\\",\\n    \\\"delete\\\",\\n];\\n\\nexport default QuerySet;\\n\",\"import { getBatchToken } from \\\"immutable-ops\\\";\\n\\nimport { SUCCESS, UPDATE, DELETE } from \\\"./constants\\\";\\nimport { warnDeprecated, clauseFiltersByAttribute } from \\\"./utils\\\";\\n\\nconst Session = class Session {\\n    /**\\n     * Creates a new Session.\\n     *\\n     * @param  {Database} db - a {@link Database} instance\\n     * @param  {Object} state - the database state\\n     * @param  {Boolean} [withMutations] - whether the session should mutate data\\n     * @param  {Object} [batchToken] - used by the backend to identify objects that can be\\n     *                                 mutated.\\n     */\\n    constructor(schema, db, state, withMutations, batchToken) {\\n        this.schema = schema;\\n        this.db = db;\\n        this.state = state || db.getEmptyState();\\n        this.initialState = this.state;\\n\\n        this.withMutations = Boolean(withMutations);\\n        this.batchToken = batchToken || getBatchToken();\\n\\n        this.modelData = {};\\n\\n        this.models = schema.getModelClasses();\\n\\n        this.sessionBoundModels = this.models.map(modelClass => {\\n            function SessionBoundModel() {\\n                return Reflect.construct(\\n                    modelClass,\\n                    arguments,\\n                    SessionBoundModel\\n                ); // eslint-disable-line prefer-rest-params\\n            }\\n            Reflect.setPrototypeOf(\\n                SessionBoundModel.prototype,\\n                modelClass.prototype\\n            );\\n            Reflect.setPrototypeOf(SessionBoundModel, modelClass);\\n\\n            Object.defineProperty(this, modelClass.modelName, {\\n                get: () => SessionBoundModel,\\n            });\\n\\n            SessionBoundModel.connect(this);\\n            return SessionBoundModel;\\n        });\\n    }\\n\\n    getDataForModel(modelName) {\\n        if (!this.modelData[modelName]) {\\n            this.modelData[modelName] = {};\\n        }\\n        return this.modelData[modelName];\\n    }\\n\\n    getModelData() {\\n        return this.modelData;\\n    }\\n\\n    markAccessed(modelName, modelIds) {\\n        const data = this.getDataForModel(modelName);\\n        if (!data.accessedInstances) {\\n            data.accessedInstances = {};\\n        }\\n        modelIds.forEach(id => {\\n            data.accessedInstances[id] = true;\\n        });\\n    }\\n\\n    get accessedModelInstances() {\\n        return Object.entries(this.getModelData()).reduce(\\n            (result, [key, value]) => {\\n                if (value.accessedInstances) {\\n                    result[key] = value.accessedInstances;\\n                }\\n                return result;\\n            },\\n            {}\\n        );\\n    }\\n\\n    markFullTableScanned(modelName) {\\n        const data = this.getDataForModel(modelName);\\n        data.fullTableScanned = true;\\n    }\\n\\n    get fullTableScannedModels() {\\n        return Object.entries(this.getModelData()).reduce(\\n            (result, [key, value]) => {\\n                if (value.fullTableScanned) {\\n                    result.push(key);\\n                }\\n                return result;\\n            },\\n            []\\n        );\\n    }\\n\\n    markAccessedIndexes(indexes) {\\n        indexes.forEach(([table, attr, value]) => {\\n            const data = this.getDataForModel(table);\\n            if (!data.accessedIndexes) {\\n                data.accessedIndexes = {};\\n            }\\n            data.accessedIndexes[attr] = [\\n                ...(data.accessedIndexes[attr] || []),\\n                value,\\n            ];\\n        });\\n    }\\n\\n    get accessedIndexes() {\\n        return Object.entries(this.getModelData()).reduce(\\n            (result, [key, value]) => {\\n                if (value.accessedIndexes) {\\n                    result[key] = value.accessedIndexes;\\n                }\\n                return result;\\n            },\\n            {}\\n        );\\n    }\\n\\n    /**\\n     * Applies update to a model state.\\n     *\\n     * @private\\n     * @param {Object} update - the update object. Must have keys\\n     *                          `type`, `payload`.\\n     */\\n    applyUpdate(updateSpec) {\\n        const tx = this._getTransaction(updateSpec);\\n        const result = this.db.update(updateSpec, tx, this.state);\\n        const { status, state, payload } = result;\\n\\n        if (status !== SUCCESS) {\\n            throw new Error(\\n                `Applying update failed with status ${status}. Payload: ${payload}`\\n            );\\n        }\\n\\n        this.state = state;\\n\\n        return payload;\\n    }\\n\\n    query(querySpec) {\\n        const result = this.db.query(querySpec, this.state);\\n\\n        this._markAccessedByQuery(querySpec, result);\\n\\n        return result;\\n    }\\n\\n    _getTransaction(updateSpec) {\\n        const { withMutations } = this;\\n        const { action } = updateSpec;\\n        let { batchToken } = this;\\n        if ([UPDATE, DELETE].includes(action)) {\\n            batchToken = getBatchToken();\\n        }\\n        return { batchToken, withMutations };\\n    }\\n\\n    _markAccessedByQuery(querySpec, result) {\\n        const { table, clauses } = querySpec;\\n        const { rows } = result;\\n\\n        const { idAttribute } = this[table];\\n        const accessedIds = new Set(rows.map(row => row[idAttribute]));\\n\\n        const anyClauseFilteredByPk = clauses.some(clause => {\\n            if (!clauseFiltersByAttribute(clause, idAttribute)) {\\n                return false;\\n            }\\n            /**\\n             * We previously knew which row we wanted to access,\\n             * so there was no need to scan the entire table.\\n             */\\n            accessedIds.add(clause.payload[idAttribute]);\\n            return true;\\n        });\\n\\n        const accessedIndexes = [];\\n        const { indexes } = this.state[table];\\n        clauses.forEach(clause => {\\n            Object.keys(indexes).forEach(attr => {\\n                if (!clauseFiltersByAttribute(clause, attr)) {\\n                    return;\\n                }\\n                const value = clause.payload[attr];\\n                accessedIndexes.push([table, attr, value]);\\n            });\\n        });\\n\\n        if (anyClauseFilteredByPk) {\\n            /**\\n             * The clauses have been ordered so that an indexed one was\\n             * the first to have been evaluated, and thus only the row\\n             * with the specified PK value has actually been accessed.\\n             */\\n            this.markAccessed(table, accessedIds);\\n        } else if (accessedIndexes.length) {\\n            /**\\n             * At least one clause was optimized using indexes.\\n             */\\n            this.markAccessed(table, accessedIds);\\n            this.markAccessedIndexes(accessedIndexes);\\n        } else {\\n            /**\\n             * At least one clause could not be efficiently optimized\\n             * or no clause was specified at all.\\n             */\\n            this.markFullTableScanned(table);\\n        }\\n    }\\n\\n    // DEPRECATED AND REMOVED METHODS\\n\\n    /**\\n     * @deprecated Access {@link Session#state} instead.\\n     */\\n    getNextState() {\\n        warnDeprecated(\\n            \\\"`Session.prototype.getNextState` has been deprecated. Access \\\" +\\n                \\\"the `Session.prototype.state` property instead.\\\"\\n        );\\n        return this.state;\\n    }\\n\\n    /**\\n     * @deprecated\\n     * The Redux integration API is now decoupled from ORM and Session.<br>\\n     * See the 0.9 migration guide in the GitHub repo.\\n     */\\n    reduce() {\\n        throw new Error(\\n            \\\"`Session.prototype.reduce` has been removed. The Redux integration API \\\" +\\n                \\\"is now decoupled from ORM and Session - see the 0.9 migration guide \\\" +\\n                \\\"in the GitHub repo.\\\"\\n        );\\n    }\\n};\\n\\nexport default Session;\\n\",\"import FieldInstallerTemplate from \\\"./FieldInstallerTemplate\\\";\\n\\nimport { reverseFieldErrorMessage } from \\\"../utils\\\";\\n\\n/**\\n * Default implementation for the template method in FieldInstallerTemplate.\\n * @private\\n * @memberof module:fields\\n */\\nexport class DefaultFieldInstaller extends FieldInstallerTemplate {\\n    installForwardsDescriptor() {\\n        Object.defineProperty(\\n            this.model.prototype,\\n            this.fieldName,\\n            this.field.createForwardsDescriptor(\\n                this.fieldName,\\n                this.model,\\n                this.toModel,\\n                this.throughModel\\n            )\\n        );\\n    }\\n\\n    installForwardsVirtualField() {\\n        this.model.virtualFields[\\n            this.fieldName\\n        ] = this.field.createForwardsVirtualField(\\n            this.fieldName,\\n            this.model,\\n            this.toModel,\\n            this.throughModel\\n        );\\n    }\\n\\n    installBackwardsDescriptor() {\\n        const backwardsDescriptor = Object.getOwnPropertyDescriptor(\\n            this.toModel.prototype,\\n            this.backwardsFieldName\\n        );\\n        if (backwardsDescriptor) {\\n            throw new Error(\\n                reverseFieldErrorMessage(\\n                    this.model.modelName,\\n                    this.fieldName,\\n                    this.toModel.modelName,\\n                    this.backwardsFieldName\\n                )\\n            );\\n        }\\n\\n        // install backwards descriptor\\n        Object.defineProperty(\\n            this.toModel.prototype,\\n            this.backwardsFieldName,\\n            this.field.createBackwardsDescriptor(\\n                this.fieldName,\\n                this.model,\\n                this.toModel,\\n                this.throughModel\\n            )\\n        );\\n    }\\n\\n    installBackwardsVirtualField() {\\n        this.toModel.virtualFields[\\n            this.backwardsFieldName\\n        ] = this.field.createBackwardsVirtualField(\\n            this.fieldName,\\n            this.model,\\n            this.toModel,\\n            this.throughModel\\n        );\\n    }\\n}\\n\\nexport default DefaultFieldInstaller;\\n\",\"/**\\n * Defines algorithm for installing a field onto a model and related models.\\n * Conforms to the template method behavioral design pattern.\\n * @private\\n * @memberof module:fields\\n */\\nexport class FieldInstallerTemplate {\\n    constructor(opts) {\\n        this.field = opts.field;\\n        this.fieldName = opts.fieldName;\\n        this.model = opts.model;\\n        this.orm = opts.orm;\\n        /**\\n         * the field itself has no knowledge of the model\\n         * it is being installed upon; we need to inform it\\n         * that it is a self-referencing field for the field\\n         * to be able to make better informed decisions\\n         */\\n        if (this.field.references(this.model)) {\\n            this.field.toModelName = \\\"this\\\";\\n        }\\n    }\\n\\n    get toModel() {\\n        if (typeof this._toModel === \\\"undefined\\\") {\\n            const { toModelName } = this.field;\\n            if (!toModelName) {\\n                this._toModel = null;\\n            } else if (toModelName === \\\"this\\\") {\\n                this._toModel = this.model;\\n            } else {\\n                this._toModel = this.orm.get(toModelName);\\n            }\\n        }\\n        return this._toModel;\\n    }\\n\\n    get throughModel() {\\n        if (typeof this._throughModel === \\\"undefined\\\") {\\n            const throughModelName = this.field.getThroughModelName(\\n                this.fieldName,\\n                this.model\\n            );\\n            if (!throughModelName) {\\n                this._throughModel = null;\\n            } else {\\n                this._throughModel = this.orm.get(throughModelName);\\n            }\\n        }\\n        return this._throughModel;\\n    }\\n\\n    get backwardsFieldName() {\\n        return this.field.getBackwardsFieldName(this.model);\\n    }\\n\\n    run() {\\n        this.installForwardsDescriptor();\\n        if (this.field.installsForwardsVirtualField) {\\n            this.installForwardsVirtualField();\\n        }\\n        /**\\n         * Install a backwards field on a model as a consequence\\n         * of having installed the forwards field on another model.\\n         */\\n        if (this.field.installsBackwardsDescriptor) {\\n            this.installBackwardsDescriptor();\\n        }\\n        if (this.field.installsBackwardsVirtualField) {\\n            this.installBackwardsVirtualField();\\n        }\\n    }\\n}\\n\\nexport default FieldInstallerTemplate;\\n\",\"import DefaultFieldInstaller from \\\"./DefaultFieldInstaller\\\";\\n\\n/**\\n * @private\\n * @memberof module:fields\\n */\\nexport class Field {\\n    get installerClass() {\\n        return DefaultFieldInstaller;\\n    }\\n\\n    getClass() {\\n        return this.constructor;\\n    }\\n\\n    references(model) {\\n        return false;\\n    }\\n\\n    getThroughModelName(fieldName, model) {\\n        return null;\\n    }\\n\\n    get installsForwardsVirtualField() {\\n        return false;\\n    }\\n\\n    get installsBackwardsDescriptor() {\\n        return false;\\n    }\\n\\n    get installsBackwardsVirtualField() {\\n        return false;\\n    }\\n\\n    get index() {\\n        return false;\\n    }\\n}\\n\\nexport default Field;\\n\",\"import { normalizeEntity } from \\\"./utils\\\";\\n\\n/**\\n * The functions in this file return custom JS property descriptors\\n * that are supposed to be assigned to Model fields.\\n *\\n * Some include the logic to look up models using foreign keys and\\n * to add or remove relationships between models.\\n *\\n * @module descriptors\\n * @private\\n */\\n\\n/**\\n * Defines a basic non-key attribute.\\n * @param  {string} fieldName - the name of the field the descriptor will be assigned to.\\n */\\nfunction attrDescriptor(fieldName) {\\n    return {\\n        get() {\\n            return this._fields[fieldName];\\n        },\\n\\n        set(value) {\\n            return this.set(fieldName, value);\\n        },\\n\\n        enumerable: true,\\n        configurable: true,\\n    };\\n}\\n\\n/**\\n * Forwards direction of a Foreign Key: returns one object.\\n * Also works as {@link .forwardsOneToOneDescriptor|forwardsOneToOneDescriptor}.\\n *\\n * For `book.author` referencing an `Author` model instance,\\n * `fieldName` would be `'author'` and `declaredToModelName` would be `'Author'`.\\n * @param  {string} fieldName - the name of the field the descriptor will be assigned to.\\n * @param  {string} declaredToModelName - the name of the model that the field references.\\n */\\nfunction forwardsManyToOneDescriptor(fieldName, declaredToModelName) {\\n    return {\\n        get() {\\n            const {\\n                session: { [declaredToModelName]: DeclaredToModel },\\n            } = this.getClass();\\n            const { [fieldName]: toId } = this._fields;\\n\\n            return DeclaredToModel.withId(toId);\\n        },\\n        set(value) {\\n            this.update({\\n                [fieldName]: normalizeEntity(value),\\n            });\\n        },\\n    };\\n}\\n\\n/**\\n * Dereferencing foreign keys in {@link module:fields.oneToOne|oneToOne}\\n * relationships works the same way as in many-to-one relationships:\\n * just look up the related model.\\n *\\n * For example, a human face tends to have a single nose.\\n * So if we want to resolve `face.nose`, we need to\\n * look up the `Nose` that has the primary key that `face` references.\\n *\\n * @see {@link module:descriptors~forwardsManyToOneDescriptor|forwardsManyToOneDescriptor}\\n */\\nfunction forwardsOneToOneDescriptor(...args) {\\n    return forwardsManyToOneDescriptor(...args);\\n}\\n\\n/**\\n * Here we resolve 1-to-1 relationships starting at the model on which the\\n * field was not installed. This means we need to find the instance of the\\n * other model whose {@link module:fields.oneToOne|oneToOne} FK field contains the current model's primary key.\\n *\\n * @param  {string} declaredFieldName - the name of the field referencing the current model.\\n * @param  {string} declaredFromModelName - the name of the other model.\\n */\\nfunction backwardsOneToOneDescriptor(declaredFieldName, declaredFromModelName) {\\n    return {\\n        get() {\\n            const {\\n                session: { [declaredFromModelName]: DeclaredFromModel },\\n            } = this.getClass();\\n\\n            return DeclaredFromModel.get({\\n                [declaredFieldName]: this.getId(),\\n            });\\n        },\\n        set() {\\n            throw new Error(\\\"Can't mutate a reverse one-to-one relation.\\\");\\n        },\\n    };\\n}\\n\\n/**\\n * The backwards direction of a n-to-1 relationship (i.e. 1-to-n),\\n * meaning this will return an a collection (`QuerySet`) of model instances.\\n *\\n * An example would be `author.books` referencing all instances of\\n * the `Book` model that reference the author using `fk()`.\\n */\\nfunction backwardsManyToOneDescriptor(\\n    declaredFieldName,\\n    declaredFromModelName\\n) {\\n    return {\\n        get() {\\n            const {\\n                session: { [declaredFromModelName]: DeclaredFromModel },\\n            } = this.getClass();\\n\\n            return DeclaredFromModel.filter({\\n                [declaredFieldName]: this.getId(),\\n            });\\n        },\\n        set() {\\n            throw new Error(\\\"Can't mutate a reverse many-to-one relation.\\\");\\n        },\\n    };\\n}\\n\\n/**\\n * This descriptor is assigned to both sides of a many-to-many relationship.\\n * To indicate the backwards direction pass `true` for `reverse`.\\n */\\nfunction manyToManyDescriptor(\\n    declaredFromModelName,\\n    declaredToModelName,\\n    throughModelName,\\n    throughFields,\\n    reverse\\n) {\\n    return {\\n        get() {\\n            const {\\n                session: {\\n                    [declaredFromModelName]: DeclaredFromModel,\\n                    [declaredToModelName]: DeclaredToModel,\\n                    [throughModelName]: ThroughModel,\\n                },\\n            } = this.getClass();\\n\\n            const ThisModel = reverse ? DeclaredToModel : DeclaredFromModel;\\n            const OtherModel = reverse ? DeclaredFromModel : DeclaredToModel;\\n\\n            const thisReferencingField = reverse\\n                ? throughFields.to\\n                : throughFields.from;\\n            const otherReferencingField = reverse\\n                ? throughFields.from\\n                : throughFields.to;\\n\\n            const thisId = this.getId();\\n\\n            const throughQs = ThroughModel.filter({\\n                [thisReferencingField]: thisId,\\n            });\\n\\n            /**\\n             * all IDs of instances of the other model that are\\n             * referenced by any instance of the current model\\n             */\\n            const referencedOtherIds = new Set(\\n                throughQs.toRefArray().map(obj => obj[otherReferencingField])\\n            );\\n\\n            /**\\n             * selects all instances of other model that are referenced\\n             * by any instance of the current model\\n             */\\n            const qs = OtherModel.filter(otherModelInstance =>\\n                referencedOtherIds.has(\\n                    otherModelInstance[OtherModel.idAttribute]\\n                )\\n            );\\n\\n            /**\\n             * Allows adding OtherModel instances to be referenced by the current instance.\\n             *\\n             * E.g. Book.first().authors.add(1, 2) would add the authors with IDs 1 and 2\\n             * to the first book's list of referenced authors.\\n             *\\n             * @return undefined\\n             */\\n            qs.add = function add(...entities) {\\n                const idsToAdd = new Set(entities.map(normalizeEntity));\\n\\n                const existingQs = throughQs.filter(through =>\\n                    idsToAdd.has(through[otherReferencingField])\\n                );\\n\\n                if (existingQs.exists()) {\\n                    const existingIds = existingQs\\n                        .toRefArray()\\n                        .map(through => through[otherReferencingField]);\\n\\n                    throw new Error(\\n                        `Tried to add already existing ${OtherModel.modelName} id(s) ${existingIds} to the ${ThisModel.modelName} instance with id ${thisId}`\\n                    );\\n                }\\n\\n                idsToAdd.forEach(id => {\\n                    ThroughModel.create({\\n                        [otherReferencingField]: id,\\n                        [thisReferencingField]: thisId,\\n                    });\\n                });\\n            };\\n\\n            /**\\n             * Removes references to all OtherModel instances from the current model.\\n             *\\n             * E.g. Book.first().authors.clear() would cause the first book's list\\n             * of referenced authors to become empty.\\n             *\\n             * @return undefined\\n             */\\n            qs.clear = function clear() {\\n                throughQs.delete();\\n            };\\n\\n            /**\\n             * Removes references to all passed OtherModel instances from the current model.\\n             *\\n             * E.g. Book.first().authors.remove(1, 2) would cause the authors with\\n             * IDs 1 and 2 to no longer be referenced by the first book.\\n             *\\n             * @return undefined\\n             */\\n            qs.remove = function remove(...entities) {\\n                const idsToRemove = new Set(entities.map(normalizeEntity));\\n\\n                const entitiesToDelete = throughQs.filter(through =>\\n                    idsToRemove.has(through[otherReferencingField])\\n                );\\n\\n                if (entitiesToDelete.count() !== idsToRemove.size) {\\n                    // Tried deleting non-existing entities.\\n                    const entitiesToDeleteIds = entitiesToDelete\\n                        .toRefArray()\\n                        .map(through => through[otherReferencingField]);\\n\\n                    const unexistingIds = [...idsToRemove].filter(\\n                        id => !entitiesToDeleteIds.includes(id)\\n                    );\\n\\n                    throw new Error(\\n                        `Tried to delete non-existing ${OtherModel.modelName} id(s) ${unexistingIds} from the ${ThisModel.modelName} instance with id ${thisId}`\\n                    );\\n                }\\n\\n                entitiesToDelete.delete();\\n            };\\n\\n            return qs;\\n        },\\n\\n        set() {\\n            throw new Error(\\n                \\\"Tried setting a M2M field. Please use the related QuerySet methods add, remove and clear.\\\"\\n            );\\n        },\\n    };\\n}\\n\\nexport {\\n    attrDescriptor,\\n    forwardsManyToOneDescriptor,\\n    forwardsOneToOneDescriptor,\\n    backwardsOneToOneDescriptor,\\n    backwardsManyToOneDescriptor,\\n    manyToManyDescriptor,\\n};\\n\",\"import Field from \\\"./Field\\\";\\n\\nimport { attrDescriptor } from \\\"../descriptors\\\";\\n\\n/**\\n * @memberof module:fields\\n */\\nexport class Attribute extends Field {\\n    constructor(opts) {\\n        super();\\n        this.opts = opts || {};\\n\\n        if (this.opts.hasOwnProperty(\\\"getDefault\\\")) {\\n            this.getDefault = this.opts.getDefault;\\n        }\\n    }\\n\\n    createForwardsDescriptor(fieldName, model) {\\n        return attrDescriptor(fieldName);\\n    }\\n}\\n\\nexport default Attribute;\\n\",\"/* eslint-disable max-classes-per-file */\\nimport Field from \\\"./Field\\\";\\nimport DefaultFieldInstaller from \\\"./DefaultFieldInstaller\\\";\\n\\nimport { reverseFieldName, normalizeModelReference } from \\\"../utils\\\";\\n\\n/**\\n * @private\\n * @memberof module:fields\\n */\\nexport class RelationalField extends Field {\\n    constructor(...args) {\\n        super();\\n        if (args.length === 1 && typeof args[0] === \\\"object\\\") {\\n            const opts = args[0];\\n            this.toModelName = normalizeModelReference(opts.to);\\n            this.relatedName = opts.relatedName;\\n            this.through = normalizeModelReference(opts.through);\\n            this.throughFields = opts.throughFields;\\n            this.as = opts.as;\\n        } else {\\n            [this.toModelName, this.relatedName] = [\\n                normalizeModelReference(args[0]),\\n                args[1],\\n            ];\\n        }\\n    }\\n\\n    getBackwardsFieldName(model) {\\n        return this.relatedName || reverseFieldName(model.modelName);\\n    }\\n\\n    createBackwardsVirtualField(fieldName, model, toModel, throughModel) {\\n        const ThisField = this.getClass();\\n        return new ThisField(model.modelName, fieldName);\\n    }\\n\\n    get installsBackwardsVirtualField() {\\n        return true;\\n    }\\n\\n    get installsBackwardsDescriptor() {\\n        return true;\\n    }\\n\\n    references(model) {\\n        return this.toModelName === model.modelName;\\n    }\\n\\n    get installerClass() {\\n        return class AliasedForwardsDescriptorInstaller extends DefaultFieldInstaller {\\n            installForwardsDescriptor() {\\n                Object.defineProperty(\\n                    this.model.prototype,\\n                    this.field.as || this.fieldName, // use supplied name if possible\\n                    this.field.createForwardsDescriptor(\\n                        this.fieldName,\\n                        this.model,\\n                        this.toModel,\\n                        this.throughModel\\n                    )\\n                );\\n            }\\n        };\\n    }\\n}\\n\\nexport default RelationalField;\\n\",\"import RelationalField from \\\"./RelationalField\\\";\\n\\nimport {\\n    forwardsManyToOneDescriptor,\\n    backwardsManyToOneDescriptor,\\n} from \\\"../descriptors\\\";\\n\\n/**\\n * @memberof module:fields\\n */\\nexport class ForeignKey extends RelationalField {\\n    createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return forwardsManyToOneDescriptor(fieldName, toModel.modelName);\\n    }\\n\\n    createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return backwardsManyToOneDescriptor(fieldName, model.modelName);\\n    }\\n\\n    get index() {\\n        return true;\\n    }\\n}\\n\\nexport default ForeignKey;\\n\",\"import RelationalField from \\\"./RelationalField\\\";\\n\\nimport { manyToManyDescriptor } from \\\"../descriptors\\\";\\n\\nimport { m2mName, m2mToFieldName, m2mFromFieldName } from \\\"../utils\\\";\\n\\n/**\\n * @memberof module:fields\\n */\\nexport class ManyToMany extends RelationalField {\\n    getDefault() {\\n        return [];\\n    }\\n\\n    getThroughModelName(fieldName, model) {\\n        return this.through || m2mName(model.modelName, fieldName);\\n    }\\n\\n    createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return manyToManyDescriptor(\\n            model.modelName,\\n            toModel.modelName,\\n            throughModel.modelName,\\n            this.getThroughFields(fieldName, model, toModel, throughModel),\\n            false\\n        );\\n    }\\n\\n    createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return manyToManyDescriptor(\\n            model.modelName,\\n            toModel.modelName,\\n            throughModel.modelName,\\n            this.getThroughFields(fieldName, model, toModel, throughModel),\\n            true\\n        );\\n    }\\n\\n    createBackwardsVirtualField(fieldName, model, toModel, throughModel) {\\n        const ThisField = this.getClass();\\n        return new ThisField({\\n            to: model.modelName,\\n            relatedName: fieldName,\\n            through: throughModel.modelName,\\n            throughFields: this.getThroughFields(\\n                fieldName,\\n                model,\\n                toModel,\\n                throughModel\\n            ),\\n        });\\n    }\\n\\n    createForwardsVirtualField(fieldName, model, toModel, throughModel) {\\n        const ThisField = this.getClass();\\n        return new ThisField({\\n            to: toModel.modelName,\\n            relatedName: fieldName,\\n            through: this.through,\\n            throughFields: this.getThroughFields(\\n                fieldName,\\n                model,\\n                toModel,\\n                throughModel\\n            ),\\n            as: this.as,\\n        });\\n    }\\n\\n    get installsForwardsVirtualField() {\\n        return true;\\n    }\\n\\n    getThroughFields(fieldName, model, toModel, throughModel) {\\n        if (this.throughFields) {\\n            const [fieldAName, fieldBName] = this.throughFields;\\n            const fieldA = throughModel.fields[fieldAName];\\n            return {\\n                to: fieldA.references(toModel) ? fieldAName : fieldBName,\\n                from: fieldA.references(toModel) ? fieldBName : fieldAName,\\n            };\\n        }\\n\\n        if (model.modelName === toModel.modelName) {\\n            /**\\n             * we have no way of determining the relationship's\\n             * direction here, so we need to assume that the user\\n             * did not use a custom through model\\n             * see ORM#registerManyToManyModelsFor\\n             */\\n            return {\\n                to: m2mToFieldName(toModel.modelName),\\n                from: m2mFromFieldName(model.modelName),\\n            };\\n        }\\n\\n        /**\\n         * determine which field references which model\\n         * and infer the directions from that\\n         */\\n        const throughModelFieldReferencing = otherModel =>\\n            Object.keys(throughModel.fields).find(someFieldName =>\\n                throughModel.fields[someFieldName].references(otherModel)\\n            );\\n\\n        return {\\n            to: throughModelFieldReferencing(toModel),\\n            from: throughModelFieldReferencing(model),\\n        };\\n    }\\n}\\n\\nexport default ManyToMany;\\n\",\"import RelationalField from \\\"./RelationalField\\\";\\n\\nimport {\\n    forwardsOneToOneDescriptor,\\n    backwardsOneToOneDescriptor,\\n} from \\\"../descriptors\\\";\\n\\n/**\\n * @memberof module:fields\\n */\\nexport class OneToOne extends RelationalField {\\n    getBackwardsFieldName(model) {\\n        return this.relatedName || model.modelName.toLowerCase();\\n    }\\n\\n    createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return forwardsOneToOneDescriptor(fieldName, toModel.modelName);\\n    }\\n\\n    createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return backwardsOneToOneDescriptor(fieldName, model.modelName);\\n    }\\n}\\n\\nexport default OneToOne;\\n\",\"import Attribute from \\\"./Attribute\\\";\\nimport ForeignKey from \\\"./ForeignKey\\\";\\nimport ManyToMany from \\\"./ManyToMany\\\";\\nimport OneToOne from \\\"./OneToOne\\\";\\n\\n/**\\n * Contains the logic for how fields on {@link Model}s work\\n * and which descriptors must be installed.\\n *\\n * If your goal is to define fields on a Model class,\\n * please use the more convenient methods {@link attr},\\n * {@link fk}, {@link many} and {@link oneToOne}.\\n *\\n * @module fields\\n */\\n\\n/**\\n * Defines a value attribute on the model.\\n * Though not required, it is recommended to define this for each non-foreign key you wish to use.\\n * Getters and setters need to be defined on each Model\\n * instantiation for undeclared data fields, which is slower.\\n * You can use the optional `getDefault` parameter to fill in unpassed values\\n * to {@link Model.create}, such as for generating ID's with UUID:\\n *\\n * ```javascript\\n * import getUUID from 'your-uuid-package-of-choice';\\n *\\n * fields = {\\n *   id: attr({ getDefault: () => getUUID() }),\\n *   title: attr(),\\n * }\\n * ```\\n *\\n * @param  {Object} [opts]\\n * @param {Function} [opts.getDefault] - If you give a function here, its return\\n *                                       value from calling with zero arguments will\\n *                                       be used as the value when creating a new Model\\n *                                       instance with {@link Model#create} if the field\\n *                                       value is not passed.\\n * @return {Attribute}\\n */\\nfunction attr(opts) {\\n    return new Attribute(opts);\\n}\\n\\n/**\\n * Defines a foreign key on a model, which points\\n * to a single entity on another model.\\n *\\n * You can pass arguments as either a single object,\\n * or two arguments.\\n *\\n * If you pass two arguments, the first one is the name\\n * of the Model the foreign key is pointing to, and\\n * the second one is an optional related name, which will\\n * be used to access the Model the foreign key\\n * is being defined from, from the target Model.\\n *\\n * If the related name is not passed, it will be set as\\n * `${toModelName}Set`.\\n *\\n * If you pass an object to `fk`, it has to be in the form\\n *\\n * ```javascript\\n * fields = {\\n *   author: fk({ to: 'Author', relatedName: 'books' })\\n * }\\n * ```\\n *\\n * Which is equal to\\n *\\n * ```javascript\\n * fields = {\\n *   author: fk('Author', 'books'),\\n * }\\n * ```\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string} [options.relatedName] - The property name that will be used to access\\n *                                         a QuerySet for all source models that reference\\n *                                         the respective target Model's instance.\\n * @param {string} [relatedName] - If you didn't pass an object as the first argument,\\n *                                 this is the property name that will be used to\\n *                                 access a QuerySet for all source models that reference\\n *                                 the respective target Model's instance.\\n * @return {ForeignKey}\\n */\\nfunction fk(...args) {\\n    return new ForeignKey(...args);\\n}\\n\\n/**\\n * Defines a many-to-many relationship between\\n * this (source) and another (target) model.\\n *\\n * The relationship is modeled with an extra model called the through model.\\n * The through model has foreign keys to both the source and target models.\\n *\\n * You can define your own through model if you want to associate more information\\n * to the relationship. A custom through model must have at least two foreign keys,\\n * one pointing to the source Model, and one pointing to the target Model.\\n *\\n * Like `fk`, this function accepts one or two string arguments specifying the other\\n * Model and the related name, or a single object argument that allows you to pass\\n * a custom through model.\\n *\\n * If you have more than one foreign key pointing to a source or target Model in the\\n * through Model, you must pass the option `throughFields`, which is an array of two\\n * strings, where the strings are the field names that identify the foreign keys to\\n * be used for the many-to-many relationship. Redux-ORM will figure out which field name\\n * points to which model by checking the \\\"through model\\\" definition.\\n *\\n * ```javascript\\n * class Authorship extends Model {}\\n * Authorship.modelName = 'Authorship';\\n * Authorship.fields = {\\n *   author: fk('Author', 'authorships'),\\n *   book: fk('Book', 'authorships'),\\n * };\\n *\\n * class Author extends Model {}\\n * Author.modelName = 'Author';\\n * Author.fields = {\\n *   books: many({\\n *     to: 'Book',\\n *     relatedName: 'authors',\\n *     through: 'Authorship',\\n *\\n *     // here this is optional: Redux-ORM can figure\\n *     // out the through fields itself since the two\\n *     // foreign key fields point to different Models\\n *     throughFields: ['author', 'book'],\\n *   })\\n * };\\n *\\n * class Book extends Model {}\\n * Book.modelName = 'Book';\\n * ```\\n *\\n * You should only define the many-to-many relationship on one side. In the\\n * above case of Authors to Books through Authorships, the relationship is\\n * defined only on the Author model.\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string|Class<Model>} [options.through] - The through Model class or its `modelName`\\n *                                                  attribute. It must declare at least one\\n *                                                  foreign key to both source and target models.\\n *                                                  If not supplied, Redux-ORM will generate one.\\n * @param {string[]} [options.throughFields] - Must be supplied only when a custom through\\n *                                             Model has more than one foreign key pointing to\\n *                                             either the source or target mode. In this case\\n *                                             Redux-ORM can't figure out the correct fields for\\n *                                             you, you must provide them. The supplied array should\\n *                                             have two elements that are the field names for the\\n *                                             through fields you want to declare the many-to-many\\n *                                             relationship with. The order doesn't matter;\\n *                                             Redux-ORM will figure out which field points to\\n *                                             the source Model and which to the target Model.\\n * @param {string} [options.relatedName] - The attribute used to access a QuerySet for all\\n *                                         source models that reference the respective target\\n *                                         Model's instance.\\n * @param {string} [relatedName] - If you didn't pass an object as the first argument,\\n *                                 this is the property name that will be used to\\n *                                 access a QuerySet for all source models that reference\\n *                                 the respective target Model's instance.\\n * @return {ManyToMany}\\n */\\nfunction many(...args) {\\n    return new ManyToMany(...args);\\n}\\n\\n/**\\n * Defines a one-to-one relationship. In database terms, this is a foreign key with the\\n * added restriction that only one entity can point to single target entity.\\n *\\n * The arguments are the same as with `fk`. If `relatedName` is not supplied,\\n * the source model name in lowercase will be used. Note that with the one-to-one\\n * relationship, the `relatedName` should be in singular, not plural.\\n *\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string} [options.relatedName] - The property name that will be used to access the source\\n *                                         model instance referencing the target model instance.\\n * @param {string} [relatedName] - The property name that will be used to access the source\\n *                                 model instance referencing the target model instance\\n * @return {OneToOne}\\n */\\nfunction oneToOne(...args) {\\n    return new OneToOne(...args);\\n}\\n\\nexport { fk, attr, many, oneToOne };\\n\",\"import Session from \\\"./Session\\\";\\nimport QuerySet from \\\"./QuerySet\\\";\\n\\nimport { attr } from \\\"./fields\\\";\\nimport ForeignKey from \\\"./fields/ForeignKey\\\";\\nimport ManyToMany from \\\"./fields/ManyToMany\\\";\\nimport OneToOne from \\\"./fields/OneToOne\\\";\\n\\nimport { CREATE, UPDATE, DELETE, FILTER } from \\\"./constants\\\";\\nimport {\\n    normalizeEntity,\\n    arrayDiffActions,\\n    objectShallowEquals,\\n    warnDeprecated,\\n    m2mName,\\n} from \\\"./utils\\\";\\n\\n/**\\n * Generates a query specification to get the instance's\\n * corresponding table row using its primary key.\\n *\\n * @private\\n * @returns {Object}\\n */\\nfunction getByIdQuery(modelInstance) {\\n    const modelClass = modelInstance.getClass();\\n    const { idAttribute, modelName } = modelClass;\\n\\n    return {\\n        table: modelName,\\n        clauses: [\\n            {\\n                type: FILTER,\\n                payload: {\\n                    [idAttribute]: modelInstance.getId(),\\n                },\\n            },\\n        ],\\n    };\\n}\\n\\n/**\\n * The heart of an ORM, the data model.\\n *\\n * The fields you specify to the Model will be used to generate\\n * a schema to the database, related property accessors, and\\n * possibly through models.\\n *\\n * In each {@link Session} you instantiate from an {@link ORM} instance,\\n * you will receive a session-specific subclass of this Model. The methods\\n * you define here will be available to you in sessions.\\n *\\n * An instance of {@link Model} represents a record in the database, though\\n * it is possible to generate multiple instances from the same record in the database.\\n *\\n * To create data models in your schema, subclass {@link Model}. To define\\n * information about the data model, override static class methods. Define instance\\n * logic by defining prototype methods (without `static` keyword).\\n */\\nconst Model = class Model {\\n    /**\\n     * Creates a Model instance from it's properties.\\n     * Don't use this to create a new record; Use the static method {@link Model#create}.\\n     * @param  {Object} props - the properties to instantiate with\\n     */\\n    constructor(props) {\\n        this._initFields(props);\\n    }\\n\\n    _initFields(props) {\\n        const propsObj = Object(props);\\n        this._fields = { ...propsObj };\\n\\n        Object.keys(propsObj).forEach(fieldName => {\\n            // In this case, we got a prop that wasn't defined as a field.\\n            // Assuming it's an arbitrary data field, making an instance-specific\\n            // descriptor for it.\\n            // Using the in operator as the property could be defined anywhere\\n            // on the prototype chain.\\n            if (!(fieldName in this)) {\\n                Object.defineProperty(this, fieldName, {\\n                    get: () => this._fields[fieldName],\\n                    set: value => this.set(fieldName, value),\\n                    configurable: true,\\n                    enumerable: true,\\n                });\\n            }\\n        });\\n    }\\n\\n    static toString() {\\n        return `ModelClass: ${this.modelName}`;\\n    }\\n\\n    /**\\n     * Returns the options object passed to the database for the table that represents\\n     * this Model class.\\n     *\\n     * Returns an empty object by default, which means the database\\n     * will use default options. You can either override this function to return the options\\n     * you want to use, or assign the options object as a static property of the same name to the\\n     * Model class.\\n     *\\n     * @return {Object} the options object passed to the database for the table\\n     *                  representing this Model class.\\n     */\\n    static options() {\\n        return {};\\n    }\\n\\n    /**\\n     * Manually mark individual instances as accessed.\\n     * This allows invalidating selector memoization within mutable sessions.\\n     *\\n     * @param {Array.<*>} ids - Array of primary key values\\n     * @return {undefined}\\n     */\\n    static markAccessed(ids) {\\n        if (typeof this._session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to mark rows of the ${this.modelName} model as accessed without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].markAccessed\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        this.session.markAccessed(this.modelName, ids);\\n    }\\n\\n    /**\\n     * Manually mark this model's table as scanned.\\n     * This allows invalidating selector memoization within mutable sessions.\\n     *\\n     * @return {undefined}\\n     */\\n    static markFullTableScanned() {\\n        if (typeof this._session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to mark the ${this.modelName} model as full table scanned without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].markFullTableScanned\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        this.session.markFullTableScanned(this.modelName);\\n    }\\n\\n    /**\\n     * Manually mark indexes as accessed.\\n     * This allows invalidating selector memoization within mutable sessions.\\n     *\\n     * @param {Array.<Array.<*,*>>} indexes - Array of column-value pairs\\n     * @return {undefined}\\n     */\\n    static markAccessedIndexes(indexes) {\\n        if (typeof this._session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to mark indexes for the ${this.modelName} model as accessed without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].markAccessedIndexes\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        this.session.markAccessedIndexes(\\n            indexes.map(([attribute, value]) => [\\n                this.modelName,\\n                attribute,\\n                value,\\n            ])\\n        );\\n    }\\n\\n    /**\\n     * Returns the id attribute of this {@link Model}.\\n     *\\n     * @return {string} The id attribute of this {@link Model}.\\n     */\\n    static get idAttribute() {\\n        if (typeof this._session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to get the ${this.modelName} model's id attribute without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and access \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].idAttribute\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        return this.session.db.describe(this.modelName).idAttribute;\\n    }\\n\\n    /**\\n     * Connect the model class to a {@link Session}.\\n     *\\n     * @private\\n     * @param  {Session} session - The session to connect to.\\n     */\\n    static connect(session) {\\n        if (!(session instanceof Session)) {\\n            throw new Error(\\n                \\\"A model can only be connected to instances of Session.\\\"\\n            );\\n        }\\n        this._session = session;\\n    }\\n\\n    /**\\n     * Get the current {@link Session} instance.\\n     *\\n     * @private\\n     * @return {Session} The current {@link Session} instance.\\n     */\\n    static get session() {\\n        return this._session;\\n    }\\n\\n    /**\\n     * Returns an instance of the model's `querySetClass` field.\\n     * By default, this will be an empty {@link QuerySet}.\\n     *\\n     * @return {Object} An instance of the model's `querySetClass`.\\n     */\\n    static getQuerySet() {\\n        const { querySetClass: QuerySetClass } = this;\\n        return new QuerySetClass(this);\\n    }\\n\\n    /**\\n     * @return {undefined}\\n     */\\n    static invalidateClassCache() {\\n        this.isSetUp = undefined;\\n        this.virtualFields = {};\\n    }\\n\\n    /**\\n     * @see {@link Model.getQuerySet}\\n     */\\n    static get query() {\\n        return this.getQuerySet();\\n    }\\n\\n    /**\\n     * Returns parameters to be passed to {@link Table} instance.\\n     *\\n     * @private\\n     */\\n    static tableOptions() {\\n        if (typeof this.backend === \\\"function\\\") {\\n            warnDeprecated(\\n                \\\"`Model.backend` has been deprecated. Please rename to `.options`.\\\"\\n            );\\n            return this.backend();\\n        }\\n        if (this.backend) {\\n            warnDeprecated(\\n                \\\"`Model.backend` has been deprecated. Please rename to `.options`.\\\"\\n            );\\n            return this.backend;\\n        }\\n        if (typeof this.options === \\\"function\\\") {\\n            return this.options();\\n        }\\n        return this.options;\\n    }\\n\\n    /**\\n     * Creates a new record in the database, instantiates a {@link Model} and returns it.\\n     *\\n     * If you pass values for many-to-many fields, instances are created on the through\\n     * model as well.\\n     *\\n     * @param  {Object} userProps - the new {@link Model}'s properties.\\n     * @return {Model} a new {@link Model} instance.\\n     */\\n    static create(userProps) {\\n        if (typeof this._session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to create a ${this.modelName} model instance without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].create\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        const props = { ...userProps };\\n\\n        const m2mRelations = {};\\n\\n        const declaredFieldNames = Object.keys(this.fields);\\n        const declaredVirtualFieldNames = Object.keys(this.virtualFields);\\n\\n        declaredFieldNames.forEach(key => {\\n            const field = this.fields[key];\\n            const valuePassed = userProps.hasOwnProperty(key);\\n            if (!(field instanceof ManyToMany)) {\\n                if (valuePassed) {\\n                    const value = userProps[key];\\n                    props[key] = normalizeEntity(value);\\n                } else if (field.getDefault) {\\n                    props[key] = field.getDefault();\\n                }\\n            } else if (valuePassed) {\\n                // Save for later processing\\n                m2mRelations[key] = userProps[key];\\n\\n                if (!field.as) {\\n                    /**\\n                     * The relationship does not have an accessor\\n                     * Discard the value from props as the field will be populated later with instances\\n                     * from the target models when refreshing the M2M relations.\\n                     * If the relationship does have an accessor (`as`) field then we do want to keep this\\n                     * original value in the props to expose the raw list of IDs from the instance.\\n                     */\\n                    delete props[key];\\n                }\\n            }\\n        });\\n\\n        // add backward many-many if required\\n        declaredVirtualFieldNames.forEach(key => {\\n            if (!m2mRelations.hasOwnProperty(key)) {\\n                const field = this.virtualFields[key];\\n                if (\\n                    userProps.hasOwnProperty(key) &&\\n                    field instanceof ManyToMany\\n                ) {\\n                    // If a value is supplied for a ManyToMany field,\\n                    // discard them from props and save for later processing.\\n                    m2mRelations[key] = userProps[key];\\n                    delete props[key];\\n                }\\n            }\\n        });\\n\\n        const newEntry = this.session.applyUpdate({\\n            action: CREATE,\\n            table: this.modelName,\\n            payload: props,\\n        });\\n\\n        const ThisModel = this;\\n        const instance = new ThisModel(newEntry);\\n        instance._refreshMany2Many(m2mRelations); // eslint-disable-line no-underscore-dangle\\n        return instance;\\n    }\\n\\n    /**\\n     * Creates a new or update existing record in the database, instantiates a {@link Model} and returns it.\\n     *\\n     * If you pass values for many-to-many fields, instances are created on the through\\n     * model as well.\\n     *\\n     * @param  {Object} userProps - the required {@link Model}'s properties.\\n     * @return {Model} a {@link Model} instance.\\n     */\\n    static upsert(userProps) {\\n        if (typeof this.session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to upsert a ${this.modelName} model instance without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].upsert\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n\\n        const { idAttribute } = this;\\n        if (userProps.hasOwnProperty(idAttribute)) {\\n            const id = userProps[idAttribute];\\n            if (this.idExists(id)) {\\n                const model = this.withId(id);\\n                model.update(userProps);\\n                return model;\\n            }\\n        }\\n\\n        return this.create(userProps);\\n    }\\n\\n    /**\\n     * Returns a {@link Model} instance for the object with id `id`.\\n     * Returns `null` if the model has no instance with id `id`.\\n     *\\n     * You can use {@link Model#idExists} to check for existence instead.\\n     *\\n     * @param  {*} id - the `id` of the object to get\\n     * @throws If object with id `id` doesn't exist\\n     * @return {Model|null} {@link Model} instance with id `id`\\n     */\\n    static withId(id) {\\n        return this.get({\\n            [this.idAttribute]: id,\\n        });\\n    }\\n\\n    /**\\n     * Returns a boolean indicating if an entity\\n     * with the id `id` exists in the state.\\n     *\\n     * @param  {*}  id - a value corresponding to the id attribute of the {@link Model} class.\\n     * @return {Boolean} a boolean indicating if entity with `id` exists in the state\\n     *\\n     * @since 0.11.0\\n     */\\n    static idExists(id) {\\n        return this.exists({\\n            [this.idAttribute]: id,\\n        });\\n    }\\n\\n    /**\\n     * Returns a boolean indicating if an entity\\n     * with the given props exists in the state.\\n     *\\n     * @param  {*}  props - a key-value that {@link Model} instances should have to be considered as existing.\\n     * @return {Boolean} a boolean indicating if entity with `props` exists in the state\\n     */\\n    static exists(lookupObj) {\\n        if (typeof this.session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to check if a ${this.modelName} model instance exists without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].exists\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n\\n        return Boolean(this._findDatabaseRows(lookupObj).length);\\n    }\\n\\n    /**\\n     * Gets the {@link Model} instance that matches properties in `lookupObj`.\\n     * Throws an error if {@link Model} if multiple records match\\n     * the properties.\\n     *\\n     * @param  {Object} lookupObj - the properties used to match a single entity.\\n     * @throws {Error} If more than one entity matches the properties in `lookupObj`.\\n     * @return {Model} a {@link Model} instance that matches the properties in `lookupObj`.\\n     */\\n    static get(lookupObj) {\\n        const ThisModel = this;\\n\\n        const rows = this._findDatabaseRows(lookupObj);\\n        if (rows.length === 0) {\\n            return null;\\n        }\\n        if (rows.length > 1) {\\n            throw new Error(\\n                `Expected to find a single row in \\\\`${this.modelName}.get\\\\`. Found ${rows.length}.`\\n            );\\n        }\\n\\n        return new ThisModel(rows[0]);\\n    }\\n\\n    /**\\n     * Gets the {@link Model} class or subclass constructor (the class that\\n     * instantiated this instance).\\n     *\\n     * @return {Model} The {@link Model} class or subclass constructor used to instantiate\\n     *                 this instance.\\n     */\\n    getClass() {\\n        return this.constructor;\\n    }\\n\\n    /**\\n     * Gets the id value of the current instance by looking up the id attribute.\\n     * @return {*} The id value of the current instance.\\n     */\\n    getId() {\\n        return this._fields[this.getClass().idAttribute];\\n    }\\n\\n    /**\\n     * Returns a reference to the plain JS object in the store.\\n     * It contains all the properties that you pass when creating the model,\\n     * except for primary keys of many-to-many relationships with a custom accessor.\\n     *\\n     * Make sure never to mutate this.\\n     *\\n     * @return {Object} a reference to the plain JS object in the store\\n     */\\n    get ref() {\\n        const ThisModel = this.getClass();\\n\\n        // eslint-disable-next-line no-underscore-dangle\\n        return ThisModel._findDatabaseRows({\\n            [ThisModel.idAttribute]: this.getId(),\\n        })[0];\\n    }\\n\\n    /**\\n     * Finds all rows in this model's table that match the given `lookupObj`.\\n     * If no `lookupObj` is passed, all rows in the model's table will be returned.\\n     *\\n     * @param  {*}  props - a key-value that {@link Model} instances should have to be considered as existing.\\n     * @return {Boolean} a boolean indicating if entity with `props` exists in the state\\n     * @private\\n     */\\n    static _findDatabaseRows(lookupObj) {\\n        const querySpec = {\\n            table: this.modelName,\\n        };\\n        if (lookupObj) {\\n            querySpec.clauses = [\\n                {\\n                    type: FILTER,\\n                    payload: lookupObj,\\n                },\\n            ];\\n        }\\n        return this.session.query(querySpec).rows;\\n    }\\n\\n    /**\\n     * Returns a string representation of the {@link Model} instance.\\n     *\\n     * @return {string} A string representation of this {@link Model} instance.\\n     */\\n    toString() {\\n        const ThisModel = this.getClass();\\n        const className = ThisModel.modelName;\\n        const fieldNames = Object.keys(ThisModel.fields);\\n        const fields = fieldNames\\n            .map(fieldName => {\\n                const field = ThisModel.fields[fieldName];\\n                if (field instanceof ManyToMany) {\\n                    const ids = this[fieldName]\\n                        .toModelArray()\\n                        .map(model => model.getId());\\n                    return `${fieldName}: [${ids.join(\\\", \\\")}]`;\\n                }\\n                const val = this._fields[fieldName];\\n                return `${fieldName}: ${val}`;\\n            })\\n            .join(\\\", \\\");\\n        return `${className}: {${fields}}`;\\n    }\\n\\n    /**\\n     * Returns a boolean indicating if `otherModel` equals this {@link Model} instance.\\n     * Equality is determined by shallow comparing their attributes.\\n     *\\n     * This equality is used when you call {@link Model#update}.\\n     * You can prevent model updates by returning `true` here.\\n     * However, a model will always be updated if its relationships are changed.\\n     *\\n     * @param  {Model} otherModel - a {@link Model} instance to compare\\n     * @return {Boolean} a boolean indicating if the {@link Model} instance's are equal.\\n     */\\n    equals(otherModel) {\\n        // eslint-disable-next-line no-underscore-dangle\\n        return objectShallowEquals(this._fields, otherModel._fields);\\n    }\\n\\n    /**\\n     * Updates a property name to given value for this {@link Model} instance.\\n     * The values are immediately committed to the database.\\n     *\\n     * @param {string} propertyName - name of the property to set\\n     * @param {*} value - value assigned to the property\\n     * @return {undefined}\\n     */\\n    set(propertyName, value) {\\n        this.update({\\n            [propertyName]: value,\\n        });\\n    }\\n\\n    /**\\n     * Assigns multiple fields and corresponding values to this {@link Model} instance.\\n     * The updates are immediately committed to the database.\\n     *\\n     * @param  {Object} userMergeObj - an object that will be merged with this instance.\\n     * @return {undefined}\\n     */\\n    update(userMergeObj) {\\n        const ThisModel = this.getClass();\\n        if (typeof ThisModel.session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to update a ${ThisModel.modelName} model instance without a session. `,\\n                    \\\"You cannot call `.update` on an instance that you did not receive from the database.\\\",\\n                ].join(\\\"\\\")\\n            );\\n        }\\n\\n        const mergeObj = { ...userMergeObj };\\n\\n        const { fields, virtualFields } = ThisModel;\\n\\n        const m2mRelations = {};\\n\\n        // If an array of entities or id's is supplied for a\\n        // many-to-many related field, clear the old relations\\n        // and add the new ones.\\n        // eslint-disable-next-line guard-for-in, no-restricted-syntax\\n        for (const mergeKey in mergeObj) {\\n            const isRealField = fields.hasOwnProperty(mergeKey);\\n\\n            if (isRealField) {\\n                const field = fields[mergeKey];\\n\\n                if (field instanceof ForeignKey || field instanceof OneToOne) {\\n                    // update one-one/fk relations\\n                    mergeObj[mergeKey] = normalizeEntity(mergeObj[mergeKey]);\\n                } else if (field instanceof ManyToMany) {\\n                    // field is forward relation\\n                    m2mRelations[mergeKey] = mergeObj[mergeKey];\\n\\n                    if (!field.as) {\\n                        /**\\n                         * The relationship does not have an accessor\\n                         * Discard the value from props as the field will be populated later with instances\\n                         * from the target models when refreshing the M2M relations.\\n                         * If the relationship does have an accessor (`as`) field then we do want to keep this\\n                         * original value in the props to expose the raw list of IDs from the instance.\\n                         */\\n                        delete mergeObj[mergeKey];\\n                    }\\n                }\\n            } else if (virtualFields.hasOwnProperty(mergeKey)) {\\n                const field = virtualFields[mergeKey];\\n                if (field instanceof ManyToMany) {\\n                    // field is backward relation\\n                    m2mRelations[mergeKey] = mergeObj[mergeKey];\\n                    delete mergeObj[mergeKey];\\n                }\\n            }\\n        }\\n\\n        const mergedFields = {\\n            ...this._fields,\\n            ...mergeObj,\\n        };\\n\\n        const updatedModel = new ThisModel(mergedFields);\\n        // only update fields if they have changed (referentially)\\n        if (!this.equals(updatedModel)) {\\n            this._initFields(mergedFields);\\n            ThisModel.session.applyUpdate({\\n                action: UPDATE,\\n                query: getByIdQuery(this),\\n                payload: mergeObj,\\n            });\\n        }\\n\\n        // update virtual fields\\n        this._refreshMany2Many(m2mRelations);\\n    }\\n\\n    /**\\n     * Updates {@link Model} instance attributes to reflect the\\n     * database state in the current session.\\n     * @return {undefined}\\n     */\\n    refreshFromState() {\\n        this._initFields(this.ref);\\n    }\\n\\n    /**\\n     * Deletes the record for this {@link Model} instance.\\n     * You'll still be able to access fields and values on the instance.\\n     *\\n     * @return {undefined}\\n     */\\n    delete() {\\n        const ThisModel = this.getClass();\\n        if (typeof ThisModel.session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to delete a ${ThisModel.modelName} model instance without a session. `,\\n                    \\\"You cannot call `.delete` on an instance that you did not receive from the database.\\\",\\n                ].join(\\\"\\\")\\n            );\\n        }\\n\\n        this._onDelete();\\n        ThisModel.session.applyUpdate({\\n            action: DELETE,\\n            query: getByIdQuery(this),\\n        });\\n    }\\n\\n    /**\\n     * Update many-many relations for model.\\n     * @param relations\\n     * @return undefined\\n     * @private\\n     */\\n    _refreshMany2Many(relations) {\\n        const ThisModel = this.getClass();\\n        const { fields, virtualFields, modelName } = ThisModel;\\n\\n        Object.keys(relations).forEach(name => {\\n            const reverse = !fields.hasOwnProperty(name);\\n            const field = virtualFields[name];\\n            const values = relations[name];\\n\\n            if (!Array.isArray(values)) {\\n                throw new TypeError(\\n                    `Failed to resolve many-to-many relationship: ${modelName}[${name}] must be an array (passed: ${values})`\\n                );\\n            }\\n\\n            const normalizedNewIds = values.map(normalizeEntity);\\n            const uniqueIds = [...new Set(normalizedNewIds)];\\n\\n            if (normalizedNewIds.length !== uniqueIds.length) {\\n                throw new Error(\\n                    `Found duplicate id(s) when passing \\\"${normalizedNewIds}\\\" to ${ThisModel.modelName}.${name} value`\\n                );\\n            }\\n\\n            const throughModelName =\\n                field.through || m2mName(ThisModel.modelName, name);\\n            const ThroughModel = ThisModel.session[throughModelName];\\n\\n            let fromField;\\n            let toField;\\n\\n            if (!reverse) {\\n                ({ from: fromField, to: toField } = field.throughFields);\\n            } else {\\n                ({ from: toField, to: fromField } = field.throughFields);\\n            }\\n\\n            const currentIds = ThroughModel.filter(\\n                through => through[fromField] === this[ThisModel.idAttribute]\\n            )\\n                .toRefArray()\\n                .map(ref => ref[toField]);\\n\\n            const diffActions = arrayDiffActions(currentIds, normalizedNewIds);\\n\\n            if (diffActions) {\\n                const { delete: idsToDelete, add: idsToAdd } = diffActions;\\n                if (idsToDelete.length > 0) {\\n                    this[field.as || name].remove(...idsToDelete);\\n                }\\n\\n                if (idsToAdd.length > 0) {\\n                    this[field.as || name].add(...idsToAdd);\\n                }\\n            }\\n        });\\n    }\\n\\n    /**\\n     * @return {undefined}\\n     * @private\\n     */\\n    _onDelete() {\\n        const { virtualFields } = this.getClass();\\n        // eslint-disable-next-line guard-for-in, no-restricted-syntax\\n        for (const key in virtualFields) {\\n            const field = virtualFields[key];\\n            if (field instanceof ManyToMany) {\\n                // Delete any many-to-many rows the entity is included in.\\n                const descriptorKey = field.as || key;\\n                this[descriptorKey].clear();\\n            } else if (field instanceof ForeignKey) {\\n                const relatedQs = this[key];\\n                if (relatedQs.exists()) {\\n                    relatedQs.update({ [field.relatedName]: null });\\n                }\\n            } else if (field instanceof OneToOne) {\\n                // Set null to any foreign keys or one to ones pointed to\\n                // this instance.\\n                if (this[key] !== null) {\\n                    this[key][field.relatedName] = null;\\n                }\\n            }\\n        }\\n    }\\n\\n    // DEPRECATED AND REMOVED METHODS\\n\\n    /**\\n     * Returns a boolean indicating if an entity\\n     * with the id `id` exists in the state.\\n     *\\n     * @param  {*}  id - a value corresponding to the id attribute of the {@link Model} class.\\n     * @return {Boolean} a boolean indicating if entity with `id` exists in the state\\n     * @deprecated Please use {@link Model.idExists} instead.\\n     */\\n    static hasId(id) {\\n        console.warn(\\n            \\\"`Model.hasId` has been deprecated. Please use `Model.idExists` instead.\\\"\\n        );\\n        return this.idExists(id);\\n    }\\n\\n    /**\\n     * @deprecated See the 0.9 migration guide on the GitHub repo.\\n     * @throws {Error} Due to deprecation.\\n     */\\n    getNextState() {\\n        throw new Error(\\n            \\\"`Model.prototype.getNextState` has been removed. See the 0.9 \\\" +\\n                \\\"migration guide on the GitHub repo.\\\"\\n        );\\n    }\\n};\\n\\nModel.fields = {\\n    id: attr(),\\n};\\nModel.virtualFields = {};\\nModel.querySetClass = QuerySet;\\n\\nexport default Model;\\n\",\"import ops from \\\"immutable-ops\\\";\\nimport filter from \\\"lodash/filter\\\";\\nimport orderBy from \\\"lodash/orderBy\\\";\\nimport reject from \\\"lodash/reject\\\";\\nimport sortBy from \\\"lodash/sortBy\\\";\\n\\nimport { EXCLUDE, FILTER, ORDER_BY } from \\\"../constants\\\";\\nimport { clauseFiltersByAttribute, clauseReducesResultSetSize } from \\\"../utils\\\";\\n\\nconst DEFAULT_TABLE_OPTIONS = {\\n    idAttribute: \\\"id\\\",\\n    arrName: \\\"items\\\",\\n    mapName: \\\"itemsById\\\",\\n    fields: {},\\n};\\n\\n/**\\n * @private\\n * @param {*} _currMax - the current max id\\n * @param {*} userPassedId - the new id passed to the create action\\n *\\n * Both may be undefined. The current max id in the case that this is the first Model\\n * being created, and the new id if the id was not explicitly passed to the\\n * database.\\n *\\n * @return {Array} the new max id and the id to use to create the new row\\n *\\n * If the id's are strings, the id must be passed explicitly every time.\\n * In this case, the current max id will remain `NaN` due to `Math.max`, but that's fine.\\n */\\nfunction idSequencer(_currMax, userPassedId) {\\n    let currMax = _currMax;\\n    let newMax;\\n    let newId;\\n\\n    if (currMax === undefined) {\\n        currMax = -1;\\n    }\\n\\n    if (userPassedId === undefined) {\\n        newMax = currMax + 1;\\n        newId = newMax;\\n    } else {\\n        newMax = Math.max(currMax + 1, userPassedId);\\n        newId = userPassedId;\\n    }\\n\\n    return [\\n        newMax, // new max id\\n        newId, // id to use for row creation\\n    ];\\n}\\n\\n/**\\n * Adapt order directions array to @{lodash.orderBy} API.\\n *\\n * @private\\n *\\n * @param {Array<Boolean|'asc'|'desc'>} orders? - an array of optional order query directions as provided to {@Link {QuerySet.orderBy}}\\n * @return {Array<'asc'|'desc'>|undefined} A normalized ordering array or undefined if none was provided.\\n */\\nfunction normalizeOrders(orders) {\\n    if (orders === undefined) {\\n        return undefined;\\n    }\\n    const convert = order => {\\n        if ([\\\"desc\\\", false].includes(order)) {\\n            return \\\"desc\\\";\\n        }\\n        return \\\"asc\\\";\\n    };\\n    return Array.isArray(orders) ? orders.map(convert) : convert(orders);\\n}\\n\\n/**\\n * Handles the underlying data structure for a {@link Model} class.\\n * @private\\n */\\nexport class Table {\\n    /**\\n     * Creates a new {@link Table} instance.\\n     * @param  {Object} userOpts - options to use.\\n     * @param  {string} [userOpts.idAttribute=id] - the id attribute of the entity.\\n     * @param  {string} [userOpts.arrName=items] - the state attribute where an array of\\n     *                                             entity id's are stored\\n     * @param  {string} [userOpts.mapName=itemsById] - the state attribute where the entity objects\\n     *                                                 are stored in a id to entity object\\n     *                                                 map.\\n     * @param  {string} [userOpts.fields={}] - mapping of field key to {@link Field} object\\n     */\\n    constructor(userOpts) {\\n        Object.assign(this, DEFAULT_TABLE_OPTIONS, userOpts);\\n    }\\n\\n    /**\\n     * Returns a reference to the object at index `id`\\n     * in state `branch`.\\n     *\\n     * @param  {Object} branch - the state\\n     * @param  {Number} id - the id of the object to get\\n     * @return {Object|undefined} A reference to the raw object in the state or\\n     *                            `undefined` if not found.\\n     */\\n    accessId(branch, id) {\\n        return branch[this.mapName][id];\\n    }\\n\\n    accessIds(branch, ids) {\\n        const map = branch[this.mapName];\\n        return ids.map(id => map[id]);\\n    }\\n\\n    idExists(branch, id) {\\n        return branch[this.mapName].hasOwnProperty(id);\\n    }\\n\\n    accessIdList(branch) {\\n        return branch[this.arrName];\\n    }\\n\\n    accessList(branch) {\\n        return this.accessIds(branch, this.accessIdList(branch));\\n    }\\n\\n    getMaxId(branch) {\\n        return this.getMeta(branch, \\\"maxId\\\");\\n    }\\n\\n    setMaxId(tx, branch, newMaxId) {\\n        return this.setMeta(tx, branch, \\\"maxId\\\", newMaxId);\\n    }\\n\\n    nextId(id) {\\n        return id + 1;\\n    }\\n\\n    /**\\n     * Returns the default state for the data structure.\\n     * @return {Object} The default state for this {@link ORM} instance's data structure\\n     */\\n    getEmptyState() {\\n        const pkIndex = {\\n            [this.arrName]: [],\\n            [this.mapName]: {},\\n        };\\n        const attrIndexes = Object.keys(this.fields)\\n            .filter(attr => attr !== this.idAttribute)\\n            .filter(attr => this.fields[attr].index)\\n            .reduce(\\n                (indexes, attr) => ({\\n                    ...indexes,\\n                    [attr]: {},\\n                }),\\n                {}\\n            );\\n        return {\\n            ...pkIndex,\\n            indexes: attrIndexes,\\n            meta: {},\\n        };\\n    }\\n\\n    setMeta(tx, branch, key, value) {\\n        const { batchToken, withMutations } = tx;\\n        if (withMutations) {\\n            const res = ops.mutable.setIn([\\\"meta\\\", key], value, branch);\\n            return res;\\n        }\\n\\n        return ops.batch.setIn(batchToken, [\\\"meta\\\", key], value, branch);\\n    }\\n\\n    getMeta(branch, key) {\\n        return branch.meta[key];\\n    }\\n\\n    query(branch, clauses) {\\n        if (clauses.length === 0) {\\n            return this.accessList(branch);\\n        }\\n\\n        const { idAttribute } = this;\\n\\n        const optimallyOrderedClauses = sortBy(clauses, clause => {\\n            if (clauseFiltersByAttribute(clause, idAttribute)) {\\n                return 1;\\n            }\\n\\n            if (clauseReducesResultSetSize(clause)) {\\n                return 2;\\n            }\\n\\n            return 3;\\n        });\\n\\n        const reducer = (rows, clause) => {\\n            const { type, payload } = clause;\\n            if (!rows) {\\n                /**\\n                 * First time this reducer is called during query.\\n                 * This is where we apply query optimizations.\\n                 */\\n                if (clauseFiltersByAttribute(clause, idAttribute)) {\\n                    /**\\n                     * Payload specified a primary key. Use PK index\\n                     * to look up the single row identified by the PK.\\n                     */\\n                    const id = payload[idAttribute];\\n                    const remainingPayload = Object.keys(payload).reduce(\\n                        (withoutPkAttr, filterAttr) => {\\n                            if (filterAttr !== idAttribute) {\\n                                withoutPkAttr[filterAttr] = payload[filterAttr];\\n                            }\\n                            return withoutPkAttr;\\n                        },\\n                        {}\\n                    );\\n                    const ids = this.idExists(branch, id) ? [id] : [];\\n                    if (Object.keys(remainingPayload).length) {\\n                        /**\\n                         * Payload has additional, non-PK columns.\\n                         * Filter accessed row by remaining payload (if one was found).\\n                         */\\n                        return reducer(this.accessIds(branch, ids), {\\n                            ...clause,\\n                            payload: remainingPayload,\\n                        });\\n                    }\\n                    /**\\n                     * No need to filter these rows any further.\\n                     * The primary key value satisfies this clause's conditions.\\n                     */\\n                    return this.accessIds(branch, ids);\\n                }\\n                if (type === FILTER && typeof payload === \\\"object\\\") {\\n                    const indexes = Object.entries(branch.indexes);\\n                    const accessedIndexes = [];\\n                    const indexAttrs = [];\\n                    indexes.forEach(([attr, index]) => {\\n                        if (clauseFiltersByAttribute(clause, attr)) {\\n                            /**\\n                             * Payload specified an indexed attribute. Use index\\n                             * to potentially decrease amount of accessed rows.\\n                             */\\n                            if (index.hasOwnProperty(payload[attr])) {\\n                                accessedIndexes.push(index[payload[attr]]);\\n                                indexAttrs.push(attr);\\n                            }\\n                        }\\n                    });\\n                    /**\\n                     * Calculate set of unique PK values corresponding to each\\n                     * foreign key's attribute value. Then retrieve all those rows.\\n                     */\\n                    if (accessedIndexes.length) {\\n                        const lastIndex = accessedIndexes.pop();\\n                        const indexedIds = accessedIndexes.reduce(\\n                            (result, index) => {\\n                                const indexSet = new Set(index);\\n                                return result.filter(\\n                                    Set.prototype.has,\\n                                    indexSet\\n                                );\\n                            },\\n                            lastIndex\\n                        );\\n                        const remainingPayload = Object.keys(payload).reduce(\\n                            (withoutIndexAttrs, filterAttr) => {\\n                                if (!indexAttrs.includes(filterAttr)) {\\n                                    withoutIndexAttrs[filterAttr] =\\n                                        payload[filterAttr];\\n                                }\\n                                return withoutIndexAttrs;\\n                            },\\n                            {}\\n                        );\\n                        if (Object.keys(remainingPayload).length) {\\n                            /**\\n                             * Payload has additional, non-indexed columns.\\n                             * Filter indexed rows by remaining payload (if any were found).\\n                             */\\n                            return reducer(this.accessIds(branch, indexedIds), {\\n                                ...clause,\\n                                payload: remainingPayload,\\n                            });\\n                        }\\n                        /**\\n                         * No need to filter these rows any further.\\n                         * The used indexes satisfy this clause's conditions.\\n                         */\\n                        return this.accessIds(branch, indexedIds);\\n                    }\\n                }\\n\\n                // Give up optimization: Retrieve all rows (full table scan).\\n                return reducer(this.accessList(branch), clause);\\n            }\\n\\n            switch (type) {\\n                case FILTER: {\\n                    return filter(rows, payload);\\n                }\\n                case EXCLUDE: {\\n                    return reject(rows, payload);\\n                }\\n                case ORDER_BY: {\\n                    const [iteratees, orders] = payload;\\n                    return orderBy(rows, iteratees, normalizeOrders(orders));\\n                }\\n                default:\\n                    return rows;\\n            }\\n        };\\n\\n        return optimallyOrderedClauses.reduce(reducer, undefined);\\n    }\\n\\n    /**\\n     * Returns the data structure including a new object `entry`\\n     * @param  {Object} tx - transaction info\\n     * @param  {Object} branch - the data structure state\\n     * @param  {Object} entry - the object to insert\\n     * @return {Object} an object with two keys: `state` and `created`.\\n     *                  `state` is the new table state and `created` is the\\n     *                  row that was created.\\n     */\\n    insert(tx, branch, entry) {\\n        const { batchToken, withMutations } = tx;\\n\\n        const hasId = entry.hasOwnProperty(this.idAttribute);\\n\\n        let workingState = branch;\\n\\n        // This will not affect string id's.\\n        const [newMaxId, id] = idSequencer(\\n            this.getMaxId(branch),\\n            entry[this.idAttribute]\\n        );\\n        workingState = this.setMaxId(tx, branch, newMaxId);\\n\\n        const finalEntry = hasId\\n            ? entry\\n            : ops.batch.set(batchToken, this.idAttribute, id, entry);\\n\\n        const indexesToAppendTo = Object.keys(workingState.indexes)\\n            .filter(\\n                fkAttr => entry.hasOwnProperty(fkAttr) && entry[fkAttr] !== null\\n            )\\n            .map(fkAttr => [fkAttr, entry[fkAttr]]);\\n\\n        if (withMutations) {\\n            ops.mutable.push(id, workingState[this.arrName]);\\n            ops.mutable.set(id, finalEntry, workingState[this.mapName]);\\n            // add id to indexes\\n            indexesToAppendTo.forEach(([attr, value]) => {\\n                const attrIndex = workingState.indexes[attr];\\n                if (attrIndex.hasOwnProperty(value)) {\\n                    ops.mutable.push(id, attrIndex[value]);\\n                } else {\\n                    ops.mutable.set(value, [id], attrIndex);\\n                }\\n            });\\n            return {\\n                state: workingState,\\n                created: finalEntry,\\n            };\\n        }\\n\\n        const nextIndexes = ops.batch.merge(\\n            batchToken,\\n            indexesToAppendTo.reduce(\\n                (indexMap, [attr, value]) => {\\n                    indexMap[attr] = ops.batch.merge(\\n                        batchToken,\\n                        {\\n                            [value]: ops.batch.push(\\n                                batchToken,\\n                                id,\\n                                indexMap[attr][value] || []\\n                            ),\\n                        },\\n                        indexMap[attr]\\n                    );\\n                    return indexMap;\\n                },\\n                { ...workingState.indexes }\\n            ),\\n            workingState.indexes\\n        );\\n\\n        const nextState = ops.batch.merge(\\n            batchToken,\\n            {\\n                [this.arrName]: ops.batch.push(\\n                    batchToken,\\n                    id,\\n                    workingState[this.arrName]\\n                ),\\n                [this.mapName]: ops.batch.merge(\\n                    batchToken,\\n                    {\\n                        [id]: finalEntry,\\n                    },\\n                    workingState[this.mapName]\\n                ),\\n                indexes: nextIndexes,\\n            },\\n            workingState\\n        );\\n\\n        return {\\n            state: nextState,\\n            created: finalEntry,\\n        };\\n    }\\n\\n    /**\\n     * Returns the data structure with objects where `rows`\\n     * are merged with `mergeObj`.\\n     *\\n     * @param  {Object} tx - transaction info\\n     * @param  {Object} branch - the data structure state\\n     * @param  {Object[]} rows - rows to update\\n     * @param  {Object} mergeObj - The object to merge with each row.\\n     * @return {Object}\\n     */\\n    update(tx, branch, rows, mergeObj) {\\n        const { batchToken, withMutations } = tx;\\n\\n        const mergeObjInto = row => {\\n            const merge = withMutations\\n                ? ops.mutable.merge\\n                : ops.batch.merge(batchToken);\\n            return merge(mergeObj, row);\\n        };\\n\\n        const set = withMutations ? ops.mutable.set : ops.batch.set(batchToken);\\n\\n        const indexedAttrs = Object.keys(branch.indexes).filter(attr =>\\n            mergeObj.hasOwnProperty(attr)\\n        );\\n        const indexIdsToAdd = [];\\n        const indexIdsToDelete = [];\\n\\n        const nextMap = rows.reduce((map, row) => {\\n            const prevAttrValues = indexedAttrs.reduce(\\n                (valueMap, attr) => ({\\n                    ...valueMap,\\n                    [attr]: row[attr],\\n                }),\\n                {}\\n            );\\n            const result = mergeObjInto(row);\\n            const nextAttrValues = indexedAttrs.reduce(\\n                (valueMap, attr) => ({\\n                    ...valueMap,\\n                    [attr]: result[attr],\\n                }),\\n                {}\\n            );\\n            const id = result[this.idAttribute];\\n            const nextRow = set(id, result, map);\\n            indexedAttrs.forEach(attr => {\\n                const { [attr]: prevValue } = prevAttrValues;\\n                const { [attr]: nextValue } = nextAttrValues;\\n                if (prevValue === nextValue) {\\n                    // attribute has not changed, no need to update any index\\n                    return;\\n                }\\n                if (prevValue !== null && typeof prevValue !== \\\"undefined\\\") {\\n                    // remove id from attribute's index for its old value\\n                    indexIdsToDelete.push([attr, prevValue, id]);\\n                }\\n                if (nextValue !== null) {\\n                    // add id to attribute's index for its new value\\n                    indexIdsToAdd.push([attr, nextValue, id]);\\n                }\\n            });\\n            return nextRow;\\n        }, branch[this.mapName]);\\n\\n        let nextIndexes = branch.indexes;\\n        if (withMutations) {\\n            indexIdsToDelete.forEach(([attr, value, id]) => {\\n                const arr = nextIndexes[attr][value];\\n                const idx = arr.indexOf(id);\\n                ops.mutable.splice(idx, 1, [], arr);\\n            });\\n            indexIdsToAdd.forEach(([attr, value, id]) => {\\n                ops.mutable.push(id, nextIndexes[attr][value]);\\n            });\\n        } else {\\n            if (indexIdsToAdd.length) {\\n                nextIndexes = ops.batch.merge(\\n                    batchToken,\\n                    indexIdsToAdd.reduce(\\n                        (indexMap, [attr, value, id]) => {\\n                            indexMap[attr] = ops.batch.merge(\\n                                batchToken,\\n                                {\\n                                    [value]: ops.batch.push(\\n                                        batchToken,\\n                                        id,\\n                                        indexMap[attr][value] || []\\n                                    ),\\n                                },\\n                                indexMap[attr]\\n                            );\\n                            return indexMap;\\n                        },\\n                        { ...nextIndexes }\\n                    ),\\n                    nextIndexes\\n                );\\n            }\\n            if (indexIdsToDelete.length) {\\n                nextIndexes = ops.batch.merge(\\n                    batchToken,\\n                    indexIdsToDelete.reduce(\\n                        (indexMap, [attr, value, id]) => {\\n                            indexMap[attr] = ops.batch.merge(\\n                                batchToken,\\n                                {\\n                                    [value]: ops.batch.filter(\\n                                        batchToken,\\n                                        rowId => rowId !== id,\\n                                        indexMap[attr][value]\\n                                    ),\\n                                },\\n                                indexMap[attr]\\n                            );\\n                            return indexMap;\\n                        },\\n                        { ...nextIndexes }\\n                    ),\\n                    nextIndexes\\n                );\\n            }\\n        }\\n\\n        return ops.batch.merge(\\n            batchToken,\\n            {\\n                [this.mapName]: nextMap,\\n                indexes: nextIndexes,\\n            },\\n            branch\\n        );\\n    }\\n\\n    /**\\n     * Returns the data structure without rows `rows`.\\n     * @param  {Object} tx - transaction info\\n     * @param  {Object} branch - the data structure state\\n     * @param  {Object[]} rows - rows to update\\n     * @return {Object} the data structure without ids in `idsToDelete`.\\n     */\\n    delete(tx, branch, rows) {\\n        const { batchToken, withMutations } = tx;\\n\\n        const { arrName, mapName } = this;\\n        const arr = branch[arrName];\\n\\n        const idsToDelete = rows.map(row => row[this.idAttribute]);\\n        if (withMutations) {\\n            idsToDelete.forEach(id => {\\n                const idx = arr.indexOf(id);\\n                ops.mutable.splice(idx, 1, [], arr);\\n                ops.mutable.omit(id, branch[mapName]);\\n            });\\n            // delete ids from all indexes\\n            Object.values(branch.indexes).forEach(attrIndex =>\\n                Object.values(attrIndex).forEach(valueIndex =>\\n                    idsToDelete.forEach(id => {\\n                        const idx = valueIndex.indexOf(id);\\n                        if (idx !== -1) {\\n                            ops.mutable.splice(idx, 1, [], valueIndex);\\n                        }\\n                    })\\n                )\\n            );\\n            return branch;\\n        }\\n\\n        const nextIndexes = ops.batch.merge(\\n            batchToken,\\n            Object.entries(branch.indexes).reduce(\\n                (indexMap, [attr, attrIndex]) => {\\n                    indexMap[attr] = ops.batch.merge(\\n                        batchToken,\\n                        Object.entries(attrIndex).reduce(\\n                            (attrIndexMap, [value, valueIndex]) => {\\n                                attrIndexMap[value] = ops.batch.filter(\\n                                    batchToken,\\n                                    id => !idsToDelete.includes(id),\\n                                    valueIndex\\n                                );\\n                                return attrIndexMap;\\n                            },\\n                            { ...indexMap[attr] }\\n                        ),\\n                        indexMap[attr]\\n                    );\\n                    return indexMap;\\n                },\\n                { ...branch.indexes }\\n            ),\\n            branch.indexes\\n        );\\n\\n        return ops.batch.merge(\\n            batchToken,\\n            {\\n                [arrName]: ops.batch.filter(\\n                    batchToken,\\n                    id => !idsToDelete.includes(id),\\n                    branch[arrName]\\n                ),\\n                [mapName]: ops.batch.omit(\\n                    batchToken,\\n                    idsToDelete,\\n                    branch[mapName]\\n                ),\\n                indexes: ops.batch.merge(\\n                    batchToken,\\n                    nextIndexes,\\n                    branch.indexes\\n                ),\\n            },\\n            branch\\n        );\\n    }\\n}\\n\\nexport default Table;\\n\",\"import ops from \\\"immutable-ops\\\";\\n\\nimport { CREATE, UPDATE, DELETE, SUCCESS, STATE_FLAG } from \\\"../constants\\\";\\n\\nimport Table from \\\"./Table\\\";\\n\\nconst BASE_EMPTY_STATE = {};\\nObject.defineProperty(BASE_EMPTY_STATE, STATE_FLAG, {\\n    enumerable: true,\\n    value: true,\\n});\\n\\n/** @private */\\nfunction replaceTableState(tableName, newTableState, tx, state) {\\n    const { batchToken, withMutations } = tx;\\n\\n    if (withMutations) {\\n        state[tableName] = newTableState;\\n        return state;\\n    }\\n\\n    return ops.batch.set(batchToken, tableName, newTableState, state);\\n}\\n\\n/** @private */\\nfunction query(tables, querySpec, state) {\\n    const { table: tableName, clauses } = querySpec;\\n    const table = tables[tableName];\\n    const rows = table.query(state[tableName], clauses);\\n    return {\\n        rows,\\n    };\\n}\\n\\n/** @private */\\nfunction update(tables, updateSpec, tx, state) {\\n    const { action, payload } = updateSpec;\\n\\n    let tableName;\\n    let nextTableState;\\n    let resultPayload;\\n\\n    if (action === CREATE) {\\n        ({ table: tableName } = updateSpec);\\n        const table = tables[tableName];\\n        const currTableState = state[tableName];\\n        const result = table.insert(tx, currTableState, payload);\\n        nextTableState = result.state;\\n        resultPayload = result.created;\\n    } else {\\n        const { query: querySpec } = updateSpec;\\n        ({ table: tableName } = querySpec);\\n        const { rows } = query(tables, querySpec, state);\\n\\n        const table = tables[tableName];\\n        const currTableState = state[tableName];\\n\\n        if (action === UPDATE) {\\n            nextTableState = table.update(tx, currTableState, rows, payload);\\n            // return updated rows\\n            resultPayload = query(tables, querySpec, state).rows;\\n        } else if (action === DELETE) {\\n            nextTableState = table.delete(tx, currTableState, rows);\\n            // return original rows that we just deleted\\n            resultPayload = rows;\\n        } else {\\n            throw new Error(`Database received unknown update type: ${action}`);\\n        }\\n    }\\n\\n    const nextDBState = replaceTableState(tableName, nextTableState, tx, state);\\n    return {\\n        status: SUCCESS,\\n        state: nextDBState,\\n        payload: resultPayload,\\n    };\\n}\\n\\n/**\\n * @memberof db\\n * @param {Object} schemaSpec\\n * @return Object database\\n */\\nexport function createDatabase(schemaSpec) {\\n    const { tables: tableSpecs } = schemaSpec;\\n    const tables = Object.entries(tableSpecs).reduce(\\n        (map, [tableName, tableSpec]) => ({\\n            ...map,\\n            [tableName]: new Table(tableSpec),\\n        }),\\n        {}\\n    );\\n\\n    const getEmptyState = () =>\\n        Object.entries(tables).reduce(\\n            (map, [tableName, table]) => ({\\n                ...map,\\n                [tableName]: table.getEmptyState(),\\n            }),\\n            BASE_EMPTY_STATE\\n        );\\n\\n    return {\\n        getEmptyState,\\n        query: query.bind(null, tables),\\n        update: update.bind(null, tables),\\n        // Used to inspect the schema.\\n        describe: tableName => tables[tableName],\\n    };\\n}\\n\\nexport default createDatabase;\\n\",\"import { ID_ARG_KEY_SELECTOR } from \\\"../constants\\\";\\n\\nexport default class SelectorSpec {\\n    constructor({ parent, orm }) {\\n        this._parent = parent;\\n        this._orm = orm;\\n        this.keySelector = ID_ARG_KEY_SELECTOR;\\n    }\\n\\n    get cachePath() {\\n        const basePath = this._parent ? this._parent.cachePath : [];\\n        return [...basePath, this.key];\\n    }\\n\\n    get orm() {\\n        return this._orm;\\n    }\\n\\n    get parent() {\\n        return this._parent;\\n    }\\n}\\n\",\"import SelectorSpec from \\\"./SelectorSpec\\\";\\n\\nexport default class ModelBasedSelectorSpec extends SelectorSpec {\\n    constructor({ model, ...other }) {\\n        super(other);\\n        this._model = model;\\n    }\\n\\n    get resultFunc() {\\n        return (session, idArg, ...other) => {\\n            const { [this._model.modelName]: ModelClass } = session;\\n            if (typeof idArg === \\\"undefined\\\") {\\n                return ModelClass.all()\\n                    .toModelArray()\\n                    .map(instance =>\\n                        this.valueForInstance(instance, session, ...other)\\n                    );\\n            }\\n            if (Array.isArray(idArg)) {\\n                return idArg.map(id =>\\n                    this.valueForInstance(\\n                        ModelClass.withId(id),\\n                        session,\\n                        ...other\\n                    )\\n                );\\n            }\\n            return this.valueForInstance(\\n                ModelClass.withId(idArg),\\n                session,\\n                ...other\\n            );\\n        };\\n    }\\n\\n    get model() {\\n        return this._model;\\n    }\\n}\\n\",\"export default function idArgSelector(state, idArg) {\\n    return idArg;\\n}\\n\",\"import ModelBasedSelectorSpec from \\\"./ModelBasedSelectorSpec\\\";\\nimport idArgSelector from \\\"./idArgSelector\\\";\\n\\nexport default class MapSelectorSpec extends ModelBasedSelectorSpec {\\n    constructor({ field, selector, ...other }) {\\n        super(other);\\n        this._field = field;\\n        this._selector = selector;\\n    }\\n\\n    createResultFunc(parentSelector) {\\n        const { idAttribute } = this._parent.toModel;\\n        return (state, ...other) => {\\n            /**\\n             * The parent selector should return a ref array\\n             * in case of a single ID being passed.\\n             * Otherwise it should return an array of ref arrays.\\n             */\\n            const parentResult = parentSelector(state, ...other);\\n            const idArg = idArgSelector(state, ...other);\\n            const single = refArray => {\\n                if (refArray === null) {\\n                    // an intermediate field could not be resolved\\n                    return null;\\n                }\\n                return refArray.map(ref =>\\n                    this._selector(state, ref[idAttribute])\\n                );\\n            };\\n            if (typeof idArg === \\\"undefined\\\" || Array.isArray(idArg)) {\\n                return parentResult.map(single);\\n            }\\n            return single(parentResult);\\n        };\\n    }\\n\\n    get selector() {\\n        return this._selector;\\n    }\\n\\n    set selector(selector) {\\n        this._selector = selector;\\n    }\\n\\n    get key() {\\n        return this._selector;\\n    }\\n}\\n\",\"import SelectorSpec from \\\"./SelectorSpec\\\";\\nimport idArgSelector from \\\"./idArgSelector\\\";\\n\\nexport default class ModelSelectorSpec extends SelectorSpec {\\n    constructor({ model, ...other }) {\\n        super(other);\\n        this._model = model;\\n    }\\n\\n    get key() {\\n        return this._model.modelName;\\n    }\\n\\n    get dependencies() {\\n        return [this._orm, idArgSelector];\\n    }\\n\\n    get resultFunc() {\\n        return ({ [this._model.modelName]: ModelClass }, idArg) => {\\n            if (typeof idArg === \\\"undefined\\\") {\\n                return ModelClass.all().toRefArray();\\n            }\\n            if (Array.isArray(idArg)) {\\n                return idArg.map(id => {\\n                    const instance = ModelClass.withId(id);\\n                    return instance ? instance.ref : null;\\n                });\\n            }\\n            const instance = ModelClass.withId(idArg);\\n            return instance ? instance.ref : null;\\n        };\\n    }\\n\\n    get model() {\\n        return this._model;\\n    }\\n}\\n\",\"import MapSelectorSpec from \\\"./MapSelectorSpec\\\";\\nimport ModelSelectorSpec from \\\"./ModelSelectorSpec\\\";\\nimport ModelBasedSelectorSpec from \\\"./ModelBasedSelectorSpec\\\";\\nimport idArgSelector from \\\"./idArgSelector\\\";\\n\\nimport QuerySet from \\\"../QuerySet\\\";\\nimport Model from \\\"../Model\\\";\\n\\nimport ForeignKey from \\\"../fields/ForeignKey\\\";\\nimport ManyToMany from \\\"../fields/ManyToMany\\\";\\n\\nexport default class FieldSelectorSpec extends ModelBasedSelectorSpec {\\n    constructor({ field, fieldModel, accessorName, isVirtual, ...other }) {\\n        super(other);\\n        this._field = field;\\n        this._fieldModel = fieldModel;\\n        this._accessorName = accessorName;\\n        this._isVirtual = isVirtual;\\n    }\\n\\n    get key() {\\n        return this._accessorName;\\n    }\\n\\n    get dependencies() {\\n        return [this._orm, idArgSelector];\\n    }\\n\\n    valueForInstance(instance, session) {\\n        if (!instance) {\\n            return null;\\n        }\\n        let value;\\n        if (this._parent instanceof ModelSelectorSpec) {\\n            /* orm.Model.field */\\n            value = instance[this._accessorName];\\n        } else {\\n            /* orm.Model.field1.field2..fieldN.field */\\n            const { [this._parent.toModelName]: ParentToModel } = session;\\n            const parentRef = this._parent.valueForInstance(instance, session);\\n            const parentInstance = parentRef\\n                ? new ParentToModel(parentRef)\\n                : null;\\n            value = parentInstance ? parentInstance[this._accessorName] : null;\\n        }\\n        if (value instanceof Model) {\\n            return value.ref;\\n        }\\n        if (value instanceof QuerySet) {\\n            return value.toRefArray();\\n        }\\n        return value;\\n    }\\n\\n    map(selector) {\\n        if (selector instanceof ModelSelectorSpec) {\\n            if (this.toModelName === selector.model.modelName) {\\n                throw new Error(\\n                    `Cannot select models in a \\\\`map()\\\\` call. If you just want the \\\\`${this._accessorName}\\\\` as a ref array then you can simply drop the \\\\`map()\\\\`. Otherwise make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`\\n                );\\n            } else {\\n                throw new Error(\\n                    `Cannot select \\\\`${selector.model.modelName}\\\\` models in this \\\\`map()\\\\` call. Make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`\\n                );\\n            }\\n        } else if (\\n            selector instanceof FieldSelectorSpec ||\\n            selector instanceof MapSelectorSpec\\n        ) {\\n            if (this.toModelName !== selector.model.modelName) {\\n                throw new Error(\\n                    `Cannot select fields of the \\\\`${selector.model.modelName}\\\\` model in this \\\\`map()\\\\` call. Make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`\\n                );\\n            }\\n        } else if (\\n            !selector ||\\n            typeof selector !== \\\"function\\\" ||\\n            !selector.recomputations\\n        ) {\\n            throw new Error(\\n                `\\\\`map()\\\\` requires a selector as an input. Received: ${JSON.stringify(\\n                    selector\\n                )} of type ${typeof selector}`\\n            );\\n        }\\n        if (\\n            !(this._field instanceof ForeignKey) &&\\n            !(this._field instanceof ManyToMany)\\n        ) {\\n            throw new Error(\\\"Cannot map selectors for non-collection fields\\\");\\n        }\\n        return new MapSelectorSpec({\\n            parent: this,\\n            model: this._model,\\n            orm: this._orm,\\n            field: this._field,\\n            selector,\\n        });\\n    }\\n\\n    get toModelName() {\\n        return this._field.toModelName === \\\"this\\\"\\n            ? this._fieldModel.modelName\\n            : this._field.toModelName;\\n    }\\n\\n    get toModel() {\\n        const db = this._orm.getDatabase();\\n        return db.describe(this.toModelName);\\n    }\\n}\\n\",\"import ForeignKey from \\\"../fields/ForeignKey\\\";\\nimport ManyToMany from \\\"../fields/ManyToMany\\\";\\nimport RelationalField from \\\"../fields/RelationalField\\\";\\n\\nimport FieldSelectorSpec from \\\"./FieldSelectorSpec\\\";\\nimport ModelSelectorSpec from \\\"./ModelSelectorSpec\\\";\\n\\n/**\\n * @module selectors\\n * @private\\n */\\n\\nexport function createFieldSelectorSpec({\\n    parent,\\n    model,\\n    field,\\n    fieldModel,\\n    accessorName,\\n    orm,\\n    isVirtual,\\n}) {\\n    const fieldSelectorSpec = new FieldSelectorSpec({\\n        parent,\\n        model,\\n        field,\\n        fieldModel,\\n        accessorName,\\n        orm,\\n        isVirtual,\\n    });\\n    /* Do not even try to create field selectors below attributes. */\\n    if (!(field instanceof RelationalField)) {\\n        // \\\"orm.Author.name.publisher\\\" would be nonsense\\n        return fieldSelectorSpec;\\n    }\\n    /* Prevent field selectors below collections. */\\n    if (parent instanceof FieldSelectorSpec) {\\n        /* eslint-disable no-underscore-dangle */\\n        if (\\n            // \\\"orm.Author.books.publisher\\\" would be nonsense\\n            (parent._field instanceof ForeignKey && parent._isVirtual) ||\\n            // \\\"orm.Genre.books.publisher\\\" would be nonsense\\n            parent._field instanceof ManyToMany\\n        ) {\\n            throw new Error(\\n                `Cannot create a selector for \\\\`${parent._accessorName}.${accessorName}\\\\` because \\\\`${parent._accessorName}\\\\` is a collection field.`\\n            );\\n        }\\n    }\\n    const { toModelName } = field;\\n    const toModel = orm.get(\\n        toModelName === \\\"this\\\" ? model.modelName : toModelName\\n    );\\n    Object.entries(toModel.fields).forEach(\\n        ([relatedFieldName, relatedField]) => {\\n            const fieldAccessorName = relatedField.as || relatedFieldName;\\n            Object.defineProperty(fieldSelectorSpec, fieldAccessorName, {\\n                get: () =>\\n                    createFieldSelectorSpec({\\n                        parent: fieldSelectorSpec,\\n                        model,\\n                        fieldModel: toModel,\\n                        field: relatedField,\\n                        accessorName: fieldAccessorName,\\n                        orm,\\n                        isVirtual: false,\\n                    }),\\n            });\\n        }\\n    );\\n    Object.entries(toModel.virtualFields).forEach(\\n        ([relatedFieldName, relatedField]) => {\\n            const fieldAccessorName = relatedField.as || relatedFieldName;\\n            if (fieldSelectorSpec.hasOwnProperty(fieldAccessorName)) {\\n                return;\\n            }\\n            Object.defineProperty(fieldSelectorSpec, fieldAccessorName, {\\n                get: () =>\\n                    createFieldSelectorSpec({\\n                        parent: fieldSelectorSpec,\\n                        model,\\n                        fieldModel: toModel,\\n                        field: relatedField,\\n                        accessorName: fieldAccessorName,\\n                        orm,\\n                        isVirtual: true,\\n                    }),\\n            });\\n        }\\n    );\\n    return fieldSelectorSpec;\\n}\\n\\nexport function createModelSelectorSpec({ model, orm }) {\\n    const modelSelectorSpec = new ModelSelectorSpec({\\n        parent: null,\\n        orm,\\n        model,\\n    });\\n\\n    Object.entries(model.fields).forEach(([fieldName, field]) => {\\n        const fieldAccessorName = field.as || fieldName;\\n        Object.defineProperty(modelSelectorSpec, fieldAccessorName, {\\n            get: () =>\\n                createFieldSelectorSpec({\\n                    parent: modelSelectorSpec,\\n                    model,\\n                    fieldModel: model,\\n                    field,\\n                    accessorName: fieldAccessorName,\\n                    orm,\\n                    isVirtual: false,\\n                }),\\n        });\\n    });\\n\\n    Object.entries(model.virtualFields).forEach(([fieldName, field]) => {\\n        const fieldAccessorName = field.as || fieldName;\\n        if (modelSelectorSpec.hasOwnProperty(fieldAccessorName)) {\\n            return;\\n        }\\n        Object.defineProperty(modelSelectorSpec, fieldAccessorName, {\\n            get: () =>\\n                createFieldSelectorSpec({\\n                    parent: modelSelectorSpec,\\n                    model,\\n                    fieldModel: model,\\n                    field,\\n                    accessorName: fieldAccessorName,\\n                    orm,\\n                    isVirtual: true,\\n                }),\\n        });\\n    });\\n\\n    return modelSelectorSpec;\\n}\\n\",\"/* eslint-disable max-classes-per-file */\\nimport Session from \\\"./Session\\\";\\nimport Model from \\\"./Model\\\";\\nimport { createDatabase as defaultCreateDatabase } from \\\"./db\\\";\\nimport { attr } from \\\"./fields\\\";\\nimport Field from \\\"./fields/Field\\\";\\nimport ForeignKey from \\\"./fields/ForeignKey\\\";\\nimport ManyToMany from \\\"./fields/ManyToMany\\\";\\n\\nimport { createModelSelectorSpec } from \\\"./selectors\\\";\\n\\nimport {\\n    m2mName,\\n    attachQuerySetMethods,\\n    m2mToFieldName,\\n    m2mFromFieldName,\\n    warnDeprecated,\\n} from \\\"./utils\\\";\\n\\nconst ORM_DEFAULTS = {\\n    createDatabase: defaultCreateDatabase,\\n};\\n\\nconst RESERVED_TABLE_OPTIONS = [\\\"indexes\\\", \\\"meta\\\"];\\nconst isReservedTableOption = word => RESERVED_TABLE_OPTIONS.includes(word);\\n\\n/**\\n * ORM - the Object Relational Mapper.\\n *\\n * Use instances of this class to:\\n *\\n * - Register your {@link Model} classes using {@link ORM#register}\\n * - Get the empty state for the underlying database with {@link ORM#getEmptyState}\\n * - Start an immutable database session with {@link ORM#session}\\n * - Start a mutating database session with {@link ORM#mutableSession}\\n *\\n * Internally, this class handles generating a schema specification from models\\n * to the database.\\n */\\nclass ORM {\\n    /**\\n     * Creates a new ORM instance.\\n     *\\n     * @param {Object} [opts]\\n     * @param {Function} [opts.stateSelector] - function that given a Redux state tree\\n     *                                          will return the ORM state's subtree,\\n     *                                          e.g. `state => state.orm`\\n     *                                          (necessary if you want to use selectors)\\n     * @param {Function} [opts.createDatabase] - function that creates a database\\n     */\\n    constructor(opts) {\\n        const { createDatabase } = { ...ORM_DEFAULTS, ...(opts || {}) };\\n        this.createDatabase = createDatabase;\\n        this.registry = [];\\n        this.implicitThroughModels = [];\\n        this.installedFields = {};\\n        this.stateSelector = opts ? opts.stateSelector : null;\\n    }\\n\\n    /**\\n     * Registers a {@link Model} class to the ORM.\\n     *\\n     * If the model has declared any ManyToMany fields, their\\n     * through models will be generated and registered with\\n     * this call, unless a custom through model has been specified.\\n     *\\n     * @param  {...Model} models - a {@link Model} class to register\\n     * @return {undefined}\\n     */\\n    register(...models) {\\n        models.forEach(model => {\\n            if (model.modelName === undefined) {\\n                throw new Error(\\n                    \\\"A model was passed that doesn't have a modelName set\\\"\\n                );\\n            }\\n\\n            model.invalidateClassCache();\\n\\n            this.registerManyToManyModelsFor(model);\\n            this.registry.push(model);\\n\\n            Object.defineProperty(this, model.modelName, {\\n                get: () => {\\n                    // make sure virtualFields are set up\\n                    this._setupModelPrototypes(this.registry);\\n\\n                    return createModelSelectorSpec({\\n                        model,\\n                        orm: this,\\n                    });\\n                },\\n            });\\n        });\\n    }\\n\\n    registerManyToManyModelsFor(model) {\\n        const { fields } = model;\\n        const thisModelName = model.modelName;\\n\\n        Object.entries(fields).forEach(([fieldName, fieldInstance]) => {\\n            if (!(fieldInstance instanceof ManyToMany)) {\\n                return;\\n            }\\n\\n            let toModelName;\\n            if (fieldInstance.toModelName === \\\"this\\\") {\\n                toModelName = thisModelName;\\n            } else {\\n                toModelName = fieldInstance.toModelName; // eslint-disable-line prefer-destructuring\\n            }\\n\\n            const selfReferencing = thisModelName === toModelName;\\n            const fromFieldName = m2mFromFieldName(thisModelName);\\n            const toFieldName = m2mToFieldName(toModelName);\\n\\n            if (fieldInstance.through) {\\n                if (selfReferencing && !fieldInstance.throughFields) {\\n                    throw new Error(\\n                        \\\"Self-referencing many-to-many relationship at \\\" +\\n                            `\\\"${thisModelName}.${fieldName}\\\" using custom ` +\\n                            `model \\\"${fieldInstance.through}\\\" has no ` +\\n                            \\\"throughFields key. Cannot determine which \\\" +\\n                            \\\"fields reference the instances partaking \\\" +\\n                            \\\"in the relationship.\\\"\\n                    );\\n                }\\n            } else {\\n                const Through = class ThroughModel extends Model {};\\n\\n                Through.modelName = m2mName(thisModelName, fieldName);\\n\\n                const PlainForeignKey = class PlainForeignKey extends ForeignKey {\\n                    get installsBackwardsVirtualField() {\\n                        return false;\\n                    }\\n\\n                    get installsBackwardsDescriptor() {\\n                        return false;\\n                    }\\n                };\\n                const ForeignKeyClass = selfReferencing\\n                    ? PlainForeignKey\\n                    : ForeignKey;\\n                Through.fields = {\\n                    id: attr(),\\n                    [fromFieldName]: new ForeignKeyClass(thisModelName),\\n                    [toFieldName]: new ForeignKeyClass(toModelName),\\n                };\\n\\n                Through.invalidateClassCache();\\n                this.implicitThroughModels.push(Through);\\n            }\\n        });\\n    }\\n\\n    /**\\n     * Gets a {@link Model} class by its name from the registry.\\n     * @param  {string} modelName - the name of the {@link Model} class to get\\n     * @throws If {@link Model} class is not found.\\n     * @return {Model} the {@link Model} class, if found\\n     */\\n    get(modelName) {\\n        const allModels = this.registry.concat(this.implicitThroughModels);\\n        const found = Object.values(allModels).find(\\n            model => model.modelName === modelName\\n        );\\n\\n        if (typeof found === \\\"undefined\\\") {\\n            throw new Error(`Did not find model ${modelName} from registry.`);\\n        }\\n        return found;\\n    }\\n\\n    getModelClasses() {\\n        this._setupModelPrototypes(this.registry);\\n        this._setupModelPrototypes(this.implicitThroughModels);\\n        return this.registry.concat(this.implicitThroughModels);\\n    }\\n\\n    generateSchemaSpec() {\\n        const models = this.getModelClasses();\\n        const tables = models.reduce((spec, modelClass) => {\\n            const tableName = modelClass.modelName;\\n            const tableSpec = modelClass.tableOptions();\\n            Object.keys(tableSpec)\\n                .filter(isReservedTableOption)\\n                .forEach(key => {\\n                    throw new Error(\\n                        `Reserved keyword \\\\`${key}\\\\` used in ${tableName}.options.`\\n                    );\\n                });\\n            spec[tableName] = {\\n                fields: { ...modelClass.fields },\\n                ...tableSpec,\\n            };\\n            return spec;\\n        }, {});\\n        return { tables };\\n    }\\n\\n    getDatabase() {\\n        if (!this.db) {\\n            this.db = this.createDatabase(this.generateSchemaSpec());\\n        }\\n        return this.db;\\n    }\\n\\n    /**\\n     * Returns the empty database state.\\n     * @return {Object} the empty state\\n     */\\n    getEmptyState() {\\n        return this.getDatabase().getEmptyState();\\n    }\\n\\n    /**\\n     * Begins an immutable database session.\\n     *\\n     * @param  {Object} state  - the state the database manages\\n     * @return {Session} a new {@link Session} instance\\n     */\\n    session(state) {\\n        return new Session(this, this.getDatabase(), state);\\n    }\\n\\n    /**\\n     * Begins a mutable database session.\\n     *\\n     * @param  {Object} state  - the state the database manages\\n     * @return {Session} a new {@link Session} instance\\n     */\\n    mutableSession(state) {\\n        return new Session(this, this.getDatabase(), state, true);\\n    }\\n\\n    /**\\n     * @private\\n     */\\n    _setupModelPrototypes(models) {\\n        models\\n            .filter(model => !model.isSetUp)\\n            .forEach(model => {\\n                const { fields, modelName, querySetClass } = model;\\n                Object.entries(fields).forEach(([fieldName, field]) => {\\n                    if (!(field instanceof Field)) {\\n                        throw new Error(\\n                            `${modelName}.${fieldName} is of type \\\"${typeof field}\\\" ` +\\n                                \\\"but must be an instance of Field. Please use the \\\" +\\n                                \\\"`attr`, `fk`, `oneToOne` and `many` \\\" +\\n                                \\\"functions to define fields.\\\"\\n                        );\\n                    }\\n                    if (!this._isFieldInstalled(modelName, fieldName)) {\\n                        this._installField(field, fieldName, model);\\n                        this._setFieldInstalled(modelName, fieldName);\\n                    }\\n                });\\n                attachQuerySetMethods(model, querySetClass);\\n                model.isSetUp = true;\\n            });\\n    }\\n\\n    /**\\n     * @private\\n     */\\n    _isFieldInstalled(modelName, fieldName) {\\n        return this.installedFields.hasOwnProperty(modelName)\\n            ? !!this.installedFields[modelName][fieldName]\\n            : false;\\n    }\\n\\n    /**\\n     * @private\\n     */\\n    _setFieldInstalled(modelName, fieldName) {\\n        if (!this.installedFields.hasOwnProperty(modelName)) {\\n            this.installedFields[modelName] = {};\\n        }\\n        this.installedFields[modelName][fieldName] = true;\\n    }\\n\\n    /**\\n     * Installs a field on a model and its related models if necessary.\\n     * @private\\n     */\\n    _installField(field, fieldName, model) {\\n        const FieldInstaller = field.installerClass;\\n        new FieldInstaller({\\n            field,\\n            fieldName,\\n            model,\\n            orm: this,\\n        }).run();\\n    }\\n\\n    // DEPRECATED AND REMOVED METHODS\\n\\n    /**\\n     * @deprecated Use {@link ORM#mutableSession} instead.\\n     */\\n    withMutations(state) {\\n        warnDeprecated(\\n            \\\"`ORM.prototype.withMutations` has been deprecated. \\\" +\\n                \\\"Use `ORM.prototype.mutableSession` instead.\\\"\\n        );\\n        return this.mutableSession(state);\\n    }\\n\\n    /**\\n     * @deprecated Use {@link ORM#session} instead.\\n     */\\n    from(state) {\\n        warnDeprecated(\\n            \\\"`ORM.prototype.from` has been deprecated. \\\" +\\n                \\\"Use `ORM.prototype.session` instead.\\\"\\n        );\\n        return this.session(state);\\n    }\\n\\n    /**\\n     * @deprecated Use {@link ORM#getEmptyState} instead.\\n     */\\n    getDefaultState() {\\n        warnDeprecated(\\n            \\\"`ORM.prototype.getDefaultState` has been deprecated. Use \\\" +\\n                \\\"`ORM.prototype.getEmptyState` instead.\\\"\\n        );\\n        return this.getEmptyState();\\n    }\\n\\n    /**\\n     * @deprecated Define a Model class instead.\\n     */\\n    define() {\\n        throw new Error(\\n            \\\"`ORM.prototype.define` has been removed. Please define a Model class.\\\"\\n        );\\n    }\\n}\\n\\nexport function DeprecatedSchema() {\\n    throw new Error(\\n        \\\"Schema has been renamed to ORM. Please import ORM instead of Schema \\\" +\\n            \\\"from Redux-ORM.\\\"\\n    );\\n}\\n\\nexport { ORM };\\n\\nexport default ORM;\\n\",\"import { STATE_FLAG } from \\\"./constants\\\";\\n\\nconst defaultEqualityCheck = (a, b) => a === b;\\nexport const eqCheck = defaultEqualityCheck;\\n\\nconst isOrmState = arg =>\\n    arg && typeof arg === \\\"object\\\" && arg.hasOwnProperty(STATE_FLAG);\\n\\nconst argsAreEqual = (lastArgs, nextArgs, equalityCheck) =>\\n    nextArgs.every(\\n        (arg, index) =>\\n            (isOrmState(arg) && isOrmState(lastArgs[index])) ||\\n            equalityCheck(arg, lastArgs[index])\\n    );\\n\\nconst rowsAreEqual = (ids, rowsA, rowsB) =>\\n    ids.every(id => rowsA[id] === rowsB[id]);\\n\\nconst accessedModelInstancesAreEqual = (previous, ormState, orm) => {\\n    const { accessedInstances } = previous;\\n\\n    return Object.entries(accessedInstances).every(([modelName, instances]) => {\\n        // if the entire table has not been changed, we have nothing to do\\n        if (previous.ormState[modelName] === ormState[modelName]) {\\n            return true;\\n        }\\n\\n        const { mapName } = orm.getDatabase().describe(modelName);\\n\\n        const { [mapName]: previousRows } = previous.ormState[modelName];\\n        const { [mapName]: rows } = ormState[modelName];\\n\\n        const accessedIds = Object.keys(instances);\\n        return rowsAreEqual(accessedIds, previousRows, rows);\\n    });\\n};\\n\\nconst accessedIndexesAreEqual = (previous, ormState) => {\\n    const { accessedIndexes } = previous;\\n\\n    return Object.entries(accessedIndexes).every(([modelName, indexes]) =>\\n        Object.entries(indexes).every(([column, values]) =>\\n            values.every(\\n                value =>\\n                    previous.ormState[modelName].indexes[column][value] ===\\n                    ormState[modelName].indexes[column][value]\\n            )\\n        )\\n    );\\n};\\n\\nconst fullTableScannedModelsAreEqual = (previous, ormState) =>\\n    previous.fullTableScannedModels.every(\\n        modelName => previous.ormState[modelName] === ormState[modelName]\\n    );\\n\\n/**\\n * A memoizer to use with redux-orm\\n * selectors. When the memoized function is first run,\\n * the memoizer will remember the models that are accessed\\n * during that function run.\\n *\\n * On subsequent runs, the memoizer will check if those\\n * models' states have changed compared to the previous run.\\n *\\n * Memoization algorithm operates like this:\\n *\\n * 1. Has the selector been run before? If not, go to 6.\\n *\\n * 2. If the selector has other input selectors in addition to the\\n *    ORM state selector, check their results for equality with the previous results.\\n *    If they aren't equal, go to 6.\\n *\\n * 3. Some filter queries may have required scanning entire tables during the last run.\\n *    If any of those tables have changed, go to 6.\\n *\\n * 4. Check which foreign key indexes the database has used to speed up queries\\n *    during the last run. If any have changed, go to 6.\\n *\\n * 5. Check which Model's instances the selector has accessed during the last run.\\n *    Check for equality with each of those states versus their states in the\\n *    previous ORM state. If all of them are equal, return the previous result.\\n *\\n * 6. Run the selector. Check the Session object used by the selector for\\n *    which Model's states were accessed, and merge them with the previously\\n *    saved information about accessed models (if-else branching can change\\n *    which models are accessed on different inputs). Save the ORM state and\\n *    other arguments the selector was called with, overriding previously\\n *    saved values. Save the selector result. Return the selector result.\\n *\\n * @private\\n * @param  {Function} func - function to memoize\\n * @param  {Function} argEqualityCheck - equality check function to use with normal\\n *                                       selector args\\n * @param  {ORM} orm - a redux-orm ORM instance\\n * @return {Function} `func` memoized.\\n */\\nexport function memoize(func, argEqualityCheck = defaultEqualityCheck, orm) {\\n    let previous = {\\n        /* Result of the previous function call */\\n        result: null,\\n        /* Arguments to the previous function call (excluding ORM state) */\\n        args: null,\\n        /**\\n         * Snapshot of the previous database.\\n         *\\n         * Lets us know how the tables looked like\\n         * during the previous function call.\\n         */\\n        ormState: null,\\n        /**\\n         * Names of models whose tables have been scanned completely\\n         * during previous function call (contains only model names)\\n         * Format example: ['Book']\\n         */\\n        fullTableScannedModels: [],\\n        /**\\n         * Map of which model instances have been accessed\\n         * during previous function call.\\n         * Contains only PKs of accessed instances.\\n         * Format example: { Book: { 1: true, 3: true } }\\n         */\\n        accessedInstances: {},\\n        /**\\n         * Map of which attribute indexes have been accessed\\n         * during previous function call.\\n         * Contains only attributes that were actually filtered on.\\n         * Author.withId(3).books would add 3 to the authorId index below.\\n         * Format example: { Book: { authorId: [1, 2], publisherId: [5] } }\\n         */\\n        accessedIndexes: {},\\n    };\\n\\n    return (...stateAndArgs) => {\\n        /**\\n         * The first argument to this function needs to be\\n         * the ORM's reducer state in the user's Redux store.\\n         */\\n        const [ormState, ...args] = stateAndArgs;\\n\\n        const selectorWasCalledBefore = Boolean(previous.args);\\n        if (\\n            selectorWasCalledBefore &&\\n            argsAreEqual(previous.args, args, argEqualityCheck) &&\\n            fullTableScannedModelsAreEqual(previous, ormState) &&\\n            accessedIndexesAreEqual(previous, ormState) &&\\n            accessedModelInstancesAreEqual(previous, ormState, orm)\\n        ) {\\n            /**\\n             * None of this selector's dependencies have changed\\n             * since the last time that we called it.\\n             */\\n            return previous.result;\\n        }\\n\\n        /**\\n         * Start a session so that the selector can access the database.\\n         * Make this session immutable. This way we can find out if\\n         * the operations that the selector performs are cacheable.\\n         */\\n        const session = orm.session(ormState);\\n        /* Replace all ORM state arguments by the session above */\\n        const argsWithSession = args.map(arg =>\\n            isOrmState(arg) ? session : arg\\n        );\\n\\n        /* This is where we call the actual function */\\n        const result = func.apply(null, argsWithSession); // eslint-disable-line prefer-spread\\n\\n        /**\\n         * The metadata for the previous call are no longer valid.\\n         * Update cached values.\\n         */\\n        previous = {\\n            /* Arguments that were passed to the selector */\\n            args,\\n            /* Selector result */\\n            result,\\n            /* Redux state slice for session.state */\\n            ormState,\\n            /* Rows retrieved by resolved primary key */\\n            accessedInstances: session.accessedModelInstances,\\n            /* Foreign key indexes that were used to speed up queries */\\n            accessedIndexes: session.accessedIndexes,\\n            /* Tables that had to be scanned completely */\\n            fullTableScannedModels: session.fullTableScannedModels,\\n        };\\n\\n        return result;\\n    };\\n}\\n\",\"import { createSelectorCreator } from \\\"reselect\\\";\\nimport createCachedSelector, { FlatMapCache } from \\\"re-reselect\\\";\\n\\nimport { memoize } from \\\"./memoize\\\";\\n\\nimport { ORM } from \\\"./ORM\\\";\\nimport SelectorSpec from \\\"./selectors/SelectorSpec\\\";\\nimport MapSelectorSpec from \\\"./selectors/MapSelectorSpec\\\";\\n\\n/**\\n * @module redux\\n * @desc Provides functions for integration with Redux.\\n */\\n\\n/**\\n * Calls all models' reducers if they exist.\\n *\\n * @return {undefined}\\n * @global\\n */\\nexport function defaultUpdater(session, action) {\\n    session.sessionBoundModels.forEach(modelClass => {\\n        if (typeof modelClass.reducer === \\\"function\\\") {\\n            // This calls this.applyUpdate to update this.state\\n            modelClass.reducer(action, modelClass, session);\\n        }\\n    });\\n}\\n\\n/**\\n * Call the returned function to pass actions to Redux-ORM.\\n *\\n * @global\\n *\\n * @param {ORM} orm - the ORM instance.\\n * @param {Function} [updater] - the function updating the ORM state based on the given action.\\n * @return {Function} reducer that will update the ORM state.\\n */\\nexport function createReducer(orm, updater = defaultUpdater) {\\n    return (state, action) => {\\n        const session = orm.session(state || orm.getEmptyState());\\n        updater(session, action);\\n        return session.state;\\n    };\\n}\\n\\n/**\\n * @private\\n * @param {SelectorSpec} spec\\n */\\nfunction createSelectorFromSpec(spec) {\\n    if (spec instanceof MapSelectorSpec) {\\n        const parentSelector = createSelectorFromSpec(spec.parent);\\n        return spec.createResultFunc(parentSelector);\\n    }\\n    return createCachedSelector(\\n        spec.dependencies,\\n        spec.resultFunc\\n    )({\\n        keySelector: spec.keySelector,\\n        cacheObject: new FlatMapCache(),\\n        selectorCreator: createSelector, // eslint-disable-line no-use-before-define\\n    });\\n}\\n\\n/**\\n * Tries to find ORM instance using the argument.\\n * @private\\n * @param {*} arg\\n */\\nfunction toORM(arg) {\\n    /* eslint-disable no-underscore-dangle */\\n    if (arg instanceof ORM) {\\n        return arg;\\n    }\\n    if (arg instanceof SelectorSpec) {\\n        return arg._orm;\\n    }\\n    return false;\\n}\\n\\nconst selectorCache = new Map();\\nconst SELECTOR_KEY = Symbol.for(\\\"REDUX_ORM_SELECTOR\\\");\\n\\n/**\\n * @private\\n * @param {function|ORM|SelectorSpec} arg\\n */\\nfunction toSelector(arg) {\\n    if (typeof arg === \\\"function\\\") {\\n        return arg;\\n    }\\n    if (arg instanceof ORM) {\\n        return arg.stateSelector;\\n    }\\n    if (arg instanceof MapSelectorSpec) {\\n        // the argument to map() needs to be callable\\n        arg.selector = toSelector(arg.selector);\\n    }\\n    if (arg instanceof SelectorSpec) {\\n        const { orm, cachePath } = arg;\\n        let level;\\n\\n        // the selector cache for the spec's ORM\\n        if (!selectorCache.has(orm)) {\\n            selectorCache.set(orm, new Map());\\n        }\\n        const ormSelectors = selectorCache.get(orm);\\n\\n        /**\\n         * Drill down into selector map by cachePath.\\n         *\\n         * The selector itself is stored under a special SELECTOR_KEY\\n         * so that we can store selectors below it as well.\\n         */\\n        level = ormSelectors;\\n        for (let i = 0; i < cachePath.length; ++i) {\\n            const storageKey = cachePath[i];\\n            if (!level.has(storageKey)) {\\n                level.set(storageKey, new Map());\\n            }\\n            level = level.get(storageKey);\\n        }\\n        if (level && level.has(SELECTOR_KEY)) {\\n            // Cache hit: the selector has been created before\\n            return level.get(SELECTOR_KEY);\\n        }\\n        // Cache miss: the selector needs to be created\\n        const selector = createSelectorFromSpec(arg);\\n        // Save the selector at the cachePath position\\n        level.set(SELECTOR_KEY, selector);\\n\\n        return selector;\\n    }\\n    throw new Error(\\n        `Failed to interpret selector argument: ${JSON.stringify(\\n            arg\\n        )} of type ${typeof arg}`\\n    );\\n}\\n\\n/**\\n * Returns a memoized selector based on passed arguments.\\n * This is similar to `reselect`'s `createSelector`,\\n * except you can also pass a single function to be memoized.\\n *\\n * If you pass multiple functions, the format will be the\\n * same as in `reselect`. The last argument is the selector\\n * function and the previous are input selectors.\\n *\\n * When you use this method to create a selector, the returned selector\\n * expects the whole `redux-orm` state branch as input. In the selector\\n * function that you pass as the last argument, any of the arguments\\n * you pass first will be considered selectors and mapped\\n * to their outputs, like in `reselect`.\\n *\\n * Here are some example selectors:\\n *\\n * ```javascript\\n * // orm is an instance of ORM\\n * // reduxState is the state of a Redux store\\n * const books = createSelector(orm.Book);\\n * books(reduxState) // array of book refs\\n *\\n * const bookAuthors = createSelector(orm.Book.authors);\\n * bookAuthors(reduxState) // two-dimensional array of author refs for each book\\n * ```\\n * Selectors can easily be applied to related models:\\n * ```javascript\\n * const bookAuthorNames = createSelector(\\n *     orm.Book.authors.map(orm.Author.name),\\n * );\\n * bookAuthorNames(reduxState, 8) // names of all authors of book with ID 8\\n * bookAuthorNames(reduxState, [8, 9]) // 2D array of names of all authors of books with IDs 8 and 9\\n * ```\\n * Also note that `orm.Author.name` did not need to be wrapped in another `createSelector` call,\\n * although that would be possible.\\n *\\n * For more complex calculations you can access\\n * entire session objects by passing an ORM instance.\\n * ```javascript\\n * const freshBananasCost = createSelector(\\n *     orm,\\n *     session => {\\n *        const banana = session.Product.get({\\n *            name: \\\"Banana\\\",\\n *        });\\n *        // amount of fresh bananas in shopping cart\\n *        const amount = session.ShoppingCart.filter({\\n *            product_id: banana.id,\\n *            is_fresh: true,\\n *        }).count();\\n *        return `USD ${amount * banana.price}`;\\n *     }\\n * );\\n * ```\\n *\\n * redux-orm uses a special memoization function to avoid recomputations.\\n *\\n * Everytime a selector runs, this function records which instances\\n * of your `Model`s were accessed.<br>\\n * On subsequent runs, the selector first checks if the previously\\n * accessed instances or `args` have changed in any way:\\n * <ul>\\n *     <li>If yes, the selector calls the function you passed to it.</li>\\n *     <li>If not, it just returns the previous result\\n *         (unless you call it for the first time).</li>\\n * </ul>\\n *\\n * This way you can use pure rendering in your React components\\n * for performance gains.\\n *\\n * @global\\n *\\n * @param  {...Function} args - zero or more input selectors\\n *                              and the selector function.\\n * @return {Function} memoized selector\\n */\\nexport function createSelector(...args) {\\n    if (!args.length) {\\n        throw new Error(\\\"Cannot create a selector without arguments.\\\");\\n    }\\n\\n    const resultArg = args.pop();\\n    const dependencies = Array.isArray(args[0]) ? args[0] : args;\\n\\n    const orm = dependencies.map(toORM).find(Boolean);\\n    const inputFuncs = dependencies.map(toSelector);\\n\\n    if (typeof resultArg === \\\"function\\\") {\\n        if (!orm) {\\n            throw new Error(\\n                \\\"Failed to resolve the current ORM database state. Please pass an ORM instance or an ORM selector as an argument to `createSelector()`.\\\"\\n            );\\n        } else if (!orm.stateSelector) {\\n            throw new Error(\\n                \\\"Failed to resolve the current ORM database state. Please pass an object to the ORM constructor that specifies a `stateSelector` function.\\\"\\n            );\\n        } else if (typeof orm.stateSelector !== \\\"function\\\") {\\n            throw new Error(\\n                `Failed to resolve the current ORM database state. Please pass a function when specifying the ORM's \\\\`stateSelector\\\\`. Received: ${JSON.stringify(\\n                    orm.stateSelector\\n                )} of type ${typeof orm.stateSelector}`\\n            );\\n        }\\n\\n        return createSelectorCreator(\\n            memoize,\\n            undefined,\\n            orm\\n        )([orm.stateSelector, ...inputFuncs], resultArg);\\n    }\\n\\n    if (resultArg instanceof ORM) {\\n        throw new Error(\\n            \\\"ORM instances cannot be the result function of selectors. You can access your models in the last function that you pass to `createSelector()`.\\\"\\n        );\\n    }\\n    if (inputFuncs.length) {\\n        console.warn(\\n            \\\"Your input selectors will be ignored: the passed result function does not require any input.\\\"\\n        );\\n    }\\n\\n    return toSelector(resultArg);\\n}\\n\",\"import QuerySet from \\\"./QuerySet\\\";\\nimport Model from \\\"./Model\\\";\\nimport { DeprecatedSchema, ORM } from \\\"./ORM\\\";\\nimport Session from \\\"./Session\\\";\\nimport { createReducer, createSelector } from \\\"./redux\\\";\\nimport ForeignKey from \\\"./fields/ForeignKey\\\";\\nimport ManyToMany from \\\"./fields/ManyToMany\\\";\\nimport OneToOne from \\\"./fields/OneToOne\\\";\\nimport Attribute from \\\"./fields/Attribute\\\";\\nimport { fk, many, oneToOne, attr } from \\\"./fields\\\";\\n\\nconst Schema = DeprecatedSchema;\\n\\nconst Backend = function RemovedBackend() {\\n    throw new Error(\\n        \\\"Having a custom Backend instance is now unsupported. \\\" +\\n            \\\"Documentation for database customization is upcoming, for now \\\" +\\n            \\\"please look at the db folder in the source.\\\"\\n    );\\n};\\n\\nexport {\\n    Attribute,\\n    QuerySet,\\n    Model,\\n    ORM,\\n    Schema,\\n    Backend,\\n    Session,\\n    ForeignKey,\\n    ManyToMany,\\n    OneToOne,\\n    fk,\\n    many,\\n    attr,\\n    oneToOne,\\n    createReducer,\\n    createSelector,\\n};\\n\\nexport default Model;\\n\"],\"sourceRoot\":\"\"}\n\\ No newline at end of file\n+{\"version\":3,\"sources\":[\"webpack://ReduxOrm/webpack/universalModuleDefinition\",\"webpack://ReduxOrm/webpack/bootstrap\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/createClass.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/inheritsLoose.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/typeof.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/toConsumableArray.js\",\"webpack://ReduxOrm/./node_modules/lodash/isArray.js\",\"webpack://ReduxOrm/./node_modules/reselect/lib/index.js\",\"webpack://ReduxOrm/./node_modules/re-reselect/dist/index.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/arrayLikeToArray.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseOrderBy.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseIteratee.js\",\"webpack://ReduxOrm/./node_modules/lodash/identity.js\",\"webpack://ReduxOrm/./node_modules/lodash/filter.js\",\"webpack://ReduxOrm/./node_modules/lodash/orderBy.js\",\"webpack://ReduxOrm/./node_modules/lodash/reject.js\",\"webpack://ReduxOrm/./node_modules/lodash/sortBy.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/setPrototypeOf.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/arrayWithoutHoles.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/iterableToArray.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/unsupportedIterableToArray.js\",\"webpack://ReduxOrm/./node_modules/@babel/runtime/helpers/nonIterableSpread.js\",\"webpack://ReduxOrm/./node_modules/lodash/_arrayMap.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseGet.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseMap.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseSortBy.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseUnary.js\",\"webpack://ReduxOrm/./node_modules/lodash/_compareMultiple.js\",\"webpack://ReduxOrm/./node_modules/lodash/_compareAscending.js\",\"webpack://ReduxOrm/./node_modules/lodash/isSymbol.js\",\"webpack://ReduxOrm/./node_modules/lodash/_arrayFilter.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseFilter.js\",\"webpack://ReduxOrm/./node_modules/lodash/negate.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseFlatten.js\",\"webpack://ReduxOrm/./node_modules/lodash/_baseRest.js\",\"webpack://ReduxOrm/./node_modules/lodash/_overRest.js\",\"webpack://ReduxOrm/./node_modules/lodash/_apply.js\",\"webpack://ReduxOrm/./node_modules/lodash/_setToString.js\",\"webpack://ReduxOrm/./node_modules/lodash/_isIterateeCall.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/internal/_isPlaceholder.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/internal/_curry1.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/internal/_arity.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/internal/_curry2.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/curryN.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/internal/_curryN.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/curry.js\",\"webpack://ReduxOrm/./node_modules/ramda/es/__.js\",\"webpack://ReduxOrm/./node_modules/immutable-ops/es/index.js\",\"webpack://ReduxOrm/./src/constants.js\",\"webpack://ReduxOrm/./src/utils.js\",\"webpack://ReduxOrm/./src/QuerySet.js\",\"webpack://ReduxOrm/./src/Session.js\",\"webpack://ReduxOrm/./src/fields/DefaultFieldInstaller.js\",\"webpack://ReduxOrm/./src/fields/FieldInstallerTemplate.js\",\"webpack://ReduxOrm/./src/fields/Field.js\",\"webpack://ReduxOrm/./src/descriptors.js\",\"webpack://ReduxOrm/./src/fields/Attribute.js\",\"webpack://ReduxOrm/./src/fields/RelationalField.js\",\"webpack://ReduxOrm/./src/fields/ForeignKey.js\",\"webpack://ReduxOrm/./src/fields/ManyToMany.js\",\"webpack://ReduxOrm/./src/fields/OneToOne.js\",\"webpack://ReduxOrm/./src/fields/index.js\",\"webpack://ReduxOrm/./src/Model.js\",\"webpack://ReduxOrm/./src/db/Table.js\",\"webpack://ReduxOrm/./src/db/Database.js\",\"webpack://ReduxOrm/./src/selectors/SelectorSpec.js\",\"webpack://ReduxOrm/./src/selectors/ModelBasedSelectorSpec.js\",\"webpack://ReduxOrm/./src/selectors/idArgSelector.js\",\"webpack://ReduxOrm/./src/selectors/MapSelectorSpec.js\",\"webpack://ReduxOrm/./src/selectors/ModelSelectorSpec.js\",\"webpack://ReduxOrm/./src/selectors/FieldSelectorSpec.js\",\"webpack://ReduxOrm/./src/selectors/index.js\",\"webpack://ReduxOrm/./src/ORM.js\",\"webpack://ReduxOrm/./src/memoize.js\",\"webpack://ReduxOrm/./src/redux.js\",\"webpack://ReduxOrm/./src/index.js\"],\"names\":[\"root\",\"factory\",\"exports\",\"module\",\"define\",\"amd\",\"window\",\"installedModules\",\"__webpack_require__\",\"moduleId\",\"i\",\"l\",\"modules\",\"call\",\"m\",\"c\",\"d\",\"name\",\"getter\",\"o\",\"Object\",\"defineProperty\",\"enumerable\",\"get\",\"r\",\"Symbol\",\"toStringTag\",\"value\",\"t\",\"mode\",\"__esModule\",\"ns\",\"create\",\"key\",\"bind\",\"n\",\"object\",\"property\",\"prototype\",\"hasOwnProperty\",\"p\",\"s\",\"_defineProperties\",\"target\",\"props\",\"length\",\"descriptor\",\"configurable\",\"writable\",\"Constructor\",\"protoProps\",\"staticProps\",\"setPrototypeOf\",\"subClass\",\"superClass\",\"constructor\",\"_typeof\",\"obj\",\"iterator\",\"arrayWithoutHoles\",\"iterableToArray\",\"unsupportedIterableToArray\",\"nonIterableSpread\",\"arr\",\"isArray\",\"Array\",\"defaultEqualityCheck\",\"a\",\"b\",\"areArgumentsShallowlyEqual\",\"equalityCheck\",\"prev\",\"next\",\"defaultMemoize\",\"func\",\"arguments\",\"undefined\",\"lastArgs\",\"lastResult\",\"apply\",\"getDependencies\",\"funcs\",\"dependencies\",\"every\",\"dep\",\"dependencyTypes\",\"map\",\"join\",\"Error\",\"createSelectorCreator\",\"memoize\",\"_len\",\"memoizeOptions\",\"_key\",\"_len2\",\"_key2\",\"recomputations\",\"resultFunc\",\"pop\",\"memoizedResultFunc\",\"concat\",\"selector\",\"params\",\"push\",\"resetRecomputations\",\"createStructuredSelector\",\"selectors\",\"selectorCreator\",\"createSelector\",\"objectKeys\",\"keys\",\"_len3\",\"values\",\"_key3\",\"reduce\",\"composition\",\"index\",\"reselect\",\"isStringOrNumber\",\"FlatObjectCache\",\"this\",\"_cache\",\"_proto\",\"set\",\"selectorFn\",\"remove\",\"clear\",\"isValidCacheKey\",\"cacheKey\",\"defaultCacheCreator\",\"defaultCacheKeyValidator\",\"createCachedSelector\",\"polymorphicOptions\",\"legacyOptions\",\"options\",\"keySelector\",\"assign\",\"cache\",\"cacheObject\",\"keySelectorCreator\",\"inputSelectors\",\"cacheResponse\",\"console\",\"warn\",\"getMatchingSelector\",\"removeMatchingSelector\",\"clearCache\",\"validateCacheSize\",\"cacheSize\",\"Number\",\"isInteger\",\"FifoObjectCache\",\"_temp\",\"_cacheOrdering\",\"_cacheSize\",\"earliest\",\"indexOf\",\"splice\",\"LruObjectCache\",\"_registerCacheHit\",\"_deleteCacheHit\",\"FlatMapCache\",\"Map\",\"FifoMapCache\",\"size\",\"LruMapCache\",\"has\",\"createStructuredCachedSelector\",\"default\",\"len\",\"arr2\",\"arrayMap\",\"baseGet\",\"baseIteratee\",\"baseMap\",\"baseSortBy\",\"baseUnary\",\"compareMultiple\",\"identity\",\"collection\",\"iteratees\",\"orders\",\"iteratee\",\"result\",\"other\",\"array\",\"predicate\",\"resIndex\",\"baseOrderBy\",\"guard\",\"arrayFilter\",\"baseFilter\",\"negate\",\"baseFlatten\",\"baseRest\",\"isIterateeCall\",\"sortBy\",\"_setPrototypeOf\",\"__proto__\",\"arrayLikeToArray\",\"iter\",\"from\",\"minLen\",\"toString\",\"slice\",\"test\",\"TypeError\",\"comparer\",\"sort\",\"compareAscending\",\"objCriteria\",\"criteria\",\"othCriteria\",\"ordersLength\",\"isSymbol\",\"valIsDefined\",\"valIsNull\",\"valIsReflexive\",\"valIsSymbol\",\"othIsDefined\",\"othIsNull\",\"othIsReflexive\",\"othIsSymbol\",\"args\",\"overRest\",\"setToString\",\"start\",\"nativeMax\",\"Math\",\"max\",\"transform\",\"otherArgs\",\"thisArg\",\"_isPlaceholder\",\"_curry1\",\"fn\",\"f1\",\"_arity\",\"a0\",\"a1\",\"a2\",\"a3\",\"a4\",\"a5\",\"a6\",\"a7\",\"a8\",\"a9\",\"_curry2\",\"f2\",\"_b\",\"_a\",\"_curryN\",\"received\",\"combined\",\"argsIdx\",\"left\",\"combinedIdx\",\"forOwn\",\"canMutate\",\"ownerID\",\"getBatchToken\",\"prepareNewObject\",\"instance\",\"addOwnerID\",\"forceArray\",\"arg\",\"normalizePath\",\"pathArg\",\"split\",\"mutableSet\",\"mutableMerge\",\"isDeep\",\"_mergeObjs\",\"baseObj\",\"mergeObjs\",\"forEach\",\"mergeObj\",\"assignValue\",\"mutableShallowMerge\",\"mutableDeepMerge\",\"mutableOmit\",\"_keys\",\"shouldMergeKey\",\"immutableMerge\",\"hasChanges\",\"nextObject\",\"willChange\",\"mergeValue\",\"currentValue\",\"recursiveMergeResult\",\"immutableDeepMerge\",\"immutableArrSet\",\"newArr\",\"copied\",\"fastArrayCopy\",\"mutableArrFilter\",\"currIndex\",\"originalIndex\",\"mutableArrSplice\",\"deleteCount\",\"_vals\",\"vals\",\"mutableArrInsert\",\"immutableArrSplice\",\"immutableArrInsert\",\"immutableOperations\",\"merge\",\"deepMerge\",\"omit\",\"keysInObj\",\"filter\",\"newObj\",\"setIn\",\"_pathArg\",\"acc\",\"currRef\",\"valueInPath\",\"pathLen\",\"rootObj\",\"curr\",\"idx\",\"currType\",\"_newObj\",\"pathRepr\",\"insert\",\"isArrayLike\",\"mutableOperations\",\"originalPathArg\",\"done\",\"immutableOps\",\"mutableOps\",\"batchOps\",\"mutable\",\"batch\",\"batched\",\"_token\",\"_fn\",\"token\",\"immutableOpsBoundToToken\",\"__\",\"getImmutableOps\",\"UPDATE\",\"DELETE\",\"FILTER\",\"EXCLUDE\",\"ALL_INSTANCES\",\"ID_ARG_KEY_SELECTOR\",\"_state\",\"idArg\",\"warnDeprecated\",\"msg\",\"log\",\"m2mName\",\"declarationModelName\",\"fieldName\",\"string\",\"charAt\",\"toUpperCase\",\"m2mFromFieldName\",\"m2mToFieldName\",\"otherModelName\",\"querySetDelegatorFactory\",\"methodName\",\"getQuerySet\",\"querySetGetterDelegatorFactory\",\"getterName\",\"attachQuerySetMethods\",\"modelClass\",\"querySetClass\",\"leftToDefine\",\"sharedMethods\",\"currClass\",\"Function\",\"getPrototypeOf\",\"forEachSuperClass\",\"cls\",\"defined\",\"getOwnPropertyDescriptor\",\"normalizeEntity\",\"entity\",\"getId\",\"ops\",\"clauseFiltersByAttribute\",\"type\",\"payload\",\"attribute\",\"attributeValue\",\"mapValues\",\"entries\",\"newObject\",\"normalizeModelReference\",\"modelNameOrClass\",\"modelName\",\"QuerySet\",\"clauses\",\"opts\",\"_opts\",\"addSharedMethod\",\"_new\",\"userOpts\",\"_evaluate\",\"rows\",\"id\",\"withId\",\"toRefArray\",\"toModelArray\",\"ModelClass\",\"count\",\"exists\",\"Boolean\",\"at\",\"first\",\"last\",\"all\",\"lookupObj\",\"normalizedLookupObj\",\"filterDescriptor\",\"exclude\",\"excludeDescriptor\",\"session\",\"_evaluated\",\"table\",\"querySpec\",\"query\",\"orderBy\",\"orderByDescriptor\",\"update\",\"applyUpdate\",\"action\",\"delete\",\"model\",\"_onDelete\",\"Session\",\"schema\",\"db\",\"state\",\"withMutations\",\"batchToken\",\"getEmptyState\",\"initialState\",\"modelData\",\"models\",\"getModelClasses\",\"sessionBoundModels\",\"SessionBoundModel\",\"Reflect\",\"construct\",\"connect\",\"getDataForModel\",\"getModelData\",\"markAccessed\",\"modelIds\",\"data\",\"accessedInstances\",\"markFullTableScanned\",\"fullTableScanned\",\"markAccessedIndexes\",\"indexes\",\"attr\",\"accessedIndexes\",\"updateSpec\",\"tx\",\"_getTransaction\",\"status\",\"_markAccessedByQuery\",\"includes\",\"idAttribute\",\"accessedIds\",\"Set\",\"row\",\"anyClauseFilteredByPk\",\"some\",\"clause\",\"add\",\"getNextState\",\"DefaultFieldInstaller\",\"installForwardsDescriptor\",\"field\",\"createForwardsDescriptor\",\"toModel\",\"throughModel\",\"installForwardsVirtualField\",\"virtualFields\",\"createForwardsVirtualField\",\"installBackwardsDescriptor\",\"backwardsFieldName\",\"toModelName\",\"createBackwardsDescriptor\",\"installBackwardsVirtualField\",\"createBackwardsVirtualField\",\"orm\",\"references\",\"run\",\"installsForwardsVirtualField\",\"installsBackwardsDescriptor\",\"installsBackwardsVirtualField\",\"_toModel\",\"_throughModel\",\"throughModelName\",\"getThroughModelName\",\"getBackwardsFieldName\",\"Field\",\"getClass\",\"forwardsManyToOneDescriptor\",\"declaredToModelName\",\"DeclaredToModel\",\"toId\",\"_fields\",\"manyToManyDescriptor\",\"declaredFromModelName\",\"throughFields\",\"reverse\",\"DeclaredFromModel\",\"ThroughModel\",\"ThisModel\",\"OtherModel\",\"thisReferencingField\",\"to\",\"otherReferencingField\",\"thisId\",\"throughQs\",\"referencedOtherIds\",\"qs\",\"otherModelInstance\",\"entities\",\"idsToAdd\",\"existingQs\",\"through\",\"existingIds\",\"idsToRemove\",\"entitiesToDelete\",\"entitiesToDeleteIds\",\"unexistingIds\",\"Attribute\",\"getDefault\",\"attrDescriptor\",\"RelationalField\",\"relatedName\",\"as\",\"toLowerCase\",\"ForeignKey\",\"declaredFieldName\",\"ManyToMany\",\"getThroughFields\",\"fieldAName\",\"fieldBName\",\"fieldA\",\"fields\",\"throughModelFieldReferencing\",\"otherModel\",\"find\",\"someFieldName\",\"OneToOne\",\"forwardsOneToOneDescriptor\",\"fk\",\"many\",\"oneToOne\",\"getByIdQuery\",\"modelInstance\",\"Model\",\"_initFields\",\"propsObj\",\"ids\",\"_session\",\"QuerySetClass\",\"invalidateClassCache\",\"isSetUp\",\"tableOptions\",\"backend\",\"userProps\",\"m2mRelations\",\"declaredFieldNames\",\"declaredVirtualFieldNames\",\"valuePassed\",\"_refreshMany2Many\",\"upsert\",\"idExists\",\"_findDatabaseRows\",\"equals\",\"entriesInA\",\"objectShallowEquals\",\"propertyName\",\"userMergeObj\",\"mergeKey\",\"mergedFields\",\"updatedModel\",\"refreshFromState\",\"ref\",\"relations\",\"normalizedNewIds\",\"uniqueIds\",\"fromField\",\"toField\",\"diffActions\",\"sourceArr\",\"targetArr\",\"itemsInBoth\",\"item\",\"deleteItems\",\"addItems\",\"arrayDiffActions\",\"idsToDelete\",\"relatedQs\",\"hasId\",\"describe\",\"DEFAULT_TABLE_OPTIONS\",\"arrName\",\"mapName\",\"Table\",\"accessId\",\"branch\",\"accessIds\",\"accessIdList\",\"accessList\",\"getMaxId\",\"getMeta\",\"setMaxId\",\"newMaxId\",\"setMeta\",\"nextId\",\"meta\",\"optimallyOrderedClauses\",\"clauseReducesResultSetSize\",\"reducer\",\"remainingPayload\",\"withoutPkAttr\",\"filterAttr\",\"indexAttrs\",\"lastIndex\",\"indexedIds\",\"indexSet\",\"withoutIndexAttrs\",\"reject\",\"convert\",\"order\",\"normalizeOrders\",\"entry\",\"workingState\",\"_currMax\",\"userPassedId\",\"newMax\",\"newId\",\"currMax\",\"idSequencer\",\"finalEntry\",\"indexesToAppendTo\",\"fkAttr\",\"attrIndex\",\"created\",\"nextIndexes\",\"indexMap\",\"indexedAttrs\",\"indexIdsToAdd\",\"indexIdsToDelete\",\"nextMap\",\"prevAttrValues\",\"valueMap\",\"mergeObjInto\",\"nextAttrValues\",\"nextRow\",\"prevValue\",\"nextValue\",\"rowId\",\"valueIndex\",\"attrIndexMap\",\"BASE_EMPTY_STATE\",\"tables\",\"tableName\",\"nextTableState\",\"resultPayload\",\"currTableState\",\"newTableState\",\"replaceTableState\",\"createDatabase\",\"schemaSpec\",\"tableSpecs\",\"tableSpec\",\"SelectorSpec\",\"parent\",\"_parent\",\"_orm\",\"cachePath\",\"ModelBasedSelectorSpec\",\"_model\",\"valueForInstance\",\"idArgSelector\",\"MapSelectorSpec\",\"_field\",\"_selector\",\"createResultFunc\",\"parentSelector\",\"parentResult\",\"single\",\"refArray\",\"ModelSelectorSpec\",\"FieldSelectorSpec\",\"fieldModel\",\"accessorName\",\"isVirtual\",\"_fieldModel\",\"_accessorName\",\"_isVirtual\",\"ParentToModel\",\"parentRef\",\"parentInstance\",\"JSON\",\"stringify\",\"getDatabase\",\"createFieldSelectorSpec\",\"fieldSelectorSpec\",\"relatedFieldName\",\"relatedField\",\"fieldAccessorName\",\"ORM_DEFAULTS\",\"defaultCreateDatabase\",\"RESERVED_TABLE_OPTIONS\",\"isReservedTableOption\",\"word\",\"ORM\",\"registry\",\"implicitThroughModels\",\"installedFields\",\"stateSelector\",\"register\",\"registerManyToManyModelsFor\",\"_setupModelPrototypes\",\"modelSelectorSpec\",\"createModelSelectorSpec\",\"thisModelName\",\"fieldInstance\",\"selfReferencing\",\"fromFieldName\",\"toFieldName\",\"Through\",\"PlainForeignKey\",\"ForeignKeyClass\",\"allModels\",\"found\",\"generateSchemaSpec\",\"spec\",\"mutableSession\",\"_isFieldInstalled\",\"_installField\",\"_setFieldInstalled\",\"FieldInstaller\",\"installerClass\",\"getDefaultState\",\"isOrmState\",\"argEqualityCheck\",\"previous\",\"ormState\",\"fullTableScannedModels\",\"stateAndArgs\",\"fullTableScannedModelsAreEqual\",\"column\",\"accessedIndexesAreEqual\",\"instances\",\"previousRows\",\"rowsA\",\"rowsB\",\"accessedModelInstancesAreEqual\",\"argsWithSession\",\"accessedModelInstances\",\"defaultUpdater\",\"createReducer\",\"updater\",\"toORM\",\"selectorCache\",\"SELECTOR_KEY\",\"for\",\"toSelector\",\"level\",\"storageKey\",\"createSelectorFromSpec\",\"resultArg\",\"inputFuncs\",\"Schema\",\"Backend\"],\"mappings\":\"CAAA,SAA2CA,EAAMC,GAC1B,iBAAZC,SAA0C,iBAAXC,OACxCA,OAAOD,QAAUD,IACQ,mBAAXG,QAAyBA,OAAOC,IAC9CD,OAAO,WAAY,GAAIH,GACG,iBAAZC,QACdA,QAAkB,SAAID,IAEtBD,EAAe,SAAIC,IARrB,CASGK,QAAQ,WACX,O,YCTE,IAAIC,EAAmB,GAGvB,SAASC,EAAoBC,GAG5B,GAAGF,EAAiBE,GACnB,OAAOF,EAAiBE,GAAUP,QAGnC,IAAIC,EAASI,EAAiBE,GAAY,CACzCC,EAAGD,EACHE,GAAG,EACHT,QAAS,IAUV,OANAU,EAAQH,GAAUI,KAAKV,EAAOD,QAASC,EAAQA,EAAOD,QAASM,GAG/DL,EAAOQ,GAAI,EAGJR,EAAOD,QA0Df,OArDAM,EAAoBM,EAAIF,EAGxBJ,EAAoBO,EAAIR,EAGxBC,EAAoBQ,EAAI,SAASd,EAASe,EAAMC,GAC3CV,EAAoBW,EAAEjB,EAASe,IAClCG,OAAOC,eAAenB,EAASe,EAAM,CAAEK,YAAY,EAAMC,IAAKL,KAKhEV,EAAoBgB,EAAI,SAAStB,GACX,oBAAXuB,QAA0BA,OAAOC,aAC1CN,OAAOC,eAAenB,EAASuB,OAAOC,YAAa,CAAEC,MAAO,WAE7DP,OAAOC,eAAenB,EAAS,aAAc,CAAEyB,OAAO,KAQvDnB,EAAoBoB,EAAI,SAASD,EAAOE,GAEvC,GADU,EAAPA,IAAUF,EAAQnB,EAAoBmB,IAC/B,EAAPE,EAAU,OAAOF,EACpB,GAAW,EAAPE,GAA8B,iBAAVF,GAAsBA,GAASA,EAAMG,WAAY,OAAOH,EAChF,IAAII,EAAKX,OAAOY,OAAO,MAGvB,GAFAxB,EAAoBgB,EAAEO,GACtBX,OAAOC,eAAeU,EAAI,UAAW,CAAET,YAAY,EAAMK,MAAOA,IACtD,EAAPE,GAA4B,iBAATF,EAAmB,IAAI,IAAIM,KAAON,EAAOnB,EAAoBQ,EAAEe,EAAIE,EAAK,SAASA,GAAO,OAAON,EAAMM,IAAQC,KAAK,KAAMD,IAC9I,OAAOF,GAIRvB,EAAoB2B,EAAI,SAAShC,GAChC,IAAIe,EAASf,GAAUA,EAAO2B,WAC7B,WAAwB,OAAO3B,EAAgB,SAC/C,WAA8B,OAAOA,GAEtC,OADAK,EAAoBQ,EAAEE,EAAQ,IAAKA,GAC5BA,GAIRV,EAAoBW,EAAI,SAASiB,EAAQC,GAAY,OAAOjB,OAAOkB,UAAUC,eAAe1B,KAAKuB,EAAQC,IAGzG7B,EAAoBgC,EAAI,GAIjBhC,EAAoBA,EAAoBiC,EAAI,I,gBClFrD,SAASC,EAAkBC,EAAQC,GACjC,IAAK,IAAIlC,EAAI,EAAGA,EAAIkC,EAAMC,OAAQnC,IAAK,CACrC,IAAIoC,EAAaF,EAAMlC,GACvBoC,EAAWxB,WAAawB,EAAWxB,aAAc,EACjDwB,EAAWC,cAAe,EACtB,UAAWD,IAAYA,EAAWE,UAAW,GACjD5B,OAAOC,eAAesB,EAAQG,EAAWb,IAAKa,IAUlD3C,EAAOD,QANP,SAAsB+C,EAAaC,EAAYC,GAG7C,OAFID,GAAYR,EAAkBO,EAAYX,UAAWY,GACrDC,GAAaT,EAAkBO,EAAaE,GACzCF,GAIT9C,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,G,gBCjBxE,IAAIsB,EAAiB,EAAQ,IAQ7BjD,EAAOD,QANP,SAAwBmD,EAAUC,GAChCD,EAASf,UAAYlB,OAAOY,OAAOsB,EAAWhB,WAC9Ce,EAASf,UAAUiB,YAAcF,EACjCD,EAAeC,EAAUC,IAI3BnD,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,G,cCTxE,SAAS0B,EAAQC,GAiBf,MAdsB,mBAAXhC,QAAoD,iBAApBA,OAAOiC,UAChDvD,EAAOD,QAAUsD,EAAU,SAAiBC,GAC1C,cAAcA,GAGhBtD,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,IAExE3B,EAAOD,QAAUsD,EAAU,SAAiBC,GAC1C,OAAOA,GAAyB,mBAAXhC,QAAyBgC,EAAIF,cAAgB9B,QAAUgC,IAAQhC,OAAOa,UAAY,gBAAkBmB,GAG3HtD,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,GAGnE0B,EAAQC,GAGjBtD,EAAOD,QAAUsD,EACjBrD,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,G,gBCrBxE,IAAI6B,EAAoB,EAAQ,IAE5BC,EAAkB,EAAQ,IAE1BC,EAA6B,EAAQ,IAErCC,EAAoB,EAAQ,IAMhC3D,EAAOD,QAJP,SAA4B6D,GAC1B,OAAOJ,EAAkBI,IAAQH,EAAgBG,IAAQF,EAA2BE,IAAQD,KAI9F3D,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,G,cCUxE,IAAIkC,EAAUC,MAAMD,QAEpB7D,EAAOD,QAAU8D,G,6BCnBjB,SAASE,EAAqBC,EAAGC,GAC/B,OAAOD,IAAMC,EAGf,SAASC,EAA2BC,EAAeC,EAAMC,GACvD,GAAa,OAATD,GAA0B,OAATC,GAAiBD,EAAK1B,SAAW2B,EAAK3B,OACzD,OAAO,EAKT,IADA,IAAIA,EAAS0B,EAAK1B,OACTnC,EAAI,EAAGA,EAAImC,EAAQnC,IAC1B,IAAK4D,EAAcC,EAAK7D,GAAI8D,EAAK9D,IAC/B,OAAO,EAIX,OAAO,EAGT,SAAS+D,EAAeC,GACtB,IAAIJ,EAAgBK,UAAU9B,OAAS,QAAsB+B,IAAjBD,UAAU,GAAmBA,UAAU,GAAKT,EAEpFW,EAAW,KACXC,EAAa,KAEjB,OAAO,WAOL,OANKT,EAA2BC,EAAeO,EAAUF,aAEvDG,EAAaJ,EAAKK,MAAM,KAAMJ,YAGhCE,EAAWF,UACJG,GAIX,SAASE,EAAgBC,GACvB,IAAIC,EAAejB,MAAMD,QAAQiB,EAAM,IAAMA,EAAM,GAAKA,EAExD,IAAKC,EAAaC,OAAM,SAAUC,GAChC,MAAsB,mBAARA,KACZ,CACF,IAAIC,EAAkBH,EAAaI,KAAI,SAAUF,GAC/C,cAAcA,KACbG,KAAK,MACR,MAAM,IAAIC,MAAM,wGAAgHH,EAAkB,KAGpJ,OAAOH,EAGT,SAASO,EAAsBC,GAC7B,IAAK,IAAIC,EAAOhB,UAAU9B,OAAQ+C,EAAiB3B,MAAM0B,EAAO,EAAIA,EAAO,EAAI,GAAIE,EAAO,EAAGA,EAAOF,EAAME,IACxGD,EAAeC,EAAO,GAAKlB,UAAUkB,GAGvC,OAAO,WACL,IAAK,IAAIC,EAAQnB,UAAU9B,OAAQoC,EAAQhB,MAAM6B,GAAQC,EAAQ,EAAGA,EAAQD,EAAOC,IACjFd,EAAMc,GAASpB,UAAUoB,GAG3B,IAAIC,EAAiB,EACjBC,EAAahB,EAAMiB,MACnBhB,EAAeF,EAAgBC,GAE/BkB,EAAqBT,EAAQX,WAAMH,EAAW,CAAC,WAGjD,OAFAoB,IAEOC,EAAWlB,MAAM,KAAMJ,aAC7ByB,OAAOR,IAGNS,EAAW5B,GAAe,WAI5B,IAHA,IAAI6B,EAAS,GACTzD,EAASqC,EAAarC,OAEjBnC,EAAI,EAAGA,EAAImC,EAAQnC,IAE1B4F,EAAOC,KAAKrB,EAAaxE,GAAGqE,MAAM,KAAMJ,YAI1C,OAAOwB,EAAmBpB,MAAM,KAAMuB,MAUxC,OAPAD,EAASJ,WAAaA,EACtBI,EAASL,eAAiB,WACxB,OAAOA,GAETK,EAASG,oBAAsB,WAC7B,OAAOR,EAAiB,GAEnBK,GAjGXnG,EAAQ4B,YAAa,EACrB5B,EAAQuE,eAAiBA,EACzBvE,EAAQuF,sBAAwBA,EAChCvF,EAAQuG,yBAoGR,SAAkCC,GAChC,IAAIC,EAAkBhC,UAAU9B,OAAS,QAAsB+B,IAAjBD,UAAU,GAAmBA,UAAU,GAAKiC,EAE1F,GAAyB,iBAAdF,EACT,MAAM,IAAIlB,MAAM,gIAAwIkB,GAE1J,IAAIG,EAAazF,OAAO0F,KAAKJ,GAC7B,OAAOC,EAAgBE,EAAWvB,KAAI,SAAUrD,GAC9C,OAAOyE,EAAUzE,OACf,WACF,IAAK,IAAI8E,EAAQpC,UAAU9B,OAAQmE,EAAS/C,MAAM8C,GAAQE,EAAQ,EAAGA,EAAQF,EAAOE,IAClFD,EAAOC,GAAStC,UAAUsC,GAG5B,OAAOD,EAAOE,QAAO,SAAUC,EAAaxF,EAAOyF,GAEjD,OADAD,EAAYN,EAAWO,IAAUzF,EAC1BwF,IACN,QAnBP,IAAIP,EAAiB1G,EAAQ0G,eAAiBnB,EAAsBhB,I,iBCnG5D,SAAWvE,EAASmH,GAAY,aAEtC,SAASC,EAAiB3F,GACxB,MAAwB,iBAAVA,GAAuC,iBAAVA,EAG7C,IAAI4F,EAA+B,WACjC,SAASA,IACPC,KAAKC,OAAS,GAGhB,IAAIC,EAASH,EAAgBjF,UAsB7B,OApBAoF,EAAOC,IAAM,SAAa1F,EAAK2F,GAC7BJ,KAAKC,OAAOxF,GAAO2F,GAGrBF,EAAOnG,IAAM,SAAaU,GACxB,OAAOuF,KAAKC,OAAOxF,IAGrByF,EAAOG,OAAS,SAAgB5F,UACvBuF,KAAKC,OAAOxF,IAGrByF,EAAOI,MAAQ,WACbN,KAAKC,OAAS,IAGhBC,EAAOK,gBAAkB,SAAyBC,GAChD,OAAOV,EAAiBU,IAGnBT,EA3B0B,GA8B/BU,EAAsBV,EAEtBW,EAA2B,WAC7B,OAAO,GAGT,SAASC,IACP,IAAK,IAAIxC,EAAOhB,UAAU9B,OAAQoC,EAAQ,IAAIhB,MAAM0B,GAAOE,EAAO,EAAGA,EAAOF,EAAME,IAChFZ,EAAMY,GAAQlB,UAAUkB,GAG1B,OAAO,SAAUuC,EAAoBC,GACnC,GAAIA,EACF,MAAM,IAAI7C,MAAM,4HAGlB,IAAI8C,EAAwC,mBAAvBF,EAAoC,CACvDG,YAAaH,GACXhH,OAAOoH,OAAO,GAAIJ,GAElBpC,EAAiB,EACjBC,EAAahB,EAAMiB,MACnBhB,EAAejB,MAAMD,QAAQiB,EAAM,IAAMA,EAAM,GAAK,GAAGmB,OAAOnB,GAOlEA,EAAMsB,MAL6B,WAEjC,OADAP,IACOC,EAAWlB,WAAM,EAAQJ,cAIlC,IAAI8D,EAAQH,EAAQI,aAAe,IAAIT,EACnCtB,EAAkB2B,EAAQ3B,iBAAmBU,EAAST,eACtDmB,EAAkBU,EAAMV,iBAAmBG,EAE3CI,EAAQK,qBACVL,EAAQC,YAAcD,EAAQK,mBAAmB,CAC/CJ,YAAaD,EAAQC,YACrBK,eAAgB1D,EAChBe,WAAYA,KAKhB,IAAII,EAAW,WACb,IAAI2B,EAAWM,EAAQC,YAAYxD,MAAMuD,EAAS3D,WAElD,GAAIoD,EAAgBC,GAAW,CAC7B,IAAIa,EAAgBJ,EAAMlH,IAAIyG,GAO9B,YALsBpD,IAAlBiE,IACFA,EAAgBlC,EAAgB5B,WAAM,EAAQE,GAC9CwD,EAAMd,IAAIK,EAAUa,IAGfA,EAAc9D,WAAM,EAAQJ,WAGrCmE,QAAQC,KAAK,oCAAuCf,EAAW,iDAiCjE,OA5BA3B,EAAS2C,oBAAsB,WAC7B,IAAIhB,EAAWM,EAAQC,YAAYxD,MAAMuD,EAAS3D,WAElD,OAAO8D,EAAMlH,IAAIyG,IAGnB3B,EAAS4C,uBAAyB,WAChC,IAAIjB,EAAWM,EAAQC,YAAYxD,MAAMuD,EAAS3D,WAClD8D,EAAMZ,OAAOG,IAGf3B,EAAS6C,WAAa,WACpBT,EAAMX,SAGRzB,EAASJ,WAAaA,EACtBI,EAASnB,aAAeA,EACxBmB,EAASoC,MAAQA,EAEjBpC,EAASL,eAAiB,WACxB,OAAOA,GAGTK,EAASG,oBAAsB,WAC7B,OAAOR,EAAiB,GAG1BK,EAASkC,YAAcD,EAAQC,YACxBlC,GAQX,SAAS8C,EAAkBC,GACzB,QAAkBxE,IAAdwE,EACF,MAAM,IAAI5D,MAAM,8CAGlB,IAAK6D,OAAOC,UAAUF,IAAcA,GAAa,EAC/C,MAAM,IAAI5D,MAAM,8DAIpB,IAAI+D,EAA+B,WACjC,SAASA,EAAgBC,GACvB,IACIJ,QADiB,IAAVI,EAAmB,GAAKA,GACdJ,UAErBD,EAAkBC,GAClB5B,KAAKC,OAAS,GACdD,KAAKiC,eAAiB,GACtBjC,KAAKkC,WAAaN,EAGpB,IAAI1B,EAAS6B,EAAgBjH,UAoC7B,OAlCAoF,EAAOC,IAAM,SAAa1F,EAAK2F,GAK7B,GAJAJ,KAAKC,OAAOxF,GAAO2F,EAEnBJ,KAAKiC,eAAelD,KAAKtE,GAErBuF,KAAKiC,eAAe5G,OAAS2E,KAAKkC,WAAY,CAChD,IAAIC,EAAWnC,KAAKiC,eAAe,GACnCjC,KAAKK,OAAO8B,KAIhBjC,EAAOnG,IAAM,SAAaU,GACxB,OAAOuF,KAAKC,OAAOxF,IAGrByF,EAAOG,OAAS,SAAgB5F,GAC9B,IAAImF,EAAQI,KAAKiC,eAAeG,QAAQ3H,GAEpCmF,GAAS,GACXI,KAAKiC,eAAeI,OAAOzC,EAAO,UAG7BI,KAAKC,OAAOxF,IAGrByF,EAAOI,MAAQ,WACbN,KAAKC,OAAS,GACdD,KAAKiC,eAAiB,IAGxB/B,EAAOK,gBAAkB,SAAyBC,GAChD,OAAOV,EAAiBU,IAGnBuB,EA/C0B,GAkD/BO,EAA8B,WAChC,SAASA,EAAeN,GACtB,IACIJ,QADiB,IAAVI,EAAmB,GAAKA,GACdJ,UAErBD,EAAkBC,GAClB5B,KAAKC,OAAS,GACdD,KAAKiC,eAAiB,GACtBjC,KAAKkC,WAAaN,EAGpB,IAAI1B,EAASoC,EAAexH,UAgD5B,OA9CAoF,EAAOC,IAAM,SAAa1F,EAAK2F,GAK7B,GAJAJ,KAAKC,OAAOxF,GAAO2F,EAEnBJ,KAAKuC,kBAAkB9H,GAEnBuF,KAAKiC,eAAe5G,OAAS2E,KAAKkC,WAAY,CAChD,IAAIC,EAAWnC,KAAKiC,eAAe,GACnCjC,KAAKK,OAAO8B,KAIhBjC,EAAOnG,IAAM,SAAaU,GAGxB,OAFAuF,KAAKuC,kBAAkB9H,GAEhBuF,KAAKC,OAAOxF,IAGrByF,EAAOG,OAAS,SAAgB5F,GAC9BuF,KAAKwC,gBAAgB/H,UAEduF,KAAKC,OAAOxF,IAGrByF,EAAOI,MAAQ,WACbN,KAAKC,OAAS,GACdD,KAAKiC,eAAiB,IAGxB/B,EAAOqC,kBAAoB,SAA2B9H,GACpDuF,KAAKwC,gBAAgB/H,GAErBuF,KAAKiC,eAAelD,KAAKtE,IAG3ByF,EAAOsC,gBAAkB,SAAyB/H,GAChD,IAAImF,EAAQI,KAAKiC,eAAeG,QAAQ3H,GAEpCmF,GAAS,GACXI,KAAKiC,eAAeI,OAAOzC,EAAO,IAItCM,EAAOK,gBAAkB,SAAyBC,GAChD,OAAOV,EAAiBU,IAGnB8B,EA3DyB,GA8D9BG,EAA4B,WAC9B,SAASA,IACPzC,KAAKC,OAAS,IAAIyC,IAGpB,IAAIxC,EAASuC,EAAa3H,UAkB1B,OAhBAoF,EAAOC,IAAM,SAAa1F,EAAK2F,GAC7BJ,KAAKC,OAAOE,IAAI1F,EAAK2F,IAGvBF,EAAOnG,IAAM,SAAaU,GACxB,OAAOuF,KAAKC,OAAOlG,IAAIU,IAGzByF,EAAOG,OAAS,SAAgB5F,GAC9BuF,KAAKC,OAAe,OAAExF,IAGxByF,EAAOI,MAAQ,WACbN,KAAKC,OAAOK,SAGPmC,EAvBuB,GA0B5BE,EAA4B,WAC9B,SAASA,EAAaX,GACpB,IACIJ,QADiB,IAAVI,EAAmB,GAAKA,GACdJ,UAErBD,EAAkBC,GAClB5B,KAAKC,OAAS,IAAIyC,IAClB1C,KAAKkC,WAAaN,EAGpB,IAAI1B,EAASyC,EAAa7H,UAwB1B,OAtBAoF,EAAOC,IAAM,SAAa1F,EAAK2F,GAG7B,GAFAJ,KAAKC,OAAOE,IAAI1F,EAAK2F,GAEjBJ,KAAKC,OAAO2C,KAAO5C,KAAKkC,WAAY,CACtC,IAAIC,EAAWnC,KAAKC,OAAOX,OAAOtC,OAAO7C,MAEzC6F,KAAKK,OAAO8B,KAIhBjC,EAAOnG,IAAM,SAAaU,GACxB,OAAOuF,KAAKC,OAAOlG,IAAIU,IAGzByF,EAAOG,OAAS,SAAgB5F,GAC9BuF,KAAKC,OAAe,OAAExF,IAGxByF,EAAOI,MAAQ,WACbN,KAAKC,OAAOK,SAGPqC,EAlCuB,GAqC5BE,EAA2B,WAC7B,SAASA,EAAYb,GACnB,IACIJ,QADiB,IAAVI,EAAmB,GAAKA,GACdJ,UAErBD,EAAkBC,GAClB5B,KAAKC,OAAS,IAAIyC,IAClB1C,KAAKkC,WAAaN,EAGpB,IAAI1B,EAAS2C,EAAY/H,UAiCzB,OA/BAoF,EAAOC,IAAM,SAAa1F,EAAK2F,GAG7B,GAFAJ,KAAKC,OAAOE,IAAI1F,EAAK2F,GAEjBJ,KAAKC,OAAO2C,KAAO5C,KAAKkC,WAAY,CACtC,IAAIC,EAAWnC,KAAKC,OAAOX,OAAOtC,OAAO7C,MAEzC6F,KAAKK,OAAO8B,KAIhBjC,EAAOnG,IAAM,SAAaU,GACxB,IAAIN,EAAQ6F,KAAKC,OAAOlG,IAAIU,GAS5B,OANIuF,KAAKC,OAAO6C,IAAIrI,KAClBuF,KAAKK,OAAO5F,GAEZuF,KAAKC,OAAOE,IAAI1F,EAAKN,IAGhBA,GAGT+F,EAAOG,OAAS,SAAgB5F,GAC9BuF,KAAKC,OAAe,OAAExF,IAGxByF,EAAOI,MAAQ,WACbN,KAAKC,OAAOK,SAGPuC,EA3CsB,GA8C/BnK,EAAQiK,aAAeA,EACvBjK,EAAQqJ,gBAAkBA,EAC1BrJ,EAAQ+J,aAAeA,EACvB/J,EAAQqH,gBAAkBA,EAC1BrH,EAAQmK,YAAcA,EACtBnK,EAAQ4J,eAAiBA,EACzB5J,EAAQiI,qBAAuBA,EAC/BjI,EAAQqK,+BAlPR,SAAwC7D,GACtC,OAAOW,EAASZ,yBAAyBC,EAAWyB,IAkPtDjI,EAAQsK,QAAUrC,EAElB/G,OAAOC,eAAenB,EAAS,aAAc,CAAEyB,OAAO,IA1XS1B,CAAQC,EAAS,EAAQ,K,cCS1FC,EAAOD,QAVP,SAA2B6D,EAAK0G,IACnB,MAAPA,GAAeA,EAAM1G,EAAIlB,UAAQ4H,EAAM1G,EAAIlB,QAE/C,IAAK,IAAInC,EAAI,EAAGgK,EAAO,IAAIzG,MAAMwG,GAAM/J,EAAI+J,EAAK/J,IAC9CgK,EAAKhK,GAAKqD,EAAIrD,GAGhB,OAAOgK,GAITvK,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,G,gBCXxE,IAAI6I,EAAW,EAAQ,IACnBC,EAAU,EAAQ,IAClBC,EAAe,EAAQ,GACvBC,EAAU,EAAQ,IAClBC,EAAa,EAAQ,IACrBC,EAAY,EAAQ,IACpBC,EAAkB,EAAQ,IAC1BC,EAAW,EAAQ,IACnBlH,EAAU,EAAQ,GAwCtB7D,EAAOD,QA7BP,SAAqBiL,EAAYC,EAAWC,GAExCD,EADEA,EAAUvI,OACA8H,EAASS,GAAW,SAASE,GACvC,OAAItH,EAAQsH,GACH,SAAS3J,GACd,OAAOiJ,EAAQjJ,EAA2B,IAApB2J,EAASzI,OAAeyI,EAAS,GAAKA,IAGzDA,KAGG,CAACJ,GAGf,IAAI9D,GAAS,EACbgE,EAAYT,EAASS,EAAWJ,EAAUH,IAE1C,IAAIU,EAAST,EAAQK,GAAY,SAASxJ,EAAOM,EAAKkJ,GAIpD,MAAO,CAAE,SAHMR,EAASS,GAAW,SAASE,GAC1C,OAAOA,EAAS3J,MAEa,QAAWyF,EAAO,MAASzF,MAG5D,OAAOoJ,EAAWQ,GAAQ,SAASnJ,EAAQoJ,GACzC,OAAOP,EAAgB7I,EAAQoJ,EAAOH,Q,cCxB1ClL,EAAOD,QAJP,SAAkByB,GAChB,OAAOA,I,cCGTxB,EAAOD,QAJP,SAAkByB,GAChB,OAAOA,I,cCOTxB,EAAOD,QAfP,SAAqBuL,EAAOC,GAM1B,IALA,IAAItE,GAAS,EACTvE,EAAkB,MAAT4I,EAAgB,EAAIA,EAAM5I,OACnC8I,EAAW,EACXJ,EAAS,KAEJnE,EAAQvE,GAAQ,CACvB,IAAIlB,EAAQ8J,EAAMrE,GACdsE,EAAU/J,EAAOyF,EAAOqE,KAC1BF,EAAOI,KAAchK,GAGzB,OAAO4J,I,gBCrBT,IAAIK,EAAc,EAAQ,GACtB5H,EAAU,EAAQ,GA6CtB7D,EAAOD,QAdP,SAAiBiL,EAAYC,EAAWC,EAAQQ,GAC9C,OAAkB,MAAdV,EACK,IAEJnH,EAAQoH,KACXA,EAAyB,MAAbA,EAAoB,GAAK,CAACA,IAGnCpH,EADLqH,EAASQ,OAAQjH,EAAYyG,KAE3BA,EAAmB,MAAVA,EAAiB,GAAK,CAACA,IAE3BO,EAAYT,EAAYC,EAAWC,M,gBC3C5C,IAAIS,EAAc,EAAQ,IACtBC,EAAa,EAAQ,IACrBlB,EAAe,EAAQ,GACvB7G,EAAU,EAAQ,GAClBgI,EAAS,EAAQ,IAyCrB7L,EAAOD,QALP,SAAgBiL,EAAYO,GAE1B,OADW1H,EAAQmH,GAAcW,EAAcC,GACnCZ,EAAYa,EAAOnB,EAAaa,EAAW,O,gBC1CzD,IAAIO,EAAc,EAAQ,IACtBL,EAAc,EAAQ,GACtBM,EAAW,EAAQ,IACnBC,EAAiB,EAAQ,IA+BzBC,EAASF,GAAS,SAASf,EAAYC,GACzC,GAAkB,MAAdD,EACF,MAAO,GAET,IAAItI,EAASuI,EAAUvI,OAMvB,OALIA,EAAS,GAAKsJ,EAAehB,EAAYC,EAAU,GAAIA,EAAU,IACnEA,EAAY,GACHvI,EAAS,GAAKsJ,EAAef,EAAU,GAAIA,EAAU,GAAIA,EAAU,MAC5EA,EAAY,CAACA,EAAU,KAElBQ,EAAYT,EAAYc,EAAYb,EAAW,GAAI,OAG5DjL,EAAOD,QAAUkM,G,cC/CjB,SAASC,EAAgBlL,EAAGqB,GAO1B,OANArC,EAAOD,QAAUmM,EAAkBjL,OAAOgC,gBAAkB,SAAyBjC,EAAGqB,GAEtF,OADArB,EAAEmL,UAAY9J,EACPrB,GAGThB,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,EACjEuK,EAAgBlL,EAAGqB,GAG5BrC,EAAOD,QAAUmM,EACjBlM,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,G,gBCXxE,IAAIyK,EAAmB,EAAQ,GAM/BpM,EAAOD,QAJP,SAA4B6D,GAC1B,GAAIE,MAAMD,QAAQD,GAAM,OAAOwI,EAAiBxI,IAIlD5D,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,G,cCHxE3B,EAAOD,QAJP,SAA0BsM,GACxB,GAAsB,oBAAX/K,QAAmD,MAAzB+K,EAAK/K,OAAOiC,WAA2C,MAAtB8I,EAAK,cAAuB,OAAOvI,MAAMwI,KAAKD,IAItHrM,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,G,gBCLxE,IAAIyK,EAAmB,EAAQ,GAW/BpM,EAAOD,QATP,SAAqCiB,EAAGuL,GACtC,GAAKvL,EAAL,CACA,GAAiB,iBAANA,EAAgB,OAAOoL,EAAiBpL,EAAGuL,GACtD,IAAIvK,EAAIf,OAAOkB,UAAUqK,SAAS9L,KAAKM,GAAGyL,MAAM,GAAI,GAEpD,MADU,WAANzK,GAAkBhB,EAAEoC,cAAapB,EAAIhB,EAAEoC,YAAYtC,MAC7C,QAANkB,GAAqB,QAANA,EAAoB8B,MAAMwI,KAAKtL,GACxC,cAANgB,GAAqB,2CAA2C0K,KAAK1K,GAAWoK,EAAiBpL,EAAGuL,QAAxG,IAIFvM,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,G,cCRxE3B,EAAOD,QAJP,WACE,MAAM,IAAI4M,UAAU,yIAItB3M,EAAOD,QAAiB,QAAIC,EAAOD,QAASC,EAAOD,QAAQ4B,YAAa,G,cCexE3B,EAAOD,QAXP,SAAkBuL,EAAOH,GAKvB,IAJA,IAAIlE,GAAS,EACTvE,EAAkB,MAAT4I,EAAgB,EAAIA,EAAM5I,OACnC0I,EAAStH,MAAMpB,KAEVuE,EAAQvE,GACf0I,EAAOnE,GAASkE,EAASG,EAAMrE,GAAQA,EAAOqE,GAEhD,OAAOF,I,cCLTpL,EAAOD,QAJP,SAAkBkC,EAAQH,GACxB,OAAiB,MAAVG,OAAiBwC,EAAYxC,EAAOH,K,cCW7C9B,EAAOD,QAXP,SAAkBuL,EAAOH,GAKvB,IAJA,IAAIlE,GAAS,EACTvE,EAAkB,MAAT4I,EAAgB,EAAIA,EAAM5I,OACnC0I,EAAStH,MAAMpB,KAEVuE,EAAQvE,GACf0I,EAAOnE,GAASkE,EAASG,EAAMrE,GAAQA,EAAOqE,GAEhD,OAAOF,I,cCGTpL,EAAOD,QAVP,SAAoBuL,EAAOsB,GACzB,IAAIlK,EAAS4I,EAAM5I,OAGnB,IADA4I,EAAMuB,KAAKD,GACJlK,KACL4I,EAAM5I,GAAU4I,EAAM5I,GAAQlB,MAEhC,OAAO8J,I,cCJTtL,EAAOD,QANP,SAAmBwE,GACjB,OAAO,SAAS/C,GACd,OAAO+C,EAAK/C,M,gBCThB,IAAIsL,EAAmB,EAAQ,IA2C/B9M,EAAOD,QA3BP,SAAyBkC,EAAQoJ,EAAOH,GAOtC,IANA,IAAIjE,GAAS,EACT8F,EAAc9K,EAAO+K,SACrBC,EAAc5B,EAAM2B,SACpBtK,EAASqK,EAAYrK,OACrBwK,EAAehC,EAAOxI,SAEjBuE,EAAQvE,GAAQ,CACvB,IAAI0I,EAAS0B,EAAiBC,EAAY9F,GAAQgG,EAAYhG,IAC9D,GAAImE,EACF,OAAInE,GAASiG,EACJ9B,EAGFA,GAAmB,QADdF,EAAOjE,IACiB,EAAI,GAU5C,OAAOhF,EAAOgF,MAAQoE,EAAMpE,Q,gBCxC9B,IAAIkG,EAAW,EAAQ,IAwCvBnN,EAAOD,QA9BP,SAA0ByB,EAAO6J,GAC/B,GAAI7J,IAAU6J,EAAO,CACnB,IAAI+B,OAAyB3I,IAAVjD,EACf6L,EAAsB,OAAV7L,EACZ8L,EAAiB9L,GAAUA,EAC3B+L,EAAcJ,EAAS3L,GAEvBgM,OAAyB/I,IAAV4G,EACfoC,EAAsB,OAAVpC,EACZqC,EAAiBrC,GAAUA,EAC3BsC,EAAcR,EAAS9B,GAE3B,IAAMoC,IAAcE,IAAgBJ,GAAe/L,EAAQ6J,GACtDkC,GAAeC,GAAgBE,IAAmBD,IAAcE,GAChEN,GAAaG,GAAgBE,IAC5BN,GAAgBM,IACjBJ,EACH,OAAO,EAET,IAAMD,IAAcE,IAAgBI,GAAenM,EAAQ6J,GACtDsC,GAAeP,GAAgBE,IAAmBD,IAAcE,GAChEE,GAAaL,GAAgBE,IAC5BE,GAAgBF,IACjBI,EACH,OAAQ,EAGZ,OAAO,I,cCpBT1N,EAAOD,QAJP,WACE,OAAO,I,cCUTC,EAAOD,QAfP,SAAqBuL,EAAOC,GAM1B,IALA,IAAItE,GAAS,EACTvE,EAAkB,MAAT4I,EAAgB,EAAIA,EAAM5I,OACnC8I,EAAW,EACXJ,EAAS,KAEJnE,EAAQvE,GAAQ,CACvB,IAAIlB,EAAQ8J,EAAMrE,GACdsE,EAAU/J,EAAOyF,EAAOqE,KAC1BF,EAAOI,KAAchK,GAGzB,OAAO4J,I,cCGTpL,EAAOD,QAfP,SAAqBuL,EAAOC,GAM1B,IALA,IAAItE,GAAS,EACTvE,EAAkB,MAAT4I,EAAgB,EAAIA,EAAM5I,OACnC8I,EAAW,EACXJ,EAAS,KAEJnE,EAAQvE,GAAQ,CACvB,IAAIlB,EAAQ8J,EAAMrE,GACdsE,EAAU/J,EAAOyF,EAAOqE,KAC1BF,EAAOI,KAAchK,GAGzB,OAAO4J,I,cCkBTpL,EAAOD,QAhBP,SAAgBwL,GACd,GAAwB,mBAAbA,EACT,MAAM,IAAIoB,UAxBQ,uBA0BpB,OAAO,WACL,IAAIiB,EAAOpJ,UACX,OAAQoJ,EAAKlL,QACX,KAAK,EAAG,OAAQ6I,EAAU7K,KAAK2G,MAC/B,KAAK,EAAG,OAAQkE,EAAU7K,KAAK2G,KAAMuG,EAAK,IAC1C,KAAK,EAAG,OAAQrC,EAAU7K,KAAK2G,KAAMuG,EAAK,GAAIA,EAAK,IACnD,KAAK,EAAG,OAAQrC,EAAU7K,KAAK2G,KAAMuG,EAAK,GAAIA,EAAK,GAAIA,EAAK,IAE9D,OAAQrC,EAAU3G,MAAMyC,KAAMuG,M,cCblC5N,EAAOD,QAJP,SAAcuL,GACZ,OAAQA,GAASA,EAAM5I,OAAU4I,EAAM,QAAK7G,I,gBCnB9C,IAAIsG,EAAW,EAAQ,IACnB8C,EAAW,EAAQ,IACnBC,EAAc,EAAQ,IAc1B9N,EAAOD,QAJP,SAAkBwE,EAAMwJ,GACtB,OAAOD,EAAYD,EAAStJ,EAAMwJ,EAAOhD,GAAWxG,EAAO,M,gBCb7D,IAAIK,EAAQ,EAAQ,IAGhBoJ,EAAYC,KAAKC,IAgCrBlO,EAAOD,QArBP,SAAkBwE,EAAMwJ,EAAOI,GAE7B,OADAJ,EAAQC,OAAoBvJ,IAAVsJ,EAAuBxJ,EAAK7B,OAAS,EAAKqL,EAAO,GAC5D,WAML,IALA,IAAIH,EAAOpJ,UACPyC,GAAS,EACTvE,EAASsL,EAAUJ,EAAKlL,OAASqL,EAAO,GACxCzC,EAAQxH,MAAMpB,KAETuE,EAAQvE,GACf4I,EAAMrE,GAAS2G,EAAKG,EAAQ9G,GAE9BA,GAAS,EAET,IADA,IAAImH,EAAYtK,MAAMiK,EAAQ,KACrB9G,EAAQ8G,GACfK,EAAUnH,GAAS2G,EAAK3G,GAG1B,OADAmH,EAAUL,GAASI,EAAU7C,GACtB1G,EAAML,EAAM8C,KAAM+G,M,cCX7BpO,EAAOD,QAVP,SAAewE,EAAM8J,EAAST,GAC5B,OAAQA,EAAKlL,QACX,KAAK,EAAG,OAAO6B,EAAK7D,KAAK2N,GACzB,KAAK,EAAG,OAAO9J,EAAK7D,KAAK2N,EAAST,EAAK,IACvC,KAAK,EAAG,OAAOrJ,EAAK7D,KAAK2N,EAAST,EAAK,GAAIA,EAAK,IAChD,KAAK,EAAG,OAAOrJ,EAAK7D,KAAK2N,EAAST,EAAK,GAAIA,EAAK,GAAIA,EAAK,IAE3D,OAAOrJ,EAAKK,MAAMyJ,EAAST,K,cCG7B5N,EAAOD,QAJP,SAAkByB,GAChB,OAAOA,I,cCATxB,EAAOD,QAJP,WACE,OAAO,I,8uBCdM,SAASuO,EAAetK,GAChC,OAAY,MAALA,GAA0B,iBAANA,IAAoD,IAAlCA,EAAE,4BCSvC,SAASuK,EAAQC,GAC9B,OAAO,SAASC,EAAGzK,GACjB,OAAyB,IAArBQ,UAAU9B,QAAgB4L,EAAetK,GACpCyK,EAEAD,EAAG5J,MAAMyC,KAAM7C,YCfb,SAASkK,EAAO1M,EAAGwM,GAEhC,OAAQxM,GACN,KAAK,EACH,OAAO,WACL,OAAOwM,EAAG5J,MAAMyC,KAAM7C,YAE1B,KAAK,EACH,OAAO,SAAUmK,GACf,OAAOH,EAAG5J,MAAMyC,KAAM7C,YAE1B,KAAK,EACH,OAAO,SAAUmK,EAAIC,GACnB,OAAOJ,EAAG5J,MAAMyC,KAAM7C,YAE1B,KAAK,EACH,OAAO,SAAUmK,EAAIC,EAAIC,GACvB,OAAOL,EAAG5J,MAAMyC,KAAM7C,YAE1B,KAAK,EACH,OAAO,SAAUmK,EAAIC,EAAIC,EAAIC,GAC3B,OAAON,EAAG5J,MAAMyC,KAAM7C,YAE1B,KAAK,EACH,OAAO,SAAUmK,EAAIC,EAAIC,EAAIC,EAAIC,GAC/B,OAAOP,EAAG5J,MAAMyC,KAAM7C,YAE1B,KAAK,EACH,OAAO,SAAUmK,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,GACnC,OAAOR,EAAG5J,MAAMyC,KAAM7C,YAE1B,KAAK,EACH,OAAO,SAAUmK,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,GACvC,OAAOT,EAAG5J,MAAMyC,KAAM7C,YAE1B,KAAK,EACH,OAAO,SAAUmK,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,GAC3C,OAAOV,EAAG5J,MAAMyC,KAAM7C,YAE1B,KAAK,EACH,OAAO,SAAUmK,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,GAC/C,OAAOX,EAAG5J,MAAMyC,KAAM7C,YAE1B,KAAK,GACH,OAAO,SAAUmK,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,EAAIC,GACnD,OAAOZ,EAAG5J,MAAMyC,KAAM7C,YAE1B,QACE,MAAM,IAAIa,MAAM,gFCrCP,SAASgK,EAAQb,GAC9B,OAAO,SAASc,EAAGtL,EAAGC,GACpB,OAAQO,UAAU9B,QAChB,KAAK,EACH,OAAO4M,EACT,KAAK,EACH,OAAOhB,EAAetK,GAAKsL,EAAKf,GAAQ,SAAUgB,GAChD,OAAOf,EAAGxK,EAAGuL,MAEjB,QACE,OAAOjB,EAAetK,IAAMsK,EAAerK,GAAKqL,EAAKhB,EAAetK,GAAKuK,GAAQ,SAAUiB,GACzF,OAAOhB,EAAGgB,EAAIvL,MACXqK,EAAerK,GAAKsK,GAAQ,SAAUgB,GACzC,OAAOf,EAAGxK,EAAGuL,MACVf,EAAGxK,EAAGC,KCsBnB,IAMe,EANWoL,GAAQ,SAAgB3M,EAAQ8L,GACxD,OAAe,IAAX9L,EACK6L,EAAQC,GAEVE,EAAOhM,ECtCD,SAAS+M,EAAQ/M,EAAQgN,EAAUlB,GAChD,OAAO,WAKL,IAJA,IAAImB,EAAW,GACXC,EAAU,EACVC,EAAOnN,EACPoN,EAAc,EACXA,EAAcJ,EAAShN,QAAUkN,EAAUpL,UAAU9B,QAAQ,CAClE,IAAI0I,EACA0E,EAAcJ,EAAShN,UAAY4L,EAAeoB,EAASI,KAAiBF,GAAWpL,UAAU9B,QACnG0I,EAASsE,EAASI,IAElB1E,EAAS5G,UAAUoL,GACnBA,GAAW,GAEbD,EAASG,GAAe1E,EACnBkD,EAAelD,KAClByE,GAAQ,GAEVC,GAAe,EAEjB,OAAOD,GAAQ,EAAIrB,EAAG5J,MAAMyC,KAAMsI,GAAYjB,EAAOmB,EAAMJ,EAAQ/M,EAAQiN,EAAUnB,KDkBjEiB,CAAQ/M,EAAQ,GAAI8L,OEJ7B,EAHUD,GAAQ,SAAeC,GAC9C,OAAO,EAAOA,EAAG9L,OAAQ8L,MClBZ,GAAE,4BAA4B,GCvB7C,SAASuB,EAAOzM,EAAKkL,GACnB,IAAK,IAAI1M,KAAOwB,EACVA,EAAIlB,eAAeN,IACrB0M,EAAGlL,EAAIxB,GAAMA,GAqBZ,SAASkO,EAAU1M,EAAK2M,GAC7B,QAAKA,GACE3M,EAdU,kCAcY2M,EAE/B,IAKWC,EALwB,mBAAX5O,OAAwB,WAC9C,OAAOA,OAAO,YACZ,WACF,MAAO,IAaT,SAAS6O,EAAiBC,EAAUH,GAKlC,OAJIA,GAVN,SAAoB3M,EAAK2M,GACvBhP,OAAOC,eAAeoC,EAxBL,+BAwBwB,CACvC9B,MAAOyO,EACPrN,cAAc,EACdzB,YAAY,IAOZkP,CAAWD,EAAUH,GAGhBG,EAGT,SAASE,EAAWC,GAClB,OAAMA,aAAezM,MAIdyM,EAHE,CAACA,GAQZ,SAASC,EAAcC,GACrB,MAAuB,iBAAZA,GACgC,IAArCA,EAAQhH,QAJK,KAKR,CAACgH,GAGHA,EAAQC,MARE,KAWZD,EAGT,SAASE,EAAW7O,EAAKN,EAAO8B,GAE9B,OADAA,EAAIxB,GAAON,EACJ8B,EA4GT,SAASsN,EAAaC,EAAQC,EAAYC,GACxC,IAAIC,EAAYV,EAAWQ,GAwB3B,OAtBID,EACFG,EAAUC,SAAQ,SAAUC,GAC1BnB,EAAOmB,GAAU,SAAU1P,EAAOM,GAE9B,IAAIqP,EADFN,GAAUE,EAAQ3O,eAAeN,IAIjCqP,EADqB,WAAnB,IAAQ3P,GACIoP,EAAaC,EAAQ,CAACrP,GAAQuP,EAAQjP,IAEtCN,EAGhBuP,EAAQjP,GAAOqP,GAEfJ,EAAQjP,GAAON,QAKrBP,OAAOoH,OAAOzD,MAAM3D,OAAQ,CAAC8P,GAAS9K,OAAO,IAAmB+K,KAG3DD,EAGT,IAAIK,EAAsBR,EAAa7O,KAAK,MAAM,GAC9CsP,EAAmBT,EAAa7O,KAAK,MAAM,GAE/C,SAASuP,EAAYC,EAAOjO,GAK1B,OAJWgN,EAAWiB,GACjBN,SAAQ,SAAUnP,UACdwB,EAAIxB,MAENwB,EAGT,SAASkO,EAAelO,EAAK+H,EAAOvJ,GAClC,OAAOwB,EAAIxB,KAASuJ,EAAMvJ,GAG5B,SAAS2P,EAAeZ,EAAQZ,EAASa,EAAYxN,GACnD,GAAI0M,EAAU1M,EAAK2M,GAAU,OAAOW,EAAaC,EAAQC,EAAYxN,GACrE,IAAI0N,EAAYV,EAAWQ,GACvBY,GAAa,EACbC,EAAarO,EAEbsO,EAAa,WACVF,IACHA,GAAa,EAEbvB,EADAwB,EAAa1Q,OAAOoH,OAAO,GAAI/E,GACF2M,KA+BjC,OA3BAe,EAAUC,SAAQ,SAAUC,GAC1BnB,EAAOmB,GAAU,SAAUW,EAAY/P,GACrC,GAAI+O,GAAUvN,EAAIlB,eAAeN,GAAM,CACrC,IAAIgQ,EAAeH,EAAW7P,GAE9B,GAA4B,WAAxB,IAAQ+P,MAA8BA,aAAsB/N,OAAQ,CACtE,GAAI0N,EAAeG,EAAYT,EAAUpP,GAAM,CAC7C,IAAIiQ,EAAuBN,EAAeZ,EAAQZ,EAAS4B,EAAYC,GAEnEC,IAAyBD,IAC3BF,IACAD,EAAW7P,GAAOiQ,GAItB,OAAO,GAIPP,EAAeG,EAAYT,EAAUpP,KACvC8P,IACAD,EAAW7P,GAAO+P,SAMjBF,EAGT,IAAIK,EAAqBP,EAAe1P,KAAK,MAAM,GAGnD,SAASkQ,EAAgBhC,EAAShJ,EAAOzF,EAAOoC,GAC9C,GAAIoM,EAAUpM,EAAKqM,GAAU,OAAOU,EAAW1J,EAAOzF,EAAOoC,GAC7D,GAAIA,EAAIqD,KAAWzF,EAAO,OAAOoC,EACjC,IAAIsO,EAvQN,SAAuBtO,GAGrB,IAFA,IAAIuO,EAAS,IAAIrO,MAAMF,EAAIlB,QAElBnC,EAAI,EAAGA,EAAIqD,EAAIlB,OAAQnC,IAC9B4R,EAAO5R,GAAKqD,EAAIrD,GAGlB,OAAO4R,EAgQMC,CAAcxO,GAG3B,OAFAsO,EAAOjL,GAASzF,EAChB2O,EAAiB+B,EAAQjC,GAClBiC,EAmCT,SAASG,EAAiB9N,EAAMX,GAI9B,IAHA,IAAI0O,EAAY,EACZC,EAAgB,EAEbD,EAAY1O,EAAIlB,QAAQ,CAGxB6B,EAFMX,EAAI0O,GAECC,GAGdD,IAFA1O,EAAI8F,OAAO4I,EAAW,GAKxBC,IAGF,OAAO3O,EAGT,SAAS4O,EAAiBvL,EAAOwL,EAAaC,EAAO9O,GACnD,IAAI+O,EAAOrC,EAAWoC,GAEtB,OADA9O,EAAI8F,OAAO9E,MAAMhB,EAAK,CAACqD,EAAOwL,GAAaxM,OAAO,IAAmB0M,KAC9D/O,EAGT,SAASgP,EAAiB3L,EAAOyL,EAAO9O,GACtC,OAAO4O,EAAiBvL,EAAO,EAAGyL,EAAO9O,GAG3C,SAASiP,EAAmB5C,EAAShJ,EAAOwL,EAAaC,EAAO9O,GAC9D,GAAIoM,EAAUpM,EAAKqM,GAAU,OAAOuC,EAAiBvL,EAAOwL,EAAaC,EAAO9O,GAChF,IAAI+O,EAAOrC,EAAWoC,GAClBR,EAAStO,EAAI6I,QAGjB,OAFA0D,EAAiB+B,EAAQjC,GACzBiC,EAAOxI,OAAO9E,MAAMsN,EAAQ,CAACjL,EAAOwL,GAAaxM,OAAO,IAAmB0M,KACpET,EAGT,SAASY,EAAmB7C,EAAShJ,EAAOyL,EAAO9O,GACjD,OAAIoM,EAAUpM,EAAKqM,GAAiB2C,EAAiB3L,EAAOyL,EAAO9O,GAC5DiP,EAAmB5C,EAAShJ,EAAO,EAAGyL,EAAO9O,GAetD,IAAImP,EAAsB,CAExBC,MApG0BvB,EAAe1P,KAAK,MAAM,GAqGpDkR,UAAWjB,EACXkB,KAjFF,SAAuBjD,EAASsB,EAAOjO,GACrC,GAAI0M,EAAU1M,EAAK2M,GAAU,OAAOqB,EAAYC,EAAOjO,GACvD,IACI6P,EADO7C,EAAWiB,GACD6B,QAAO,SAAUtR,GACpC,OAAOwB,EAAIlB,eAAeN,MAG5B,GAAyB,IAArBqR,EAAUzQ,OAAc,OAAOY,EACnC,IAAI+P,EAASpS,OAAOoH,OAAO,GAAI/E,GAK/B,OAJA6P,EAAUlC,SAAQ,SAAUnP,UACnBuR,EAAOvR,MAEhBqO,EAAiBkD,EAAQpD,GAClBoD,GAqEPC,MAjPF,SAAwBrD,EAASsD,EAAU/R,EAAO8B,GAChD,IAAImN,EAAUD,EAAc+C,GACxBzB,EAxBN,SAAqByB,EAAUjQ,GAI7B,IAHA,IAAImN,EAAUD,EAAc+C,GACxBC,EAAMlQ,EAED/C,EAAI,EAAGA,EAAIkQ,EAAQ/N,OAAQnC,IAAK,CACvC,IACIkT,EAAUD,EADH/C,EAAQlQ,IAGnB,GAAIA,IAAMkQ,EAAQ/N,OAAS,EACzB,OAAO+Q,EAGT,GAAyB,WAArB,IAAQA,GAGV,OAFAD,EAAMC,GAWSC,CAAYjD,EAASnN,GACxC,GAAI9B,IAAUsQ,EAAc,OAAOxO,EACnC,IACIkQ,EADAG,EAAUlD,EAAQ/N,OASlBkR,EALFJ,EADExD,EAAU1M,EAAK2M,GACX3M,EAEArC,OAAOoH,OAAO8H,EAAiB,GAAIF,GAAU3M,GAqCrD,OAjCAmN,EAAQQ,SAAQ,SAAU4C,EAAMC,GAC9B,GAAIA,IAAQH,EAAU,EAAtB,CAKA,IAAIF,EAAUD,EAAIK,GAEdE,EAAW,IAAQN,GAEvB,GAAiB,WAAbM,EAAJ,CAYA,GAAiB,cAAbA,EAA0B,CAC5B,IAAIC,EAAU7D,EAAiB,GAAIF,GAInC,OAFAuD,EAAIK,GAAQG,OACZR,EAAMQ,GAIR,IAAIC,EAAW,GAAGhO,OAAOwK,EAAQqD,EAAM,GAAI,KAAK7N,OAAO4N,GACvD,MAAM,IAAIxO,MAAM,oEAAoEY,OAAOgO,EAAU,MApBnG,GAAIjE,EAAUyD,EAASxD,GACrBuD,EAAMC,MACD,CACL,IAAIJ,EAASlD,EAAiB,GAAIF,GAClCuD,EAAIK,GAAQ5S,OAAOoH,OAAOgL,EAAQI,GAClCD,EAAMH,QAdRG,EAAIK,GAAQrS,KA+BToS,GAoMPM,OAAQpB,EACR1M,KApBF,SAA0B6J,EAAS0C,EAAM/O,GACvC,OAAOkP,EAAmB7C,EAASrM,EAAIlB,OAAQiQ,EAAM/O,IAoBrDwP,OAjBF,SAA4BnD,EAAS1L,EAAMX,GACzC,GAAIoM,EAAUpM,EAAKqM,GAAU,OAAOoC,EAAiB9N,EAAMX,GAC3D,IAAIsO,EAAStO,EAAIwP,OAAO7O,GACxB,OAAI2N,EAAOxP,SAAWkB,EAAIlB,OAAekB,GACzCuM,EAAiB+B,EAAQjC,GAClBiC,IAaPxI,OAAQmJ,EAERrL,IAnGF,SAAsByI,EAASnO,EAAKN,EAAO8B,GACzC,GApRF,SAAqB9B,GACnB,OAAOA,GAA4B,WAAnB,IAAQA,IAA+C,iBAAjBA,EAAMkB,QAAuBlB,EAAMkB,QAAU,GAAKlB,EAAMkB,OAAS,GAAM,EAmRzHyR,CAAY7Q,GAAM,OAAO2O,EAAgBhC,EAASnO,EAAKN,EAAO8B,GAClE,GAAI0M,EAAU1M,EAAK2M,GAAU,OAAOU,EAAW7O,EAAKN,EAAO8B,GAC3D,GAAIA,EAAIxB,KAASN,EAAO,OAAO8B,EAC/B,IAAI+P,EAASpS,OAAOoH,OAAO,GAAI/E,GAG/B,OAFA6M,EAAiBkD,EAAQpD,GACzBoD,EAAOvR,GAAON,EACP6R,IA8FLe,EAAoB,CAEtBpB,MAAO5B,EACP6B,UAAW5B,EACX6B,KAAM5B,EACNgC,MAtTF,SAAsBC,EAAU/R,EAAO8B,GAQrC,IAPA,IAAI+Q,EAAkB7D,EAAc+C,GAChCI,EAAUU,EAAgB3R,OAC1B4R,GAAO,EACPR,EAAM,EACNN,EAAMlQ,EACNuQ,EAAOQ,EAAgBP,IAEnBQ,GACN,GAAIR,IAAQH,EAAU,EACpBH,EAAIK,GAAQrS,EACZ8S,GAAO,MACF,CACL,IAAIP,EAAW,IAAQP,EAAIK,IAE3B,GAAiB,cAAbE,EAA0B,CAC5B,IAAIV,EAAS,GACblD,EAAiBkD,EAAQ,MACzBG,EAAIK,GAAQR,OACP,GAAiB,WAAbU,EAAuB,CAChC,IAAIE,EAAW,GAAGhO,OAAOoO,EAAgBP,EAAM,GAAI,KAAK7N,OAAO4N,GAC/D,MAAM,IAAIxO,MAAM,oEAAoEY,OAAOgO,EAAU,MAGvGT,EAAMA,EAAIK,GAEVA,EAAOQ,IADPP,GAKJ,OAAOxQ,GA0RP4Q,OAAQtB,EACRxM,KAnFF,SAAwBsM,EAAO9O,GAC7B,IAAI+O,EAAOrC,EAAWoC,GAEtB,OADA9O,EAAIwC,KAAKxB,MAAMhB,EAAK,IAAmB+O,IAChC/O,GAiFPwP,OAAQf,EACR3I,OAAQ8I,EAERhL,IAAKmJ,GA2CA,IACQ,EA1CR,WACL,IAAI4D,EAAetT,OAAOoH,OAAO,GAAI0K,GACrChD,EAAOwE,GAAc,SAAU/S,EAAOM,GACpCyS,EAAazS,GAAO,EAAMN,EAAMO,KAAK,KAAM,UAE7C,IAAIyS,EAAavT,OAAOoH,OAAO,GAAI+L,GACnCrE,EAAOyE,GAAY,SAAUhT,EAAOM,GAClC0S,EAAW1S,GAAO,EAAMN,MAE1B,IAAIiT,EAAWxT,OAAOoH,OAAO,GAAI0K,GAwBjC,OAvBAhD,EAAO0E,GAAU,SAAUjT,EAAOM,GAChC2S,EAAS3S,GAAO,EAAMN,MAsBjBP,OAAOoH,OAAOkM,EAAc,CACjCG,QAASF,EACTG,MAAOF,EACPG,QAtBF,SAAiBC,EAAQC,GACvB,IAAIC,EACAvG,EAEkB,mBAAXqG,GACTrG,EAAKqG,EACLE,EAAQ7E,MAER6E,EAAQF,EACRrG,EAAKsG,GAGP,IAAIE,EAA2B/T,OAAOoH,OAAO,GAAI0K,GAIjD,OAHAhD,EAAOiF,GAA0B,SAAUxT,EAAOM,GAChDkT,EAAyBlT,GAAO,EAAMN,EAAMO,KAAK,KAAMgT,OAElDvG,EAAGwG,IAOVC,GAAI,EACJ/E,cAAeA,IAGFgF,GC3bV,MAAMC,EAAS,mBACTC,EAAS,mBAGTC,EAAS,mBACTC,EAAU,oBAUVC,EAAgBjU,OAAO,2BACvBkU,EAAsB,CAACC,EAAQC,SACvB,IAAVA,EAAwBH,EAAgBG,ECRnD,SAASC,EAAeC,GAKpB,OAH4B,mBAAjBjN,QAAQC,KACTD,QAAQC,KAAK7G,KAAK4G,SAClBA,QAAQkN,IAAI9T,KAAK4G,UACbiN,GAoBlB,SAASE,EAAQC,EAAsBC,GACnC,OAAOD,IAjBSE,EAiByBD,GAhB3BE,OAAO,GAAGC,cAAgBF,EAAOxJ,MAAM,IADzD,IAAoBwJ,EA6BpB,SAASG,EAAiBL,GACtB,MAAQ,OAAMA,MAalB,SAASM,EAAeC,GACpB,MAAQ,KAAIA,MAShB,SAASC,EAAyBC,GAC9B,OAAO,YAA8B5I,GACjC,OAAOvG,KAAKoP,cAAcD,MAAe5I,IAKjD,SAAS8I,EAA+BC,GACpC,OAAO,WAEH,OADWtP,KAAKoP,cACNE,IAclB,SAASC,EAAsBC,EAAYC,GACvC,MAAMC,EAAeD,EAAcE,cAAcvK,SAVrD,SAA2BvJ,EAAUqB,GACjC,IAAI0S,EAAY/T,EAChB,KAAO+T,IAAcC,SAAS/U,WAC1BoC,EAAK0S,GACLA,EAAYhW,OAAOkW,eAAeF,GAWtCG,CAAkBN,EAAgBO,IAC9B,IAAK,IAAI9W,EAAI,EAAGA,EAAIwW,EAAarU,OAAQnC,IAAK,CAC1C,IAAI+W,GAAU,EACd,MAAMd,EAAaO,EAAaxW,GAC1BoC,EAAa1B,OAAOsW,yBACtBF,EAAIlV,UACJqU,QAEsB,IAAf7T,SACuB,IAAnBA,EAAWvB,KAClBuB,EAAWvB,IAAMsV,EAA+BF,GAChDvV,OAAOC,eAAe2V,EAAYL,EAAY7T,IAE9CkU,EAAWL,GAAcD,EACrBC,GAGRc,GAAU,GAEVA,GACAP,EAAarN,OAAOnJ,IAAK,MAazC,SAASiX,EAAgBC,GACrB,OACIA,SAEwB,mBAAjBA,EAAOC,MAEPD,EAAOC,QAEXD,EAgDX,MAAQvH,cAAaA,GAAKyH,EAK1B,SAASC,IAAyB,KAAEC,EAAF,QAAQC,GAAWC,GACjD,GAAIF,IAASxC,EAAQ,OAAO,EAE5B,GAAuB,iBAAZyC,EAMP,OAAO,EAGX,IAAKA,EAAQ1V,eAAe2V,GAAY,OAAO,EAC/C,MAAMC,EAAiBF,EAAQC,GAC/B,OAAuB,OAAnBC,QACmBvT,IAAnBuT,EAgBR,SAASC,GAAUhW,EAAQsC,GACvB,OAAOtD,OAAOiX,QAAQjW,GAAQ8E,OAAO,CAACoR,GAAYrW,EAAKN,MACnD2W,EAAUrW,GAAOyC,EAAK/C,GACf2W,GACR,IAIP,SAASC,GAAwBC,GAC7B,OAAKA,GAAgD,iBAArBA,EAGzBA,EAAiBC,UAFbD,ECnNf,MAAME,GAAQ,WASV,WAAY1B,EAAY2B,EAASC,GAC7BxX,OAAOoH,OAAOhB,KAAM,CAChBwP,aACA2B,QAASA,GAAW,KAGxBnR,KAAKqR,MAAQD,EAfP,EAkBHE,gBAAP,SAAuBnC,GACnBnP,KAAK2P,cAAgB3P,KAAK2P,cAAc/Q,OAAOuQ,IAnBzC,2BAsBVoC,KAAA,SAAKJ,EAASK,GACV,MAAMJ,EAAO,IAAKpR,KAAKqR,SAAUG,GACjC,OAAO,IAAIxR,KAAKjE,YAAYiE,KAAKwP,WAAY2B,EAASC,IAxBhD,EA2BVjM,SAAA,WACInF,KAAKyR,YAIL,MAAQ,6BAHSzR,KAAK0R,KACjB5T,IAAI,EAAG6T,QAAS3R,KAAKwP,WAAWoC,OAAOD,GAAIxM,YAC3CpH,KAAK,aA/BJ,EA0CV8T,WAAA,WACI,OAAO7R,KAAKyR,aA3CN,EAkDVK,aAAA,WACI,MAAQtC,WAAYuC,GAAe/R,KACnC,OAAOA,KAAKyR,YAAY3T,IAAK1C,GAAU,IAAI2W,EAAW3W,KApDhD,EA4DV4W,MAAA,WAEI,OADAhS,KAAKyR,YACEzR,KAAK0R,KAAKrW,QA9DX,EAuEV4W,OAAA,WACI,OAAOC,QAAQlS,KAAKgS,UAxEd,EAqFVG,GAAA,SAAGvS,GACC,MAAQ4P,WAAYuC,GAAe/R,KAE7B0R,EAAO1R,KAAKyR,YAClB,GAAI7R,GAAS,GAAKA,EAAQ8R,EAAKrW,OAC3B,OAAO,IAAI0W,EAAWL,EAAK9R,KA1FzB,EAoGVwS,MAAA,WACI,OAAOpS,KAAKmS,GAAG,IArGT,EA4GVE,KAAA,WACI,MAAMX,EAAO1R,KAAKyR,YAClB,OAAOzR,KAAKmS,GAAGT,EAAKrW,OAAS,IA9GvB,EAqHViX,IAAA,WACI,OAAOtS,KAAKuR,KAAKvR,KAAKmR,UAtHhB,EAgIVpF,OAAA,SAAOwG,GAKH,MAAMC,EACmB,iBAAdD,EACD3B,GAAU2B,EAAWpC,GACrBoC,EAEJE,EAAmB,CACrBjC,KAAMxC,EACNyC,QAAS+B,GAMb,OAAOxS,KAAKuR,KAAKvR,KAAKmR,QAAQvS,OAAO6T,KAlJ/B,EA6JVC,QAAA,SAAQH,GAKJ,MAAMC,EACmB,iBAAdD,EACD3B,GAAU2B,EAAWpC,GACrBoC,EACJI,EAAoB,CACtBnC,KAAMvC,EACNwC,QAAS+B,GAOb,OAAOxS,KAAKuR,KAAKvR,KAAKmR,QAAQvS,OAAO+T,KA/K/B,EAuLVlB,UAAA,WACI,QAAuC,IAA5BzR,KAAKwP,WAAWoD,QACvB,MAAM,IAAI5U,MACN,CACK,sBAAqBgC,KAAKwP,WAAWyB,8CACtC,4DACC,cAAajR,KAAKwP,WAAWyB,uCAChClT,KAAK,KAGf,IAAKiC,KAAK6S,WAAY,CAClB,MAAM,QAAED,EAAS3B,UAAW6B,GAAU9S,KAAKwP,WACrCuD,EAAY,CACdD,QACA3B,QAASnR,KAAKmR,SAElBnR,KAAK0R,KAAOkB,EAAQI,MAAMD,GAAWrB,KACrC1R,KAAK6S,YAAa,EAEtB,OAAO7S,KAAK0R,MA1MN,EA4NVuB,QAAA,SAAQrP,EAAWC,GACf,MAAMqP,EAAoB,CACtB1C,KFjPY,qBEkPZC,QAAS,CAAC7M,EAAWC,IAOzB,OAAO7D,KAAKuR,KAAKvR,KAAKmR,QAAQvS,OAAOsU,KAtO/B,EAiPVC,OAAA,SAAOtJ,GACH,MAAM,QAAE+I,EAAS3B,UAAW6B,GAAU9S,KAAKwP,WAE3CoD,EAAQQ,YAAY,CAChBC,OAAQvF,EACRkF,MAAO,CACHF,QACA3B,QAASnR,KAAKmR,SAElBV,QAAS5G,IAGb7J,KAAK6S,YAAa,GA7PZ,EAoQVS,OAAA,WACI,MAAM,QAAEV,EAAS3B,UAAW6B,GAAU9S,KAAKwP,WAE3CxP,KAAK8R,eAAelI,QACf2J,GAAUA,EAAMC,aAGrBZ,EAAQQ,YAAY,CAChBC,OAAQtF,EACRiF,MAAO,CACHF,QACA3B,QAASnR,KAAKmR,WAItBnR,KAAK6S,YAAa,GAnRZ,EAoTV/U,IAAA,WACI,MAAM,IAAIE,MACN,uGAtTE,EA+TV4L,QAAA,WACI,MAAM,IAAI5L,MACN,+GAjUE,6BA6RV,WACI,MAAM,IAAIA,MACN,8JA/RE,oBAwSV,WACIsQ,EACI,oGA1SE,KAuUd4C,GAASvB,cAAgB,CACrB,QACA,KACA,MACA,OACA,QACA,SACA,UACA,UACA,SACA,UAGWuB,UCtHAuC,OAlPF,WAUT,WAAYC,EAAQC,EAAIC,EAAOC,EAAeC,GAC1C9T,KAAK0T,OAASA,EACd1T,KAAK2T,GAAKA,EACV3T,KAAK4T,MAAQA,GAASD,EAAGI,gBACzB/T,KAAKgU,aAAehU,KAAK4T,MAEzB5T,KAAK6T,cAAgB3B,QAAQ2B,GAC7B7T,KAAK8T,WAAaA,GAAcjL,IAEhC7I,KAAKiU,UAAY,GAEjBjU,KAAKkU,OAASR,EAAOS,kBAErBnU,KAAKoU,mBAAqBpU,KAAKkU,OAAOpW,IAAK0R,IACvC,SAAS6E,IACL,OAAOC,QAAQC,UACX/E,EACArS,UACAkX,GAcR,OAXAC,QAAQ1Y,eACJyY,EAAkBvZ,UAClB0U,EAAW1U,WAEfwZ,QAAQ1Y,eAAeyY,EAAmB7E,GAE1C5V,OAAOC,eAAemG,KAAMwP,EAAWyB,UAAW,CAC9ClX,IAAK,IAAMsa,IAGfA,EAAkBG,QAAQxU,MACnBqU,IA1CN,2BA8CTI,gBAAA,SAAgBxD,GAIZ,OAHKjR,KAAKiU,UAAUhD,KAChBjR,KAAKiU,UAAUhD,GAAa,IAEzBjR,KAAKiU,UAAUhD,IAlDjB,EAqDTyD,aAAA,WACI,OAAO1U,KAAKiU,WAtDP,EAyDTU,aAAA,SAAa1D,EAAW2D,GACpB,MAAMC,EAAO7U,KAAKyU,gBAAgBxD,GAC7B4D,EAAKC,oBACND,EAAKC,kBAAoB,IAE7BF,EAAShL,QAAS+H,IACdkD,EAAKC,kBAAkBnD,IAAM,KA/D5B,EA+EToD,qBAAA,SAAqB9D,GACJjR,KAAKyU,gBAAgBxD,GAC7B+D,kBAAmB,GAjFnB,EAgGTC,oBAAA,SAAoBC,GAChBA,EAAQtL,QAAQ,EAAEkJ,EAAOqC,EAAMhb,MAC3B,MAAM0a,EAAO7U,KAAKyU,gBAAgB3B,GAC7B+B,EAAKO,kBACNP,EAAKO,gBAAkB,IAE3BP,EAAKO,gBAAgBD,GAAQ,IACrBN,EAAKO,gBAAgBD,IAAS,GAClChb,MAxGH,EAgITiZ,YAAA,SAAYiC,GACR,MAAMC,EAAKtV,KAAKuV,gBAAgBF,GAC1BtR,EAAS/D,KAAK2T,GAAGR,OAAOkC,EAAYC,EAAItV,KAAK4T,QAC7C,OAAE4B,EAAF,MAAU5B,EAAV,QAAiBnD,GAAY1M,EAEnC,GHlIe,YGkIXyR,EACA,MAAM,IAAIxX,MACL,sCAAqCwX,eAAoB/E,KAMlE,OAFAzQ,KAAK4T,MAAQA,EAENnD,GA7IF,EAgJTuC,MAAA,SAAMD,GACF,MAAMhP,EAAS/D,KAAK2T,GAAGX,MAAMD,EAAW/S,KAAK4T,OAI7C,OAFA5T,KAAKyV,qBAAqB1C,EAAWhP,GAE9BA,GArJF,EAwJTwR,gBAAA,SAAgBF,GACZ,MAAM,cAAExB,GAAkB7T,MACpB,OAAEqT,GAAWgC,EACnB,IAAI,WAAEvB,GAAe9T,KAIrB,MAHI,CAAC8N,EAAQC,GAAQ2H,SAASrC,KAC1BS,EAAajL,KAEV,CAAEiL,aAAYD,kBA/JhB,EAkKT4B,qBAAA,SAAqB1C,EAAWhP,GAC5B,MAAM,MAAE+O,EAAF,QAAS3B,GAAY4B,GACrB,KAAErB,GAAS3N,GAEX,YAAE4R,GAAgB3V,KAAK8S,GACvB8C,EAAc,IAAIC,IAAInE,EAAK5T,IAAKgY,GAAQA,EAAIH,KAE5CI,EAAwB5E,EAAQ6E,KAAMC,KACnC1F,GAAyB0F,EAAQN,KAOtCC,EAAYM,IAAID,EAAOxF,QAAQkF,KACxB,IAGLP,EAAkB,IAClB,QAAEF,GAAYlV,KAAK4T,MAAMd,GAC/B3B,EAAQvH,QAASqM,IACbrc,OAAO0F,KAAK4V,GAAStL,QAASuL,IAC1B,IAAK5E,GAAyB0F,EAAQd,GAClC,OAEJ,MAAMhb,EAAQ8b,EAAOxF,QAAQ0E,GAC7BC,EAAgBrW,KAAK,CAAC+T,EAAOqC,EAAMhb,QAIvC4b,EAMA/V,KAAK2U,aAAa7B,EAAO8C,GAClBR,EAAgB/Z,QAIvB2E,KAAK2U,aAAa7B,EAAO8C,GACzB5V,KAAKiV,oBAAoBG,IAMzBpV,KAAK+U,qBAAqBjC,IAnNzB,EA4NTqD,aAAA,WAKI,OAJA7H,EACI,gHAGGtO,KAAK4T,OAjOP,EAyOTlU,OAAA,WACI,MAAM,IAAI1B,MACN,mKA3OC,yCAmET,WACI,OAAOpE,OAAOiX,QAAQ7Q,KAAK0U,gBAAgBhV,OACvC,CAACqE,GAAStJ,EAAKN,MACPA,EAAM2a,oBACN/Q,EAAOtJ,GAAON,EAAM2a,mBAEjB/Q,GAEX,MA3EC,kCAoFT,WACI,OAAOnK,OAAOiX,QAAQ7Q,KAAK0U,gBAAgBhV,OACvC,CAACqE,GAAStJ,EAAKN,MACPA,EAAM6a,kBACNjR,EAAOhF,KAAKtE,GAETsJ,GAEX,MA5FC,2BA6GT,WACI,OAAOnK,OAAOiX,QAAQ7Q,KAAK0U,gBAAgBhV,OACvC,CAACqE,GAAStJ,EAAKN,MACPA,EAAMib,kBACNrR,EAAOtJ,GAAON,EAAMib,iBAEjBrR,GAEX,QArHC,K,mBCsEEqS,OAlEf,mGACIC,0BAAA,WACIzc,OAAOC,eACHmG,KAAKuT,MAAMzY,UACXkF,KAAK2O,UACL3O,KAAKsW,MAAMC,yBACPvW,KAAK2O,UACL3O,KAAKuT,MACLvT,KAAKwW,QACLxW,KAAKyW,gBATrB,EAcIC,4BAAA,WACI1W,KAAKuT,MAAMoD,cACP3W,KAAK2O,WACL3O,KAAKsW,MAAMM,2BACX5W,KAAK2O,UACL3O,KAAKuT,MACLvT,KAAKwW,QACLxW,KAAKyW,eArBjB,EAyBII,2BAAA,WAKI,GAJ4Bjd,OAAOsW,yBAC/BlQ,KAAKwW,QAAQ1b,UACbkF,KAAK8W,oBAGL,MAAM,IAAI9Y,OH2GlBiT,EGzGgBjR,KAAKuT,MAAMtC,UH0G3BtC,EGzGgB3O,KAAK2O,UH0GrBoI,EGzGgB/W,KAAKwW,QAAQvF,UH4GtB,CACF,iBG5GWjR,KAAK8W,qCH6GhB,aAAYC,kCACZ,YAAW9F,KAAatC,MAC3B5Q,KAAK,MAVX,IACIkT,EACAtC,EACAoI,EGlGInd,OAAOC,eACHmG,KAAKwW,QAAQ1b,UACbkF,KAAK8W,mBACL9W,KAAKsW,MAAMU,0BACPhX,KAAK2O,UACL3O,KAAKuT,MACLvT,KAAKwW,QACLxW,KAAKyW,gBAjDrB,EAsDIQ,6BAAA,WACIjX,KAAKwW,QAAQG,cACT3W,KAAK8W,oBACL9W,KAAKsW,MAAMY,4BACXlX,KAAK2O,UACL3O,KAAKuT,MACLvT,KAAKwW,QACLxW,KAAKyW,eA7DjB,GCHA,WACI,WAAYrF,GACRpR,KAAKsW,MAAQlF,EAAKkF,MAClBtW,KAAK2O,UAAYyC,EAAKzC,UACtB3O,KAAKuT,MAAQnC,EAAKmC,MAClBvT,KAAKmX,IAAM/F,EAAK+F,IAOZnX,KAAKsW,MAAMc,WAAWpX,KAAKuT,SAC3BvT,KAAKsW,MAAMS,YAAc,QAbrC,mBAkDIM,IAAA,WACIrX,KAAKqW,4BACDrW,KAAKsW,MAAMgB,8BACXtX,KAAK0W,8BAML1W,KAAKsW,MAAMiB,6BACXvX,KAAK6W,6BAEL7W,KAAKsW,MAAMkB,+BACXxX,KAAKiX,gCA/DjB,0BAiBI,WACI,QAA6B,IAAlBjX,KAAKyX,SAA0B,CACtC,MAAM,YAAEV,GAAgB/W,KAAKsW,MAIzBtW,KAAKyX,SAHJV,EAEsB,SAAhBA,EACS/W,KAAKuT,MAELvT,KAAKmX,IAAIpd,IAAIgd,GAJb,KAOxB,OAAO/W,KAAKyX,WA5BpB,wBA+BI,WACI,QAAkC,IAAvBzX,KAAK0X,cAA+B,CAC3C,MAAMC,EAAmB3X,KAAKsW,MAAMsB,oBAChC5X,KAAK2O,UACL3O,KAAKuT,OAKLvT,KAAK0X,cAHJC,EAGoB3X,KAAKmX,IAAIpd,IAAI4d,GAFb,KAK7B,OAAO3X,KAAK0X,gBA3CpB,8BA8CI,WACI,OAAO1X,KAAKsW,MAAMuB,sBAAsB7X,KAAKuT,WA/CrD,MCkCeuE,OAlCf,oDAKIC,SAAA,WACI,OAAO/X,KAAKjE,aANpB,EASIqb,WAAA,SAAW7D,GACP,OAAO,GAVf,EAaIqE,oBAAA,SAAoBjJ,EAAW4E,GAC3B,OAAO,MAdf,iCACI,WACI,OAAO6C,KAFf,wCAiBI,WACI,OAAO,IAlBf,uCAqBI,WACI,OAAO,IAtBf,yCAyBI,WACI,OAAO,IA1Bf,iBA6BI,WACI,OAAO,MA9Bf,KCmCA,SAAS4B,GAA4BrJ,EAAWsJ,GAC5C,MAAO,CACHle,MACI,MACI6Y,SAAW,CAACqF,GAAsBC,IAClClY,KAAK+X,YACD,CAACpJ,GAAYwJ,GAASnY,KAAKoY,QAEnC,OAAOF,EAAgBtG,OAAOuG,IAElChY,IAAIhG,GACA6F,KAAKmT,OAAO,CACR,CAACxE,GAAYwB,EAAgBhW,OA6E7C,SAASke,GACLC,EACAL,EACAN,EACAY,EACAC,GAEA,MAAO,CACHze,MACI,MACI6Y,SACI,CAAC0F,GAAwBG,EACzB,CAACR,GAAsBC,EACvB,CAACP,GAAmBe,IAExB1Y,KAAK+X,WAEHY,EAAYH,EAAUN,EAAkBO,EACxCG,EAAaJ,EAAUC,EAAoBP,EAE3CW,EAAuBL,EACvBD,EAAcO,GACdP,EAActT,KACd8T,EAAwBP,EACxBD,EAActT,KACdsT,EAAcO,GAEdE,EAAShZ,KAAKqQ,QAEd4I,EAAYP,EAAa3M,OAAO,CAClC,CAAC8M,GAAuBG,IAOtBE,EAAqB,IAAIrD,IAC3BoD,EAAUpH,aAAa/T,IAAK7B,GAAQA,EAAI8c,KAOtCI,EAAKP,EAAW7M,OAAQqN,GAC1BF,EAAmBpW,IACfsW,EAAmBR,EAAWjD,eAkFtC,OAtEAwD,EAAGjD,IAAM,YAAgBmD,GACrB,MAAMC,EAAW,IAAIzD,IAAIwD,EAASvb,IAAIqS,IAEhCoJ,EAAaN,EAAUlN,OAAQyN,GACjCF,EAASxW,IAAI0W,EAAQT,KAGzB,GAAIQ,EAAWtH,SAAU,CACrB,MAAMwH,EAAcF,EACf1H,aACA/T,IAAK0b,GAAYA,EAAQT,IAE9B,MAAM,IAAI/a,MACL,iCAAgC4a,EAAW3H,mBAAmBwI,YAAsBd,EAAU1H,8BAA8B+H,KAIrIM,EAAS1P,QAAS+H,IACd+G,EAAale,OAAO,CAChB,CAACue,GAAwBpH,EACzB,CAACkH,GAAuBG,OAapCG,EAAG7Y,MAAQ,WACP2Y,EAAU3F,UAWd6F,EAAG9Y,OAAS,YAAmBgZ,GAC3B,MAAMK,EAAc,IAAI7D,IAAIwD,EAASvb,IAAIqS,IAEnCwJ,EAAmBV,EAAUlN,OAAQyN,GACvCE,EAAY5W,IAAI0W,EAAQT,KAG5B,GAAIY,EAAiB3H,UAAY0H,EAAY9W,KAAM,CAE/C,MAAMgX,EAAsBD,EACvB9H,aACA/T,IAAK0b,GAAYA,EAAQT,IAExBc,EAAgB,IAAIH,GAAa3N,OAClC4F,IAAQiI,EAAoBlE,SAAS/D,IAG1C,MAAM,IAAI3T,MACL,gCAA+B4a,EAAW3H,mBAAmB4I,cAA0BlB,EAAU1H,8BAA8B+H,KAIxIW,EAAiBrG,UAGd6F,GAGXhZ,MACI,MAAM,IAAInC,MACN,+FClPD8b,OAff,YACI,WAAY1I,GAAM,aACd,sBACKA,KAAOA,GAAQ,GAEhB,EAAKA,KAAKrW,eAAe,gBACzB,EAAKgf,WAAa,EAAK3I,KAAK2I,YALlB,EADtB,6BAUIxD,yBAAA,SAAyB5H,EAAW4E,GAChC,ODDR,SAAwB5E,GACpB,MAAO,CACH5U,MACI,OAAOiG,KAAKoY,QAAQzJ,IAGxBxO,IAAIhG,GACA,OAAO6F,KAAKG,IAAIwO,EAAWxU,IAG/BL,YAAY,EACZyB,cAAc,GCVPye,CAAerL,IAX9B,GAA+BmJ,IC4DhBmC,OAzDf,YACI,cAAe1T,GAAM,MAEjB,GADA,qBACoB,IAAhBA,EAAKlL,QAAmC,iBAAZkL,EAAK,GAAiB,CAClD,MAAM6K,EAAO7K,EAAK,GAClB,EAAKwQ,YAAchG,GAAwBK,EAAK0H,IAChD,EAAKoB,YAAc9I,EAAK8I,YACxB,EAAKV,QAAUzI,GAAwBK,EAAKoI,SAC5C,EAAKjB,cAAgBnH,EAAKmH,cAC1B,EAAK4B,GAAK/I,EAAK+I,QAEd,EAAKpD,YAAa,EAAKmD,aAAe,CACnCnJ,GAAwBxK,EAAK,IAC7BA,EAAK,IAZI,SADzB,qCAkBIsR,sBAAA,SAAsBtE,GAClB,OAAOvT,KAAKka,aAAgC3G,EAAMtC,URsCrCmJ,cAAgB,OQzDrC,EAsBIlD,4BAAA,SAA4BvI,EAAW4E,EAAOiD,EAASC,GAEnD,OAAO,IADWzW,KAAK+X,WAChB,CAAcxE,EAAMtC,UAAWtC,IAxB9C,EAmCIyI,WAAA,SAAW7D,GACP,OAAOvT,KAAK+W,cAAgBxD,EAAMtC,WApC1C,gDA2BI,WACI,OAAO,IA5Bf,uCA+BI,WACI,OAAO,IAhCf,0BAuCI,WACI,kGACIoF,0BAAA,WACIzc,OAAOC,eACHmG,KAAKuT,MAAMzY,UACXkF,KAAKsW,MAAM6D,IAAMna,KAAK2O,UACtB3O,KAAKsW,MAAMC,yBACPvW,KAAK2O,UACL3O,KAAKuT,MACLvT,KAAKwW,QACLxW,KAAKyW,gBATrB,GAAwDL,QAxChE,GAAqC0B,ICctBuC,OAdf,mGACI9D,yBAAA,SAAyB5H,EAAW4E,EAAOiD,EAASC,GAChD,OAAOuB,GAA4BrJ,EAAW6H,EAAQvF,YAF9D,EAKI+F,0BAAA,SAA0BrI,EAAW4E,EAAOiD,EAASC,GACjD,OH2FJ6D,EG3FwC3L,EH4FxC2J,EG5FmD/E,EAAMtC,UH8FlD,CACHlX,MACI,MACI6Y,SAAW,CAAC0F,GAAwBG,IACpCzY,KAAK+X,WAET,OAAOU,EAAkB1M,OAAO,CAC5B,CAACuO,GAAoBta,KAAKqQ,WAGlClQ,MACI,MAAM,IAAInC,MAAM,kDAf5B,IACIsc,EACAhC,GGlGJ,wBASI,WACI,OAAO,MAVf,GAAgC2B,ICsGjBM,OAvGf,mGACIR,WAAA,WACI,MAAO,IAFf,EAKInC,oBAAA,SAAoBjJ,EAAW4E,GAC3B,OAAOvT,KAAKwZ,SAAW/K,EAAQ8E,EAAMtC,UAAWtC,IANxD,EASI4H,yBAAA,SAAyB5H,EAAW4E,EAAOiD,EAASC,GAChD,OAAO4B,GACH9E,EAAMtC,UACNuF,EAAQvF,UACRwF,EAAaxF,UACbjR,KAAKwa,iBAAiB7L,EAAW4E,EAAOiD,EAASC,IACjD,IAfZ,EAmBIO,0BAAA,SAA0BrI,EAAW4E,EAAOiD,EAASC,GACjD,OAAO4B,GACH9E,EAAMtC,UACNuF,EAAQvF,UACRwF,EAAaxF,UACbjR,KAAKwa,iBAAiB7L,EAAW4E,EAAOiD,EAASC,IACjD,IAzBZ,EA6BIS,4BAAA,SAA4BvI,EAAW4E,EAAOiD,EAASC,GAEnD,OAAO,IADWzW,KAAK+X,WAChB,CAAc,CACjBe,GAAIvF,EAAMtC,UACViJ,YAAavL,EACb6K,QAAS/C,EAAaxF,UACtBsH,cAAevY,KAAKwa,iBAChB7L,EACA4E,EACAiD,EACAC,MAvChB,EA4CIG,2BAAA,SAA2BjI,EAAW4E,EAAOiD,EAASC,GAElD,OAAO,IADWzW,KAAK+X,WAChB,CAAc,CACjBe,GAAItC,EAAQvF,UACZiJ,YAAavL,EACb6K,QAASxZ,KAAKwZ,QACdjB,cAAevY,KAAKwa,iBAChB7L,EACA4E,EACAiD,EACAC,GAEJ0D,GAAIna,KAAKma,MAxDrB,EAgEIK,iBAAA,SAAiB7L,EAAW4E,EAAOiD,EAASC,GACxC,GAAIzW,KAAKuY,cAAe,CACpB,MAAOkC,EAAYC,GAAc1a,KAAKuY,cAChCoC,EAASlE,EAAamE,OAAOH,GACnC,MAAO,CACH3B,GAAI6B,EAAOvD,WAAWZ,GAAWiE,EAAaC,EAC9CzV,KAAM0V,EAAOvD,WAAWZ,GAAWkE,EAAaD,GAIxD,GAAIlH,EAAMtC,YAAcuF,EAAQvF,UAO5B,MAAO,CACH6H,GAAI9J,EAAewH,EAAQvF,WAC3BhM,KAAM8J,EAAiBwE,EAAMtC,YAQrC,MAAM4J,EAAgCC,GAClClhB,OAAO0F,KAAKmX,EAAamE,QAAQG,KAAMC,GACnCvE,EAAamE,OAAOI,GAAe5D,WAAW0D,IAGtD,MAAO,CACHhC,GAAI+B,EAA6BrE,GACjCvR,KAAM4V,EAA6BtH,KAlG/C,+CA4DI,WACI,OAAO,MA7Df,GAAgC0G,ICejBgB,OAdf,mGACIpD,sBAAA,SAAsBtE,GAClB,OAAOvT,KAAKka,aAAe3G,EAAMtC,UAAUmJ,eAFnD,EAKI7D,yBAAA,SAAyB5H,EAAW4E,EAAOiD,EAASC,GAChD,OLsDR,YAAuClQ,GACnC,OAAOyR,MAA+BzR,GKvD3B2U,CAA2BvM,EAAW6H,EAAQvF,YAN7D,EASI+F,0BAAA,SAA0BrI,EAAW4E,EAAOiD,EAASC,GACjD,OL8D6B6D,EK9DM3L,EL8Da2J,EK9DF/E,EAAMtC,UL+DjD,CACHlX,MACI,MACI6Y,SAAW,CAAC0F,GAAwBG,IACpCzY,KAAK+X,WAET,OAAOU,EAAkB1e,IAAI,CACzB,CAACugB,GAAoBta,KAAKqQ,WAGlClQ,MACI,MAAM,IAAInC,MAAM,iDAZ5B,IAAqCsc,EAAmBhC,GKxExD,GAA8B2B,IC+B9B,SAAS9E,GAAK/D,GACV,OAAO,IAAI0I,GAAU1I,GAmDzB,SAAS+J,MAAM5U,GACX,OAAO,IAAI8T,MAAc9T,GAqF7B,SAAS6U,MAAQ7U,GACb,OAAO,IAAIgU,MAAchU,GAyB7B,SAAS8U,MAAY9U,GACjB,OAAO,IAAI0U,MAAY1U,GCtL3B,SAAS+U,GAAaC,GAClB,MAAM/L,EAAa+L,EAAcxD,YAC3B,YAAEpC,EAAF,UAAe1E,GAAczB,EAEnC,MAAO,CACHsD,MAAO7B,EACPE,QAAS,CACL,CACIX,KAAMxC,EACNyC,QAAS,CACL,CAACkF,GAAc4F,EAAclL,YAyBjD,MAAMmL,GAAK,WAMP,WAAYpgB,GACR4E,KAAKyb,YAAYrgB,GAPd,2BAUPqgB,YAAA,SAAYrgB,GACR,MAAMsgB,EAAW9hB,OAAOwB,GACxB4E,KAAKoY,QAAU,IAAKsD,GAEpB9hB,OAAO0F,KAAKoc,GAAU9R,QAAS+E,IAMrBA,KAAa3O,MACfpG,OAAOC,eAAemG,KAAM2O,EAAW,CACnC5U,IAAK,IAAMiG,KAAKoY,QAAQzJ,GACxBxO,IAAMhG,GAAU6F,KAAKG,IAAIwO,EAAWxU,GACpCoB,cAAc,EACdzB,YAAY,OAzBrB,EA+BAqL,SAAP,WACI,MAAQ,eAAcnF,KAAKiR,WAhCxB,EA+CAnQ,QAAP,WACI,MAAO,IAhDJ,EA0DA6T,aAAP,SAAoBgH,GAChB,QAA6B,IAAlB3b,KAAK4b,SACZ,MAAM,IAAI5d,MACN,CACK,6BAA4BgC,KAAKiR,kDAClC,6DACC,cAAajR,KAAKiR,uCACrBlT,KAAK,KAGfiC,KAAK4S,QAAQ+B,aAAa3U,KAAKiR,UAAW0K,IApEvC,EA6EA5G,qBAAP,WACI,QAA6B,IAAlB/U,KAAK4b,SACZ,MAAM,IAAI5d,MACN,CACK,qBAAoBgC,KAAKiR,4DAC1B,6DACC,cAAajR,KAAKiR,+CACrBlT,KAAK,KAGfiC,KAAK4S,QAAQmC,qBAAqB/U,KAAKiR,YAvFpC,EAiGAgE,oBAAP,SAA2BC,GACvB,QAA6B,IAAlBlV,KAAK4b,SACZ,MAAM,IAAI5d,MACN,CACK,iCAAgCgC,KAAKiR,kDACtC,6DACC,cAAajR,KAAKiR,8CACrBlT,KAAK,KAGfiC,KAAK4S,QAAQqC,oBACTC,EAAQpX,IAAI,EAAE4S,EAAWvW,KAAW,CAChC6F,KAAKiR,UACLP,EACAvW,MA/GL,EA4IAqa,QAAP,SAAe5B,GACX,KAAMA,aAAmBa,IACrB,MAAM,IAAIzV,MACN,0DAGRgC,KAAK4b,SAAWhJ,GAlJb,EAqKAxD,YAAP,WACI,MAAQK,cAAeoM,GAAkB7b,KACzC,OAAO,IAAI6b,EAAc7b,OAvKtB,EA6KA8b,qBAAP,WACI9b,KAAK+b,aAAU3e,EACf4C,KAAK2W,cAAgB,IA/KlB,EA8LAqF,aAAP,WACI,MAA4B,mBAAjBhc,KAAKic,SACZ3N,EACI,qEAEGtO,KAAKic,WAEZjc,KAAKic,SACL3N,EACI,qEAEGtO,KAAKic,SAEY,mBAAjBjc,KAAKc,QACLd,KAAKc,UAETd,KAAKc,SA9MT,EA0NAtG,OAAP,SAAc0hB,GACV,QAA6B,IAAlBlc,KAAK4b,SACZ,MAAM,IAAI5d,MACN,CACK,qBAAoBgC,KAAKiR,+CAC1B,6DACC,cAAajR,KAAKiR,iCACrBlT,KAAK,KAGf,MAAM3C,EAAQ,IAAK8gB,GAEbC,EAAe,GAEfC,EAAqBxiB,OAAO0F,KAAKU,KAAK4a,QACtCyB,EAA4BziB,OAAO0F,KAAKU,KAAK2W,eAEnDyF,EAAmBxS,QAASnP,IACxB,MAAM6b,EAAQtW,KAAK4a,OAAOngB,GACpB6hB,EAAcJ,EAAUnhB,eAAeN,GAC7C,GAAM6b,aAAiBiE,GAOZ+B,IAEPH,EAAa1hB,GAAOyhB,EAAUzhB,GAEzB6b,EAAM6D,WAQA/e,EAAMX,SAlBjB,GAAI6hB,EAAa,CACb,MAAMniB,EAAQ+hB,EAAUzhB,GACxBW,EAAMX,GAAO0V,EAAgBhW,QACtBmc,EAAMyD,aACb3e,EAAMX,GAAO6b,EAAMyD,WAAWmC,MAoB1CG,EAA0BzS,QAASnP,IAC/B,IAAK0hB,EAAaphB,eAAeN,GAAM,CACnC,MAAM6b,EAAQtW,KAAK2W,cAAclc,GAE7ByhB,EAAUnhB,eAAeN,IACzB6b,aAAiBiE,KAIjB4B,EAAa1hB,GAAOyhB,EAAUzhB,UACvBW,EAAMX,OAKzB,MAOMsO,EAAW,IADC/I,KANDA,KAAK4S,QAAQQ,YAAY,CACtCC,OdhVU,mBciVVP,MAAO9S,KAAKiR,UACZR,QAASrV,KAMb,OADA2N,EAASwT,kBAAkBJ,GACpBpT,GA/RJ,EA2SAyT,OAAP,SAAcN,GACV,QAA4B,IAAjBlc,KAAK4S,QACZ,MAAM,IAAI5U,MACN,CACK,qBAAoBgC,KAAKiR,+CAC1B,6DACC,cAAajR,KAAKiR,iCACrBlT,KAAK,KAIf,MAAM,YAAE4X,GAAgB3V,KACxB,GAAIkc,EAAUnhB,eAAe4a,GAAc,CACvC,MAAMhE,EAAKuK,EAAUvG,GACrB,GAAI3V,KAAKyc,SAAS9K,GAAK,CACnB,MAAM4B,EAAQvT,KAAK4R,OAAOD,GAE1B,OADA4B,EAAMJ,OAAO+I,GACN3I,GAIf,OAAOvT,KAAKxF,OAAO0hB,IAhUhB,EA6UAtK,OAAP,SAAcD,GACV,OAAO3R,KAAKjG,IAAI,CACZ,CAACiG,KAAK2V,aAAchE,KA/UrB,EA4VA8K,SAAP,SAAgB9K,GACZ,OAAO3R,KAAKiS,OAAO,CACf,CAACjS,KAAK2V,aAAchE,KA9VrB,EAyWAM,OAAP,SAAcM,GACV,QAA4B,IAAjBvS,KAAK4S,QACZ,MAAM,IAAI5U,MACN,CACK,uBAAsBgC,KAAKiR,sDAC5B,6DACC,cAAajR,KAAKiR,iCACrBlT,KAAK,KAIf,OAAOmU,QAAQlS,KAAK0c,kBAAkBnK,GAAWlX,SApX9C,EAgYAtB,IAAP,SAAWwY,GACP,MAEMb,EAAO1R,KAAK0c,kBAAkBnK,GACpC,GAAoB,IAAhBb,EAAKrW,OACL,OAAO,KAEX,GAAIqW,EAAKrW,OAAS,EACd,MAAM,IAAI2C,MACL,sCAAqCgC,KAAKiR,0BAA0BS,EAAKrW,WAIlF,OAAO,IAZW2E,KAYG0R,EAAK,KA7YvB,EAuZPqG,SAAA,WACI,OAAO/X,KAAKjE,aAxZT,EA+ZPsU,MAAA,WACI,OAAOrQ,KAAKoY,QAAQpY,KAAK+X,WAAWpC,cAhajC,EA6bA+G,kBAAP,SAAyBnK,GACrB,MAAMQ,EAAY,CACdD,MAAO9S,KAAKiR,WAUhB,OARIsB,IACAQ,EAAU5B,QAAU,CAChB,CACIX,KAAMxC,EACNyC,QAAS8B,KAIdvS,KAAK4S,QAAQI,MAAMD,GAAWrB,MAzclC,EAidPvM,SAAA,WACI,MAAMwT,EAAY3Y,KAAK+X,WAgBvB,MAAQ,GAfUY,EAAU1H,eACTrX,OAAO0F,KAAKqZ,EAAUiC,QAEpC9c,IAAK6Q,IAEF,GADcgK,EAAUiC,OAAOjM,aACV4L,GAAY,CAI7B,MAAQ,GAAE5L,OAHE3O,KAAK2O,GACZmD,eACAhU,IAAKyV,GAAUA,EAAMlD,SACGtS,KAAK,SAGtC,MAAQ,GAAE4Q,MADE3O,KAAKoY,QAAQzJ,OAG5B5Q,KAAK,UAjeP,EAgfP4e,OAAA,SAAO7B,GAEH,Ob1YR,SAA6Bne,EAAGC,GAC5B,MAAMggB,EAAahjB,OAAOiX,QAAQjX,OAAO+C,IAEzC,OAAIigB,EAAWvhB,SAAWzB,OAAO0F,KAAK1C,GAAGvB,QAIlCuhB,EAAWjf,MACd,EAAElD,EAAKN,KAAWyC,EAAE7B,eAAeN,IAAQmC,EAAEnC,KAASN,GakY/C0iB,CAAoB7c,KAAKoY,QAAS0C,EAAW1C,UAlfjD,EA6fPjY,IAAA,SAAI2c,EAAc3iB,GACd6F,KAAKmT,OAAO,CACR,CAAC2J,GAAe3iB,KA/fjB,EA0gBPgZ,OAAA,SAAO4J,GACH,MAAMpE,EAAY3Y,KAAK+X,WACvB,QAAiC,IAAtBY,EAAU/F,QACjB,MAAM,IAAI5U,MACN,CACK,qBAAoB2a,EAAU1H,+CAC/B,wFACFlT,KAAK,KAIf,MAAM8L,EAAW,IAAKkT,IAEhB,OAAEnC,EAAF,cAAUjE,GAAkBgC,EAE5BwD,EAAe,GAMrB,IAAK,MAAMa,KAAYnT,EAAU,CAG7B,GAFoB+Q,EAAO7f,eAAeiiB,GAEzB,CACb,MAAM1G,EAAQsE,EAAOoC,GAEjB1G,aAAiB+D,IAAc/D,aAAiB2E,GAEhDpR,EAASmT,GAAY7M,EAAgBtG,EAASmT,IACvC1G,aAAiBiE,KAExB4B,EAAaa,GAAYnT,EAASmT,GAE7B1G,EAAM6D,WAQAtQ,EAASmT,SAGrB,GAAIrG,EAAc5b,eAAeiiB,GAAW,CACjCrG,EAAcqG,aACPzC,KAEjB4B,EAAaa,GAAYnT,EAASmT,UAC3BnT,EAASmT,KAK5B,MAAMC,EAAe,IACdjd,KAAKoY,WACLvO,GAGDqT,EAAe,IAAIvE,EAAUsE,GAE9Bjd,KAAK2c,OAAOO,KACbld,KAAKyb,YAAYwB,GACjBtE,EAAU/F,QAAQQ,YAAY,CAC1BC,OAAQvF,EACRkF,MAAOsI,GAAatb,MACpByQ,QAAS5G,KAKjB7J,KAAKuc,kBAAkBJ,IAllBpB,EA0lBPgB,iBAAA,WACInd,KAAKyb,YAAYzb,KAAKod,MA3lBnB,EAomBP9J,OAAA,WACI,MAAMqF,EAAY3Y,KAAK+X,WACvB,QAAiC,IAAtBY,EAAU/F,QACjB,MAAM,IAAI5U,MACN,CACK,qBAAoB2a,EAAU1H,+CAC/B,wFACFlT,KAAK,KAIfiC,KAAKwT,YACLmF,EAAU/F,QAAQQ,YAAY,CAC1BC,OAAQtF,EACRiF,MAAOsI,GAAatb,SAlnBrB,EA4nBPuc,kBAAA,SAAkBc,GACd,MAAM1E,EAAY3Y,KAAK+X,YACjB,OAAE6C,EAAF,cAAUjE,EAAV,UAAyB1F,GAAc0H,EAE7C/e,OAAO0F,KAAK+d,GAAWzT,QAASnQ,IAC5B,MAAM+e,GAAWoC,EAAO7f,eAAetB,GACjC6c,EAAQK,EAAcld,GACtB+F,EAAS6d,EAAU5jB,GAEzB,IAAKgD,MAAMD,QAAQgD,GACf,MAAM,IAAI8F,UACL,gDAA+C2L,KAAaxX,gCAAmC+F,MAIxG,MAAM8d,EAAmB9d,EAAO1B,IAAIqS,GAC9BoN,EAAY,IAAI,IAAI1H,IAAIyH,IAE9B,GAAIA,EAAiBjiB,SAAWkiB,EAAUliB,OACtC,MAAM,IAAI2C,MACL,uCAAsCsf,SAAwB3E,EAAU1H,aAAaxX,WAI9F,MAAMke,EACFrB,EAAMkD,SAAW/K,EAAQkK,EAAU1H,UAAWxX,GAC5Cif,EAAeC,EAAU/F,QAAQ+E,GAEvC,IAAI6F,EACAC,EAECjF,IAGEvT,KAAMwY,EAAS3E,GAAI0E,GAAclH,EAAMiC,iBAFvCtT,KAAMuY,EAAW1E,GAAI2E,GAAYnH,EAAMiC,eAK9C,MAMMmF,EbljBlB,SAA0BC,EAAWC,GACjC,MAAMC,EAAcF,EAAU5R,OAAQ+R,GAASF,EAAUlI,SAASoI,IAC5DC,EAAcJ,EAAU5R,OAAQ+R,IAAUD,EAAYnI,SAASoI,IAC/DE,EAAWJ,EAAU7R,OAAQ+R,IAAUD,EAAYnI,SAASoI,IAElE,OAAIC,EAAY1iB,QAAU2iB,EAAS3iB,OACxB,CACHiY,OAAQyK,EACR7H,IAAK8H,GAGN,KauiBqBC,CANDvF,EAAa3M,OAC3ByN,GAAYA,EAAQgE,KAAexd,KAAK2Y,EAAUhD,cAElD9D,aACA/T,IAAKsf,GAAQA,EAAIK,IAE2BH,GAEjD,GAAII,EAAa,CACb,MAAQpK,OAAQ4K,EAAahI,IAAKoD,GAAaoE,EAC3CQ,EAAY7iB,OAAS,GACrB2E,KAAKsW,EAAM6D,IAAM1gB,GAAM4G,UAAU6d,GAGjC5E,EAASje,OAAS,GAClB2E,KAAKsW,EAAM6D,IAAM1gB,GAAMyc,OAAOoD,OAhrBvC,EA0rBP9F,UAAA,WACI,MAAM,cAAEmD,GAAkB3W,KAAK+X,WAE/B,IAAK,MAAMtd,KAAOkc,EAAe,CAC7B,MAAML,EAAQK,EAAclc,GAC5B,GAAI6b,aAAiBiE,GAAY,CAG7Bva,KADsBsW,EAAM6D,IAAM1f,GACd6F,aACjB,GAAIgW,aAAiB+D,GAAY,CACpC,MAAM8D,EAAYne,KAAKvF,GACnB0jB,EAAUlM,UACVkM,EAAUhL,OAAO,CAAE,CAACmD,EAAM4D,aAAc,YAErC5D,aAAiB2E,IAGN,OAAdjb,KAAKvF,KACLuF,KAAKvF,GAAK6b,EAAM4D,aAAe,QA5sBxC,EA4tBAkE,MAAP,SAAazM,GAIT,OAHArQ,QAAQC,KACJ,2EAEGvB,KAAKyc,SAAS9K,IAhuBlB,EAuuBPwE,aAAA,WACI,MAAM,IAAInY,MACN,qGAzuBD,sBA4aP,WACI,MAAM2a,EAAY3Y,KAAK+X,WAGvB,OAAOY,EAAU+D,kBAAkB,CAC/B,CAAC/D,EAAUhD,aAAc3V,KAAKqQ,UAC/B,MAlbA,wBAyHP,WACI,QAA6B,IAAlBrQ,KAAK4b,SACZ,MAAM,IAAI5d,MACN,CACK,oBAAmBgC,KAAKiR,qDACzB,+DACC,cAAajR,KAAKiR,sCACrBlT,KAAK,KAGf,OAAOiC,KAAK4S,QAAQe,GAAG0K,SAASre,KAAKiR,WAAW0E,cAnI7C,mBA2JP,WACI,OAAO3V,KAAK4b,WA5JT,iBAqLP,WACI,OAAO5b,KAAKoP,kBAtLT,KA+uBXoM,GAAMZ,OAAS,CACXjJ,GAAIwD,MAERqG,GAAM7E,cAAgB,GACtB6E,GAAM/L,cAAgByB,GAEPsK,U,gFCvyBf,MAAM8C,GAAwB,CAC1B3I,YAAa,KACb4I,QAAS,QACTC,QAAS,YACT5D,OAAQ,IA8mBG6D,OA7iBf,WAYI,WAAYjN,GACR5X,OAAOoH,OAAOhB,KAAMse,GAAuB9M,GAbnD,2BAyBIkN,SAAA,SAASC,EAAQhN,GACb,OAAOgN,EAAO3e,KAAKwe,SAAS7M,IA1BpC,EA6BIiN,UAAA,SAAUD,EAAQhD,GACd,MAAM7d,EAAM6gB,EAAO3e,KAAKwe,SACxB,OAAO7C,EAAI7d,IAAK6T,GAAO7T,EAAI6T,KA/BnC,EAkCI8K,SAAA,SAASkC,EAAQhN,GACb,OAAOgN,EAAO3e,KAAKwe,SAASzjB,eAAe4W,IAnCnD,EAsCIkN,aAAA,SAAaF,GACT,OAAOA,EAAO3e,KAAKue,UAvC3B,EA0CIO,WAAA,SAAWH,GACP,OAAO3e,KAAK4e,UAAUD,EAAQ3e,KAAK6e,aAAaF,KA3CxD,EA8CII,SAAA,SAASJ,GACL,OAAO3e,KAAKgf,QAAQL,EAAQ,UA/CpC,EAkDIM,SAAA,SAAS3J,EAAIqJ,EAAQO,GACjB,OAAOlf,KAAKmf,QAAQ7J,EAAIqJ,EAAQ,QAASO,IAnDjD,EAsDIE,OAAA,SAAOzN,GACH,OAAOA,EAAK,GAvDpB,EA8DIoC,cAAA,WAeI,MAAO,IAdS,CACZ,CAAC/T,KAAKue,SAAU,GAChB,CAACve,KAAKwe,SAAU,IAchBtJ,QAZgBtb,OAAO0F,KAAKU,KAAK4a,QAChC7O,OAAQoJ,GAASA,IAASnV,KAAK2V,aAC/B5J,OAAQoJ,GAASnV,KAAK4a,OAAOzF,GAAMvV,OACnCF,OACG,CAACwV,EAASC,KAAV,IACOD,EACH,CAACC,GAAO,KAEZ,IAKJkK,KAAM,KAhFlB,EAoFIF,QAAA,SAAQ7J,EAAIqJ,EAAQlkB,EAAKN,GACrB,MAAM,WAAE2Z,EAAF,cAAcD,GAAkByB,EACtC,GAAIzB,EAAe,CAEf,OADYvD,EAAIjD,QAAQpB,MAAM,CAAC,OAAQxR,GAAMN,EAAOwkB,GAIxD,OAAOrO,EAAIhD,MAAMrB,MAAM6H,EAAY,CAAC,OAAQrZ,GAAMN,EAAOwkB,IA3FjE,EA8FIK,QAAA,SAAQL,EAAQlkB,GACZ,OAAOkkB,EAAOU,KAAK5kB,IA/F3B,EAkGIuY,MAAA,SAAM2L,EAAQxN,GACV,GAAuB,IAAnBA,EAAQ9V,OACR,OAAO2E,KAAK8e,WAAWH,GAG3B,MAAM,YAAEhJ,GAAgB3V,KAElBsf,EAA0B1a,KAAOuM,EAAU8E,GACzC1F,GAAyB0F,EAAQN,GAC1B,EdiCvB,UAAoC,KAAEnF,IAClC,MAAO,CAACxC,EAAQC,GAASyH,SAASlF,Gc/BtB+O,CAA2BtJ,GACpB,EAGJ,GAGLuJ,EAAU,CAAC9N,EAAMuE,KACnB,MAAM,KAAEzF,EAAF,QAAQC,GAAYwF,EAC1B,IAAKvE,EAAM,CAKP,GAAInB,GAAyB0F,EAAQN,GAAc,CAK/C,MAAMhE,EAAKlB,EAAQkF,GACb8J,EAAmB7lB,OAAO0F,KAAKmR,GAAS/Q,OAC1C,CAACggB,EAAeC,KACRA,IAAehK,IACf+J,EAAcC,GAAclP,EAAQkP,IAEjCD,GAEX,IAEE/D,EAAM3b,KAAKyc,SAASkC,EAAQhN,GAAM,CAACA,GAAM,GAC/C,OAAI/X,OAAO0F,KAAKmgB,GAAkBpkB,OAKvBmkB,EAAQxf,KAAK4e,UAAUD,EAAQhD,GAAM,IACrC1F,EACHxF,QAASgP,IAOVzf,KAAK4e,UAAUD,EAAQhD,GAElC,GAAInL,IAASxC,GAA6B,iBAAZyC,EAAsB,CAChD,MAAMyE,EAAUtb,OAAOiX,QAAQ8N,EAAOzJ,SAChCE,EAAkB,GAClBwK,EAAa,GAiBnB,GAhBA1K,EAAQtL,QAAQ,EAAEuL,EAAMvV,MAChB2Q,GAAyB0F,EAAQd,IAK7BvV,EAAM7E,eAAe0V,EAAQ0E,MAC7BC,EAAgBrW,KAAKa,EAAM6Q,EAAQ0E,KACnCyK,EAAW7gB,KAAKoW,MAQxBC,EAAgB/Z,OAAQ,CACxB,MAAMwkB,EAAYzK,EAAgB1W,MAC5BohB,EAAa1K,EAAgB1V,OAC/B,CAACqE,EAAQnE,KACL,MAAMmgB,EAAW,IAAIlK,IAAIjW,GACzB,OAAOmE,EAAOgI,OACV8J,IAAI/a,UAAUgI,IACdid,IAGRF,GAEEJ,EAAmB7lB,OAAO0F,KAAKmR,GAAS/Q,OAC1C,CAACsgB,EAAmBL,KACXC,EAAWlK,SAASiK,KACrBK,EAAkBL,GACdlP,EAAQkP,IAETK,GAEX,IAEJ,OAAIpmB,OAAO0F,KAAKmgB,GAAkBpkB,OAKvBmkB,EAAQxf,KAAK4e,UAAUD,EAAQmB,GAAa,IAC5C7J,EACHxF,QAASgP,IAOVzf,KAAK4e,UAAUD,EAAQmB,IAKtC,OAAON,EAAQxf,KAAK8e,WAAWH,GAAS1I,GAG5C,OAAQzF,GACJ,KAAKxC,EACD,OAAOjC,KAAO2F,EAAMjB,GAExB,KAAKxC,EACD,OAAOgS,KAAOvO,EAAMjB,GAExB,If3SQ,qBe2SO,CACX,MAAO7M,EAAWC,GAAU4M,EAC5B,OAAOwC,KAAQvB,EAAM9N,EAtPzC,SAAyBC,GACrB,QAAezG,IAAXyG,EACA,OAEJ,MAAMqc,EAAWC,GACT,CAAC,QAAQ,GAAOzK,SAASyK,GAClB,OAEJ,MAEX,OAAO1jB,MAAMD,QAAQqH,GAAUA,EAAO/F,IAAIoiB,GAAWA,EAAQrc,GA4Obuc,CAAgBvc,IAEpD,QACI,OAAO6N,IAInB,OAAO4N,EAAwB5f,OAAO8f,OAASpiB,IA5OvD,EAwPIyP,OAAA,SAAOyI,EAAIqJ,EAAQ0B,GACf,MAAM,WAAEvM,EAAF,cAAcD,GAAkByB,EAEhC8I,EAAQiC,EAAMtlB,eAAeiF,KAAK2V,aAExC,IAAI2K,EAAe3B,EAGnB,MAAOO,EAAUvN,GAhTzB,SAAqB4O,EAAUC,GAC3B,IACIC,EACAC,EAFAC,EAAUJ,EAgBd,YAZgBnjB,IAAZujB,IACAA,GAAW,QAGMvjB,IAAjBojB,GACAC,EAASE,EAAU,EACnBD,EAAQD,IAERA,EAAS7Z,KAAKC,IAAI8Z,EAAU,EAAGH,GAC/BE,EAAQF,GAGL,CACHC,EACAC,GA6RuBE,CACnB5gB,KAAK+e,SAASJ,GACd0B,EAAMrgB,KAAK2V,cAEf2K,EAAetgB,KAAKif,SAAS3J,EAAIqJ,EAAQO,GAEzC,MAAM2B,EAAazC,EACbiC,EACA/P,EAAIhD,MAAMnN,IAAI2T,EAAY9T,KAAK2V,YAAahE,EAAI0O,GAEhDS,EAAoBlnB,OAAO0F,KAAKghB,EAAapL,SAC9CnJ,OACIgV,GACGV,EAAMtlB,eAAegmB,IAA6B,OAAlBV,EAAMU,IAE7CjjB,IAAKijB,GAAW,CAACA,EAAQV,EAAMU,KAEpC,GAAIlN,EAYA,OAXAvD,EAAIjD,QAAQtO,KAAK4S,EAAI2O,EAAatgB,KAAKue,UACvCjO,EAAIjD,QAAQlN,IAAIwR,EAAIkP,EAAYP,EAAatgB,KAAKwe,UAElDsC,EAAkBlX,QAAQ,EAAEuL,EAAMhb,MAC9B,MAAM6mB,EAAYV,EAAapL,QAAQC,GACnC6L,EAAUjmB,eAAeZ,GACzBmW,EAAIjD,QAAQtO,KAAK4S,EAAIqP,EAAU7mB,IAE/BmW,EAAIjD,QAAQlN,IAAIhG,EAAO,CAACwX,GAAKqP,KAG9B,CACHpN,MAAO0M,EACPW,QAASJ,GAIjB,MAAMK,EAAc5Q,EAAIhD,MAAM3B,MAC1BmI,EACAgN,EAAkBphB,OACd,CAACyhB,GAAWhM,EAAMhb,MACdgnB,EAAShM,GAAQ7E,EAAIhD,MAAM3B,MACvBmI,EACA,CACI,CAAC3Z,GAAQmW,EAAIhD,MAAMvO,KACf+U,EACAnC,EACAwP,EAAShM,GAAMhb,IAAU,KAGjCgnB,EAAShM,IAENgM,GAEX,IAAKb,EAAapL,UAEtBoL,EAAapL,SAuBjB,MAAO,CACHtB,MArBctD,EAAIhD,MAAM3B,MACxBmI,EACA,CACI,CAAC9T,KAAKue,SAAUjO,EAAIhD,MAAMvO,KACtB+U,EACAnC,EACA2O,EAAatgB,KAAKue,UAEtB,CAACve,KAAKwe,SAAUlO,EAAIhD,MAAM3B,MACtBmI,EACA,CACI,CAACnC,GAAKkP,GAEVP,EAAatgB,KAAKwe,UAEtBtJ,QAASgM,GAEbZ,GAKAW,QAASJ,IA/UrB,EA6VI1N,OAAA,SAAOmC,EAAIqJ,EAAQjN,EAAM7H,GACrB,MAAM,WAAEiK,EAAF,cAAcD,GAAkByB,EAShCnV,EAAM0T,EAAgBvD,EAAIjD,QAAQlN,IAAMmQ,EAAIhD,MAAMnN,IAAI2T,GAEtDsN,EAAexnB,OAAO0F,KAAKqf,EAAOzJ,SAASnJ,OAAQoJ,GACrDtL,EAAS9O,eAAeoa,IAEtBkM,EAAgB,GAChBC,EAAmB,GAEnBC,EAAU7P,EAAKhS,OAAO,CAAC5B,EAAKgY,KAC9B,MAAM0L,EAAiBJ,EAAa1hB,OAChC,CAAC+hB,EAAUtM,KAAX,IACOsM,EACH,CAACtM,GAAOW,EAAIX,KAEhB,IAEEpR,EAvBY+R,KACJjC,EACRvD,EAAIjD,QAAQ1B,MACZ2E,EAAIhD,MAAM3B,MAAMmI,IACTjK,EAAUiM,GAmBR4L,CAAa5L,GACtB6L,EAAiBP,EAAa1hB,OAChC,CAAC+hB,EAAUtM,KAAX,IACOsM,EACH,CAACtM,GAAOpR,EAAOoR,KAEnB,IAEExD,EAAK5N,EAAO/D,KAAK2V,aACjBiM,EAAUzhB,EAAIwR,EAAI5N,EAAQjG,GAiBhC,OAhBAsjB,EAAaxX,QAASuL,IAClB,MAAQ,CAACA,GAAO0M,GAAcL,GACtB,CAACrM,GAAO2M,GAAcH,EAC1BE,IAAcC,IAIdD,SAEAP,EAAiBviB,KAAK,CAACoW,EAAM0M,EAAWlQ,IAE1B,OAAdmQ,GAEAT,EAActiB,KAAK,CAACoW,EAAM2M,EAAWnQ,OAGtCiQ,GACRjD,EAAO3e,KAAKwe,UAEf,IAAI0C,EAAcvC,EAAOzJ,QA2DzB,OA1DIrB,GACAyN,EAAiB1X,QAAQ,EAAEuL,EAAMhb,EAAOwX,MACpC,MAAMpV,EAAM2kB,EAAY/L,GAAMhb,GACxBsS,EAAMlQ,EAAI6F,QAAQuP,GACxBrB,EAAIjD,QAAQhL,OAAOoK,EAAK,EAAG,GAAIlQ,KAEnC8kB,EAAczX,QAAQ,EAAEuL,EAAMhb,EAAOwX,MACjCrB,EAAIjD,QAAQtO,KAAK4S,EAAIuP,EAAY/L,GAAMhb,QAGvCknB,EAAchmB,SACd6lB,EAAc5Q,EAAIhD,MAAM3B,MACpBmI,EACAuN,EAAc3hB,OACV,CAACyhB,GAAWhM,EAAMhb,EAAOwX,MACrBwP,EAAShM,GAAQ7E,EAAIhD,MAAM3B,MACvBmI,EACA,CACI,CAAC3Z,GAAQmW,EAAIhD,MAAMvO,KACf+U,EACAnC,EACAwP,EAAShM,GAAMhb,IAAU,KAGjCgnB,EAAShM,IAENgM,GAEX,IAAKD,IAETA,IAGJI,EAAiBjmB,SACjB6lB,EAAc5Q,EAAIhD,MAAM3B,MACpBmI,EACAwN,EAAiB5hB,OACb,CAACyhB,GAAWhM,EAAMhb,EAAOwX,MACrBwP,EAAShM,GAAQ7E,EAAIhD,MAAM3B,MACvBmI,EACA,CACI,CAAC3Z,GAAQmW,EAAIhD,MAAMvB,OACf+H,EACCiO,GAAUA,IAAUpQ,EACrBwP,EAAShM,GAAMhb,KAGvBgnB,EAAShM,IAENgM,GAEX,IAAKD,IAETA,KAKL5Q,EAAIhD,MAAM3B,MACbmI,EACA,CACI,CAAC9T,KAAKwe,SAAU+C,EAChBrM,QAASgM,GAEbvC,IArdZ,EAgeIrL,OAAA,SAAOgC,EAAIqJ,EAAQjN,GACf,MAAM,WAAEoC,EAAF,cAAcD,GAAkByB,GAEhC,QAAEiJ,EAAF,QAAWC,GAAYxe,KACvBzD,EAAMoiB,EAAOJ,GAEbL,EAAcxM,EAAK5T,IAAKgY,GAAQA,EAAI9V,KAAK2V,cAC/C,GAAI9B,EAiBA,OAhBAqK,EAAYtU,QAAS+H,IACjB,MAAMlF,EAAMlQ,EAAI6F,QAAQuP,GACxBrB,EAAIjD,QAAQhL,OAAOoK,EAAK,EAAG,GAAIlQ,GAC/B+T,EAAIjD,QAAQxB,KAAK8F,EAAIgN,EAAOH,MAGhC5kB,OAAO4F,OAAOmf,EAAOzJ,SAAStL,QAASoX,GACnCpnB,OAAO4F,OAAOwhB,GAAWpX,QAASoY,GAC9B9D,EAAYtU,QAAS+H,IACjB,MAAMlF,EAAMuV,EAAW5f,QAAQuP,IAClB,IAATlF,GACA6D,EAAIjD,QAAQhL,OAAOoK,EAAK,EAAG,GAAIuV,OAKxCrD,EAGX,MAAMuC,EAAc5Q,EAAIhD,MAAM3B,MAC1BmI,EACAla,OAAOiX,QAAQ8N,EAAOzJ,SAASxV,OAC3B,CAACyhB,GAAWhM,EAAM6L,MACdG,EAAShM,GAAQ7E,EAAIhD,MAAM3B,MACvBmI,EACAla,OAAOiX,QAAQmQ,GAAWthB,OACtB,CAACuiB,GAAe9nB,EAAO6nB,MACnBC,EAAa9nB,GAASmW,EAAIhD,MAAMvB,OAC5B+H,EACCnC,IAAQuM,EAAYxI,SAAS/D,GAC9BqQ,GAEGC,GAEX,IAAKd,EAAShM,KAElBgM,EAAShM,IAENgM,GAEX,IAAKxC,EAAOzJ,UAEhByJ,EAAOzJ,SAGX,OAAO5E,EAAIhD,MAAM3B,MACbmI,EACA,CACI,CAACyK,GAAUjO,EAAIhD,MAAMvB,OACjB+H,EACCnC,IAAQuM,EAAYxI,SAAS/D,GAC9BgN,EAAOJ,IAEX,CAACC,GAAUlO,EAAIhD,MAAMzB,KACjBiI,EACAoK,EACAS,EAAOH,IAEXtJ,QAAS5E,EAAIhD,MAAM3B,MACfmI,EACAoN,EACAvC,EAAOzJ,UAGfyJ,IAxiBZ,KCxEA,MAAMuD,GAAmB,GAmBzB,SAASlP,GAAMmP,EAAQpP,EAAWa,GAC9B,MAAQd,MAAOsP,EAAT,QAAoBjR,GAAY4B,EAGtC,MAAO,CACHrB,KAHUyQ,EAAOC,GACFpP,MAAMY,EAAMwO,GAAYjR,IAO/C,SAASgC,GAAOgP,EAAQ9M,EAAYC,EAAI1B,GACpC,MAAM,OAAEP,EAAF,QAAU5C,GAAY4E,EAE5B,IAAI+M,EACAC,EACAC,EAEJ,GhBxCkB,qBgBwCdjP,EAAmB,GAChBP,MAAOsP,GAAc/M,GACxB,MAAMvC,EAAQqP,EAAOC,GACfG,EAAiB3O,EAAMwO,GACvBre,EAAS+O,EAAMjG,OAAOyI,EAAIiN,EAAgB9R,GAChD4R,EAAiBte,EAAO6P,MACxB0O,EAAgBve,EAAOkd,YACpB,CACH,MAAQjO,MAAOD,GAAcsC,IAC1BvC,MAAOsP,GAAcrP,GACxB,MAAM,KAAErB,GAASsB,GAAMmP,EAAQpP,EAAWa,GAEpCd,EAAQqP,EAAOC,GACfG,EAAiB3O,EAAMwO,GAE7B,GAAI/O,IAAWvF,EACXuU,EAAiBvP,EAAMK,OAAOmC,EAAIiN,EAAgB7Q,EAAMjB,GAExD6R,EAAgBtP,GAAMmP,EAAQpP,EAAWa,GAAOlC,SAC7C,IAAI2B,IAAWtF,EAKlB,MAAM,IAAI/P,MAAO,0CAAyCqV,GAJ1DgP,EAAiBvP,EAAMQ,OAAOgC,EAAIiN,EAAgB7Q,GAElD4Q,EAAgB5Q,GAOxB,MAAO,CACH8D,OhBhEe,UgBiEf5B,MA5DR,SAA2BwO,EAAWI,EAAelN,EAAI1B,GACrD,MAAM,WAAEE,EAAF,cAAcD,GAAkByB,EAEtC,OAAIzB,GACAD,EAAMwO,GAAaI,EACZ5O,GAGJtD,EAAIhD,MAAMnN,IAAI2T,EAAYsO,EAAWI,EAAe5O,GAiDvC6O,CAAkBL,EAAWC,EAAgB/M,EAAI1B,GAIjEnD,QAAS6R,GAnEjB1oB,OAAOC,eAAeqoB,GhBKI,gCgBL0B,CAChDpoB,YAAY,EACZK,OAAO,IAsGIuoB,OA5BR,SAAwBC,GAC3B,MAAQR,OAAQS,GAAeD,EACzBR,EAASvoB,OAAOiX,QAAQ+R,GAAYljB,OACtC,CAAC5B,GAAMskB,EAAWS,MAAlB,IACO/kB,EACH,CAACskB,GAAY,IAAI3D,GAAMoE,KAE3B,IAYJ,MAAO,CACH9O,cAVkB,IAClBna,OAAOiX,QAAQsR,GAAQziB,OACnB,CAAC5B,GAAMskB,EAAWtP,MAAlB,IACOhV,EACH,CAACskB,GAAYtP,EAAMiB,kBAEvBmO,IAKJlP,MAAOA,GAAMtY,KAAK,KAAMynB,GACxBhP,OAAQA,GAAOzY,KAAK,KAAMynB,GAE1B9D,SAAW+D,GAAcD,EAAOC,K,ICzGnBU,G,WACjB,YAAY,OAAEC,EAAF,IAAU5L,IAClBnX,KAAKgjB,QAAUD,EACf/iB,KAAKijB,KAAO9L,EACZnX,KAAKe,YAAcoN,E,mCAGvB,WAEI,MAAO,IADUnO,KAAKgjB,QAAUhjB,KAAKgjB,QAAQE,UAAY,GACpCljB,KAAKvF,O,eAG9B,WACI,OAAOuF,KAAKijB,O,kBAGhB,WACI,OAAOjjB,KAAKgjB,Y,KCjBCG,G,YACjB,YAAY,MAAE5P,KAAUvP,IAAS,aAC7B,cAAMA,IAAN,MACKof,OAAS7P,EAFe,E,8CAKjC,WACI,MAAO,CAACX,EAASvE,KAAUrK,KACvB,MAAQ,CAAChE,KAAKojB,OAAOnS,WAAYc,GAAea,EAChD,YAAqB,IAAVvE,EACA0D,EAAWO,MACbR,eACAhU,IAAKiL,GACF/I,KAAKqjB,iBAAiBta,EAAU6J,KAAY5O,IAGpDvH,MAAMD,QAAQ6R,GACPA,EAAMvQ,IAAK6T,GACd3R,KAAKqjB,iBACDtR,EAAWH,OAAOD,GAClBiB,KACG5O,IAIRhE,KAAKqjB,iBACRtR,EAAWH,OAAOvD,GAClBuE,KACG5O,M,iBAKf,WACI,OAAOhE,KAAKojB,W,GAlCgCN,ICFrC,SAASQ,GAAc1P,EAAOvF,GACzC,OAAOA,E,ICEUkV,G,YACjB,YAAY,MAAEjN,EAAF,SAASzX,KAAamF,IAAS,aACvC,cAAMA,IAAN,MACKwf,OAASlN,EACd,EAAKmN,UAAY5kB,EAHsB,E,6BAM3C6kB,iBAAA,SAAiBC,GACb,MAAM,YAAEhO,GAAgB3V,KAAKgjB,QAAQxM,QACrC,MAAO,CAAC5C,KAAU5P,KAMd,MAAM4f,EAAeD,EAAe/P,KAAU5P,GACxCqK,EAAQiV,GAAc1P,KAAU5P,GAChC6f,EAAUC,GACK,OAAbA,EAEO,KAEJA,EAAShmB,IAAKsf,GACjBpd,KAAKyjB,UAAU7P,EAAOwJ,EAAIzH,KAGlC,YAAqB,IAAVtH,GAAyB5R,MAAMD,QAAQ6R,GACvCuV,EAAa9lB,IAAI+lB,GAErBA,EAAOD,K,2BAItB,WACI,OAAO5jB,KAAKyjB,W,IAGhB,SAAa5kB,GACTmB,KAAKyjB,UAAY5kB,I,eAGrB,WACI,OAAOmB,KAAKyjB,c,GA1CyBN,ICAxBY,G,YACjB,YAAY,MAAExQ,KAAUvP,IAAS,aAC7B,cAAMA,IAAN,MACKof,OAAS7P,EAFe,E,uCAKjC,WACI,OAAOvT,KAAKojB,OAAOnS,Y,wBAGvB,WACI,MAAO,CAACjR,KAAKijB,KAAMK,M,sBAGvB,WACI,MAAO,EAAG,CAACtjB,KAAKojB,OAAOnS,WAAYc,GAAc1D,KAC7C,QAAqB,IAAVA,EACP,OAAO0D,EAAWO,MAAMT,aAE5B,GAAIpV,MAAMD,QAAQ6R,GACd,OAAOA,EAAMvQ,IAAK6T,IACd,MAAM5I,EAAWgJ,EAAWH,OAAOD,GACnC,OAAO5I,EAAWA,EAASqU,IAAM,OAGzC,MAAMrU,EAAWgJ,EAAWH,OAAOvD,GACnC,OAAOtF,EAAWA,EAASqU,IAAM,Q,iBAIzC,WACI,OAAOpd,KAAKojB,W,GA/B2BN,ICQ1BkB,G,YACjB,YAAY,MAAE1N,EAAF,WAAS2N,EAAT,aAAqBC,EAArB,UAAmCC,KAAcngB,IAAS,aAClE,cAAMA,IAAN,MACKwf,OAASlN,EACd,EAAK8N,YAAcH,EACnB,EAAKI,cAAgBH,EACrB,EAAKI,WAAaH,EALgD,E,qCAgBtEd,iBAAA,SAAiBta,EAAU6J,GACvB,IAAK7J,EACD,OAAO,KAEX,IAAI5O,EACJ,GAAI6F,KAAKgjB,mBAAmBe,GAExB5pB,EAAQ4O,EAAS/I,KAAKqkB,mBACnB,CAEH,MAAQ,CAACrkB,KAAKgjB,QAAQjM,aAAcwN,GAAkB3R,EAChD4R,EAAYxkB,KAAKgjB,QAAQK,iBAAiBta,EAAU6J,GACpD6R,EAAiBD,EACjB,IAAID,EAAcC,GAClB,KACNrqB,EAAQsqB,EAAiBA,EAAezkB,KAAKqkB,eAAiB,KAElE,OAAIlqB,aAAiBqhB,GACVrhB,EAAMijB,IAEbjjB,aAAiB+W,GACV/W,EAAM0X,aAEV1X,G,EAGX2D,IAAA,SAAIe,GACA,GAAIA,aAAoBklB,GACpB,MAAI/jB,KAAK+W,cAAgBlY,EAAS0U,MAAMtC,UAC9B,IAAIjT,MACL,oEAAmEgC,KAAKqkB,4IAA4IrkB,KAAK+W,uDAGxN,IAAI/Y,MACL,mBAAkBa,EAAS0U,MAAMtC,sGAAsGjR,KAAK+W,uDAGlJ,GACHlY,aAAoBmlB,GACpBnlB,aAAoB0kB,IAEpB,GAAIvjB,KAAK+W,cAAgBlY,EAAS0U,MAAMtC,UACpC,MAAM,IAAIjT,MACL,iCAAgCa,EAAS0U,MAAMtC,qGAAqGjR,KAAK+W,4DAG/J,IACFlY,GACmB,mBAAbA,IACNA,EAASL,eAEV,MAAM,IAAIR,MACL,wDAAuD0mB,KAAKC,UACzD9lB,qBACgBA,KAG5B,KACMmB,KAAKwjB,kBAAkBnJ,IACvBra,KAAKwjB,kBAAkBjJ,IAEzB,MAAM,IAAIvc,MAAM,kDAEpB,OAAO,IAAIulB,GAAgB,CACvBR,OAAQ/iB,KACRuT,MAAOvT,KAAKojB,OACZjM,IAAKnX,KAAKijB,KACV3M,MAAOtW,KAAKwjB,OACZ3kB,c,sBA5ER,WACI,OAAOmB,KAAKqkB,gB,wBAGhB,WACI,MAAO,CAACrkB,KAAKijB,KAAMK,M,uBA2EvB,WACI,MAAmC,SAA5BtjB,KAAKwjB,OAAOzM,YACb/W,KAAKokB,YAAYnT,UACjBjR,KAAKwjB,OAAOzM,c,mBAGtB,WAEI,OADW/W,KAAKijB,KAAK2B,cACXvG,SAASre,KAAK+W,iB,GAjGeoM,ICCxC,SAAS0B,IAAwB,OACpC9B,EADoC,MAEpCxP,EAFoC,MAGpC+C,EAHoC,WAIpC2N,EAJoC,aAKpCC,EALoC,IAMpC/M,EANoC,UAOpCgN,IAEA,MAAMW,EAAoB,IAAId,GAAkB,CAC5CjB,SACAxP,QACA+C,QACA2N,aACAC,eACA/M,MACAgN,cAGJ,KAAM7N,aAAiB2D,IAEnB,OAAO6K,EAGX,GAAI/B,aAAkBiB,KAIbjB,EAAOS,kBAAkBnJ,IAAc0I,EAAOuB,YAE/CvB,EAAOS,kBAAkBjJ,IAEzB,MAAM,IAAIvc,MACL,kCAAiC+kB,EAAOsB,iBAAiBH,iBAA4BnB,EAAOsB,0CAIzG,MAAM,YAAEtN,GAAgBT,EAClBE,EAAUW,EAAIpd,IACA,SAAhBgd,EAAyBxD,EAAMtC,UAAY8F,GAuC/C,OArCAnd,OAAOiX,QAAQ2F,EAAQoE,QAAQhR,QAC3B,EAAEmb,EAAkBC,MAChB,MAAMC,EAAoBD,EAAa7K,IAAM4K,EAC7CnrB,OAAOC,eAAeirB,EAAmBG,EAAmB,CACxDlrB,IAAK,IACD8qB,GAAwB,CACpB9B,OAAQ+B,EACRvR,QACA0Q,WAAYzN,EACZF,MAAO0O,EACPd,aAAce,EACd9N,MACAgN,WAAW,QAK/BvqB,OAAOiX,QAAQ2F,EAAQG,eAAe/M,QAClC,EAAEmb,EAAkBC,MAChB,MAAMC,EAAoBD,EAAa7K,IAAM4K,EACzCD,EAAkB/pB,eAAekqB,IAGrCrrB,OAAOC,eAAeirB,EAAmBG,EAAmB,CACxDlrB,IAAK,IACD8qB,GAAwB,CACpB9B,OAAQ+B,EACRvR,QACA0Q,WAAYzN,EACZF,MAAO0O,EACPd,aAAce,EACd9N,MACAgN,WAAW,QAKxBW,ECvEX,MAAMI,GAAe,CACjBxC,eAAgByC,IAGdC,GAAyB,CAAC,UAAW,QACrCC,GAAyBC,GAASF,GAAuB1P,SAAS4P,G,IAelEC,G,WAWF,WAAYnU,GACR,MAAM,eAAEsR,GAAmB,IAAKwC,MAAkB9T,GAAQ,IAC1DpR,KAAK0iB,eAAiBA,EACtB1iB,KAAKwlB,SAAW,GAChBxlB,KAAKylB,sBAAwB,GAC7BzlB,KAAK0lB,gBAAkB,GACvB1lB,KAAK2lB,cAAgBvU,EAAOA,EAAKuU,cAAgB,K,2BAarDC,SAAA,YAAY1R,GACRA,EAAOtK,QAAS2J,IACZ,QAAwBnW,IAApBmW,EAAMtC,UACN,MAAM,IAAIjT,MACN,wDAIRuV,EAAMuI,uBAEN9b,KAAK6lB,4BAA4BtS,GACjCvT,KAAKwlB,SAASzmB,KAAKwU,GAEnB3Z,OAAOC,eAAemG,KAAMuT,EAAMtC,UAAW,CACzClX,IAAK,KAEDiG,KAAK8lB,sBAAsB9lB,KAAKwlB,UDQ7C,UAAiC,MAAEjS,EAAF,IAAS4D,IAC7C,MAAM4O,EAAoB,IAAIhC,GAAkB,CAC5ChB,OAAQ,KACR5L,MACA5D,UAsCJ,OAnCA3Z,OAAOiX,QAAQ0C,EAAMqH,QAAQhR,QAAQ,EAAE+E,EAAW2H,MAC9C,MAAM2O,EAAoB3O,EAAM6D,IAAMxL,EACtC/U,OAAOC,eAAeksB,EAAmBd,EAAmB,CACxDlrB,IAAK,IACD8qB,GAAwB,CACpB9B,OAAQgD,EACRxS,QACA0Q,WAAY1Q,EACZ+C,QACA4N,aAAce,EACd9N,MACAgN,WAAW,QAK3BvqB,OAAOiX,QAAQ0C,EAAMoD,eAAe/M,QAAQ,EAAE+E,EAAW2H,MACrD,MAAM2O,EAAoB3O,EAAM6D,IAAMxL,EAClCoX,EAAkBhrB,eAAekqB,IAGrCrrB,OAAOC,eAAeksB,EAAmBd,EAAmB,CACxDlrB,IAAK,IACD8qB,GAAwB,CACpB9B,OAAQgD,EACRxS,QACA0Q,WAAY1Q,EACZ+C,QACA4N,aAAce,EACd9N,MACAgN,WAAW,QAKpB4B,EChDgBC,CAAwB,CAC3BzS,QACA4D,IAAKnX,a,EAOzB6lB,4BAAA,SAA4BtS,GACxB,MAAM,OAAEqH,GAAWrH,EACb0S,EAAgB1S,EAAMtC,UAE5BrX,OAAOiX,QAAQ+J,GAAQhR,QAAQ,EAAE+E,EAAWuX,MACxC,KAAMA,aAAyB3L,IAC3B,OAGJ,IAAIxD,EAEAA,EAD8B,SAA9BmP,EAAcnP,YACAkP,EAEAC,EAAcnP,YAGhC,MAAMoP,EAAkBF,IAAkBlP,EACpCqP,EAAgBrX,EAAiBkX,GACjCI,EAAcrX,EAAe+H,GAEnC,GAAImP,EAAc1M,SACd,GAAI2M,IAAoBD,EAAc3N,cAClC,MAAM,IAAIva,MAED,kDAAGioB,KAAiBtX,0BACXuX,EAAc1M,+HAMjC,CACH,MAAM8M,EAAO,kFAA8B9K,IAE3C8K,EAAQrV,UAAYxC,EAAQwX,EAAetX,GAE3C,MAAM4X,EAAe,+HACjB,WACI,OAAO,IAFM,uCAKjB,WACI,OAAO,MANM,GAAiClM,IAShDmM,EAAkBL,EAClBI,EACAlM,GACNiM,EAAQ1L,OAAS,CACbjJ,GAAIwD,KACJ,CAACiR,GAAgB,IAAII,EAAgBP,GACrC,CAACI,GAAc,IAAIG,EAAgBzP,IAGvCuP,EAAQxK,uBACR9b,KAAKylB,sBAAsB1mB,KAAKunB,O,EAW5CvsB,IAAA,SAAIkX,GACA,MAAMwV,EAAYzmB,KAAKwlB,SAAS5mB,OAAOoB,KAAKylB,uBACtCiB,EAAQ9sB,OAAO4F,OAAOinB,GAAW1L,KAClCxH,GAAUA,EAAMtC,YAAcA,GAGnC,QAAqB,IAAVyV,EACP,MAAM,IAAI1oB,MAAO,sBAAqBiT,oBAE1C,OAAOyV,G,EAGXvS,gBAAA,WAGI,OAFAnU,KAAK8lB,sBAAsB9lB,KAAKwlB,UAChCxlB,KAAK8lB,sBAAsB9lB,KAAKylB,uBACzBzlB,KAAKwlB,SAAS5mB,OAAOoB,KAAKylB,wB,EAGrCkB,mBAAA,WAkBI,MAAO,CAAExE,OAjBMniB,KAAKmU,kBACEzU,OAAO,CAACknB,EAAMpX,KAChC,MAAM4S,EAAY5S,EAAWyB,UACvB4R,EAAYrT,EAAWwM,eAY7B,OAXApiB,OAAO0F,KAAKujB,GACP9W,OAAOsZ,IACPzb,QAASnP,IACN,MAAM,IAAIuD,MACL,sBAAqBvD,eAAiB2nB,gBAGnDwE,EAAKxE,GAAa,CACdxH,OAAQ,IAAKpL,EAAWoL,WACrBiI,GAEA+D,GACR,M,EAIPhC,YAAA,WAII,OAHK5kB,KAAK2T,KACN3T,KAAK2T,GAAK3T,KAAK0iB,eAAe1iB,KAAK2mB,uBAEhC3mB,KAAK2T,I,EAOhBI,cAAA,WACI,OAAO/T,KAAK4kB,cAAc7Q,iB,EAS9BnB,QAAA,SAAQgB,GACJ,OAAO,IAAIH,GAAQzT,KAAMA,KAAK4kB,cAAehR,I,EASjDiT,eAAA,SAAejT,GACX,OAAO,IAAIH,GAAQzT,KAAMA,KAAK4kB,cAAehR,GAAO,I,EAMxDkS,sBAAA,SAAsB5R,GAClBA,EACKnI,OAAQwH,IAAWA,EAAMwI,SACzBnS,QAAS2J,IACN,MAAM,OAAEqH,EAAF,UAAU3J,EAAV,cAAqBxB,GAAkB8D,EAC7C3Z,OAAOiX,QAAQ+J,GAAQhR,QAAQ,EAAE+E,EAAW2H,MACxC,KAAMA,aAAiBwB,IACnB,MAAM,IAAI9Z,MACL,GAAEiT,KAAatC,wBAAgC2H,+HAMnDtW,KAAK8mB,kBAAkB7V,EAAWtC,KACnC3O,KAAK+mB,cAAczQ,EAAO3H,EAAW4E,GACrCvT,KAAKgnB,mBAAmB/V,EAAWtC,MAG3CY,EAAsBgE,EAAO9D,GAC7B8D,EAAMwI,SAAU,K,EAO5B+K,kBAAA,SAAkB7V,EAAWtC,GACzB,QAAO3O,KAAK0lB,gBAAgB3qB,eAAekW,MACnCjR,KAAK0lB,gBAAgBzU,GAAWtC,I,EAO5CqY,mBAAA,SAAmB/V,EAAWtC,GACrB3O,KAAK0lB,gBAAgB3qB,eAAekW,KACrCjR,KAAK0lB,gBAAgBzU,GAAa,IAEtCjR,KAAK0lB,gBAAgBzU,GAAWtC,IAAa,G,EAOjDoY,cAAA,SAAczQ,EAAO3H,EAAW4E,GAE5B,IAAI0T,EADmB3Q,EAAM4Q,gBACV,CACf5Q,QACA3H,YACA4E,QACA4D,IAAKnX,OACNqX,O,EAQPxD,cAAA,SAAcD,GAKV,OAJAtF,EACI,kGAGGtO,KAAK6mB,eAAejT,I,EAM/B3O,KAAA,SAAK2O,GAKD,OAJAtF,EACI,kFAGGtO,KAAK4S,QAAQgB,I,EAMxBuT,gBAAA,WAKI,OAJA7Y,EACI,mGAGGtO,KAAK+T,iB,EAMhBnb,OAAA,WACI,MAAM,IAAIoF,MACN,0E,KAcGunB,I,2BC5Vf,MAAM7oB,GAAuB,CAACC,EAAGC,IAAMD,IAAMC,EAGvCwqB,GAAcle,GAChBA,GAAsB,iBAARA,GAAoBA,EAAInO,ezBMhB,iCyBqFnB,SAASmD,GAAQhB,EAAMmqB,EAAmB3qB,GAAsBya,GACnE,IAAImQ,EAAW,CAEXvjB,OAAQ,KAERwC,KAAM,KAONghB,SAAU,KAMVC,uBAAwB,GAOxB1S,kBAAmB,GAQnBM,gBAAiB,IAGrB,MAAO,IAAIqS,KAKP,MAAOF,KAAahhB,GAAQkhB,EAG5B,GADgCvV,QAAQoV,EAAS/gB,QApInClJ,EAuIGiqB,EAAS/gB,KAvIQzJ,EAuIIuqB,EAAN9gB,EAtI3B5I,MACL,CAACuL,EAAKtJ,IACDwnB,GAAWle,IAAQke,GAAW/pB,EAASuC,KACxC9C,EAAcoM,EAAK7L,EAASuC,OAuCD,EAAC0nB,EAAUC,IAC9CD,EAASE,uBAAuB7pB,MAC3BsT,GAAcqW,EAASC,SAAStW,KAAesW,EAAStW,IA2FrDyW,CAA+BJ,EAAUC,IA3GrB,EAACD,EAAUC,KACvC,MAAM,gBAAEnS,GAAoBkS,EAE5B,OAAO1tB,OAAOiX,QAAQuE,GAAiBzX,MAAM,EAAEsT,EAAWiE,KACtDtb,OAAOiX,QAAQqE,GAASvX,MAAM,EAAEgqB,EAAQnoB,KACpCA,EAAO7B,MACFxD,GACGmtB,EAASC,SAAStW,GAAWiE,QAAQyS,GAAQxtB,KAC7CotB,EAAStW,GAAWiE,QAAQyS,GAAQxtB,OAoG5CytB,CAAwBN,EAAUC,IA/HP,EAACD,EAAUC,EAAUpQ,KACxD,MAAM,kBAAErC,GAAsBwS,EAE9B,OAAO1tB,OAAOiX,QAAQiE,GAAmBnX,MAAM,EAAEsT,EAAW4W,MAExD,GAAIP,EAASC,SAAStW,KAAesW,EAAStW,GAC1C,OAAO,EAGX,MAAM,QAAEuN,GAAYrH,EAAIyN,cAAcvG,SAASpN,IAEvC,CAACuN,GAAUsJ,GAAiBR,EAASC,SAAStW,IAC9C,CAACuN,GAAU9M,GAAS6V,EAAStW,GAE/B2E,EAAchc,OAAO0F,KAAKuoB,GAChC,OAlBmBE,EAkBcD,EAlBPE,EAkBqBtW,EAA3BkE,EAjBpBjY,MAAOgU,GAAOoW,EAAMpW,KAAQqW,EAAMrW,IADrB,IAAMoW,EAAOC,KAmItBC,CAA+BX,EAAUC,EAAUpQ,GAMnD,OAAOmQ,EAASvjB,OAhJP,IAAC1G,EAAoBP,EAwJlC,MAAM8V,EAAUuE,EAAIvE,QAAQ2U,GAEtBW,EAAkB3hB,EAAKzI,IAAKoL,GAC9Bke,GAAWle,GAAO0J,EAAU1J,GAI1BnF,EAAS7G,EAAKK,MAAM,KAAM2qB,GAqBhC,OAfAZ,EAAW,CAEP/gB,OAEAxC,SAEAwjB,WAEAzS,kBAAmBlC,EAAQuV,uBAE3B/S,gBAAiBxC,EAAQwC,gBAEzBoS,uBAAwB5U,EAAQ4U,wBAG7BzjB,GCxKR,SAASqkB,GAAexV,EAASS,GACpCT,EAAQwB,mBAAmBxK,QAAS4F,IACE,mBAAvBA,EAAWgQ,SAElBhQ,EAAWgQ,QAAQnM,EAAQ7D,EAAYoD,KAc5C,SAASyV,GAAclR,EAAKmR,EAAUF,IACzC,MAAO,CAACxU,EAAOP,KACX,MAAMT,EAAUuE,EAAIvE,QAAQgB,GAASuD,EAAIpD,iBAEzC,OADAuU,EAAQ1V,EAASS,GACVT,EAAQgB,OA4BvB,SAAS2U,GAAMrf,GAEX,OAAIA,aAAeqc,GACRrc,EAEPA,aAAe4Z,IACR5Z,EAAI+Z,KAKnB,MAAMuF,GAAgB,IAAI9lB,IACpB+lB,GAAexuB,OAAOyuB,IAAI,sBAMhC,SAASC,GAAWzf,GAChB,GAAmB,mBAARA,EACP,OAAOA,EAEX,GAAIA,aAAeqc,GACf,OAAOrc,EAAIyc,cAMf,GAJIzc,aAAeqa,KAEfra,EAAIrK,SAAW8pB,GAAWzf,EAAIrK,WAE9BqK,aAAe4Z,GAAc,CAC7B,MAAM,IAAE3L,EAAF,UAAO+L,GAAcha,EAC3B,IAAI0f,EAGCJ,GAAc1lB,IAAIqU,IACnBqR,GAAcroB,IAAIgX,EAAK,IAAIzU,KAU/BkmB,EARqBJ,GAAczuB,IAAIod,GASvC,IAAK,IAAIje,EAAI,EAAGA,EAAIgqB,EAAU7nB,SAAUnC,EAAG,CACvC,MAAM2vB,EAAa3F,EAAUhqB,GACxB0vB,EAAM9lB,IAAI+lB,IACXD,EAAMzoB,IAAI0oB,EAAY,IAAInmB,KAE9BkmB,EAAQA,EAAM7uB,IAAI8uB,GAEtB,GAAID,GAASA,EAAM9lB,IAAI2lB,IAEnB,OAAOG,EAAM7uB,IAAI0uB,IAGrB,MAAM5pB,EA9Ed,SAASiqB,EAAuBlC,GAC5B,GAAIA,aAAgBrD,GAAiB,CACjC,MAAMI,EAAiBmF,EAAuBlC,EAAK7D,QACnD,OAAO6D,EAAKlD,iBAAiBC,GAEjC,OAAOhjB,KACHimB,EAAKlpB,aACLkpB,EAAKnoB,WAFFkC,CAGL,CACEI,YAAa6lB,EAAK7lB,YAClBG,YAAa,IAAIuB,gBACjBtD,gBAAiBC,KAmEA0pB,CAAuB5f,GAIxC,OAFA0f,EAAMzoB,IAAIsoB,GAAc5pB,GAEjBA,EAEX,MAAM,IAAIb,MACL,0CAAyC0mB,KAAKC,UAC3Czb,qBACgBA,KAiFrB,SAAS9J,MAAkBmH,GAC9B,IAAKA,EAAKlL,OACN,MAAM,IAAI2C,MAAM,+CAGpB,MAAM+qB,EAAYxiB,EAAK7H,MACjBhB,EAAejB,MAAMD,QAAQ+J,EAAK,IAAMA,EAAK,GAAKA,EAElD4Q,EAAMzZ,EAAaI,IAAIyqB,IAAOxN,KAAK7I,SACnC8W,EAAatrB,EAAaI,IAAI6qB,IAEpC,GAAyB,mBAAdI,EAA0B,CACjC,IAAK5R,EACD,MAAM,IAAInZ,MACN,0IAED,IAAKmZ,EAAIwO,cACZ,MAAM,IAAI3nB,MACN,6IAED,GAAiC,mBAAtBmZ,EAAIwO,cAClB,MAAM,IAAI3nB,MACL,mIAAkI0mB,KAAKC,UACpIxN,EAAIwO,iCACYxO,EAAIwO,iBAIhC,OAAO1nB,iCACHC,QACAd,EACA+Z,EAHGlZ,CAIL,CAACkZ,EAAIwO,iBAAkBqD,GAAaD,GAG1C,GAAIA,aAAqBxD,GACrB,MAAM,IAAIvnB,MACN,kJASR,OANIgrB,EAAW3tB,QACXiG,QAAQC,KACJ,gGAIDonB,GAAWI,GC7PtB,MAAME,GH0UC,WACH,MAAM,IAAIjrB,MACN,wFG1UFkrB,GAAU,WACZ,MAAM,IAAIlrB,MACN,mKAyBOwd\",\"file\":\"redux-orm.min.js\",\"sourcesContent\":[\"(function webpackUniversalModuleDefinition(root, factory) {\\n\\tif(typeof exports === 'object' && typeof module === 'object')\\n\\t\\tmodule.exports = factory();\\n\\telse if(typeof define === 'function' && define.amd)\\n\\t\\tdefine(\\\"ReduxOrm\\\", [], factory);\\n\\telse if(typeof exports === 'object')\\n\\t\\texports[\\\"ReduxOrm\\\"] = factory();\\n\\telse\\n\\t\\troot[\\\"ReduxOrm\\\"] = factory();\\n})(window, function() {\\nreturn \",\" \\t// The module cache\\n \\tvar installedModules = {};\\n\\n \\t// The require function\\n \\tfunction __webpack_require__(moduleId) {\\n\\n \\t\\t// Check if module is in cache\\n \\t\\tif(installedModules[moduleId]) {\\n \\t\\t\\treturn installedModules[moduleId].exports;\\n \\t\\t}\\n \\t\\t// Create a new module (and put it into the cache)\\n \\t\\tvar module = installedModules[moduleId] = {\\n \\t\\t\\ti: moduleId,\\n \\t\\t\\tl: false,\\n \\t\\t\\texports: {}\\n \\t\\t};\\n\\n \\t\\t// Execute the module function\\n \\t\\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\\n\\n \\t\\t// Flag the module as loaded\\n \\t\\tmodule.l = true;\\n\\n \\t\\t// Return the exports of the module\\n \\t\\treturn module.exports;\\n \\t}\\n\\n\\n \\t// expose the modules object (__webpack_modules__)\\n \\t__webpack_require__.m = modules;\\n\\n \\t// expose the module cache\\n \\t__webpack_require__.c = installedModules;\\n\\n \\t// define getter function for harmony exports\\n \\t__webpack_require__.d = function(exports, name, getter) {\\n \\t\\tif(!__webpack_require__.o(exports, name)) {\\n \\t\\t\\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\\n \\t\\t}\\n \\t};\\n\\n \\t// define __esModule on exports\\n \\t__webpack_require__.r = function(exports) {\\n \\t\\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\\n \\t\\t\\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\\n \\t\\t}\\n \\t\\tObject.defineProperty(exports, '__esModule', { value: true });\\n \\t};\\n\\n \\t// create a fake namespace object\\n \\t// mode & 1: value is a module id, require it\\n \\t// mode & 2: merge all properties of value into the ns\\n \\t// mode & 4: return value when already ns object\\n \\t// mode & 8|1: behave like require\\n \\t__webpack_require__.t = function(value, mode) {\\n \\t\\tif(mode & 1) value = __webpack_require__(value);\\n \\t\\tif(mode & 8) return value;\\n \\t\\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\\n \\t\\tvar ns = Object.create(null);\\n \\t\\t__webpack_require__.r(ns);\\n \\t\\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\\n \\t\\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\\n \\t\\treturn ns;\\n \\t};\\n\\n \\t// getDefaultExport function for compatibility with non-harmony modules\\n \\t__webpack_require__.n = function(module) {\\n \\t\\tvar getter = module && module.__esModule ?\\n \\t\\t\\tfunction getDefault() { return module['default']; } :\\n \\t\\t\\tfunction getModuleExports() { return module; };\\n \\t\\t__webpack_require__.d(getter, 'a', getter);\\n \\t\\treturn getter;\\n \\t};\\n\\n \\t// Object.prototype.hasOwnProperty.call\\n \\t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\\n\\n \\t// __webpack_public_path__\\n \\t__webpack_require__.p = \\\"\\\";\\n\\n\\n \\t// Load entry module and return exports\\n \\treturn __webpack_require__(__webpack_require__.s = 37);\\n\",\"function _defineProperties(target, props) {\\n  for (var i = 0; i < props.length; i++) {\\n    var descriptor = props[i];\\n    descriptor.enumerable = descriptor.enumerable || false;\\n    descriptor.configurable = true;\\n    if (\\\"value\\\" in descriptor) descriptor.writable = true;\\n    Object.defineProperty(target, descriptor.key, descriptor);\\n  }\\n}\\n\\nfunction _createClass(Constructor, protoProps, staticProps) {\\n  if (protoProps) _defineProperties(Constructor.prototype, protoProps);\\n  if (staticProps) _defineProperties(Constructor, staticProps);\\n  return Constructor;\\n}\\n\\nmodule.exports = _createClass;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\",\"var setPrototypeOf = require(\\\"./setPrototypeOf.js\\\");\\n\\nfunction _inheritsLoose(subClass, superClass) {\\n  subClass.prototype = Object.create(superClass.prototype);\\n  subClass.prototype.constructor = subClass;\\n  setPrototypeOf(subClass, superClass);\\n}\\n\\nmodule.exports = _inheritsLoose;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\",\"function _typeof(obj) {\\n  \\\"@babel/helpers - typeof\\\";\\n\\n  if (typeof Symbol === \\\"function\\\" && typeof Symbol.iterator === \\\"symbol\\\") {\\n    module.exports = _typeof = function _typeof(obj) {\\n      return typeof obj;\\n    };\\n\\n    module.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\\n  } else {\\n    module.exports = _typeof = function _typeof(obj) {\\n      return obj && typeof Symbol === \\\"function\\\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \\\"symbol\\\" : typeof obj;\\n    };\\n\\n    module.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\\n  }\\n\\n  return _typeof(obj);\\n}\\n\\nmodule.exports = _typeof;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\",\"var arrayWithoutHoles = require(\\\"./arrayWithoutHoles.js\\\");\\n\\nvar iterableToArray = require(\\\"./iterableToArray.js\\\");\\n\\nvar unsupportedIterableToArray = require(\\\"./unsupportedIterableToArray.js\\\");\\n\\nvar nonIterableSpread = require(\\\"./nonIterableSpread.js\\\");\\n\\nfunction _toConsumableArray(arr) {\\n  return arrayWithoutHoles(arr) || iterableToArray(arr) || unsupportedIterableToArray(arr) || nonIterableSpread();\\n}\\n\\nmodule.exports = _toConsumableArray;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\",\"/**\\n * Checks if `value` is classified as an `Array` object.\\n *\\n * @static\\n * @memberOf _\\n * @since 0.1.0\\n * @category Lang\\n * @param {*} value The value to check.\\n * @returns {boolean} Returns `true` if `value` is an array, else `false`.\\n * @example\\n *\\n * _.isArray([1, 2, 3]);\\n * // => true\\n *\\n * _.isArray(document.body.children);\\n * // => false\\n *\\n * _.isArray('abc');\\n * // => false\\n *\\n * _.isArray(_.noop);\\n * // => false\\n */\\nvar isArray = Array.isArray;\\n\\nmodule.exports = isArray;\\n\",\"'use strict';\\n\\nexports.__esModule = true;\\nexports.defaultMemoize = defaultMemoize;\\nexports.createSelectorCreator = createSelectorCreator;\\nexports.createStructuredSelector = createStructuredSelector;\\nfunction defaultEqualityCheck(a, b) {\\n  return a === b;\\n}\\n\\nfunction areArgumentsShallowlyEqual(equalityCheck, prev, next) {\\n  if (prev === null || next === null || prev.length !== next.length) {\\n    return false;\\n  }\\n\\n  // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.\\n  var length = prev.length;\\n  for (var i = 0; i < length; i++) {\\n    if (!equalityCheck(prev[i], next[i])) {\\n      return false;\\n    }\\n  }\\n\\n  return true;\\n}\\n\\nfunction defaultMemoize(func) {\\n  var equalityCheck = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultEqualityCheck;\\n\\n  var lastArgs = null;\\n  var lastResult = null;\\n  // we reference arguments instead of spreading them for performance reasons\\n  return function () {\\n    if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {\\n      // apply arguments instead of spreading for performance.\\n      lastResult = func.apply(null, arguments);\\n    }\\n\\n    lastArgs = arguments;\\n    return lastResult;\\n  };\\n}\\n\\nfunction getDependencies(funcs) {\\n  var dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs;\\n\\n  if (!dependencies.every(function (dep) {\\n    return typeof dep === 'function';\\n  })) {\\n    var dependencyTypes = dependencies.map(function (dep) {\\n      return typeof dep;\\n    }).join(', ');\\n    throw new Error('Selector creators expect all input-selectors to be functions, ' + ('instead received the following types: [' + dependencyTypes + ']'));\\n  }\\n\\n  return dependencies;\\n}\\n\\nfunction createSelectorCreator(memoize) {\\n  for (var _len = arguments.length, memoizeOptions = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\\n    memoizeOptions[_key - 1] = arguments[_key];\\n  }\\n\\n  return function () {\\n    for (var _len2 = arguments.length, funcs = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\\n      funcs[_key2] = arguments[_key2];\\n    }\\n\\n    var recomputations = 0;\\n    var resultFunc = funcs.pop();\\n    var dependencies = getDependencies(funcs);\\n\\n    var memoizedResultFunc = memoize.apply(undefined, [function () {\\n      recomputations++;\\n      // apply arguments instead of spreading for performance.\\n      return resultFunc.apply(null, arguments);\\n    }].concat(memoizeOptions));\\n\\n    // If a selector is called with the exact same arguments we don't need to traverse our dependencies again.\\n    var selector = defaultMemoize(function () {\\n      var params = [];\\n      var length = dependencies.length;\\n\\n      for (var i = 0; i < length; i++) {\\n        // apply arguments instead of spreading and mutate a local list of params for performance.\\n        params.push(dependencies[i].apply(null, arguments));\\n      }\\n\\n      // apply arguments instead of spreading for performance.\\n      return memoizedResultFunc.apply(null, params);\\n    });\\n\\n    selector.resultFunc = resultFunc;\\n    selector.recomputations = function () {\\n      return recomputations;\\n    };\\n    selector.resetRecomputations = function () {\\n      return recomputations = 0;\\n    };\\n    return selector;\\n  };\\n}\\n\\nvar createSelector = exports.createSelector = createSelectorCreator(defaultMemoize);\\n\\nfunction createStructuredSelector(selectors) {\\n  var selectorCreator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : createSelector;\\n\\n  if (typeof selectors !== 'object') {\\n    throw new Error('createStructuredSelector expects first argument to be an object ' + ('where each property is a selector, instead received a ' + typeof selectors));\\n  }\\n  var objectKeys = Object.keys(selectors);\\n  return selectorCreator(objectKeys.map(function (key) {\\n    return selectors[key];\\n  }), function () {\\n    for (var _len3 = arguments.length, values = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {\\n      values[_key3] = arguments[_key3];\\n    }\\n\\n    return values.reduce(function (composition, value, index) {\\n      composition[objectKeys[index]] = value;\\n      return composition;\\n    }, {});\\n  });\\n}\",\"(function (global, factory) {\\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('reselect')) :\\n  typeof define === 'function' && define.amd ? define(['exports', 'reselect'], factory) :\\n  (global = global || self, factory(global['Re-reselect'] = {}, global.Reselect));\\n}(this, (function (exports, reselect) { 'use strict';\\n\\n  function isStringOrNumber(value) {\\n    return typeof value === 'string' || typeof value === 'number';\\n  }\\n\\n  var FlatObjectCache = /*#__PURE__*/function () {\\n    function FlatObjectCache() {\\n      this._cache = {};\\n    }\\n\\n    var _proto = FlatObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return FlatObjectCache;\\n  }();\\n\\n  var defaultCacheCreator = FlatObjectCache;\\n\\n  var defaultCacheKeyValidator = function defaultCacheKeyValidator() {\\n    return true;\\n  };\\n\\n  function createCachedSelector() {\\n    for (var _len = arguments.length, funcs = new Array(_len), _key = 0; _key < _len; _key++) {\\n      funcs[_key] = arguments[_key];\\n    }\\n\\n    return function (polymorphicOptions, legacyOptions) {\\n      if (legacyOptions) {\\n        throw new Error('[re-reselect] \\\"options\\\" as second argument is not supported anymore. Please provide an option object as single argument.');\\n      }\\n\\n      var options = typeof polymorphicOptions === 'function' ? {\\n        keySelector: polymorphicOptions\\n      } : Object.assign({}, polymorphicOptions); // https://github.com/reduxjs/reselect/blob/v4.0.0/src/index.js#L54\\n\\n      var recomputations = 0;\\n      var resultFunc = funcs.pop();\\n      var dependencies = Array.isArray(funcs[0]) ? funcs[0] : [].concat(funcs);\\n\\n      var resultFuncWithRecomputations = function resultFuncWithRecomputations() {\\n        recomputations++;\\n        return resultFunc.apply(void 0, arguments);\\n      };\\n\\n      funcs.push(resultFuncWithRecomputations);\\n      var cache = options.cacheObject || new defaultCacheCreator();\\n      var selectorCreator = options.selectorCreator || reselect.createSelector;\\n      var isValidCacheKey = cache.isValidCacheKey || defaultCacheKeyValidator;\\n\\n      if (options.keySelectorCreator) {\\n        options.keySelector = options.keySelectorCreator({\\n          keySelector: options.keySelector,\\n          inputSelectors: dependencies,\\n          resultFunc: resultFunc\\n        });\\n      } // Application receives this function\\n\\n\\n      var selector = function selector() {\\n        var cacheKey = options.keySelector.apply(options, arguments);\\n\\n        if (isValidCacheKey(cacheKey)) {\\n          var cacheResponse = cache.get(cacheKey);\\n\\n          if (cacheResponse === undefined) {\\n            cacheResponse = selectorCreator.apply(void 0, funcs);\\n            cache.set(cacheKey, cacheResponse);\\n          }\\n\\n          return cacheResponse.apply(void 0, arguments);\\n        }\\n\\n        console.warn(\\\"[re-reselect] Invalid cache key \\\\\\\"\\\" + cacheKey + \\\"\\\\\\\" has been returned by keySelector function.\\\");\\n        return undefined;\\n      }; // Further selector methods\\n\\n\\n      selector.getMatchingSelector = function () {\\n        var cacheKey = options.keySelector.apply(options, arguments); // @NOTE It might update cache hit count in LRU-like caches\\n\\n        return cache.get(cacheKey);\\n      };\\n\\n      selector.removeMatchingSelector = function () {\\n        var cacheKey = options.keySelector.apply(options, arguments);\\n        cache.remove(cacheKey);\\n      };\\n\\n      selector.clearCache = function () {\\n        cache.clear();\\n      };\\n\\n      selector.resultFunc = resultFunc;\\n      selector.dependencies = dependencies;\\n      selector.cache = cache;\\n\\n      selector.recomputations = function () {\\n        return recomputations;\\n      };\\n\\n      selector.resetRecomputations = function () {\\n        return recomputations = 0;\\n      };\\n\\n      selector.keySelector = options.keySelector;\\n      return selector;\\n    };\\n  }\\n\\n  function createStructuredCachedSelector(selectors) {\\n    return reselect.createStructuredSelector(selectors, createCachedSelector);\\n  }\\n\\n  function validateCacheSize(cacheSize) {\\n    if (cacheSize === undefined) {\\n      throw new Error('Missing the required property \\\"cacheSize\\\".');\\n    }\\n\\n    if (!Number.isInteger(cacheSize) || cacheSize <= 0) {\\n      throw new Error('The \\\"cacheSize\\\" property must be a positive integer value.');\\n    }\\n  }\\n\\n  var FifoObjectCache = /*#__PURE__*/function () {\\n    function FifoObjectCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = FifoObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n\\n      this._cacheOrdering.push(key);\\n\\n      if (this._cacheOrdering.length > this._cacheSize) {\\n        var earliest = this._cacheOrdering[0];\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      var index = this._cacheOrdering.indexOf(key);\\n\\n      if (index > -1) {\\n        this._cacheOrdering.splice(index, 1);\\n      }\\n\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return FifoObjectCache;\\n  }();\\n\\n  var LruObjectCache = /*#__PURE__*/function () {\\n    function LruObjectCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = LruObjectCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache[key] = selectorFn;\\n\\n      this._registerCacheHit(key);\\n\\n      if (this._cacheOrdering.length > this._cacheSize) {\\n        var earliest = this._cacheOrdering[0];\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      this._registerCacheHit(key);\\n\\n      return this._cache[key];\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._deleteCacheHit(key);\\n\\n      delete this._cache[key];\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache = {};\\n      this._cacheOrdering = [];\\n    };\\n\\n    _proto._registerCacheHit = function _registerCacheHit(key) {\\n      this._deleteCacheHit(key);\\n\\n      this._cacheOrdering.push(key);\\n    };\\n\\n    _proto._deleteCacheHit = function _deleteCacheHit(key) {\\n      var index = this._cacheOrdering.indexOf(key);\\n\\n      if (index > -1) {\\n        this._cacheOrdering.splice(index, 1);\\n      }\\n    };\\n\\n    _proto.isValidCacheKey = function isValidCacheKey(cacheKey) {\\n      return isStringOrNumber(cacheKey);\\n    };\\n\\n    return LruObjectCache;\\n  }();\\n\\n  var FlatMapCache = /*#__PURE__*/function () {\\n    function FlatMapCache() {\\n      this._cache = new Map();\\n    }\\n\\n    var _proto = FlatMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache.get(key);\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return FlatMapCache;\\n  }();\\n\\n  var FifoMapCache = /*#__PURE__*/function () {\\n    function FifoMapCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = new Map();\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = FifoMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n\\n      if (this._cache.size > this._cacheSize) {\\n        var earliest = this._cache.keys().next().value;\\n\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      return this._cache.get(key);\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return FifoMapCache;\\n  }();\\n\\n  var LruMapCache = /*#__PURE__*/function () {\\n    function LruMapCache(_temp) {\\n      var _ref = _temp === void 0 ? {} : _temp,\\n          cacheSize = _ref.cacheSize;\\n\\n      validateCacheSize(cacheSize);\\n      this._cache = new Map();\\n      this._cacheSize = cacheSize;\\n    }\\n\\n    var _proto = LruMapCache.prototype;\\n\\n    _proto.set = function set(key, selectorFn) {\\n      this._cache.set(key, selectorFn);\\n\\n      if (this._cache.size > this._cacheSize) {\\n        var earliest = this._cache.keys().next().value;\\n\\n        this.remove(earliest);\\n      }\\n    };\\n\\n    _proto.get = function get(key) {\\n      var value = this._cache.get(key); // Register cache hit\\n\\n\\n      if (this._cache.has(key)) {\\n        this.remove(key);\\n\\n        this._cache.set(key, value);\\n      }\\n\\n      return value;\\n    };\\n\\n    _proto.remove = function remove(key) {\\n      this._cache[\\\"delete\\\"](key);\\n    };\\n\\n    _proto.clear = function clear() {\\n      this._cache.clear();\\n    };\\n\\n    return LruMapCache;\\n  }();\\n\\n  exports.FifoMapCache = FifoMapCache;\\n  exports.FifoObjectCache = FifoObjectCache;\\n  exports.FlatMapCache = FlatMapCache;\\n  exports.FlatObjectCache = FlatObjectCache;\\n  exports.LruMapCache = LruMapCache;\\n  exports.LruObjectCache = LruObjectCache;\\n  exports.createCachedSelector = createCachedSelector;\\n  exports.createStructuredCachedSelector = createStructuredCachedSelector;\\n  exports.default = createCachedSelector;\\n\\n  Object.defineProperty(exports, '__esModule', { value: true });\\n\\n})));\\n//# sourceMappingURL=index.js.map\\n\",\"function _arrayLikeToArray(arr, len) {\\n  if (len == null || len > arr.length) len = arr.length;\\n\\n  for (var i = 0, arr2 = new Array(len); i < len; i++) {\\n    arr2[i] = arr[i];\\n  }\\n\\n  return arr2;\\n}\\n\\nmodule.exports = _arrayLikeToArray;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\",\"var arrayMap = require('./_arrayMap'),\\n    baseGet = require('./_baseGet'),\\n    baseIteratee = require('./_baseIteratee'),\\n    baseMap = require('./_baseMap'),\\n    baseSortBy = require('./_baseSortBy'),\\n    baseUnary = require('./_baseUnary'),\\n    compareMultiple = require('./_compareMultiple'),\\n    identity = require('./identity'),\\n    isArray = require('./isArray');\\n\\n/**\\n * The base implementation of `_.orderBy` without param guards.\\n *\\n * @private\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.\\n * @param {string[]} orders The sort orders of `iteratees`.\\n * @returns {Array} Returns the new sorted array.\\n */\\nfunction baseOrderBy(collection, iteratees, orders) {\\n  if (iteratees.length) {\\n    iteratees = arrayMap(iteratees, function(iteratee) {\\n      if (isArray(iteratee)) {\\n        return function(value) {\\n          return baseGet(value, iteratee.length === 1 ? iteratee[0] : iteratee);\\n        }\\n      }\\n      return iteratee;\\n    });\\n  } else {\\n    iteratees = [identity];\\n  }\\n\\n  var index = -1;\\n  iteratees = arrayMap(iteratees, baseUnary(baseIteratee));\\n\\n  var result = baseMap(collection, function(value, key, collection) {\\n    var criteria = arrayMap(iteratees, function(iteratee) {\\n      return iteratee(value);\\n    });\\n    return { 'criteria': criteria, 'index': ++index, 'value': value };\\n  });\\n\\n  return baseSortBy(result, function(object, other) {\\n    return compareMultiple(object, other, orders);\\n  });\\n}\\n\\nmodule.exports = baseOrderBy;\\n\",\"/**\\n * This method returns the first argument it receives.\\n *\\n * @static\\n * @since 0.1.0\\n * @memberOf _\\n * @category Util\\n * @param {*} value Any value.\\n * @returns {*} Returns `value`.\\n * @example\\n *\\n * var object = { 'a': 1 };\\n *\\n * console.log(_.identity(object) === object);\\n * // => true\\n */\\nfunction identity(value) {\\n  return value;\\n}\\n\\nmodule.exports = identity;\\n\",\"/**\\n * This method returns the first argument it receives.\\n *\\n * @static\\n * @since 0.1.0\\n * @memberOf _\\n * @category Util\\n * @param {*} value Any value.\\n * @returns {*} Returns `value`.\\n * @example\\n *\\n * var object = { 'a': 1 };\\n *\\n * console.log(_.identity(object) === object);\\n * // => true\\n */\\nfunction identity(value) {\\n  return value;\\n}\\n\\nmodule.exports = identity;\\n\",\"/**\\n * A specialized version of `_.filter` for arrays without support for\\n * iteratee shorthands.\\n *\\n * @private\\n * @param {Array} [array] The array to iterate over.\\n * @param {Function} predicate The function invoked per iteration.\\n * @returns {Array} Returns the new filtered array.\\n */\\nfunction arrayFilter(array, predicate) {\\n  var index = -1,\\n      length = array == null ? 0 : array.length,\\n      resIndex = 0,\\n      result = [];\\n\\n  while (++index < length) {\\n    var value = array[index];\\n    if (predicate(value, index, array)) {\\n      result[resIndex++] = value;\\n    }\\n  }\\n  return result;\\n}\\n\\nmodule.exports = arrayFilter;\\n\",\"var baseOrderBy = require('./_baseOrderBy'),\\n    isArray = require('./isArray');\\n\\n/**\\n * This method is like `_.sortBy` except that it allows specifying the sort\\n * orders of the iteratees to sort by. If `orders` is unspecified, all values\\n * are sorted in ascending order. Otherwise, specify an order of \\\"desc\\\" for\\n * descending or \\\"asc\\\" for ascending sort order of corresponding values.\\n *\\n * @static\\n * @memberOf _\\n * @since 4.0.0\\n * @category Collection\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]\\n *  The iteratees to sort by.\\n * @param {string[]} [orders] The sort orders of `iteratees`.\\n * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.\\n * @returns {Array} Returns the new sorted array.\\n * @example\\n *\\n * var users = [\\n *   { 'user': 'fred',   'age': 48 },\\n *   { 'user': 'barney', 'age': 34 },\\n *   { 'user': 'fred',   'age': 40 },\\n *   { 'user': 'barney', 'age': 36 }\\n * ];\\n *\\n * // Sort by `user` in ascending order and by `age` in descending order.\\n * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);\\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]\\n */\\nfunction orderBy(collection, iteratees, orders, guard) {\\n  if (collection == null) {\\n    return [];\\n  }\\n  if (!isArray(iteratees)) {\\n    iteratees = iteratees == null ? [] : [iteratees];\\n  }\\n  orders = guard ? undefined : orders;\\n  if (!isArray(orders)) {\\n    orders = orders == null ? [] : [orders];\\n  }\\n  return baseOrderBy(collection, iteratees, orders);\\n}\\n\\nmodule.exports = orderBy;\\n\",\"var arrayFilter = require('./_arrayFilter'),\\n    baseFilter = require('./_baseFilter'),\\n    baseIteratee = require('./_baseIteratee'),\\n    isArray = require('./isArray'),\\n    negate = require('./negate');\\n\\n/**\\n * The opposite of `_.filter`; this method returns the elements of `collection`\\n * that `predicate` does **not** return truthy for.\\n *\\n * @static\\n * @memberOf _\\n * @since 0.1.0\\n * @category Collection\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {Function} [predicate=_.identity] The function invoked per iteration.\\n * @returns {Array} Returns the new filtered array.\\n * @see _.filter\\n * @example\\n *\\n * var users = [\\n *   { 'user': 'barney', 'age': 36, 'active': false },\\n *   { 'user': 'fred',   'age': 40, 'active': true }\\n * ];\\n *\\n * _.reject(users, function(o) { return !o.active; });\\n * // => objects for ['fred']\\n *\\n * // The `_.matches` iteratee shorthand.\\n * _.reject(users, { 'age': 40, 'active': true });\\n * // => objects for ['barney']\\n *\\n * // The `_.matchesProperty` iteratee shorthand.\\n * _.reject(users, ['active', false]);\\n * // => objects for ['fred']\\n *\\n * // The `_.property` iteratee shorthand.\\n * _.reject(users, 'active');\\n * // => objects for ['barney']\\n */\\nfunction reject(collection, predicate) {\\n  var func = isArray(collection) ? arrayFilter : baseFilter;\\n  return func(collection, negate(baseIteratee(predicate, 3)));\\n}\\n\\nmodule.exports = reject;\\n\",\"var baseFlatten = require('./_baseFlatten'),\\n    baseOrderBy = require('./_baseOrderBy'),\\n    baseRest = require('./_baseRest'),\\n    isIterateeCall = require('./_isIterateeCall');\\n\\n/**\\n * Creates an array of elements, sorted in ascending order by the results of\\n * running each element in a collection thru each iteratee. This method\\n * performs a stable sort, that is, it preserves the original sort order of\\n * equal elements. The iteratees are invoked with one argument: (value).\\n *\\n * @static\\n * @memberOf _\\n * @since 0.1.0\\n * @category Collection\\n * @param {Array|Object} collection The collection to iterate over.\\n * @param {...(Function|Function[])} [iteratees=[_.identity]]\\n *  The iteratees to sort by.\\n * @returns {Array} Returns the new sorted array.\\n * @example\\n *\\n * var users = [\\n *   { 'user': 'fred',   'age': 48 },\\n *   { 'user': 'barney', 'age': 36 },\\n *   { 'user': 'fred',   'age': 30 },\\n *   { 'user': 'barney', 'age': 34 }\\n * ];\\n *\\n * _.sortBy(users, [function(o) { return o.user; }]);\\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 30]]\\n *\\n * _.sortBy(users, ['user', 'age']);\\n * // => objects for [['barney', 34], ['barney', 36], ['fred', 30], ['fred', 48]]\\n */\\nvar sortBy = baseRest(function(collection, iteratees) {\\n  if (collection == null) {\\n    return [];\\n  }\\n  var length = iteratees.length;\\n  if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {\\n    iteratees = [];\\n  } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {\\n    iteratees = [iteratees[0]];\\n  }\\n  return baseOrderBy(collection, baseFlatten(iteratees, 1), []);\\n});\\n\\nmodule.exports = sortBy;\\n\",\"function _setPrototypeOf(o, p) {\\n  module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {\\n    o.__proto__ = p;\\n    return o;\\n  };\\n\\n  module.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\\n  return _setPrototypeOf(o, p);\\n}\\n\\nmodule.exports = _setPrototypeOf;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\",\"var arrayLikeToArray = require(\\\"./arrayLikeToArray.js\\\");\\n\\nfunction _arrayWithoutHoles(arr) {\\n  if (Array.isArray(arr)) return arrayLikeToArray(arr);\\n}\\n\\nmodule.exports = _arrayWithoutHoles;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\",\"function _iterableToArray(iter) {\\n  if (typeof Symbol !== \\\"undefined\\\" && iter[Symbol.iterator] != null || iter[\\\"@@iterator\\\"] != null) return Array.from(iter);\\n}\\n\\nmodule.exports = _iterableToArray;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\",\"var arrayLikeToArray = require(\\\"./arrayLikeToArray.js\\\");\\n\\nfunction _unsupportedIterableToArray(o, minLen) {\\n  if (!o) return;\\n  if (typeof o === \\\"string\\\") return arrayLikeToArray(o, minLen);\\n  var n = Object.prototype.toString.call(o).slice(8, -1);\\n  if (n === \\\"Object\\\" && o.constructor) n = o.constructor.name;\\n  if (n === \\\"Map\\\" || n === \\\"Set\\\") return Array.from(o);\\n  if (n === \\\"Arguments\\\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return arrayLikeToArray(o, minLen);\\n}\\n\\nmodule.exports = _unsupportedIterableToArray;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\",\"function _nonIterableSpread() {\\n  throw new TypeError(\\\"Invalid attempt to spread non-iterable instance.\\\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\\\");\\n}\\n\\nmodule.exports = _nonIterableSpread;\\nmodule.exports[\\\"default\\\"] = module.exports, module.exports.__esModule = true;\",\"/**\\n * A specialized version of `_.map` for arrays without support for iteratee\\n * shorthands.\\n *\\n * @private\\n * @param {Array} [array] The array to iterate over.\\n * @param {Function} iteratee The function invoked per iteration.\\n * @returns {Array} Returns the new mapped array.\\n */\\nfunction arrayMap(array, iteratee) {\\n  var index = -1,\\n      length = array == null ? 0 : array.length,\\n      result = Array(length);\\n\\n  while (++index < length) {\\n    result[index] = iteratee(array[index], index, array);\\n  }\\n  return result;\\n}\\n\\nmodule.exports = arrayMap;\\n\",\"/**\\n * Gets the value at `key` of `object`.\\n *\\n * @private\\n * @param {Object} [object] The object to query.\\n * @param {string} key The key of the property to get.\\n * @returns {*} Returns the property value.\\n */\\nfunction getValue(object, key) {\\n  return object == null ? undefined : object[key];\\n}\\n\\nmodule.exports = getValue;\\n\",\"/**\\n * A specialized version of `_.map` for arrays without support for iteratee\\n * shorthands.\\n *\\n * @private\\n * @param {Array} [array] The array to iterate over.\\n * @param {Function} iteratee The function invoked per iteration.\\n * @returns {Array} Returns the new mapped array.\\n */\\nfunction arrayMap(array, iteratee) {\\n  var index = -1,\\n      length = array == null ? 0 : array.length,\\n      result = Array(length);\\n\\n  while (++index < length) {\\n    result[index] = iteratee(array[index], index, array);\\n  }\\n  return result;\\n}\\n\\nmodule.exports = arrayMap;\\n\",\"/**\\n * The base implementation of `_.sortBy` which uses `comparer` to define the\\n * sort order of `array` and replaces criteria objects with their corresponding\\n * values.\\n *\\n * @private\\n * @param {Array} array The array to sort.\\n * @param {Function} comparer The function to define sort order.\\n * @returns {Array} Returns `array`.\\n */\\nfunction baseSortBy(array, comparer) {\\n  var length = array.length;\\n\\n  array.sort(comparer);\\n  while (length--) {\\n    array[length] = array[length].value;\\n  }\\n  return array;\\n}\\n\\nmodule.exports = baseSortBy;\\n\",\"/**\\n * The base implementation of `_.unary` without support for storing metadata.\\n *\\n * @private\\n * @param {Function} func The function to cap arguments for.\\n * @returns {Function} Returns the new capped function.\\n */\\nfunction baseUnary(func) {\\n  return function(value) {\\n    return func(value);\\n  };\\n}\\n\\nmodule.exports = baseUnary;\\n\",\"var compareAscending = require('./_compareAscending');\\n\\n/**\\n * Used by `_.orderBy` to compare multiple properties of a value to another\\n * and stable sort them.\\n *\\n * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,\\n * specify an order of \\\"desc\\\" for descending or \\\"asc\\\" for ascending sort order\\n * of corresponding values.\\n *\\n * @private\\n * @param {Object} object The object to compare.\\n * @param {Object} other The other object to compare.\\n * @param {boolean[]|string[]} orders The order to sort by for each property.\\n * @returns {number} Returns the sort order indicator for `object`.\\n */\\nfunction compareMultiple(object, other, orders) {\\n  var index = -1,\\n      objCriteria = object.criteria,\\n      othCriteria = other.criteria,\\n      length = objCriteria.length,\\n      ordersLength = orders.length;\\n\\n  while (++index < length) {\\n    var result = compareAscending(objCriteria[index], othCriteria[index]);\\n    if (result) {\\n      if (index >= ordersLength) {\\n        return result;\\n      }\\n      var order = orders[index];\\n      return result * (order == 'desc' ? -1 : 1);\\n    }\\n  }\\n  // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications\\n  // that causes it, under certain circumstances, to provide the same value for\\n  // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247\\n  // for more details.\\n  //\\n  // This also ensures a stable sort in V8 and other engines.\\n  // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.\\n  return object.index - other.index;\\n}\\n\\nmodule.exports = compareMultiple;\\n\",\"var isSymbol = require('./isSymbol');\\n\\n/**\\n * Compares values to sort them in ascending order.\\n *\\n * @private\\n * @param {*} value The value to compare.\\n * @param {*} other The other value to compare.\\n * @returns {number} Returns the sort order indicator for `value`.\\n */\\nfunction compareAscending(value, other) {\\n  if (value !== other) {\\n    var valIsDefined = value !== undefined,\\n        valIsNull = value === null,\\n        valIsReflexive = value === value,\\n        valIsSymbol = isSymbol(value);\\n\\n    var othIsDefined = other !== undefined,\\n        othIsNull = other === null,\\n        othIsReflexive = other === other,\\n        othIsSymbol = isSymbol(other);\\n\\n    if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||\\n        (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) ||\\n        (valIsNull && othIsDefined && othIsReflexive) ||\\n        (!valIsDefined && othIsReflexive) ||\\n        !valIsReflexive) {\\n      return 1;\\n    }\\n    if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||\\n        (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) ||\\n        (othIsNull && valIsDefined && valIsReflexive) ||\\n        (!othIsDefined && valIsReflexive) ||\\n        !othIsReflexive) {\\n      return -1;\\n    }\\n  }\\n  return 0;\\n}\\n\\nmodule.exports = compareAscending;\\n\",\"/**\\n * This method returns `false`.\\n *\\n * @static\\n * @memberOf _\\n * @since 4.13.0\\n * @category Util\\n * @returns {boolean} Returns `false`.\\n * @example\\n *\\n * _.times(2, _.stubFalse);\\n * // => [false, false]\\n */\\nfunction stubFalse() {\\n  return false;\\n}\\n\\nmodule.exports = stubFalse;\\n\",\"/**\\n * A specialized version of `_.filter` for arrays without support for\\n * iteratee shorthands.\\n *\\n * @private\\n * @param {Array} [array] The array to iterate over.\\n * @param {Function} predicate The function invoked per iteration.\\n * @returns {Array} Returns the new filtered array.\\n */\\nfunction arrayFilter(array, predicate) {\\n  var index = -1,\\n      length = array == null ? 0 : array.length,\\n      resIndex = 0,\\n      result = [];\\n\\n  while (++index < length) {\\n    var value = array[index];\\n    if (predicate(value, index, array)) {\\n      result[resIndex++] = value;\\n    }\\n  }\\n  return result;\\n}\\n\\nmodule.exports = arrayFilter;\\n\",\"/**\\n * A specialized version of `_.filter` for arrays without support for\\n * iteratee shorthands.\\n *\\n * @private\\n * @param {Array} [array] The array to iterate over.\\n * @param {Function} predicate The function invoked per iteration.\\n * @returns {Array} Returns the new filtered array.\\n */\\nfunction arrayFilter(array, predicate) {\\n  var index = -1,\\n      length = array == null ? 0 : array.length,\\n      resIndex = 0,\\n      result = [];\\n\\n  while (++index < length) {\\n    var value = array[index];\\n    if (predicate(value, index, array)) {\\n      result[resIndex++] = value;\\n    }\\n  }\\n  return result;\\n}\\n\\nmodule.exports = arrayFilter;\\n\",\"/** Error message constants. */\\nvar FUNC_ERROR_TEXT = 'Expected a function';\\n\\n/**\\n * Creates a function that negates the result of the predicate `func`. The\\n * `func` predicate is invoked with the `this` binding and arguments of the\\n * created function.\\n *\\n * @static\\n * @memberOf _\\n * @since 3.0.0\\n * @category Function\\n * @param {Function} predicate The predicate to negate.\\n * @returns {Function} Returns the new negated function.\\n * @example\\n *\\n * function isEven(n) {\\n *   return n % 2 == 0;\\n * }\\n *\\n * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));\\n * // => [1, 3, 5]\\n */\\nfunction negate(predicate) {\\n  if (typeof predicate != 'function') {\\n    throw new TypeError(FUNC_ERROR_TEXT);\\n  }\\n  return function() {\\n    var args = arguments;\\n    switch (args.length) {\\n      case 0: return !predicate.call(this);\\n      case 1: return !predicate.call(this, args[0]);\\n      case 2: return !predicate.call(this, args[0], args[1]);\\n      case 3: return !predicate.call(this, args[0], args[1], args[2]);\\n    }\\n    return !predicate.apply(this, args);\\n  };\\n}\\n\\nmodule.exports = negate;\\n\",\"/**\\n * Gets the first element of `array`.\\n *\\n * @static\\n * @memberOf _\\n * @since 0.1.0\\n * @alias first\\n * @category Array\\n * @param {Array} array The array to query.\\n * @returns {*} Returns the first element of `array`.\\n * @example\\n *\\n * _.head([1, 2, 3]);\\n * // => 1\\n *\\n * _.head([]);\\n * // => undefined\\n */\\nfunction head(array) {\\n  return (array && array.length) ? array[0] : undefined;\\n}\\n\\nmodule.exports = head;\\n\",\"var identity = require('./identity'),\\n    overRest = require('./_overRest'),\\n    setToString = require('./_setToString');\\n\\n/**\\n * The base implementation of `_.rest` which doesn't validate or coerce arguments.\\n *\\n * @private\\n * @param {Function} func The function to apply a rest parameter to.\\n * @param {number} [start=func.length-1] The start position of the rest parameter.\\n * @returns {Function} Returns the new function.\\n */\\nfunction baseRest(func, start) {\\n  return setToString(overRest(func, start, identity), func + '');\\n}\\n\\nmodule.exports = baseRest;\\n\",\"var apply = require('./_apply');\\n\\n/* Built-in method references for those with the same name as other `lodash` methods. */\\nvar nativeMax = Math.max;\\n\\n/**\\n * A specialized version of `baseRest` which transforms the rest array.\\n *\\n * @private\\n * @param {Function} func The function to apply a rest parameter to.\\n * @param {number} [start=func.length-1] The start position of the rest parameter.\\n * @param {Function} transform The rest array transform.\\n * @returns {Function} Returns the new function.\\n */\\nfunction overRest(func, start, transform) {\\n  start = nativeMax(start === undefined ? (func.length - 1) : start, 0);\\n  return function() {\\n    var args = arguments,\\n        index = -1,\\n        length = nativeMax(args.length - start, 0),\\n        array = Array(length);\\n\\n    while (++index < length) {\\n      array[index] = args[start + index];\\n    }\\n    index = -1;\\n    var otherArgs = Array(start + 1);\\n    while (++index < start) {\\n      otherArgs[index] = args[index];\\n    }\\n    otherArgs[start] = transform(array);\\n    return apply(func, this, otherArgs);\\n  };\\n}\\n\\nmodule.exports = overRest;\\n\",\"/**\\n * A faster alternative to `Function#apply`, this function invokes `func`\\n * with the `this` binding of `thisArg` and the arguments of `args`.\\n *\\n * @private\\n * @param {Function} func The function to invoke.\\n * @param {*} thisArg The `this` binding of `func`.\\n * @param {Array} args The arguments to invoke `func` with.\\n * @returns {*} Returns the result of `func`.\\n */\\nfunction apply(func, thisArg, args) {\\n  switch (args.length) {\\n    case 0: return func.call(thisArg);\\n    case 1: return func.call(thisArg, args[0]);\\n    case 2: return func.call(thisArg, args[0], args[1]);\\n    case 3: return func.call(thisArg, args[0], args[1], args[2]);\\n  }\\n  return func.apply(thisArg, args);\\n}\\n\\nmodule.exports = apply;\\n\",\"/**\\n * This method returns the first argument it receives.\\n *\\n * @static\\n * @since 0.1.0\\n * @memberOf _\\n * @category Util\\n * @param {*} value Any value.\\n * @returns {*} Returns `value`.\\n * @example\\n *\\n * var object = { 'a': 1 };\\n *\\n * console.log(_.identity(object) === object);\\n * // => true\\n */\\nfunction identity(value) {\\n  return value;\\n}\\n\\nmodule.exports = identity;\\n\",\"/**\\n * This method returns `false`.\\n *\\n * @static\\n * @memberOf _\\n * @since 4.13.0\\n * @category Util\\n * @returns {boolean} Returns `false`.\\n * @example\\n *\\n * _.times(2, _.stubFalse);\\n * // => [false, false]\\n */\\nfunction stubFalse() {\\n  return false;\\n}\\n\\nmodule.exports = stubFalse;\\n\",\"export default function _isPlaceholder(a) {\\n       return a != null && typeof a === 'object' && a['@@functional/placeholder'] === true;\\n}\",\"import _isPlaceholder from './_isPlaceholder.js';\\n\\n/**\\n * Optimized internal one-arity curry function.\\n *\\n * @private\\n * @category Function\\n * @param {Function} fn The function to curry.\\n * @return {Function} The curried function.\\n */\\nexport default function _curry1(fn) {\\n  return function f1(a) {\\n    if (arguments.length === 0 || _isPlaceholder(a)) {\\n      return f1;\\n    } else {\\n      return fn.apply(this, arguments);\\n    }\\n  };\\n}\",\"export default function _arity(n, fn) {\\n  /* eslint-disable no-unused-vars */\\n  switch (n) {\\n    case 0:\\n      return function () {\\n        return fn.apply(this, arguments);\\n      };\\n    case 1:\\n      return function (a0) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 2:\\n      return function (a0, a1) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 3:\\n      return function (a0, a1, a2) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 4:\\n      return function (a0, a1, a2, a3) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 5:\\n      return function (a0, a1, a2, a3, a4) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 6:\\n      return function (a0, a1, a2, a3, a4, a5) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 7:\\n      return function (a0, a1, a2, a3, a4, a5, a6) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 8:\\n      return function (a0, a1, a2, a3, a4, a5, a6, a7) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 9:\\n      return function (a0, a1, a2, a3, a4, a5, a6, a7, a8) {\\n        return fn.apply(this, arguments);\\n      };\\n    case 10:\\n      return function (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) {\\n        return fn.apply(this, arguments);\\n      };\\n    default:\\n      throw new Error('First argument to _arity must be a non-negative integer no greater than ten');\\n  }\\n}\",\"import _curry1 from './_curry1.js';\\nimport _isPlaceholder from './_isPlaceholder.js';\\n\\n/**\\n * Optimized internal two-arity curry function.\\n *\\n * @private\\n * @category Function\\n * @param {Function} fn The function to curry.\\n * @return {Function} The curried function.\\n */\\nexport default function _curry2(fn) {\\n  return function f2(a, b) {\\n    switch (arguments.length) {\\n      case 0:\\n        return f2;\\n      case 1:\\n        return _isPlaceholder(a) ? f2 : _curry1(function (_b) {\\n          return fn(a, _b);\\n        });\\n      default:\\n        return _isPlaceholder(a) && _isPlaceholder(b) ? f2 : _isPlaceholder(a) ? _curry1(function (_a) {\\n          return fn(_a, b);\\n        }) : _isPlaceholder(b) ? _curry1(function (_b) {\\n          return fn(a, _b);\\n        }) : fn(a, b);\\n    }\\n  };\\n}\",\"import _arity from './internal/_arity.js';\\nimport _curry1 from './internal/_curry1.js';\\nimport _curry2 from './internal/_curry2.js';\\nimport _curryN from './internal/_curryN.js';\\n\\n/**\\n * Returns a curried equivalent of the provided function, with the specified\\n * arity. The curried function has two unusual capabilities. First, its\\n * arguments needn't be provided one at a time. If `g` is `R.curryN(3, f)`, the\\n * following are equivalent:\\n *\\n *   - `g(1)(2)(3)`\\n *   - `g(1)(2, 3)`\\n *   - `g(1, 2)(3)`\\n *   - `g(1, 2, 3)`\\n *\\n * Secondly, the special placeholder value [`R.__`](#__) may be used to specify\\n * \\\"gaps\\\", allowing partial application of any combination of arguments,\\n * regardless of their positions. If `g` is as above and `_` is [`R.__`](#__),\\n * the following are equivalent:\\n *\\n *   - `g(1, 2, 3)`\\n *   - `g(_, 2, 3)(1)`\\n *   - `g(_, _, 3)(1)(2)`\\n *   - `g(_, _, 3)(1, 2)`\\n *   - `g(_, 2)(1)(3)`\\n *   - `g(_, 2)(1, 3)`\\n *   - `g(_, 2)(_, 3)(1)`\\n *\\n * @func\\n * @memberOf R\\n * @since v0.5.0\\n * @category Function\\n * @sig Number -> (* -> a) -> (* -> a)\\n * @param {Number} length The arity for the returned function.\\n * @param {Function} fn The function to curry.\\n * @return {Function} A new, curried function.\\n * @see R.curry\\n * @example\\n *\\n *      const sumArgs = (...args) => R.sum(args);\\n *\\n *      const curriedAddFourNumbers = R.curryN(4, sumArgs);\\n *      const f = curriedAddFourNumbers(1, 2);\\n *      const g = f(3);\\n *      g(4); //=> 10\\n */\\nvar curryN = /*#__PURE__*/_curry2(function curryN(length, fn) {\\n  if (length === 1) {\\n    return _curry1(fn);\\n  }\\n  return _arity(length, _curryN(length, [], fn));\\n});\\nexport default curryN;\",\"import _arity from './_arity.js';\\nimport _isPlaceholder from './_isPlaceholder.js';\\n\\n/**\\n * Internal curryN function.\\n *\\n * @private\\n * @category Function\\n * @param {Number} length The arity of the curried function.\\n * @param {Array} received An array of arguments received thus far.\\n * @param {Function} fn The function to curry.\\n * @return {Function} The curried function.\\n */\\nexport default function _curryN(length, received, fn) {\\n  return function () {\\n    var combined = [];\\n    var argsIdx = 0;\\n    var left = length;\\n    var combinedIdx = 0;\\n    while (combinedIdx < received.length || argsIdx < arguments.length) {\\n      var result;\\n      if (combinedIdx < received.length && (!_isPlaceholder(received[combinedIdx]) || argsIdx >= arguments.length)) {\\n        result = received[combinedIdx];\\n      } else {\\n        result = arguments[argsIdx];\\n        argsIdx += 1;\\n      }\\n      combined[combinedIdx] = result;\\n      if (!_isPlaceholder(result)) {\\n        left -= 1;\\n      }\\n      combinedIdx += 1;\\n    }\\n    return left <= 0 ? fn.apply(this, combined) : _arity(left, _curryN(length, combined, fn));\\n  };\\n}\",\"import _curry1 from './internal/_curry1.js';\\nimport curryN from './curryN.js';\\n\\n/**\\n * Returns a curried equivalent of the provided function. The curried function\\n * has two unusual capabilities. First, its arguments needn't be provided one\\n * at a time. If `f` is a ternary function and `g` is `R.curry(f)`, the\\n * following are equivalent:\\n *\\n *   - `g(1)(2)(3)`\\n *   - `g(1)(2, 3)`\\n *   - `g(1, 2)(3)`\\n *   - `g(1, 2, 3)`\\n *\\n * Secondly, the special placeholder value [`R.__`](#__) may be used to specify\\n * \\\"gaps\\\", allowing partial application of any combination of arguments,\\n * regardless of their positions. If `g` is as above and `_` is [`R.__`](#__),\\n * the following are equivalent:\\n *\\n *   - `g(1, 2, 3)`\\n *   - `g(_, 2, 3)(1)`\\n *   - `g(_, _, 3)(1)(2)`\\n *   - `g(_, _, 3)(1, 2)`\\n *   - `g(_, 2)(1)(3)`\\n *   - `g(_, 2)(1, 3)`\\n *   - `g(_, 2)(_, 3)(1)`\\n *\\n * @func\\n * @memberOf R\\n * @since v0.1.0\\n * @category Function\\n * @sig (* -> a) -> (* -> a)\\n * @param {Function} fn The function to curry.\\n * @return {Function} A new, curried function.\\n * @see R.curryN, R.partial\\n * @example\\n *\\n *      const addFourNumbers = (a, b, c, d) => a + b + c + d;\\n *\\n *      const curriedAddFourNumbers = R.curry(addFourNumbers);\\n *      const f = curriedAddFourNumbers(1, 2);\\n *      const g = f(3);\\n *      g(4); //=> 10\\n */\\nvar curry = /*#__PURE__*/_curry1(function curry(fn) {\\n  return curryN(fn.length, fn);\\n});\\nexport default curry;\",\"/**\\n * A special placeholder value used to specify \\\"gaps\\\" within curried functions,\\n * allowing partial application of any combination of arguments, regardless of\\n * their positions.\\n *\\n * If `g` is a curried ternary function and `_` is `R.__`, the following are\\n * equivalent:\\n *\\n *   - `g(1, 2, 3)`\\n *   - `g(_, 2, 3)(1)`\\n *   - `g(_, _, 3)(1)(2)`\\n *   - `g(_, _, 3)(1, 2)`\\n *   - `g(_, 2, _)(1, 3)`\\n *   - `g(_, 2)(1)(3)`\\n *   - `g(_, 2)(1, 3)`\\n *   - `g(_, 2)(_, 3)(1)`\\n *\\n * @name __\\n * @constant\\n * @memberOf R\\n * @since v0.6.0\\n * @category Function\\n * @example\\n *\\n *      const greet = R.replace('{name}', R.__, 'Hello, {name}!');\\n *      greet('Alice'); //=> 'Hello, Alice!'\\n */\\nexport default { '@@functional/placeholder': true };\",\"import _toConsumableArray from \\\"@babel/runtime/helpers/toConsumableArray\\\";\\nimport _typeof from \\\"@babel/runtime/helpers/typeof\\\";\\nimport { curry, __ as placeholder } from 'ramda';\\n\\nfunction forOwn(obj, fn) {\\n  for (var key in obj) {\\n    if (obj.hasOwnProperty(key)) {\\n      fn(obj[key], key);\\n    }\\n  }\\n}\\n\\nfunction isArrayLike(value) {\\n  return value && _typeof(value) === 'object' && typeof value.length === 'number' && value.length >= 0 && value.length % 1 === 0;\\n}\\n\\nvar OWNER_ID_TAG = '@@_______immutableOpsOwnerID';\\n\\nfunction fastArrayCopy(arr) {\\n  var copied = new Array(arr.length);\\n\\n  for (var i = 0; i < arr.length; i++) {\\n    copied[i] = arr[i];\\n  }\\n\\n  return copied;\\n}\\n\\nexport function canMutate(obj, ownerID) {\\n  if (!ownerID) return false;\\n  return obj[OWNER_ID_TAG] === ownerID;\\n}\\nvar newOwnerID = typeof Symbol === 'function' ? function () {\\n  return Symbol('ownerID');\\n} : function () {\\n  return {};\\n};\\nexport var getBatchToken = newOwnerID;\\n\\nfunction addOwnerID(obj, ownerID) {\\n  Object.defineProperty(obj, OWNER_ID_TAG, {\\n    value: ownerID,\\n    configurable: true,\\n    enumerable: false\\n  });\\n  return obj;\\n}\\n\\nfunction prepareNewObject(instance, ownerID) {\\n  if (ownerID) {\\n    addOwnerID(instance, ownerID);\\n  }\\n\\n  return instance;\\n}\\n\\nfunction forceArray(arg) {\\n  if (!(arg instanceof Array)) {\\n    return [arg];\\n  }\\n\\n  return arg;\\n}\\n\\nvar PATH_SEPARATOR = '.';\\n\\nfunction normalizePath(pathArg) {\\n  if (typeof pathArg === 'string') {\\n    if (pathArg.indexOf(PATH_SEPARATOR) === -1) {\\n      return [pathArg];\\n    }\\n\\n    return pathArg.split(PATH_SEPARATOR);\\n  }\\n\\n  return pathArg;\\n}\\n\\nfunction mutableSet(key, value, obj) {\\n  obj[key] = value;\\n  return obj;\\n}\\n\\nfunction mutableSetIn(_pathArg, value, obj) {\\n  var originalPathArg = normalizePath(_pathArg);\\n  var pathLen = originalPathArg.length;\\n  var done = false;\\n  var idx = 0;\\n  var acc = obj;\\n  var curr = originalPathArg[idx];\\n\\n  while (!done) {\\n    if (idx === pathLen - 1) {\\n      acc[curr] = value;\\n      done = true;\\n    } else {\\n      var currType = _typeof(acc[curr]);\\n\\n      if (currType === 'undefined') {\\n        var newObj = {};\\n        prepareNewObject(newObj, null);\\n        acc[curr] = newObj;\\n      } else if (currType !== 'object') {\\n        var pathRepr = \\\"\\\".concat(originalPathArg[idx - 1], \\\".\\\").concat(curr);\\n        throw new Error(\\\"A non-object value was encountered when traversing setIn path at \\\".concat(pathRepr, \\\".\\\"));\\n      }\\n\\n      acc = acc[curr];\\n      idx++;\\n      curr = originalPathArg[idx];\\n    }\\n  }\\n\\n  return obj;\\n}\\n\\nfunction valueInPath(_pathArg, obj) {\\n  var pathArg = normalizePath(_pathArg);\\n  var acc = obj;\\n\\n  for (var i = 0; i < pathArg.length; i++) {\\n    var curr = pathArg[i];\\n    var currRef = acc[curr];\\n\\n    if (i === pathArg.length - 1) {\\n      return currRef;\\n    }\\n\\n    if (_typeof(currRef) === 'object') {\\n      acc = currRef;\\n    } else {\\n      return undefined;\\n    }\\n  }\\n\\n  return undefined;\\n}\\n\\nfunction immutableSetIn(ownerID, _pathArg, value, obj) {\\n  var pathArg = normalizePath(_pathArg);\\n  var currentValue = valueInPath(pathArg, obj);\\n  if (value === currentValue) return obj;\\n  var pathLen = pathArg.length;\\n  var acc;\\n\\n  if (canMutate(obj, ownerID)) {\\n    acc = obj;\\n  } else {\\n    acc = Object.assign(prepareNewObject({}, ownerID), obj);\\n  }\\n\\n  var rootObj = acc;\\n  pathArg.forEach(function (curr, idx) {\\n    if (idx === pathLen - 1) {\\n      acc[curr] = value;\\n      return;\\n    }\\n\\n    var currRef = acc[curr];\\n\\n    var currType = _typeof(currRef);\\n\\n    if (currType === 'object') {\\n      if (canMutate(currRef, ownerID)) {\\n        acc = currRef;\\n      } else {\\n        var newObj = prepareNewObject({}, ownerID);\\n        acc[curr] = Object.assign(newObj, currRef);\\n        acc = newObj;\\n      }\\n\\n      return;\\n    }\\n\\n    if (currType === 'undefined') {\\n      var _newObj = prepareNewObject({}, ownerID);\\n\\n      acc[curr] = _newObj;\\n      acc = _newObj;\\n      return;\\n    }\\n\\n    var pathRepr = \\\"\\\".concat(pathArg[idx - 1], \\\".\\\").concat(curr);\\n    throw new Error(\\\"A non-object value was encountered when traversing setIn path at \\\".concat(pathRepr, \\\".\\\"));\\n  });\\n  return rootObj;\\n}\\n\\nfunction mutableMerge(isDeep, _mergeObjs, baseObj) {\\n  var mergeObjs = forceArray(_mergeObjs);\\n\\n  if (isDeep) {\\n    mergeObjs.forEach(function (mergeObj) {\\n      forOwn(mergeObj, function (value, key) {\\n        if (isDeep && baseObj.hasOwnProperty(key)) {\\n          var assignValue;\\n\\n          if (_typeof(value) === 'object') {\\n            assignValue = mutableMerge(isDeep, [value], baseObj[key]);\\n          } else {\\n            assignValue = value;\\n          }\\n\\n          baseObj[key] = assignValue;\\n        } else {\\n          baseObj[key] = value;\\n        }\\n      });\\n    });\\n  } else {\\n    Object.assign.apply(Object, [baseObj].concat(_toConsumableArray(mergeObjs)));\\n  }\\n\\n  return baseObj;\\n}\\n\\nvar mutableShallowMerge = mutableMerge.bind(null, false);\\nvar mutableDeepMerge = mutableMerge.bind(null, true);\\n\\nfunction mutableOmit(_keys, obj) {\\n  var keys = forceArray(_keys);\\n  keys.forEach(function (key) {\\n    delete obj[key];\\n  });\\n  return obj;\\n}\\n\\nfunction shouldMergeKey(obj, other, key) {\\n  return obj[key] !== other[key];\\n}\\n\\nfunction immutableMerge(isDeep, ownerID, _mergeObjs, obj) {\\n  if (canMutate(obj, ownerID)) return mutableMerge(isDeep, _mergeObjs, obj);\\n  var mergeObjs = forceArray(_mergeObjs);\\n  var hasChanges = false;\\n  var nextObject = obj;\\n\\n  var willChange = function willChange() {\\n    if (!hasChanges) {\\n      hasChanges = true;\\n      nextObject = Object.assign({}, obj);\\n      prepareNewObject(nextObject, ownerID);\\n    }\\n  };\\n\\n  mergeObjs.forEach(function (mergeObj) {\\n    forOwn(mergeObj, function (mergeValue, key) {\\n      if (isDeep && obj.hasOwnProperty(key)) {\\n        var currentValue = nextObject[key];\\n\\n        if (_typeof(mergeValue) === 'object' && !(mergeValue instanceof Array)) {\\n          if (shouldMergeKey(nextObject, mergeObj, key)) {\\n            var recursiveMergeResult = immutableMerge(isDeep, ownerID, mergeValue, currentValue);\\n\\n            if (recursiveMergeResult !== currentValue) {\\n              willChange();\\n              nextObject[key] = recursiveMergeResult;\\n            }\\n          }\\n\\n          return true; // continue forOwn\\n        }\\n      }\\n\\n      if (shouldMergeKey(nextObject, mergeObj, key)) {\\n        willChange();\\n        nextObject[key] = mergeValue;\\n      }\\n\\n      return undefined;\\n    });\\n  });\\n  return nextObject;\\n}\\n\\nvar immutableDeepMerge = immutableMerge.bind(null, true);\\nvar immutableShallowMerge = immutableMerge.bind(null, false);\\n\\nfunction immutableArrSet(ownerID, index, value, arr) {\\n  if (canMutate(arr, ownerID)) return mutableSet(index, value, arr);\\n  if (arr[index] === value) return arr;\\n  var newArr = fastArrayCopy(arr);\\n  newArr[index] = value;\\n  prepareNewObject(newArr, ownerID);\\n  return newArr;\\n}\\n\\nfunction immutableSet(ownerID, key, value, obj) {\\n  if (isArrayLike(obj)) return immutableArrSet(ownerID, key, value, obj);\\n  if (canMutate(obj, ownerID)) return mutableSet(key, value, obj);\\n  if (obj[key] === value) return obj;\\n  var newObj = Object.assign({}, obj);\\n  prepareNewObject(newObj, ownerID);\\n  newObj[key] = value;\\n  return newObj;\\n}\\n\\nfunction immutableOmit(ownerID, _keys, obj) {\\n  if (canMutate(obj, ownerID)) return mutableOmit(_keys, obj);\\n  var keys = forceArray(_keys);\\n  var keysInObj = keys.filter(function (key) {\\n    return obj.hasOwnProperty(key);\\n  }); // None of the keys were in the object, so we can return `obj`.\\n\\n  if (keysInObj.length === 0) return obj;\\n  var newObj = Object.assign({}, obj);\\n  keysInObj.forEach(function (key) {\\n    delete newObj[key];\\n  });\\n  prepareNewObject(newObj, ownerID);\\n  return newObj;\\n}\\n\\nfunction mutableArrPush(_vals, arr) {\\n  var vals = forceArray(_vals);\\n  arr.push.apply(arr, _toConsumableArray(vals));\\n  return arr;\\n}\\n\\nfunction mutableArrFilter(func, arr) {\\n  var currIndex = 0;\\n  var originalIndex = 0;\\n\\n  while (currIndex < arr.length) {\\n    var item = arr[currIndex];\\n\\n    if (!func(item, originalIndex)) {\\n      arr.splice(currIndex, 1);\\n    } else {\\n      currIndex++;\\n    }\\n\\n    originalIndex++;\\n  }\\n\\n  return arr;\\n}\\n\\nfunction mutableArrSplice(index, deleteCount, _vals, arr) {\\n  var vals = forceArray(_vals);\\n  arr.splice.apply(arr, [index, deleteCount].concat(_toConsumableArray(vals)));\\n  return arr;\\n}\\n\\nfunction mutableArrInsert(index, _vals, arr) {\\n  return mutableArrSplice(index, 0, _vals, arr);\\n}\\n\\nfunction immutableArrSplice(ownerID, index, deleteCount, _vals, arr) {\\n  if (canMutate(arr, ownerID)) return mutableArrSplice(index, deleteCount, _vals, arr);\\n  var vals = forceArray(_vals);\\n  var newArr = arr.slice();\\n  prepareNewObject(newArr, ownerID);\\n  newArr.splice.apply(newArr, [index, deleteCount].concat(_toConsumableArray(vals)));\\n  return newArr;\\n}\\n\\nfunction immutableArrInsert(ownerID, index, _vals, arr) {\\n  if (canMutate(arr, ownerID)) return mutableArrInsert(index, _vals, arr);\\n  return immutableArrSplice(ownerID, index, 0, _vals, arr);\\n}\\n\\nfunction immutableArrPush(ownerID, vals, arr) {\\n  return immutableArrInsert(ownerID, arr.length, vals, arr);\\n}\\n\\nfunction immutableArrFilter(ownerID, func, arr) {\\n  if (canMutate(arr, ownerID)) return mutableArrFilter(func, arr);\\n  var newArr = arr.filter(func);\\n  if (newArr.length === arr.length) return arr;\\n  prepareNewObject(newArr, ownerID);\\n  return newArr;\\n}\\n\\nvar immutableOperations = {\\n  // object operations\\n  merge: immutableShallowMerge,\\n  deepMerge: immutableDeepMerge,\\n  omit: immutableOmit,\\n  setIn: immutableSetIn,\\n  // array operations\\n  insert: immutableArrInsert,\\n  push: immutableArrPush,\\n  filter: immutableArrFilter,\\n  splice: immutableArrSplice,\\n  // both\\n  set: immutableSet\\n};\\nvar mutableOperations = {\\n  // object operations\\n  merge: mutableShallowMerge,\\n  deepMerge: mutableDeepMerge,\\n  omit: mutableOmit,\\n  setIn: mutableSetIn,\\n  // array operations\\n  insert: mutableArrInsert,\\n  push: mutableArrPush,\\n  filter: mutableArrFilter,\\n  splice: mutableArrSplice,\\n  // both\\n  set: mutableSet\\n};\\nexport function getImmutableOps() {\\n  var immutableOps = Object.assign({}, immutableOperations);\\n  forOwn(immutableOps, function (value, key) {\\n    immutableOps[key] = curry(value.bind(null, null));\\n  });\\n  var mutableOps = Object.assign({}, mutableOperations);\\n  forOwn(mutableOps, function (value, key) {\\n    mutableOps[key] = curry(value);\\n  });\\n  var batchOps = Object.assign({}, immutableOperations);\\n  forOwn(batchOps, function (value, key) {\\n    batchOps[key] = curry(value);\\n  });\\n\\n  function batched(_token, _fn) {\\n    var token;\\n    var fn;\\n\\n    if (typeof _token === 'function') {\\n      fn = _token;\\n      token = getBatchToken();\\n    } else {\\n      token = _token;\\n      fn = _fn;\\n    }\\n\\n    var immutableOpsBoundToToken = Object.assign({}, immutableOperations);\\n    forOwn(immutableOpsBoundToToken, function (value, key) {\\n      immutableOpsBoundToToken[key] = curry(value.bind(null, token));\\n    });\\n    return fn(immutableOpsBoundToToken);\\n  }\\n\\n  return Object.assign(immutableOps, {\\n    mutable: mutableOps,\\n    batch: batchOps,\\n    batched: batched,\\n    __: placeholder,\\n    getBatchToken: getBatchToken\\n  });\\n}\\nexport var ops = getImmutableOps();\\nexport default ops;\",\"export const UPDATE = \\\"REDUX_ORM_UPDATE\\\";\\nexport const DELETE = \\\"REDUX_ORM_DELETE\\\";\\nexport const CREATE = \\\"REDUX_ORM_CREATE\\\";\\n\\nexport const FILTER = \\\"REDUX_ORM_FILTER\\\";\\nexport const EXCLUDE = \\\"REDUX_ORM_EXCLUDE\\\";\\nexport const ORDER_BY = \\\"REDUX_ORM_ORDER_BY\\\";\\n\\nexport const SUCCESS = \\\"SUCCESS\\\";\\nexport const FAILURE = \\\"FAILURE\\\";\\n\\n// for detecting ORM state objects\\nexport const STATE_FLAG = \\\"@@_______REDUX_ORM_STATE_FLAG\\\";\\n\\n// for caching selectors based on their ID argument\\nexport const ALL_INSTANCES = Symbol(\\\"REDUX_ORM_ALL_INSTANCES\\\");\\nexport const ID_ARG_KEY_SELECTOR = (_state, idArg) =>\\n    typeof idArg === \\\"undefined\\\" ? ALL_INSTANCES : idArg;\\n\",\"import ops from \\\"immutable-ops\\\";\\nimport { FILTER, EXCLUDE } from \\\"./constants\\\";\\n\\n/**\\n * @module utils\\n * @private\\n */\\n\\n/** @private */\\nfunction warnDeprecated(msg) {\\n    const logger =\\n        typeof console.warn === \\\"function\\\"\\n            ? console.warn.bind(console)\\n            : console.log.bind(console);\\n    return logger(msg);\\n}\\n\\n/** @private */\\nfunction capitalize(string) {\\n    return string.charAt(0).toUpperCase() + string.slice(1);\\n}\\n\\n/**\\n * Returns the branch name for a many-to-many relation.\\n * The name is the combination of the model name and the field name the relation\\n * was declared. The field name's first letter is capitalized.\\n *\\n * Example: model `Author` has a many-to-many relation to the model `Book`, defined\\n * in the `Author` field `books`. The many-to-many branch name will be `AuthorBooks`.\\n *\\n * @param  {string} declarationModelName - the name of the model the many-to-many relation was declared on\\n * @param  {string} fieldName            - the field name where the many-to-many relation was declared on\\n * @return {string} The branch name for the many-to-many relation.\\n */\\nfunction m2mName(declarationModelName, fieldName) {\\n    return declarationModelName + capitalize(fieldName);\\n}\\n\\n/**\\n * Returns the fieldname that saves a foreign key to the\\n * model id where the many-to-many relation was declared.\\n *\\n * Example: `Author` => `fromAuthorId`\\n *\\n * @param  {string} declarationModelName - the name of the model where the relation was declared\\n * @return {string} the field name in the through model for `declarationModelName`'s foreign key.\\n */\\nfunction m2mFromFieldName(declarationModelName) {\\n    return `from${declarationModelName}Id`;\\n}\\n\\n/**\\n * Returns the fieldname that saves a foreign key in a many-to-many through model to the\\n * model where the many-to-many relation was declared.\\n *\\n * Example: `Book` => `toBookId`\\n *\\n * @param  {string} otherModelName - the name of the model that was the target of the many-to-many\\n *                                   declaration.\\n * @return {string} the field name in the through model for `otherModelName`'s foreign key..\\n */\\nfunction m2mToFieldName(otherModelName) {\\n    return `to${otherModelName}Id`;\\n}\\n\\n/** */\\nfunction reverseFieldName(modelName) {\\n    return modelName.toLowerCase() + \\\"Set\\\"; // eslint-disable-line prefer-template\\n}\\n\\n/** @private */\\nfunction querySetDelegatorFactory(methodName) {\\n    return function querySetDelegator(...args) {\\n        return this.getQuerySet()[methodName](...args);\\n    };\\n}\\n\\n/** @private */\\nfunction querySetGetterDelegatorFactory(getterName) {\\n    return function querySetGetterDelegator() {\\n        const qs = this.getQuerySet();\\n        return qs[getterName];\\n    };\\n}\\n\\n/** @private */\\nfunction forEachSuperClass(subClass, func) {\\n    let currClass = subClass;\\n    while (currClass !== Function.prototype) {\\n        func(currClass);\\n        currClass = Object.getPrototypeOf(currClass);\\n    }\\n}\\n\\n/** */\\nfunction attachQuerySetMethods(modelClass, querySetClass) {\\n    const leftToDefine = querySetClass.sharedMethods.slice();\\n\\n    // There is no way to get a property descriptor for the whole prototype chain;\\n    // only from an objects own properties. Therefore we traverse the whole prototype\\n    // chain for querySet.\\n    forEachSuperClass(querySetClass, (cls) => {\\n        for (let i = 0; i < leftToDefine.length; i++) {\\n            let defined = false;\\n            const methodName = leftToDefine[i];\\n            const descriptor = Object.getOwnPropertyDescriptor(\\n                cls.prototype,\\n                methodName\\n            );\\n            if (typeof descriptor !== \\\"undefined\\\") {\\n                if (typeof descriptor.get !== \\\"undefined\\\") {\\n                    descriptor.get = querySetGetterDelegatorFactory(methodName);\\n                    Object.defineProperty(modelClass, methodName, descriptor);\\n                } else {\\n                    modelClass[methodName] = querySetDelegatorFactory(\\n                        methodName\\n                    );\\n                }\\n                defined = true;\\n            }\\n            if (defined) {\\n                leftToDefine.splice(i--, 1);\\n            }\\n        }\\n    });\\n}\\n\\n/**\\n * Normalizes `entity` to an id, where `entity` can be an id\\n * or a Model instance.\\n *\\n * @param  {*} entity - either a Model instance or an id value\\n * @return {*} the id value of `entity`\\n */\\nfunction normalizeEntity(entity) {\\n    if (\\n        entity !== null &&\\n        typeof entity !== \\\"undefined\\\" &&\\n        typeof entity.getId === \\\"function\\\"\\n    ) {\\n        return entity.getId();\\n    }\\n    return entity;\\n}\\n\\n/** */\\nfunction reverseFieldErrorMessage(\\n    modelName,\\n    fieldName,\\n    toModelName,\\n    backwardsFieldName\\n) {\\n    return [\\n        `Reverse field ${backwardsFieldName} already defined`,\\n        ` on model ${toModelName}. To fix, set a custom related`,\\n        ` name on ${modelName}.${fieldName}.`,\\n    ].join(\\\"\\\");\\n}\\n\\n/**\\n * Fastest way to check if two objects are equal.\\n * Object and array values have to be referentially equal.\\n */\\nfunction objectShallowEquals(a, b) {\\n    const entriesInA = Object.entries(Object(a));\\n\\n    if (entriesInA.length !== Object.keys(b).length) {\\n        return false;\\n    }\\n\\n    return entriesInA.every(\\n        ([key, value]) => b.hasOwnProperty(key) && b[key] === value\\n    );\\n}\\n\\n/** */\\nfunction arrayDiffActions(sourceArr, targetArr) {\\n    const itemsInBoth = sourceArr.filter((item) => targetArr.includes(item));\\n    const deleteItems = sourceArr.filter((item) => !itemsInBoth.includes(item));\\n    const addItems = targetArr.filter((item) => !itemsInBoth.includes(item));\\n\\n    if (deleteItems.length || addItems.length) {\\n        return {\\n            delete: deleteItems,\\n            add: addItems,\\n        };\\n    }\\n    return null;\\n}\\n\\nconst { getBatchToken } = ops;\\n\\n/**\\n * @return boolean\\n */\\nfunction clauseFiltersByAttribute({ type, payload }, attribute) {\\n    if (type !== FILTER) return false;\\n\\n    if (typeof payload !== \\\"object\\\") {\\n        /**\\n         * payload could also be a function in which case\\n         * we would have no way of knowing what it does,\\n         * so we default to false for non-objects\\n         */\\n        return false;\\n    }\\n\\n    if (!payload.hasOwnProperty(attribute)) return false;\\n    const attributeValue = payload[attribute];\\n    if (attributeValue === null) return false;\\n    if (attributeValue === undefined) return false;\\n\\n    return true;\\n}\\n\\n/**\\n * @return boolean\\n */\\nfunction clauseReducesResultSetSize({ type }) {\\n    return [FILTER, EXCLUDE].includes(type);\\n}\\n\\n/**\\n * @param {Object} object\\n * @return Object\\n */\\nfunction mapValues(object, func) {\\n    return Object.entries(object).reduce((newObject, [key, value]) => {\\n        newObject[key] = func(value);\\n        return newObject;\\n    }, {});\\n}\\n\\n/** */\\nfunction normalizeModelReference(modelNameOrClass) {\\n    if (!modelNameOrClass || typeof modelNameOrClass === \\\"string\\\") {\\n        return modelNameOrClass;\\n    }\\n    return modelNameOrClass.modelName;\\n}\\n\\nexport {\\n    attachQuerySetMethods,\\n    m2mName,\\n    m2mFromFieldName,\\n    m2mToFieldName,\\n    reverseFieldName,\\n    normalizeEntity,\\n    reverseFieldErrorMessage,\\n    objectShallowEquals,\\n    ops,\\n    arrayDiffActions,\\n    getBatchToken,\\n    clauseFiltersByAttribute,\\n    clauseReducesResultSetSize,\\n    warnDeprecated,\\n    mapValues,\\n    normalizeModelReference,\\n};\\n\",\"import { normalizeEntity, warnDeprecated, mapValues } from \\\"./utils\\\";\\n\\nimport { UPDATE, DELETE, FILTER, EXCLUDE, ORDER_BY } from \\\"./constants\\\";\\n\\n/**\\n * This class is used to build and make queries to the database\\n * and operating the resulting set (such as updating attributes\\n * or deleting the records).\\n *\\n * The queries are built lazily. For example:\\n *\\n * ```javascript\\n * const qs = Book.all()\\n *     .filter(book => book.releaseYear > 1999)\\n *     .orderBy('name');\\n * ```\\n *\\n * Doesn't execute a query. The query is executed only when\\n * you need information from the query result, such as {@link QuerySet#count},\\n * {@link QuerySet#toRefArray}. After the query is executed, the resulting\\n * set is cached in the QuerySet instance.\\n *\\n * QuerySet instances also return copies, so chaining filters doesn't\\n * mutate the previous instances.\\n */\\nconst QuerySet = class QuerySet {\\n    /**\\n     * Creates a QuerySet. The constructor is mainly for internal use;\\n     * You should access QuerySet instances from {@link Model}.\\n     *\\n     * @param  {Model} modelClass - the model class of objects in this QuerySet.\\n     * @param  {any[]} clauses - query clauses needed to evaluate the set.\\n     * @param {Object} [opts] - additional options\\n     */\\n    constructor(modelClass, clauses, opts) {\\n        Object.assign(this, {\\n            modelClass,\\n            clauses: clauses || [],\\n        });\\n\\n        this._opts = opts;\\n    }\\n\\n    static addSharedMethod(methodName) {\\n        this.sharedMethods = this.sharedMethods.concat(methodName);\\n    }\\n\\n    _new(clauses, userOpts) {\\n        const opts = { ...this._opts, ...userOpts };\\n        return new this.constructor(this.modelClass, clauses, opts);\\n    }\\n\\n    toString() {\\n        this._evaluate();\\n        const contents = this.rows\\n            .map(({ id }) => this.modelClass.withId(id).toString())\\n            .join(\\\"\\\\n    - \\\");\\n        return `QuerySet contents:\\\\n    - ${contents}`;\\n    }\\n\\n    /**\\n     * Returns an array of the plain objects represented by the QuerySet.\\n     * The plain objects are direct references to the store.\\n     *\\n     * @return {Object[]} references to the plain JS objects represented by\\n     *                    the QuerySet\\n     */\\n    toRefArray() {\\n        return this._evaluate();\\n    }\\n\\n    /**\\n     * Returns an array of {@link Model} instances represented by the QuerySet.\\n     * @return {Model[]} model instances represented by the QuerySet\\n     */\\n    toModelArray() {\\n        const { modelClass: ModelClass } = this;\\n        return this._evaluate().map((props) => new ModelClass(props));\\n    }\\n\\n    /**\\n     * Returns the number of {@link Model} instances represented by the QuerySet.\\n     *\\n     * @return {number} length of the QuerySet\\n     */\\n    count() {\\n        this._evaluate();\\n        return this.rows.length;\\n    }\\n\\n    /**\\n     * Checks if the {@link QuerySet} instance has any records matching the query\\n     * in the database.\\n     *\\n     * @return {Boolean} `true` if the {@link QuerySet} instance contains entities, else `false`.\\n     */\\n    exists() {\\n        return Boolean(this.count());\\n    }\\n\\n    /**\\n     * Returns the {@link Model} instance at index `index` in the {@link QuerySet} instance if\\n     * `withRefs` flag is set to `false`, or a reference to the plain JavaScript\\n     * object in the model state if `true`.\\n     *\\n     * @param  {number} index - index of the model instance to get\\n     * @return {Model|undefined} a {@link Model} instance at index\\n     *                           `index` in the {@link QuerySet} instance,\\n     *                           or undefined if the index is out of bounds.\\n     */\\n    at(index) {\\n        const { modelClass: ModelClass } = this;\\n\\n        const rows = this._evaluate();\\n        if (index >= 0 && index < rows.length) {\\n            return new ModelClass(rows[index]);\\n        }\\n\\n        return undefined;\\n    }\\n\\n    /**\\n     * Returns the {@link Model} instance at index 0 in the {@link QuerySet} instance.\\n     * @return {Model}\\n     */\\n    first() {\\n        return this.at(0);\\n    }\\n\\n    /**\\n     * Returns the {@link Model} instance at index `QuerySet.count() - 1`\\n     * @return {Model}\\n     */\\n    last() {\\n        const rows = this._evaluate();\\n        return this.at(rows.length - 1);\\n    }\\n\\n    /**\\n     * Returns a new {@link QuerySet} instance with the same entities.\\n     * @return {QuerySet} a new QuerySet with the same entities.\\n     */\\n    all() {\\n        return this._new(this.clauses);\\n    }\\n\\n    /**\\n     * Returns a new {@link QuerySet} instance with entities that match properties in `lookupObj`.\\n     *\\n     * @param  {Object} lookupObj - the properties to match objects with. Can also be a function.\\n     *                              It works the same as [Lodash filter](https://lodash.com/docs/#filter).\\n     * @return {QuerySet} a new {@link QuerySet} instance with objects that passed the filter.\\n     */\\n    filter(lookupObj) {\\n        /**\\n         * allow foreign keys to be specified as model instances,\\n         * transform model instances to their primary keys\\n         */\\n        const normalizedLookupObj =\\n            typeof lookupObj === \\\"object\\\"\\n                ? mapValues(lookupObj, normalizeEntity)\\n                : lookupObj;\\n\\n        const filterDescriptor = {\\n            type: FILTER,\\n            payload: normalizedLookupObj,\\n        };\\n        /**\\n         * create a new QuerySet\\n         * including only rows matching the lookupObj\\n         */\\n        return this._new(this.clauses.concat(filterDescriptor));\\n    }\\n\\n    /**\\n     * Returns a new {@link QuerySet} instance with entities that do not match\\n     * properties in `lookupObj`.\\n     *\\n     * @param  {Object} lookupObj - the properties to unmatch objects with. Can also be a function.\\n     *                              It works the same as [Lodash reject](https://lodash.com/docs/#reject).\\n     * @return {QuerySet} a new {@link QuerySet} instance with objects that did not pass the filter.\\n     */\\n    exclude(lookupObj) {\\n        /**\\n         * allow foreign keys to be specified as model instances,\\n         * transform model instances to their primary keys\\n         */\\n        const normalizedLookupObj =\\n            typeof lookupObj === \\\"object\\\"\\n                ? mapValues(lookupObj, normalizeEntity)\\n                : lookupObj;\\n        const excludeDescriptor = {\\n            type: EXCLUDE,\\n            payload: normalizedLookupObj,\\n        };\\n\\n        /**\\n         * create a new QuerySet\\n         * excluding all rows matching the lookupObj\\n         */\\n        return this._new(this.clauses.concat(excludeDescriptor));\\n    }\\n\\n    /**\\n     * Performs the actual database query.\\n     * @private\\n     * @return {Array} rows corresponding to the QuerySet's clauses\\n     */\\n    _evaluate() {\\n        if (typeof this.modelClass.session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to query the ${this.modelClass.modelName} model's table without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and use \\\",\\n                    `\\\\`session[\\\"${this.modelClass.modelName}\\\"]\\\\` for querying instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        if (!this._evaluated) {\\n            const { session, modelName: table } = this.modelClass;\\n            const querySpec = {\\n                table,\\n                clauses: this.clauses,\\n            };\\n            this.rows = session.query(querySpec).rows;\\n            this._evaluated = true;\\n        }\\n        return this.rows;\\n    }\\n\\n    /**\\n     * Returns a new {@link QuerySet} instance with entities ordered by `iteratees` in ascending\\n     * order, unless otherwise specified. Delegates to [Lodash orderBy](https://lodash.com/docs/#orderBy).\\n     *\\n     * @param  {string[]|Function[]} iteratees - an array where each item can be a string or a\\n     *                                           function. If a string is supplied, it should\\n     *                                           correspond to property on the entity that will\\n     *                                           determine the order. If a function is supplied,\\n     *                                           it should return the value to order by.\\n     * @param {Array<Boolean|'asc'|'desc'>} [orders] - the sort orders of `iteratees`. If unspecified, all iteratees\\n     *                               will be sorted in ascending order. `true` and `'asc'`\\n     *                               correspond to ascending order, and `false` and `'desc'`\\n     *                               to descending order.\\n     * @return {QuerySet} a new {@link QuerySet} with objects ordered by `iteratees`.\\n     */\\n    orderBy(iteratees, orders) {\\n        const orderByDescriptor = {\\n            type: ORDER_BY,\\n            payload: [iteratees, orders],\\n        };\\n\\n        /**\\n         * create a new QuerySet\\n         * sorting all rows according to the passed arguments\\n         */\\n        return this._new(this.clauses.concat(orderByDescriptor));\\n    }\\n\\n    /**\\n     * Records an update specified with `mergeObj` to all the objects\\n     * in the {@link QuerySet} instance.\\n     *\\n     * @param  {Object} mergeObj - an object to merge with all the objects in this\\n     *                             queryset.\\n     * @return {undefined}\\n     */\\n    update(mergeObj) {\\n        const { session, modelName: table } = this.modelClass;\\n\\n        session.applyUpdate({\\n            action: UPDATE,\\n            query: {\\n                table,\\n                clauses: this.clauses,\\n            },\\n            payload: mergeObj,\\n        });\\n\\n        this._evaluated = false;\\n    }\\n\\n    /**\\n     * Records a deletion of all the objects in this {@link QuerySet} instance.\\n     * @return {undefined}\\n     */\\n    delete() {\\n        const { session, modelName: table } = this.modelClass;\\n\\n        this.toModelArray().forEach(\\n            (model) => model._onDelete() // eslint-disable-line no-underscore-dangle\\n        );\\n\\n        session.applyUpdate({\\n            action: DELETE,\\n            query: {\\n                table,\\n                clauses: this.clauses,\\n            },\\n        });\\n\\n        this._evaluated = false;\\n    }\\n\\n    // DEPRECATED AND REMOVED METHODS\\n\\n    /**\\n     * @deprecated\\n     * Use {@link QuerySet#toModelArray} or predicate functions that\\n     * instantiate Models from refs, e.g. `new Model(ref)`.\\n     */\\n    get withModels() {\\n        throw new Error(\\n            \\\"`QuerySet.prototype.withModels` has been removed. \\\" +\\n                \\\"Use `.toModelArray()` or predicate functions that \\\" +\\n                \\\"instantiate Models from refs, e.g. `new Model(ref)`.\\\"\\n        );\\n    }\\n\\n    /**\\n     * @deprecated Query building operates on refs only now.\\n     */\\n    get withRefs() {\\n        warnDeprecated(\\n            \\\"`QuerySet.prototype.withRefs` has been deprecated. \\\" +\\n                \\\"Query building operates on refs only now.\\\"\\n        );\\n        return undefined;\\n    }\\n\\n    /**\\n     * @deprecated\\n     * Call {@link QuerySet#toModelArray} or {@link QuerySet#toRefArray} first to map.\\n     */\\n    map() {\\n        throw new Error(\\n            \\\"`QuerySet.prototype.map` has been removed. \\\" +\\n                \\\"Call `.toModelArray()` or `.toRefArray()` first to map.\\\"\\n        );\\n    }\\n\\n    /**\\n     * @deprecated\\n     * Call {@link QuerySet#toModelArray} or {@link QuerySet#toRefArray} first to iterate.\\n     */\\n    forEach() {\\n        throw new Error(\\n            \\\"`QuerySet.prototype.forEach` has been removed. \\\" +\\n                \\\"Call `.toModelArray()` or `.toRefArray()` first to iterate.\\\"\\n        );\\n    }\\n};\\n\\nQuerySet.sharedMethods = [\\n    \\\"count\\\",\\n    \\\"at\\\",\\n    \\\"all\\\",\\n    \\\"last\\\",\\n    \\\"first\\\",\\n    \\\"filter\\\",\\n    \\\"exclude\\\",\\n    \\\"orderBy\\\",\\n    \\\"update\\\",\\n    \\\"delete\\\",\\n];\\n\\nexport default QuerySet;\\n\",\"import { getBatchToken } from \\\"immutable-ops\\\";\\n\\nimport { SUCCESS, UPDATE, DELETE } from \\\"./constants\\\";\\nimport { warnDeprecated, clauseFiltersByAttribute } from \\\"./utils\\\";\\n\\nconst Session = class Session {\\n    /**\\n     * Creates a new Session.\\n     *\\n     * @param  {Database} db - a {@link Database} instance\\n     * @param  {Object} state - the database state\\n     * @param  {Boolean} [withMutations] - whether the session should mutate data\\n     * @param  {Object} [batchToken] - used by the backend to identify objects that can be\\n     *                                 mutated.\\n     */\\n    constructor(schema, db, state, withMutations, batchToken) {\\n        this.schema = schema;\\n        this.db = db;\\n        this.state = state || db.getEmptyState();\\n        this.initialState = this.state;\\n\\n        this.withMutations = Boolean(withMutations);\\n        this.batchToken = batchToken || getBatchToken();\\n\\n        this.modelData = {};\\n\\n        this.models = schema.getModelClasses();\\n\\n        this.sessionBoundModels = this.models.map((modelClass) => {\\n            function SessionBoundModel() {\\n                return Reflect.construct(\\n                    modelClass,\\n                    arguments,\\n                    SessionBoundModel\\n                ); // eslint-disable-line prefer-rest-params\\n            }\\n            Reflect.setPrototypeOf(\\n                SessionBoundModel.prototype,\\n                modelClass.prototype\\n            );\\n            Reflect.setPrototypeOf(SessionBoundModel, modelClass);\\n\\n            Object.defineProperty(this, modelClass.modelName, {\\n                get: () => SessionBoundModel,\\n            });\\n\\n            SessionBoundModel.connect(this);\\n            return SessionBoundModel;\\n        });\\n    }\\n\\n    getDataForModel(modelName) {\\n        if (!this.modelData[modelName]) {\\n            this.modelData[modelName] = {};\\n        }\\n        return this.modelData[modelName];\\n    }\\n\\n    getModelData() {\\n        return this.modelData;\\n    }\\n\\n    markAccessed(modelName, modelIds) {\\n        const data = this.getDataForModel(modelName);\\n        if (!data.accessedInstances) {\\n            data.accessedInstances = {};\\n        }\\n        modelIds.forEach((id) => {\\n            data.accessedInstances[id] = true;\\n        });\\n    }\\n\\n    get accessedModelInstances() {\\n        return Object.entries(this.getModelData()).reduce(\\n            (result, [key, value]) => {\\n                if (value.accessedInstances) {\\n                    result[key] = value.accessedInstances;\\n                }\\n                return result;\\n            },\\n            {}\\n        );\\n    }\\n\\n    markFullTableScanned(modelName) {\\n        const data = this.getDataForModel(modelName);\\n        data.fullTableScanned = true;\\n    }\\n\\n    get fullTableScannedModels() {\\n        return Object.entries(this.getModelData()).reduce(\\n            (result, [key, value]) => {\\n                if (value.fullTableScanned) {\\n                    result.push(key);\\n                }\\n                return result;\\n            },\\n            []\\n        );\\n    }\\n\\n    markAccessedIndexes(indexes) {\\n        indexes.forEach(([table, attr, value]) => {\\n            const data = this.getDataForModel(table);\\n            if (!data.accessedIndexes) {\\n                data.accessedIndexes = {};\\n            }\\n            data.accessedIndexes[attr] = [\\n                ...(data.accessedIndexes[attr] || []),\\n                value,\\n            ];\\n        });\\n    }\\n\\n    get accessedIndexes() {\\n        return Object.entries(this.getModelData()).reduce(\\n            (result, [key, value]) => {\\n                if (value.accessedIndexes) {\\n                    result[key] = value.accessedIndexes;\\n                }\\n                return result;\\n            },\\n            {}\\n        );\\n    }\\n\\n    /**\\n     * Applies update to a model state.\\n     *\\n     * @private\\n     * @param {Object} update - the update object. Must have keys\\n     *                          `type`, `payload`.\\n     */\\n    applyUpdate(updateSpec) {\\n        const tx = this._getTransaction(updateSpec);\\n        const result = this.db.update(updateSpec, tx, this.state);\\n        const { status, state, payload } = result;\\n\\n        if (status !== SUCCESS) {\\n            throw new Error(\\n                `Applying update failed with status ${status}. Payload: ${payload}`\\n            );\\n        }\\n\\n        this.state = state;\\n\\n        return payload;\\n    }\\n\\n    query(querySpec) {\\n        const result = this.db.query(querySpec, this.state);\\n\\n        this._markAccessedByQuery(querySpec, result);\\n\\n        return result;\\n    }\\n\\n    _getTransaction(updateSpec) {\\n        const { withMutations } = this;\\n        const { action } = updateSpec;\\n        let { batchToken } = this;\\n        if ([UPDATE, DELETE].includes(action)) {\\n            batchToken = getBatchToken();\\n        }\\n        return { batchToken, withMutations };\\n    }\\n\\n    _markAccessedByQuery(querySpec, result) {\\n        const { table, clauses } = querySpec;\\n        const { rows } = result;\\n\\n        const { idAttribute } = this[table];\\n        const accessedIds = new Set(rows.map((row) => row[idAttribute]));\\n\\n        const anyClauseFilteredByPk = clauses.some((clause) => {\\n            if (!clauseFiltersByAttribute(clause, idAttribute)) {\\n                return false;\\n            }\\n            /**\\n             * We previously knew which row we wanted to access,\\n             * so there was no need to scan the entire table.\\n             */\\n            accessedIds.add(clause.payload[idAttribute]);\\n            return true;\\n        });\\n\\n        const accessedIndexes = [];\\n        const { indexes } = this.state[table];\\n        clauses.forEach((clause) => {\\n            Object.keys(indexes).forEach((attr) => {\\n                if (!clauseFiltersByAttribute(clause, attr)) {\\n                    return;\\n                }\\n                const value = clause.payload[attr];\\n                accessedIndexes.push([table, attr, value]);\\n            });\\n        });\\n\\n        if (anyClauseFilteredByPk) {\\n            /**\\n             * The clauses have been ordered so that an indexed one was\\n             * the first to have been evaluated, and thus only the row\\n             * with the specified PK value has actually been accessed.\\n             */\\n            this.markAccessed(table, accessedIds);\\n        } else if (accessedIndexes.length) {\\n            /**\\n             * At least one clause was optimized using indexes.\\n             */\\n            this.markAccessed(table, accessedIds);\\n            this.markAccessedIndexes(accessedIndexes);\\n        } else {\\n            /**\\n             * At least one clause could not be efficiently optimized\\n             * or no clause was specified at all.\\n             */\\n            this.markFullTableScanned(table);\\n        }\\n    }\\n\\n    // DEPRECATED AND REMOVED METHODS\\n\\n    /**\\n     * @deprecated Access {@link Session#state} instead.\\n     */\\n    getNextState() {\\n        warnDeprecated(\\n            \\\"`Session.prototype.getNextState` has been deprecated. Access \\\" +\\n                \\\"the `Session.prototype.state` property instead.\\\"\\n        );\\n        return this.state;\\n    }\\n\\n    /**\\n     * @deprecated\\n     * The Redux integration API is now decoupled from ORM and Session.<br>\\n     * See the 0.9 migration guide in the GitHub repo.\\n     */\\n    reduce() {\\n        throw new Error(\\n            \\\"`Session.prototype.reduce` has been removed. The Redux integration API \\\" +\\n                \\\"is now decoupled from ORM and Session - see the 0.9 migration guide \\\" +\\n                \\\"in the GitHub repo.\\\"\\n        );\\n    }\\n};\\n\\nexport default Session;\\n\",\"import FieldInstallerTemplate from \\\"./FieldInstallerTemplate\\\";\\n\\nimport { reverseFieldErrorMessage } from \\\"../utils\\\";\\n\\n/**\\n * Default implementation for the template method in FieldInstallerTemplate.\\n * @private\\n * @memberof module:fields\\n */\\nexport class DefaultFieldInstaller extends FieldInstallerTemplate {\\n    installForwardsDescriptor() {\\n        Object.defineProperty(\\n            this.model.prototype,\\n            this.fieldName,\\n            this.field.createForwardsDescriptor(\\n                this.fieldName,\\n                this.model,\\n                this.toModel,\\n                this.throughModel\\n            )\\n        );\\n    }\\n\\n    installForwardsVirtualField() {\\n        this.model.virtualFields[\\n            this.fieldName\\n        ] = this.field.createForwardsVirtualField(\\n            this.fieldName,\\n            this.model,\\n            this.toModel,\\n            this.throughModel\\n        );\\n    }\\n\\n    installBackwardsDescriptor() {\\n        const backwardsDescriptor = Object.getOwnPropertyDescriptor(\\n            this.toModel.prototype,\\n            this.backwardsFieldName\\n        );\\n        if (backwardsDescriptor) {\\n            throw new Error(\\n                reverseFieldErrorMessage(\\n                    this.model.modelName,\\n                    this.fieldName,\\n                    this.toModel.modelName,\\n                    this.backwardsFieldName\\n                )\\n            );\\n        }\\n\\n        // install backwards descriptor\\n        Object.defineProperty(\\n            this.toModel.prototype,\\n            this.backwardsFieldName,\\n            this.field.createBackwardsDescriptor(\\n                this.fieldName,\\n                this.model,\\n                this.toModel,\\n                this.throughModel\\n            )\\n        );\\n    }\\n\\n    installBackwardsVirtualField() {\\n        this.toModel.virtualFields[\\n            this.backwardsFieldName\\n        ] = this.field.createBackwardsVirtualField(\\n            this.fieldName,\\n            this.model,\\n            this.toModel,\\n            this.throughModel\\n        );\\n    }\\n}\\n\\nexport default DefaultFieldInstaller;\\n\",\"/**\\n * Defines algorithm for installing a field onto a model and related models.\\n * Conforms to the template method behavioral design pattern.\\n * @private\\n * @memberof module:fields\\n */\\nexport class FieldInstallerTemplate {\\n    constructor(opts) {\\n        this.field = opts.field;\\n        this.fieldName = opts.fieldName;\\n        this.model = opts.model;\\n        this.orm = opts.orm;\\n        /**\\n         * the field itself has no knowledge of the model\\n         * it is being installed upon; we need to inform it\\n         * that it is a self-referencing field for the field\\n         * to be able to make better informed decisions\\n         */\\n        if (this.field.references(this.model)) {\\n            this.field.toModelName = \\\"this\\\";\\n        }\\n    }\\n\\n    get toModel() {\\n        if (typeof this._toModel === \\\"undefined\\\") {\\n            const { toModelName } = this.field;\\n            if (!toModelName) {\\n                this._toModel = null;\\n            } else if (toModelName === \\\"this\\\") {\\n                this._toModel = this.model;\\n            } else {\\n                this._toModel = this.orm.get(toModelName);\\n            }\\n        }\\n        return this._toModel;\\n    }\\n\\n    get throughModel() {\\n        if (typeof this._throughModel === \\\"undefined\\\") {\\n            const throughModelName = this.field.getThroughModelName(\\n                this.fieldName,\\n                this.model\\n            );\\n            if (!throughModelName) {\\n                this._throughModel = null;\\n            } else {\\n                this._throughModel = this.orm.get(throughModelName);\\n            }\\n        }\\n        return this._throughModel;\\n    }\\n\\n    get backwardsFieldName() {\\n        return this.field.getBackwardsFieldName(this.model);\\n    }\\n\\n    run() {\\n        this.installForwardsDescriptor();\\n        if (this.field.installsForwardsVirtualField) {\\n            this.installForwardsVirtualField();\\n        }\\n        /**\\n         * Install a backwards field on a model as a consequence\\n         * of having installed the forwards field on another model.\\n         */\\n        if (this.field.installsBackwardsDescriptor) {\\n            this.installBackwardsDescriptor();\\n        }\\n        if (this.field.installsBackwardsVirtualField) {\\n            this.installBackwardsVirtualField();\\n        }\\n    }\\n}\\n\\nexport default FieldInstallerTemplate;\\n\",\"import DefaultFieldInstaller from \\\"./DefaultFieldInstaller\\\";\\n\\n/**\\n * @private\\n * @memberof module:fields\\n */\\nexport class Field {\\n    get installerClass() {\\n        return DefaultFieldInstaller;\\n    }\\n\\n    getClass() {\\n        return this.constructor;\\n    }\\n\\n    references(model) {\\n        return false;\\n    }\\n\\n    getThroughModelName(fieldName, model) {\\n        return null;\\n    }\\n\\n    get installsForwardsVirtualField() {\\n        return false;\\n    }\\n\\n    get installsBackwardsDescriptor() {\\n        return false;\\n    }\\n\\n    get installsBackwardsVirtualField() {\\n        return false;\\n    }\\n\\n    get index() {\\n        return false;\\n    }\\n}\\n\\nexport default Field;\\n\",\"import { normalizeEntity } from \\\"./utils\\\";\\n\\n/**\\n * The functions in this file return custom JS property descriptors\\n * that are supposed to be assigned to Model fields.\\n *\\n * Some include the logic to look up models using foreign keys and\\n * to add or remove relationships between models.\\n *\\n * @module descriptors\\n * @private\\n */\\n\\n/**\\n * Defines a basic non-key attribute.\\n * @param  {string} fieldName - the name of the field the descriptor will be assigned to.\\n */\\nfunction attrDescriptor(fieldName) {\\n    return {\\n        get() {\\n            return this._fields[fieldName];\\n        },\\n\\n        set(value) {\\n            return this.set(fieldName, value);\\n        },\\n\\n        enumerable: true,\\n        configurable: true,\\n    };\\n}\\n\\n/**\\n * Forwards direction of a Foreign Key: returns one object.\\n * Also works as {@link .forwardsOneToOneDescriptor|forwardsOneToOneDescriptor}.\\n *\\n * For `book.author` referencing an `Author` model instance,\\n * `fieldName` would be `'author'` and `declaredToModelName` would be `'Author'`.\\n * @param  {string} fieldName - the name of the field the descriptor will be assigned to.\\n * @param  {string} declaredToModelName - the name of the model that the field references.\\n */\\nfunction forwardsManyToOneDescriptor(fieldName, declaredToModelName) {\\n    return {\\n        get() {\\n            const {\\n                session: { [declaredToModelName]: DeclaredToModel },\\n            } = this.getClass();\\n            const { [fieldName]: toId } = this._fields;\\n\\n            return DeclaredToModel.withId(toId);\\n        },\\n        set(value) {\\n            this.update({\\n                [fieldName]: normalizeEntity(value),\\n            });\\n        },\\n    };\\n}\\n\\n/**\\n * Dereferencing foreign keys in {@link module:fields.oneToOne|oneToOne}\\n * relationships works the same way as in many-to-one relationships:\\n * just look up the related model.\\n *\\n * For example, a human face tends to have a single nose.\\n * So if we want to resolve `face.nose`, we need to\\n * look up the `Nose` that has the primary key that `face` references.\\n *\\n * @see {@link module:descriptors~forwardsManyToOneDescriptor|forwardsManyToOneDescriptor}\\n */\\nfunction forwardsOneToOneDescriptor(...args) {\\n    return forwardsManyToOneDescriptor(...args);\\n}\\n\\n/**\\n * Here we resolve 1-to-1 relationships starting at the model on which the\\n * field was not installed. This means we need to find the instance of the\\n * other model whose {@link module:fields.oneToOne|oneToOne} FK field contains the current model's primary key.\\n *\\n * @param  {string} declaredFieldName - the name of the field referencing the current model.\\n * @param  {string} declaredFromModelName - the name of the other model.\\n */\\nfunction backwardsOneToOneDescriptor(declaredFieldName, declaredFromModelName) {\\n    return {\\n        get() {\\n            const {\\n                session: { [declaredFromModelName]: DeclaredFromModel },\\n            } = this.getClass();\\n\\n            return DeclaredFromModel.get({\\n                [declaredFieldName]: this.getId(),\\n            });\\n        },\\n        set() {\\n            throw new Error(\\\"Can't mutate a reverse one-to-one relation.\\\");\\n        },\\n    };\\n}\\n\\n/**\\n * The backwards direction of a n-to-1 relationship (i.e. 1-to-n),\\n * meaning this will return an a collection (`QuerySet`) of model instances.\\n *\\n * An example would be `author.books` referencing all instances of\\n * the `Book` model that reference the author using `fk()`.\\n */\\nfunction backwardsManyToOneDescriptor(\\n    declaredFieldName,\\n    declaredFromModelName\\n) {\\n    return {\\n        get() {\\n            const {\\n                session: { [declaredFromModelName]: DeclaredFromModel },\\n            } = this.getClass();\\n\\n            return DeclaredFromModel.filter({\\n                [declaredFieldName]: this.getId(),\\n            });\\n        },\\n        set() {\\n            throw new Error(\\\"Can't mutate a reverse many-to-one relation.\\\");\\n        },\\n    };\\n}\\n\\n/**\\n * This descriptor is assigned to both sides of a many-to-many relationship.\\n * To indicate the backwards direction pass `true` for `reverse`.\\n */\\nfunction manyToManyDescriptor(\\n    declaredFromModelName,\\n    declaredToModelName,\\n    throughModelName,\\n    throughFields,\\n    reverse\\n) {\\n    return {\\n        get() {\\n            const {\\n                session: {\\n                    [declaredFromModelName]: DeclaredFromModel,\\n                    [declaredToModelName]: DeclaredToModel,\\n                    [throughModelName]: ThroughModel,\\n                },\\n            } = this.getClass();\\n\\n            const ThisModel = reverse ? DeclaredToModel : DeclaredFromModel;\\n            const OtherModel = reverse ? DeclaredFromModel : DeclaredToModel;\\n\\n            const thisReferencingField = reverse\\n                ? throughFields.to\\n                : throughFields.from;\\n            const otherReferencingField = reverse\\n                ? throughFields.from\\n                : throughFields.to;\\n\\n            const thisId = this.getId();\\n\\n            const throughQs = ThroughModel.filter({\\n                [thisReferencingField]: thisId,\\n            });\\n\\n            /**\\n             * all IDs of instances of the other model that are\\n             * referenced by any instance of the current model\\n             */\\n            const referencedOtherIds = new Set(\\n                throughQs.toRefArray().map((obj) => obj[otherReferencingField])\\n            );\\n\\n            /**\\n             * selects all instances of other model that are referenced\\n             * by any instance of the current model\\n             */\\n            const qs = OtherModel.filter((otherModelInstance) =>\\n                referencedOtherIds.has(\\n                    otherModelInstance[OtherModel.idAttribute]\\n                )\\n            );\\n\\n            /**\\n             * Allows adding OtherModel instances to be referenced by the current instance.\\n             *\\n             * E.g. Book.first().authors.add(1, 2) would add the authors with IDs 1 and 2\\n             * to the first book's list of referenced authors.\\n             *\\n             * @return undefined\\n             */\\n            qs.add = function add(...entities) {\\n                const idsToAdd = new Set(entities.map(normalizeEntity));\\n\\n                const existingQs = throughQs.filter((through) =>\\n                    idsToAdd.has(through[otherReferencingField])\\n                );\\n\\n                if (existingQs.exists()) {\\n                    const existingIds = existingQs\\n                        .toRefArray()\\n                        .map((through) => through[otherReferencingField]);\\n\\n                    throw new Error(\\n                        `Tried to add already existing ${OtherModel.modelName} id(s) ${existingIds} to the ${ThisModel.modelName} instance with id ${thisId}`\\n                    );\\n                }\\n\\n                idsToAdd.forEach((id) => {\\n                    ThroughModel.create({\\n                        [otherReferencingField]: id,\\n                        [thisReferencingField]: thisId,\\n                    });\\n                });\\n            };\\n\\n            /**\\n             * Removes references to all OtherModel instances from the current model.\\n             *\\n             * E.g. Book.first().authors.clear() would cause the first book's list\\n             * of referenced authors to become empty.\\n             *\\n             * @return undefined\\n             */\\n            qs.clear = function clear() {\\n                throughQs.delete();\\n            };\\n\\n            /**\\n             * Removes references to all passed OtherModel instances from the current model.\\n             *\\n             * E.g. Book.first().authors.remove(1, 2) would cause the authors with\\n             * IDs 1 and 2 to no longer be referenced by the first book.\\n             *\\n             * @return undefined\\n             */\\n            qs.remove = function remove(...entities) {\\n                const idsToRemove = new Set(entities.map(normalizeEntity));\\n\\n                const entitiesToDelete = throughQs.filter((through) =>\\n                    idsToRemove.has(through[otherReferencingField])\\n                );\\n\\n                if (entitiesToDelete.count() !== idsToRemove.size) {\\n                    // Tried deleting non-existing entities.\\n                    const entitiesToDeleteIds = entitiesToDelete\\n                        .toRefArray()\\n                        .map((through) => through[otherReferencingField]);\\n\\n                    const unexistingIds = [...idsToRemove].filter(\\n                        (id) => !entitiesToDeleteIds.includes(id)\\n                    );\\n\\n                    throw new Error(\\n                        `Tried to delete non-existing ${OtherModel.modelName} id(s) ${unexistingIds} from the ${ThisModel.modelName} instance with id ${thisId}`\\n                    );\\n                }\\n\\n                entitiesToDelete.delete();\\n            };\\n\\n            return qs;\\n        },\\n\\n        set() {\\n            throw new Error(\\n                \\\"Tried setting a M2M field. Please use the related QuerySet methods add, remove and clear.\\\"\\n            );\\n        },\\n    };\\n}\\n\\nexport {\\n    attrDescriptor,\\n    forwardsManyToOneDescriptor,\\n    forwardsOneToOneDescriptor,\\n    backwardsOneToOneDescriptor,\\n    backwardsManyToOneDescriptor,\\n    manyToManyDescriptor,\\n};\\n\",\"import Field from \\\"./Field\\\";\\n\\nimport { attrDescriptor } from \\\"../descriptors\\\";\\n\\n/**\\n * @memberof module:fields\\n */\\nexport class Attribute extends Field {\\n    constructor(opts) {\\n        super();\\n        this.opts = opts || {};\\n\\n        if (this.opts.hasOwnProperty(\\\"getDefault\\\")) {\\n            this.getDefault = this.opts.getDefault;\\n        }\\n    }\\n\\n    createForwardsDescriptor(fieldName, model) {\\n        return attrDescriptor(fieldName);\\n    }\\n}\\n\\nexport default Attribute;\\n\",\"/* eslint-disable max-classes-per-file */\\nimport Field from \\\"./Field\\\";\\nimport DefaultFieldInstaller from \\\"./DefaultFieldInstaller\\\";\\n\\nimport { reverseFieldName, normalizeModelReference } from \\\"../utils\\\";\\n\\n/**\\n * @private\\n * @memberof module:fields\\n */\\nexport class RelationalField extends Field {\\n    constructor(...args) {\\n        super();\\n        if (args.length === 1 && typeof args[0] === \\\"object\\\") {\\n            const opts = args[0];\\n            this.toModelName = normalizeModelReference(opts.to);\\n            this.relatedName = opts.relatedName;\\n            this.through = normalizeModelReference(opts.through);\\n            this.throughFields = opts.throughFields;\\n            this.as = opts.as;\\n        } else {\\n            [this.toModelName, this.relatedName] = [\\n                normalizeModelReference(args[0]),\\n                args[1],\\n            ];\\n        }\\n    }\\n\\n    getBackwardsFieldName(model) {\\n        return this.relatedName || reverseFieldName(model.modelName);\\n    }\\n\\n    createBackwardsVirtualField(fieldName, model, toModel, throughModel) {\\n        const ThisField = this.getClass();\\n        return new ThisField(model.modelName, fieldName);\\n    }\\n\\n    get installsBackwardsVirtualField() {\\n        return true;\\n    }\\n\\n    get installsBackwardsDescriptor() {\\n        return true;\\n    }\\n\\n    references(model) {\\n        return this.toModelName === model.modelName;\\n    }\\n\\n    get installerClass() {\\n        return class AliasedForwardsDescriptorInstaller extends DefaultFieldInstaller {\\n            installForwardsDescriptor() {\\n                Object.defineProperty(\\n                    this.model.prototype,\\n                    this.field.as || this.fieldName, // use supplied name if possible\\n                    this.field.createForwardsDescriptor(\\n                        this.fieldName,\\n                        this.model,\\n                        this.toModel,\\n                        this.throughModel\\n                    )\\n                );\\n            }\\n        };\\n    }\\n}\\n\\nexport default RelationalField;\\n\",\"import RelationalField from \\\"./RelationalField\\\";\\n\\nimport {\\n    forwardsManyToOneDescriptor,\\n    backwardsManyToOneDescriptor,\\n} from \\\"../descriptors\\\";\\n\\n/**\\n * @memberof module:fields\\n */\\nexport class ForeignKey extends RelationalField {\\n    createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return forwardsManyToOneDescriptor(fieldName, toModel.modelName);\\n    }\\n\\n    createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return backwardsManyToOneDescriptor(fieldName, model.modelName);\\n    }\\n\\n    get index() {\\n        return true;\\n    }\\n}\\n\\nexport default ForeignKey;\\n\",\"import RelationalField from \\\"./RelationalField\\\";\\n\\nimport { manyToManyDescriptor } from \\\"../descriptors\\\";\\n\\nimport { m2mName, m2mToFieldName, m2mFromFieldName } from \\\"../utils\\\";\\n\\n/**\\n * @memberof module:fields\\n */\\nexport class ManyToMany extends RelationalField {\\n    getDefault() {\\n        return [];\\n    }\\n\\n    getThroughModelName(fieldName, model) {\\n        return this.through || m2mName(model.modelName, fieldName);\\n    }\\n\\n    createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return manyToManyDescriptor(\\n            model.modelName,\\n            toModel.modelName,\\n            throughModel.modelName,\\n            this.getThroughFields(fieldName, model, toModel, throughModel),\\n            false\\n        );\\n    }\\n\\n    createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return manyToManyDescriptor(\\n            model.modelName,\\n            toModel.modelName,\\n            throughModel.modelName,\\n            this.getThroughFields(fieldName, model, toModel, throughModel),\\n            true\\n        );\\n    }\\n\\n    createBackwardsVirtualField(fieldName, model, toModel, throughModel) {\\n        const ThisField = this.getClass();\\n        return new ThisField({\\n            to: model.modelName,\\n            relatedName: fieldName,\\n            through: throughModel.modelName,\\n            throughFields: this.getThroughFields(\\n                fieldName,\\n                model,\\n                toModel,\\n                throughModel\\n            ),\\n        });\\n    }\\n\\n    createForwardsVirtualField(fieldName, model, toModel, throughModel) {\\n        const ThisField = this.getClass();\\n        return new ThisField({\\n            to: toModel.modelName,\\n            relatedName: fieldName,\\n            through: this.through,\\n            throughFields: this.getThroughFields(\\n                fieldName,\\n                model,\\n                toModel,\\n                throughModel\\n            ),\\n            as: this.as,\\n        });\\n    }\\n\\n    get installsForwardsVirtualField() {\\n        return true;\\n    }\\n\\n    getThroughFields(fieldName, model, toModel, throughModel) {\\n        if (this.throughFields) {\\n            const [fieldAName, fieldBName] = this.throughFields;\\n            const fieldA = throughModel.fields[fieldAName];\\n            return {\\n                to: fieldA.references(toModel) ? fieldAName : fieldBName,\\n                from: fieldA.references(toModel) ? fieldBName : fieldAName,\\n            };\\n        }\\n\\n        if (model.modelName === toModel.modelName) {\\n            /**\\n             * we have no way of determining the relationship's\\n             * direction here, so we need to assume that the user\\n             * did not use a custom through model\\n             * see ORM#registerManyToManyModelsFor\\n             */\\n            return {\\n                to: m2mToFieldName(toModel.modelName),\\n                from: m2mFromFieldName(model.modelName),\\n            };\\n        }\\n\\n        /**\\n         * determine which field references which model\\n         * and infer the directions from that\\n         */\\n        const throughModelFieldReferencing = (otherModel) =>\\n            Object.keys(throughModel.fields).find((someFieldName) =>\\n                throughModel.fields[someFieldName].references(otherModel)\\n            );\\n\\n        return {\\n            to: throughModelFieldReferencing(toModel),\\n            from: throughModelFieldReferencing(model),\\n        };\\n    }\\n}\\n\\nexport default ManyToMany;\\n\",\"import RelationalField from \\\"./RelationalField\\\";\\n\\nimport {\\n    forwardsOneToOneDescriptor,\\n    backwardsOneToOneDescriptor,\\n} from \\\"../descriptors\\\";\\n\\n/**\\n * @memberof module:fields\\n */\\nexport class OneToOne extends RelationalField {\\n    getBackwardsFieldName(model) {\\n        return this.relatedName || model.modelName.toLowerCase();\\n    }\\n\\n    createForwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return forwardsOneToOneDescriptor(fieldName, toModel.modelName);\\n    }\\n\\n    createBackwardsDescriptor(fieldName, model, toModel, throughModel) {\\n        return backwardsOneToOneDescriptor(fieldName, model.modelName);\\n    }\\n}\\n\\nexport default OneToOne;\\n\",\"import Attribute from \\\"./Attribute\\\";\\nimport ForeignKey from \\\"./ForeignKey\\\";\\nimport ManyToMany from \\\"./ManyToMany\\\";\\nimport OneToOne from \\\"./OneToOne\\\";\\n\\n/**\\n * Contains the logic for how fields on {@link Model}s work\\n * and which descriptors must be installed.\\n *\\n * If your goal is to define fields on a Model class,\\n * please use the more convenient methods {@link attr},\\n * {@link fk}, {@link many} and {@link oneToOne}.\\n *\\n * @module fields\\n */\\n\\n/**\\n * Defines a value attribute on the model.\\n * Though not required, it is recommended to define this for each non-foreign key you wish to use.\\n * Getters and setters need to be defined on each Model\\n * instantiation for undeclared data fields, which is slower.\\n * You can use the optional `getDefault` parameter to fill in unpassed values\\n * to {@link Model.create}, such as for generating ID's with UUID:\\n *\\n * ```javascript\\n * import getUUID from 'your-uuid-package-of-choice';\\n *\\n * fields = {\\n *   id: attr({ getDefault: () => getUUID() }),\\n *   title: attr(),\\n * }\\n * ```\\n *\\n * @param  {Object} [opts]\\n * @param {Function} [opts.getDefault] - If you give a function here, its return\\n *                                       value from calling with zero arguments will\\n *                                       be used as the value when creating a new Model\\n *                                       instance with {@link Model#create} if the field\\n *                                       value is not passed.\\n * @return {Attribute}\\n */\\nfunction attr(opts) {\\n    return new Attribute(opts);\\n}\\n\\n/**\\n * Defines a foreign key on a model, which points\\n * to a single entity on another model.\\n *\\n * You can pass arguments as either a single object,\\n * or two arguments.\\n *\\n * If you pass two arguments, the first one is the name\\n * of the Model the foreign key is pointing to, and\\n * the second one is an optional related name, which will\\n * be used to access the Model the foreign key\\n * is being defined from, from the target Model.\\n *\\n * If the related name is not passed, it will be set as\\n * `${toModelName}Set`.\\n *\\n * If you pass an object to `fk`, it has to be in the form\\n *\\n * ```javascript\\n * fields = {\\n *   author: fk({ to: 'Author', relatedName: 'books' })\\n * }\\n * ```\\n *\\n * Which is equal to\\n *\\n * ```javascript\\n * fields = {\\n *   author: fk('Author', 'books'),\\n * }\\n * ```\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string} [options.relatedName] - The property name that will be used to access\\n *                                         a QuerySet for all source models that reference\\n *                                         the respective target Model's instance.\\n * @param {string} [relatedName] - If you didn't pass an object as the first argument,\\n *                                 this is the property name that will be used to\\n *                                 access a QuerySet for all source models that reference\\n *                                 the respective target Model's instance.\\n * @return {ForeignKey}\\n */\\nfunction fk(...args) {\\n    return new ForeignKey(...args);\\n}\\n\\n/**\\n * Defines a many-to-many relationship between\\n * this (source) and another (target) model.\\n *\\n * The relationship is modeled with an extra model called the through model.\\n * The through model has foreign keys to both the source and target models.\\n *\\n * You can define your own through model if you want to associate more information\\n * to the relationship. A custom through model must have at least two foreign keys,\\n * one pointing to the source Model, and one pointing to the target Model.\\n *\\n * Like `fk`, this function accepts one or two string arguments specifying the other\\n * Model and the related name, or a single object argument that allows you to pass\\n * a custom through model.\\n *\\n * If you have more than one foreign key pointing to a source or target Model in the\\n * through Model, you must pass the option `throughFields`, which is an array of two\\n * strings, where the strings are the field names that identify the foreign keys to\\n * be used for the many-to-many relationship. Redux-ORM will figure out which field name\\n * points to which model by checking the \\\"through model\\\" definition.\\n *\\n * ```javascript\\n * class Authorship extends Model {}\\n * Authorship.modelName = 'Authorship';\\n * Authorship.fields = {\\n *   author: fk('Author', 'authorships'),\\n *   book: fk('Book', 'authorships'),\\n * };\\n *\\n * class Author extends Model {}\\n * Author.modelName = 'Author';\\n * Author.fields = {\\n *   books: many({\\n *     to: 'Book',\\n *     relatedName: 'authors',\\n *     through: 'Authorship',\\n *\\n *     // here this is optional: Redux-ORM can figure\\n *     // out the through fields itself since the two\\n *     // foreign key fields point to different Models\\n *     throughFields: ['author', 'book'],\\n *   })\\n * };\\n *\\n * class Book extends Model {}\\n * Book.modelName = 'Book';\\n * ```\\n *\\n * You should only define the many-to-many relationship on one side. In the\\n * above case of Authors to Books through Authorships, the relationship is\\n * defined only on the Author model.\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string|Class<Model>} [options.through] - The through Model class or its `modelName`\\n *                                                  attribute. It must declare at least one\\n *                                                  foreign key to both source and target models.\\n *                                                  If not supplied, Redux-ORM will generate one.\\n * @param {string[]} [options.throughFields] - Must be supplied only when a custom through\\n *                                             Model has more than one foreign key pointing to\\n *                                             either the source or target mode. In this case\\n *                                             Redux-ORM can't figure out the correct fields for\\n *                                             you, you must provide them. The supplied array should\\n *                                             have two elements that are the field names for the\\n *                                             through fields you want to declare the many-to-many\\n *                                             relationship with. The order doesn't matter;\\n *                                             Redux-ORM will figure out which field points to\\n *                                             the source Model and which to the target Model.\\n * @param {string} [options.relatedName] - The attribute used to access a QuerySet for all\\n *                                         source models that reference the respective target\\n *                                         Model's instance.\\n * @param {string} [relatedName] - If you didn't pass an object as the first argument,\\n *                                 this is the property name that will be used to\\n *                                 access a QuerySet for all source models that reference\\n *                                 the respective target Model's instance.\\n * @return {ManyToMany}\\n */\\nfunction many(...args) {\\n    return new ManyToMany(...args);\\n}\\n\\n/**\\n * Defines a one-to-one relationship. In database terms, this is a foreign key with the\\n * added restriction that only one entity can point to single target entity.\\n *\\n * The arguments are the same as with `fk`. If `relatedName` is not supplied,\\n * the source model name in lowercase will be used. Note that with the one-to-one\\n * relationship, the `relatedName` should be in singular, not plural.\\n *\\n *\\n * @param {string|Class<Model>|Object} options - The target Model class, its `modelName`\\n *                                               attribute or an options object that\\n *                                               contains either as the `to` key.\\n * @param {string|Class<Model>} options.to - The target Model class or its `modelName` attribute.\\n * @param {string} [options.as] - Name for the new accessor defined for this field. If you don't\\n *                                supply this, the key that this field is defined under will be\\n *                                overridden.\\n * @param {string} [options.relatedName] - The property name that will be used to access the source\\n *                                         model instance referencing the target model instance.\\n * @param {string} [relatedName] - The property name that will be used to access the source\\n *                                 model instance referencing the target model instance\\n * @return {OneToOne}\\n */\\nfunction oneToOne(...args) {\\n    return new OneToOne(...args);\\n}\\n\\nexport { fk, attr, many, oneToOne };\\n\",\"import Session from \\\"./Session\\\";\\nimport QuerySet from \\\"./QuerySet\\\";\\n\\nimport { attr } from \\\"./fields\\\";\\nimport ForeignKey from \\\"./fields/ForeignKey\\\";\\nimport ManyToMany from \\\"./fields/ManyToMany\\\";\\nimport OneToOne from \\\"./fields/OneToOne\\\";\\n\\nimport { CREATE, UPDATE, DELETE, FILTER } from \\\"./constants\\\";\\nimport {\\n    normalizeEntity,\\n    arrayDiffActions,\\n    objectShallowEquals,\\n    warnDeprecated,\\n    m2mName,\\n} from \\\"./utils\\\";\\n\\n/**\\n * Generates a query specification to get the instance's\\n * corresponding table row using its primary key.\\n *\\n * @private\\n * @returns {Object}\\n */\\nfunction getByIdQuery(modelInstance) {\\n    const modelClass = modelInstance.getClass();\\n    const { idAttribute, modelName } = modelClass;\\n\\n    return {\\n        table: modelName,\\n        clauses: [\\n            {\\n                type: FILTER,\\n                payload: {\\n                    [idAttribute]: modelInstance.getId(),\\n                },\\n            },\\n        ],\\n    };\\n}\\n\\n/**\\n * The heart of an ORM, the data model.\\n *\\n * The fields you specify to the Model will be used to generate\\n * a schema to the database, related property accessors, and\\n * possibly through models.\\n *\\n * In each {@link Session} you instantiate from an {@link ORM} instance,\\n * you will receive a session-specific subclass of this Model. The methods\\n * you define here will be available to you in sessions.\\n *\\n * An instance of {@link Model} represents a record in the database, though\\n * it is possible to generate multiple instances from the same record in the database.\\n *\\n * To create data models in your schema, subclass {@link Model}. To define\\n * information about the data model, override static class methods. Define instance\\n * logic by defining prototype methods (without `static` keyword).\\n */\\nconst Model = class Model {\\n    /**\\n     * Creates a Model instance from it's properties.\\n     * Don't use this to create a new record; Use the static method {@link Model#create}.\\n     * @param  {Object} props - the properties to instantiate with\\n     */\\n    constructor(props) {\\n        this._initFields(props);\\n    }\\n\\n    _initFields(props) {\\n        const propsObj = Object(props);\\n        this._fields = { ...propsObj };\\n\\n        Object.keys(propsObj).forEach((fieldName) => {\\n            // In this case, we got a prop that wasn't defined as a field.\\n            // Assuming it's an arbitrary data field, making an instance-specific\\n            // descriptor for it.\\n            // Using the in operator as the property could be defined anywhere\\n            // on the prototype chain.\\n            if (!(fieldName in this)) {\\n                Object.defineProperty(this, fieldName, {\\n                    get: () => this._fields[fieldName],\\n                    set: (value) => this.set(fieldName, value),\\n                    configurable: true,\\n                    enumerable: true,\\n                });\\n            }\\n        });\\n    }\\n\\n    static toString() {\\n        return `ModelClass: ${this.modelName}`;\\n    }\\n\\n    /**\\n     * Returns the options object passed to the database for the table that represents\\n     * this Model class.\\n     *\\n     * Returns an empty object by default, which means the database\\n     * will use default options. You can either override this function to return the options\\n     * you want to use, or assign the options object as a static property of the same name to the\\n     * Model class.\\n     *\\n     * @return {Object} the options object passed to the database for the table\\n     *                  representing this Model class.\\n     */\\n    static options() {\\n        return {};\\n    }\\n\\n    /**\\n     * Manually mark individual instances as accessed.\\n     * This allows invalidating selector memoization within mutable sessions.\\n     *\\n     * @param {Array.<*>} ids - Array of primary key values\\n     * @return {undefined}\\n     */\\n    static markAccessed(ids) {\\n        if (typeof this._session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to mark rows of the ${this.modelName} model as accessed without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].markAccessed\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        this.session.markAccessed(this.modelName, ids);\\n    }\\n\\n    /**\\n     * Manually mark this model's table as scanned.\\n     * This allows invalidating selector memoization within mutable sessions.\\n     *\\n     * @return {undefined}\\n     */\\n    static markFullTableScanned() {\\n        if (typeof this._session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to mark the ${this.modelName} model as full table scanned without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].markFullTableScanned\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        this.session.markFullTableScanned(this.modelName);\\n    }\\n\\n    /**\\n     * Manually mark indexes as accessed.\\n     * This allows invalidating selector memoization within mutable sessions.\\n     *\\n     * @param {Array.<Array.<*,*>>} indexes - Array of column-value pairs\\n     * @return {undefined}\\n     */\\n    static markAccessedIndexes(indexes) {\\n        if (typeof this._session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to mark indexes for the ${this.modelName} model as accessed without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].markAccessedIndexes\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        this.session.markAccessedIndexes(\\n            indexes.map(([attribute, value]) => [\\n                this.modelName,\\n                attribute,\\n                value,\\n            ])\\n        );\\n    }\\n\\n    /**\\n     * Returns the id attribute of this {@link Model}.\\n     *\\n     * @return {string} The id attribute of this {@link Model}.\\n     */\\n    static get idAttribute() {\\n        if (typeof this._session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to get the ${this.modelName} model's id attribute without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and access \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].idAttribute\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        return this.session.db.describe(this.modelName).idAttribute;\\n    }\\n\\n    /**\\n     * Connect the model class to a {@link Session}.\\n     *\\n     * @private\\n     * @param  {Session} session - The session to connect to.\\n     */\\n    static connect(session) {\\n        if (!(session instanceof Session)) {\\n            throw new Error(\\n                \\\"A model can only be connected to instances of Session.\\\"\\n            );\\n        }\\n        this._session = session;\\n    }\\n\\n    /**\\n     * Get the current {@link Session} instance.\\n     *\\n     * @private\\n     * @return {Session} The current {@link Session} instance.\\n     */\\n    static get session() {\\n        return this._session;\\n    }\\n\\n    /**\\n     * Returns an instance of the model's `querySetClass` field.\\n     * By default, this will be an empty {@link QuerySet}.\\n     *\\n     * @return {Object} An instance of the model's `querySetClass`.\\n     */\\n    static getQuerySet() {\\n        const { querySetClass: QuerySetClass } = this;\\n        return new QuerySetClass(this);\\n    }\\n\\n    /**\\n     * @return {undefined}\\n     */\\n    static invalidateClassCache() {\\n        this.isSetUp = undefined;\\n        this.virtualFields = {};\\n    }\\n\\n    /**\\n     * @see {@link Model.getQuerySet}\\n     */\\n    static get query() {\\n        return this.getQuerySet();\\n    }\\n\\n    /**\\n     * Returns parameters to be passed to {@link Table} instance.\\n     *\\n     * @private\\n     */\\n    static tableOptions() {\\n        if (typeof this.backend === \\\"function\\\") {\\n            warnDeprecated(\\n                \\\"`Model.backend` has been deprecated. Please rename to `.options`.\\\"\\n            );\\n            return this.backend();\\n        }\\n        if (this.backend) {\\n            warnDeprecated(\\n                \\\"`Model.backend` has been deprecated. Please rename to `.options`.\\\"\\n            );\\n            return this.backend;\\n        }\\n        if (typeof this.options === \\\"function\\\") {\\n            return this.options();\\n        }\\n        return this.options;\\n    }\\n\\n    /**\\n     * Creates a new record in the database, instantiates a {@link Model} and returns it.\\n     *\\n     * If you pass values for many-to-many fields, instances are created on the through\\n     * model as well.\\n     *\\n     * @param  {Object} userProps - the new {@link Model}'s properties.\\n     * @return {Model} a new {@link Model} instance.\\n     */\\n    static create(userProps) {\\n        if (typeof this._session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to create a ${this.modelName} model instance without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].create\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n        const props = { ...userProps };\\n\\n        const m2mRelations = {};\\n\\n        const declaredFieldNames = Object.keys(this.fields);\\n        const declaredVirtualFieldNames = Object.keys(this.virtualFields);\\n\\n        declaredFieldNames.forEach((key) => {\\n            const field = this.fields[key];\\n            const valuePassed = userProps.hasOwnProperty(key);\\n            if (!(field instanceof ManyToMany)) {\\n                if (valuePassed) {\\n                    const value = userProps[key];\\n                    props[key] = normalizeEntity(value);\\n                } else if (field.getDefault) {\\n                    props[key] = field.getDefault(userProps);\\n                }\\n            } else if (valuePassed) {\\n                // Save for later processing\\n                m2mRelations[key] = userProps[key];\\n\\n                if (!field.as) {\\n                    /**\\n                     * The relationship does not have an accessor\\n                     * Discard the value from props as the field will be populated later with instances\\n                     * from the target models when refreshing the M2M relations.\\n                     * If the relationship does have an accessor (`as`) field then we do want to keep this\\n                     * original value in the props to expose the raw list of IDs from the instance.\\n                     */\\n                    delete props[key];\\n                }\\n            }\\n        });\\n\\n        // add backward many-many if required\\n        declaredVirtualFieldNames.forEach((key) => {\\n            if (!m2mRelations.hasOwnProperty(key)) {\\n                const field = this.virtualFields[key];\\n                if (\\n                    userProps.hasOwnProperty(key) &&\\n                    field instanceof ManyToMany\\n                ) {\\n                    // If a value is supplied for a ManyToMany field,\\n                    // discard them from props and save for later processing.\\n                    m2mRelations[key] = userProps[key];\\n                    delete props[key];\\n                }\\n            }\\n        });\\n\\n        const newEntry = this.session.applyUpdate({\\n            action: CREATE,\\n            table: this.modelName,\\n            payload: props,\\n        });\\n\\n        const ThisModel = this;\\n        const instance = new ThisModel(newEntry);\\n        instance._refreshMany2Many(m2mRelations); // eslint-disable-line no-underscore-dangle\\n        return instance;\\n    }\\n\\n    /**\\n     * Creates a new or update existing record in the database, instantiates a {@link Model} and returns it.\\n     *\\n     * If you pass values for many-to-many fields, instances are created on the through\\n     * model as well.\\n     *\\n     * @param  {Object} userProps - the required {@link Model}'s properties.\\n     * @return {Model} a {@link Model} instance.\\n     */\\n    static upsert(userProps) {\\n        if (typeof this.session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to upsert a ${this.modelName} model instance without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].upsert\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n\\n        const { idAttribute } = this;\\n        if (userProps.hasOwnProperty(idAttribute)) {\\n            const id = userProps[idAttribute];\\n            if (this.idExists(id)) {\\n                const model = this.withId(id);\\n                model.update(userProps);\\n                return model;\\n            }\\n        }\\n\\n        return this.create(userProps);\\n    }\\n\\n    /**\\n     * Returns a {@link Model} instance for the object with id `id`.\\n     * Returns `null` if the model has no instance with id `id`.\\n     *\\n     * You can use {@link Model#idExists} to check for existence instead.\\n     *\\n     * @param  {*} id - the `id` of the object to get\\n     * @throws If object with id `id` doesn't exist\\n     * @return {Model|null} {@link Model} instance with id `id`\\n     */\\n    static withId(id) {\\n        return this.get({\\n            [this.idAttribute]: id,\\n        });\\n    }\\n\\n    /**\\n     * Returns a boolean indicating if an entity\\n     * with the id `id` exists in the state.\\n     *\\n     * @param  {*}  id - a value corresponding to the id attribute of the {@link Model} class.\\n     * @return {Boolean} a boolean indicating if entity with `id` exists in the state\\n     *\\n     * @since 0.11.0\\n     */\\n    static idExists(id) {\\n        return this.exists({\\n            [this.idAttribute]: id,\\n        });\\n    }\\n\\n    /**\\n     * Returns a boolean indicating if an entity\\n     * with the given props exists in the state.\\n     *\\n     * @param  {*}  props - a key-value that {@link Model} instances should have to be considered as existing.\\n     * @return {Boolean} a boolean indicating if entity with `props` exists in the state\\n     */\\n    static exists(lookupObj) {\\n        if (typeof this.session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to check if a ${this.modelName} model instance exists without a session. `,\\n                    \\\"Create a session using `session = orm.session()` and call \\\",\\n                    `\\\\`session[\\\"${this.modelName}\\\"].exists\\\\` instead.`,\\n                ].join(\\\"\\\")\\n            );\\n        }\\n\\n        return Boolean(this._findDatabaseRows(lookupObj).length);\\n    }\\n\\n    /**\\n     * Gets the {@link Model} instance that matches properties in `lookupObj`.\\n     * Throws an error if {@link Model} if multiple records match\\n     * the properties.\\n     *\\n     * @param  {Object} lookupObj - the properties used to match a single entity.\\n     * @throws {Error} If more than one entity matches the properties in `lookupObj`.\\n     * @return {Model} a {@link Model} instance that matches the properties in `lookupObj`.\\n     */\\n    static get(lookupObj) {\\n        const ThisModel = this;\\n\\n        const rows = this._findDatabaseRows(lookupObj);\\n        if (rows.length === 0) {\\n            return null;\\n        }\\n        if (rows.length > 1) {\\n            throw new Error(\\n                `Expected to find a single row in \\\\`${this.modelName}.get\\\\`. Found ${rows.length}.`\\n            );\\n        }\\n\\n        return new ThisModel(rows[0]);\\n    }\\n\\n    /**\\n     * Gets the {@link Model} class or subclass constructor (the class that\\n     * instantiated this instance).\\n     *\\n     * @return {Model} The {@link Model} class or subclass constructor used to instantiate\\n     *                 this instance.\\n     */\\n    getClass() {\\n        return this.constructor;\\n    }\\n\\n    /**\\n     * Gets the id value of the current instance by looking up the id attribute.\\n     * @return {*} The id value of the current instance.\\n     */\\n    getId() {\\n        return this._fields[this.getClass().idAttribute];\\n    }\\n\\n    /**\\n     * Returns a reference to the plain JS object in the store.\\n     * It contains all the properties that you pass when creating the model,\\n     * except for primary keys of many-to-many relationships with a custom accessor.\\n     *\\n     * Make sure never to mutate this.\\n     *\\n     * @return {Object} a reference to the plain JS object in the store\\n     */\\n    get ref() {\\n        const ThisModel = this.getClass();\\n\\n        // eslint-disable-next-line no-underscore-dangle\\n        return ThisModel._findDatabaseRows({\\n            [ThisModel.idAttribute]: this.getId(),\\n        })[0];\\n    }\\n\\n    /**\\n     * Finds all rows in this model's table that match the given `lookupObj`.\\n     * If no `lookupObj` is passed, all rows in the model's table will be returned.\\n     *\\n     * @param  {*}  props - a key-value that {@link Model} instances should have to be considered as existing.\\n     * @return {Boolean} a boolean indicating if entity with `props` exists in the state\\n     * @private\\n     */\\n    static _findDatabaseRows(lookupObj) {\\n        const querySpec = {\\n            table: this.modelName,\\n        };\\n        if (lookupObj) {\\n            querySpec.clauses = [\\n                {\\n                    type: FILTER,\\n                    payload: lookupObj,\\n                },\\n            ];\\n        }\\n        return this.session.query(querySpec).rows;\\n    }\\n\\n    /**\\n     * Returns a string representation of the {@link Model} instance.\\n     *\\n     * @return {string} A string representation of this {@link Model} instance.\\n     */\\n    toString() {\\n        const ThisModel = this.getClass();\\n        const className = ThisModel.modelName;\\n        const fieldNames = Object.keys(ThisModel.fields);\\n        const fields = fieldNames\\n            .map((fieldName) => {\\n                const field = ThisModel.fields[fieldName];\\n                if (field instanceof ManyToMany) {\\n                    const ids = this[fieldName]\\n                        .toModelArray()\\n                        .map((model) => model.getId());\\n                    return `${fieldName}: [${ids.join(\\\", \\\")}]`;\\n                }\\n                const val = this._fields[fieldName];\\n                return `${fieldName}: ${val}`;\\n            })\\n            .join(\\\", \\\");\\n        return `${className}: {${fields}}`;\\n    }\\n\\n    /**\\n     * Returns a boolean indicating if `otherModel` equals this {@link Model} instance.\\n     * Equality is determined by shallow comparing their attributes.\\n     *\\n     * This equality is used when you call {@link Model#update}.\\n     * You can prevent model updates by returning `true` here.\\n     * However, a model will always be updated if its relationships are changed.\\n     *\\n     * @param  {Model} otherModel - a {@link Model} instance to compare\\n     * @return {Boolean} a boolean indicating if the {@link Model} instance's are equal.\\n     */\\n    equals(otherModel) {\\n        // eslint-disable-next-line no-underscore-dangle\\n        return objectShallowEquals(this._fields, otherModel._fields);\\n    }\\n\\n    /**\\n     * Updates a property name to given value for this {@link Model} instance.\\n     * The values are immediately committed to the database.\\n     *\\n     * @param {string} propertyName - name of the property to set\\n     * @param {*} value - value assigned to the property\\n     * @return {undefined}\\n     */\\n    set(propertyName, value) {\\n        this.update({\\n            [propertyName]: value,\\n        });\\n    }\\n\\n    /**\\n     * Assigns multiple fields and corresponding values to this {@link Model} instance.\\n     * The updates are immediately committed to the database.\\n     *\\n     * @param  {Object} userMergeObj - an object that will be merged with this instance.\\n     * @return {undefined}\\n     */\\n    update(userMergeObj) {\\n        const ThisModel = this.getClass();\\n        if (typeof ThisModel.session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to update a ${ThisModel.modelName} model instance without a session. `,\\n                    \\\"You cannot call `.update` on an instance that you did not receive from the database.\\\",\\n                ].join(\\\"\\\")\\n            );\\n        }\\n\\n        const mergeObj = { ...userMergeObj };\\n\\n        const { fields, virtualFields } = ThisModel;\\n\\n        const m2mRelations = {};\\n\\n        // If an array of entities or id's is supplied for a\\n        // many-to-many related field, clear the old relations\\n        // and add the new ones.\\n        // eslint-disable-next-line guard-for-in, no-restricted-syntax\\n        for (const mergeKey in mergeObj) {\\n            const isRealField = fields.hasOwnProperty(mergeKey);\\n\\n            if (isRealField) {\\n                const field = fields[mergeKey];\\n\\n                if (field instanceof ForeignKey || field instanceof OneToOne) {\\n                    // update one-one/fk relations\\n                    mergeObj[mergeKey] = normalizeEntity(mergeObj[mergeKey]);\\n                } else if (field instanceof ManyToMany) {\\n                    // field is forward relation\\n                    m2mRelations[mergeKey] = mergeObj[mergeKey];\\n\\n                    if (!field.as) {\\n                        /**\\n                         * The relationship does not have an accessor\\n                         * Discard the value from props as the field will be populated later with instances\\n                         * from the target models when refreshing the M2M relations.\\n                         * If the relationship does have an accessor (`as`) field then we do want to keep this\\n                         * original value in the props to expose the raw list of IDs from the instance.\\n                         */\\n                        delete mergeObj[mergeKey];\\n                    }\\n                }\\n            } else if (virtualFields.hasOwnProperty(mergeKey)) {\\n                const field = virtualFields[mergeKey];\\n                if (field instanceof ManyToMany) {\\n                    // field is backward relation\\n                    m2mRelations[mergeKey] = mergeObj[mergeKey];\\n                    delete mergeObj[mergeKey];\\n                }\\n            }\\n        }\\n\\n        const mergedFields = {\\n            ...this._fields,\\n            ...mergeObj,\\n        };\\n\\n        const updatedModel = new ThisModel(mergedFields);\\n        // only update fields if they have changed (referentially)\\n        if (!this.equals(updatedModel)) {\\n            this._initFields(mergedFields);\\n            ThisModel.session.applyUpdate({\\n                action: UPDATE,\\n                query: getByIdQuery(this),\\n                payload: mergeObj,\\n            });\\n        }\\n\\n        // update virtual fields\\n        this._refreshMany2Many(m2mRelations);\\n    }\\n\\n    /**\\n     * Updates {@link Model} instance attributes to reflect the\\n     * database state in the current session.\\n     * @return {undefined}\\n     */\\n    refreshFromState() {\\n        this._initFields(this.ref);\\n    }\\n\\n    /**\\n     * Deletes the record for this {@link Model} instance.\\n     * You'll still be able to access fields and values on the instance.\\n     *\\n     * @return {undefined}\\n     */\\n    delete() {\\n        const ThisModel = this.getClass();\\n        if (typeof ThisModel.session === \\\"undefined\\\") {\\n            throw new Error(\\n                [\\n                    `Tried to delete a ${ThisModel.modelName} model instance without a session. `,\\n                    \\\"You cannot call `.delete` on an instance that you did not receive from the database.\\\",\\n                ].join(\\\"\\\")\\n            );\\n        }\\n\\n        this._onDelete();\\n        ThisModel.session.applyUpdate({\\n            action: DELETE,\\n            query: getByIdQuery(this),\\n        });\\n    }\\n\\n    /**\\n     * Update many-many relations for model.\\n     * @param relations\\n     * @return undefined\\n     * @private\\n     */\\n    _refreshMany2Many(relations) {\\n        const ThisModel = this.getClass();\\n        const { fields, virtualFields, modelName } = ThisModel;\\n\\n        Object.keys(relations).forEach((name) => {\\n            const reverse = !fields.hasOwnProperty(name);\\n            const field = virtualFields[name];\\n            const values = relations[name];\\n\\n            if (!Array.isArray(values)) {\\n                throw new TypeError(\\n                    `Failed to resolve many-to-many relationship: ${modelName}[${name}] must be an array (passed: ${values})`\\n                );\\n            }\\n\\n            const normalizedNewIds = values.map(normalizeEntity);\\n            const uniqueIds = [...new Set(normalizedNewIds)];\\n\\n            if (normalizedNewIds.length !== uniqueIds.length) {\\n                throw new Error(\\n                    `Found duplicate id(s) when passing \\\"${normalizedNewIds}\\\" to ${ThisModel.modelName}.${name} value`\\n                );\\n            }\\n\\n            const throughModelName =\\n                field.through || m2mName(ThisModel.modelName, name);\\n            const ThroughModel = ThisModel.session[throughModelName];\\n\\n            let fromField;\\n            let toField;\\n\\n            if (!reverse) {\\n                ({ from: fromField, to: toField } = field.throughFields);\\n            } else {\\n                ({ from: toField, to: fromField } = field.throughFields);\\n            }\\n\\n            const currentIds = ThroughModel.filter(\\n                (through) => through[fromField] === this[ThisModel.idAttribute]\\n            )\\n                .toRefArray()\\n                .map((ref) => ref[toField]);\\n\\n            const diffActions = arrayDiffActions(currentIds, normalizedNewIds);\\n\\n            if (diffActions) {\\n                const { delete: idsToDelete, add: idsToAdd } = diffActions;\\n                if (idsToDelete.length > 0) {\\n                    this[field.as || name].remove(...idsToDelete);\\n                }\\n\\n                if (idsToAdd.length > 0) {\\n                    this[field.as || name].add(...idsToAdd);\\n                }\\n            }\\n        });\\n    }\\n\\n    /**\\n     * @return {undefined}\\n     * @private\\n     */\\n    _onDelete() {\\n        const { virtualFields } = this.getClass();\\n        // eslint-disable-next-line guard-for-in, no-restricted-syntax\\n        for (const key in virtualFields) {\\n            const field = virtualFields[key];\\n            if (field instanceof ManyToMany) {\\n                // Delete any many-to-many rows the entity is included in.\\n                const descriptorKey = field.as || key;\\n                this[descriptorKey].clear();\\n            } else if (field instanceof ForeignKey) {\\n                const relatedQs = this[key];\\n                if (relatedQs.exists()) {\\n                    relatedQs.update({ [field.relatedName]: null });\\n                }\\n            } else if (field instanceof OneToOne) {\\n                // Set null to any foreign keys or one to ones pointed to\\n                // this instance.\\n                if (this[key] !== null) {\\n                    this[key][field.relatedName] = null;\\n                }\\n            }\\n        }\\n    }\\n\\n    // DEPRECATED AND REMOVED METHODS\\n\\n    /**\\n     * Returns a boolean indicating if an entity\\n     * with the id `id` exists in the state.\\n     *\\n     * @param  {*}  id - a value corresponding to the id attribute of the {@link Model} class.\\n     * @return {Boolean} a boolean indicating if entity with `id` exists in the state\\n     * @deprecated Please use {@link Model.idExists} instead.\\n     */\\n    static hasId(id) {\\n        console.warn(\\n            \\\"`Model.hasId` has been deprecated. Please use `Model.idExists` instead.\\\"\\n        );\\n        return this.idExists(id);\\n    }\\n\\n    /**\\n     * @deprecated See the 0.9 migration guide on the GitHub repo.\\n     * @throws {Error} Due to deprecation.\\n     */\\n    getNextState() {\\n        throw new Error(\\n            \\\"`Model.prototype.getNextState` has been removed. See the 0.9 \\\" +\\n                \\\"migration guide on the GitHub repo.\\\"\\n        );\\n    }\\n};\\n\\nModel.fields = {\\n    id: attr(),\\n};\\nModel.virtualFields = {};\\nModel.querySetClass = QuerySet;\\n\\nexport default Model;\\n\",\"import ops from \\\"immutable-ops\\\";\\nimport filter from \\\"lodash/filter\\\";\\nimport orderBy from \\\"lodash/orderBy\\\";\\nimport reject from \\\"lodash/reject\\\";\\nimport sortBy from \\\"lodash/sortBy\\\";\\n\\nimport { EXCLUDE, FILTER, ORDER_BY } from \\\"../constants\\\";\\nimport { clauseFiltersByAttribute, clauseReducesResultSetSize } from \\\"../utils\\\";\\n\\nconst DEFAULT_TABLE_OPTIONS = {\\n    idAttribute: \\\"id\\\",\\n    arrName: \\\"items\\\",\\n    mapName: \\\"itemsById\\\",\\n    fields: {},\\n};\\n\\n/**\\n * @private\\n * @param {*} _currMax - the current max id\\n * @param {*} userPassedId - the new id passed to the create action\\n *\\n * Both may be undefined. The current max id in the case that this is the first Model\\n * being created, and the new id if the id was not explicitly passed to the\\n * database.\\n *\\n * @return {Array} the new max id and the id to use to create the new row\\n *\\n * If the id's are strings, the id must be passed explicitly every time.\\n * In this case, the current max id will remain `NaN` due to `Math.max`, but that's fine.\\n */\\nfunction idSequencer(_currMax, userPassedId) {\\n    let currMax = _currMax;\\n    let newMax;\\n    let newId;\\n\\n    if (currMax === undefined) {\\n        currMax = -1;\\n    }\\n\\n    if (userPassedId === undefined) {\\n        newMax = currMax + 1;\\n        newId = newMax;\\n    } else {\\n        newMax = Math.max(currMax + 1, userPassedId);\\n        newId = userPassedId;\\n    }\\n\\n    return [\\n        newMax, // new max id\\n        newId, // id to use for row creation\\n    ];\\n}\\n\\n/**\\n * Adapt order directions array to @{lodash.orderBy} API.\\n *\\n * @private\\n *\\n * @param {Array<Boolean|'asc'|'desc'>} orders? - an array of optional order query directions as provided to {@Link {QuerySet.orderBy}}\\n * @return {Array<'asc'|'desc'>|undefined} A normalized ordering array or undefined if none was provided.\\n */\\nfunction normalizeOrders(orders) {\\n    if (orders === undefined) {\\n        return undefined;\\n    }\\n    const convert = (order) => {\\n        if ([\\\"desc\\\", false].includes(order)) {\\n            return \\\"desc\\\";\\n        }\\n        return \\\"asc\\\";\\n    };\\n    return Array.isArray(orders) ? orders.map(convert) : convert(orders);\\n}\\n\\n/**\\n * Handles the underlying data structure for a {@link Model} class.\\n * @private\\n */\\nexport class Table {\\n    /**\\n     * Creates a new {@link Table} instance.\\n     * @param  {Object} userOpts - options to use.\\n     * @param  {string} [userOpts.idAttribute=id] - the id attribute of the entity.\\n     * @param  {string} [userOpts.arrName=items] - the state attribute where an array of\\n     *                                             entity id's are stored\\n     * @param  {string} [userOpts.mapName=itemsById] - the state attribute where the entity objects\\n     *                                                 are stored in a id to entity object\\n     *                                                 map.\\n     * @param  {string} [userOpts.fields={}] - mapping of field key to {@link Field} object\\n     */\\n    constructor(userOpts) {\\n        Object.assign(this, DEFAULT_TABLE_OPTIONS, userOpts);\\n    }\\n\\n    /**\\n     * Returns a reference to the object at index `id`\\n     * in state `branch`.\\n     *\\n     * @param  {Object} branch - the state\\n     * @param  {Number} id - the id of the object to get\\n     * @return {Object|undefined} A reference to the raw object in the state or\\n     *                            `undefined` if not found.\\n     */\\n    accessId(branch, id) {\\n        return branch[this.mapName][id];\\n    }\\n\\n    accessIds(branch, ids) {\\n        const map = branch[this.mapName];\\n        return ids.map((id) => map[id]);\\n    }\\n\\n    idExists(branch, id) {\\n        return branch[this.mapName].hasOwnProperty(id);\\n    }\\n\\n    accessIdList(branch) {\\n        return branch[this.arrName];\\n    }\\n\\n    accessList(branch) {\\n        return this.accessIds(branch, this.accessIdList(branch));\\n    }\\n\\n    getMaxId(branch) {\\n        return this.getMeta(branch, \\\"maxId\\\");\\n    }\\n\\n    setMaxId(tx, branch, newMaxId) {\\n        return this.setMeta(tx, branch, \\\"maxId\\\", newMaxId);\\n    }\\n\\n    nextId(id) {\\n        return id + 1;\\n    }\\n\\n    /**\\n     * Returns the default state for the data structure.\\n     * @return {Object} The default state for this {@link ORM} instance's data structure\\n     */\\n    getEmptyState() {\\n        const pkIndex = {\\n            [this.arrName]: [],\\n            [this.mapName]: {},\\n        };\\n        const attrIndexes = Object.keys(this.fields)\\n            .filter((attr) => attr !== this.idAttribute)\\n            .filter((attr) => this.fields[attr].index)\\n            .reduce(\\n                (indexes, attr) => ({\\n                    ...indexes,\\n                    [attr]: {},\\n                }),\\n                {}\\n            );\\n        return {\\n            ...pkIndex,\\n            indexes: attrIndexes,\\n            meta: {},\\n        };\\n    }\\n\\n    setMeta(tx, branch, key, value) {\\n        const { batchToken, withMutations } = tx;\\n        if (withMutations) {\\n            const res = ops.mutable.setIn([\\\"meta\\\", key], value, branch);\\n            return res;\\n        }\\n\\n        return ops.batch.setIn(batchToken, [\\\"meta\\\", key], value, branch);\\n    }\\n\\n    getMeta(branch, key) {\\n        return branch.meta[key];\\n    }\\n\\n    query(branch, clauses) {\\n        if (clauses.length === 0) {\\n            return this.accessList(branch);\\n        }\\n\\n        const { idAttribute } = this;\\n\\n        const optimallyOrderedClauses = sortBy(clauses, (clause) => {\\n            if (clauseFiltersByAttribute(clause, idAttribute)) {\\n                return 1;\\n            }\\n\\n            if (clauseReducesResultSetSize(clause)) {\\n                return 2;\\n            }\\n\\n            return 3;\\n        });\\n\\n        const reducer = (rows, clause) => {\\n            const { type, payload } = clause;\\n            if (!rows) {\\n                /**\\n                 * First time this reducer is called during query.\\n                 * This is where we apply query optimizations.\\n                 */\\n                if (clauseFiltersByAttribute(clause, idAttribute)) {\\n                    /**\\n                     * Payload specified a primary key. Use PK index\\n                     * to look up the single row identified by the PK.\\n                     */\\n                    const id = payload[idAttribute];\\n                    const remainingPayload = Object.keys(payload).reduce(\\n                        (withoutPkAttr, filterAttr) => {\\n                            if (filterAttr !== idAttribute) {\\n                                withoutPkAttr[filterAttr] = payload[filterAttr];\\n                            }\\n                            return withoutPkAttr;\\n                        },\\n                        {}\\n                    );\\n                    const ids = this.idExists(branch, id) ? [id] : [];\\n                    if (Object.keys(remainingPayload).length) {\\n                        /**\\n                         * Payload has additional, non-PK columns.\\n                         * Filter accessed row by remaining payload (if one was found).\\n                         */\\n                        return reducer(this.accessIds(branch, ids), {\\n                            ...clause,\\n                            payload: remainingPayload,\\n                        });\\n                    }\\n                    /**\\n                     * No need to filter these rows any further.\\n                     * The primary key value satisfies this clause's conditions.\\n                     */\\n                    return this.accessIds(branch, ids);\\n                }\\n                if (type === FILTER && typeof payload === \\\"object\\\") {\\n                    const indexes = Object.entries(branch.indexes);\\n                    const accessedIndexes = [];\\n                    const indexAttrs = [];\\n                    indexes.forEach(([attr, index]) => {\\n                        if (clauseFiltersByAttribute(clause, attr)) {\\n                            /**\\n                             * Payload specified an indexed attribute. Use index\\n                             * to potentially decrease amount of accessed rows.\\n                             */\\n                            if (index.hasOwnProperty(payload[attr])) {\\n                                accessedIndexes.push(index[payload[attr]]);\\n                                indexAttrs.push(attr);\\n                            }\\n                        }\\n                    });\\n                    /**\\n                     * Calculate set of unique PK values corresponding to each\\n                     * foreign key's attribute value. Then retrieve all those rows.\\n                     */\\n                    if (accessedIndexes.length) {\\n                        const lastIndex = accessedIndexes.pop();\\n                        const indexedIds = accessedIndexes.reduce(\\n                            (result, index) => {\\n                                const indexSet = new Set(index);\\n                                return result.filter(\\n                                    Set.prototype.has,\\n                                    indexSet\\n                                );\\n                            },\\n                            lastIndex\\n                        );\\n                        const remainingPayload = Object.keys(payload).reduce(\\n                            (withoutIndexAttrs, filterAttr) => {\\n                                if (!indexAttrs.includes(filterAttr)) {\\n                                    withoutIndexAttrs[filterAttr] =\\n                                        payload[filterAttr];\\n                                }\\n                                return withoutIndexAttrs;\\n                            },\\n                            {}\\n                        );\\n                        if (Object.keys(remainingPayload).length) {\\n                            /**\\n                             * Payload has additional, non-indexed columns.\\n                             * Filter indexed rows by remaining payload (if any were found).\\n                             */\\n                            return reducer(this.accessIds(branch, indexedIds), {\\n                                ...clause,\\n                                payload: remainingPayload,\\n                            });\\n                        }\\n                        /**\\n                         * No need to filter these rows any further.\\n                         * The used indexes satisfy this clause's conditions.\\n                         */\\n                        return this.accessIds(branch, indexedIds);\\n                    }\\n                }\\n\\n                // Give up optimization: Retrieve all rows (full table scan).\\n                return reducer(this.accessList(branch), clause);\\n            }\\n\\n            switch (type) {\\n                case FILTER: {\\n                    return filter(rows, payload);\\n                }\\n                case EXCLUDE: {\\n                    return reject(rows, payload);\\n                }\\n                case ORDER_BY: {\\n                    const [iteratees, orders] = payload;\\n                    return orderBy(rows, iteratees, normalizeOrders(orders));\\n                }\\n                default:\\n                    return rows;\\n            }\\n        };\\n\\n        return optimallyOrderedClauses.reduce(reducer, undefined);\\n    }\\n\\n    /**\\n     * Returns the data structure including a new object `entry`\\n     * @param  {Object} tx - transaction info\\n     * @param  {Object} branch - the data structure state\\n     * @param  {Object} entry - the object to insert\\n     * @return {Object} an object with two keys: `state` and `created`.\\n     *                  `state` is the new table state and `created` is the\\n     *                  row that was created.\\n     */\\n    insert(tx, branch, entry) {\\n        const { batchToken, withMutations } = tx;\\n\\n        const hasId = entry.hasOwnProperty(this.idAttribute);\\n\\n        let workingState = branch;\\n\\n        // This will not affect string id's.\\n        const [newMaxId, id] = idSequencer(\\n            this.getMaxId(branch),\\n            entry[this.idAttribute]\\n        );\\n        workingState = this.setMaxId(tx, branch, newMaxId);\\n\\n        const finalEntry = hasId\\n            ? entry\\n            : ops.batch.set(batchToken, this.idAttribute, id, entry);\\n\\n        const indexesToAppendTo = Object.keys(workingState.indexes)\\n            .filter(\\n                (fkAttr) =>\\n                    entry.hasOwnProperty(fkAttr) && entry[fkAttr] !== null\\n            )\\n            .map((fkAttr) => [fkAttr, entry[fkAttr]]);\\n\\n        if (withMutations) {\\n            ops.mutable.push(id, workingState[this.arrName]);\\n            ops.mutable.set(id, finalEntry, workingState[this.mapName]);\\n            // add id to indexes\\n            indexesToAppendTo.forEach(([attr, value]) => {\\n                const attrIndex = workingState.indexes[attr];\\n                if (attrIndex.hasOwnProperty(value)) {\\n                    ops.mutable.push(id, attrIndex[value]);\\n                } else {\\n                    ops.mutable.set(value, [id], attrIndex);\\n                }\\n            });\\n            return {\\n                state: workingState,\\n                created: finalEntry,\\n            };\\n        }\\n\\n        const nextIndexes = ops.batch.merge(\\n            batchToken,\\n            indexesToAppendTo.reduce(\\n                (indexMap, [attr, value]) => {\\n                    indexMap[attr] = ops.batch.merge(\\n                        batchToken,\\n                        {\\n                            [value]: ops.batch.push(\\n                                batchToken,\\n                                id,\\n                                indexMap[attr][value] || []\\n                            ),\\n                        },\\n                        indexMap[attr]\\n                    );\\n                    return indexMap;\\n                },\\n                { ...workingState.indexes }\\n            ),\\n            workingState.indexes\\n        );\\n\\n        const nextState = ops.batch.merge(\\n            batchToken,\\n            {\\n                [this.arrName]: ops.batch.push(\\n                    batchToken,\\n                    id,\\n                    workingState[this.arrName]\\n                ),\\n                [this.mapName]: ops.batch.merge(\\n                    batchToken,\\n                    {\\n                        [id]: finalEntry,\\n                    },\\n                    workingState[this.mapName]\\n                ),\\n                indexes: nextIndexes,\\n            },\\n            workingState\\n        );\\n\\n        return {\\n            state: nextState,\\n            created: finalEntry,\\n        };\\n    }\\n\\n    /**\\n     * Returns the data structure with objects where `rows`\\n     * are merged with `mergeObj`.\\n     *\\n     * @param  {Object} tx - transaction info\\n     * @param  {Object} branch - the data structure state\\n     * @param  {Object[]} rows - rows to update\\n     * @param  {Object} mergeObj - The object to merge with each row.\\n     * @return {Object}\\n     */\\n    update(tx, branch, rows, mergeObj) {\\n        const { batchToken, withMutations } = tx;\\n\\n        const mergeObjInto = (row) => {\\n            const merge = withMutations\\n                ? ops.mutable.merge\\n                : ops.batch.merge(batchToken);\\n            return merge(mergeObj, row);\\n        };\\n\\n        const set = withMutations ? ops.mutable.set : ops.batch.set(batchToken);\\n\\n        const indexedAttrs = Object.keys(branch.indexes).filter((attr) =>\\n            mergeObj.hasOwnProperty(attr)\\n        );\\n        const indexIdsToAdd = [];\\n        const indexIdsToDelete = [];\\n\\n        const nextMap = rows.reduce((map, row) => {\\n            const prevAttrValues = indexedAttrs.reduce(\\n                (valueMap, attr) => ({\\n                    ...valueMap,\\n                    [attr]: row[attr],\\n                }),\\n                {}\\n            );\\n            const result = mergeObjInto(row);\\n            const nextAttrValues = indexedAttrs.reduce(\\n                (valueMap, attr) => ({\\n                    ...valueMap,\\n                    [attr]: result[attr],\\n                }),\\n                {}\\n            );\\n            const id = result[this.idAttribute];\\n            const nextRow = set(id, result, map);\\n            indexedAttrs.forEach((attr) => {\\n                const { [attr]: prevValue } = prevAttrValues;\\n                const { [attr]: nextValue } = nextAttrValues;\\n                if (prevValue === nextValue) {\\n                    // attribute has not changed, no need to update any index\\n                    return;\\n                }\\n                if (prevValue !== null && typeof prevValue !== \\\"undefined\\\") {\\n                    // remove id from attribute's index for its old value\\n                    indexIdsToDelete.push([attr, prevValue, id]);\\n                }\\n                if (nextValue !== null) {\\n                    // add id to attribute's index for its new value\\n                    indexIdsToAdd.push([attr, nextValue, id]);\\n                }\\n            });\\n            return nextRow;\\n        }, branch[this.mapName]);\\n\\n        let nextIndexes = branch.indexes;\\n        if (withMutations) {\\n            indexIdsToDelete.forEach(([attr, value, id]) => {\\n                const arr = nextIndexes[attr][value];\\n                const idx = arr.indexOf(id);\\n                ops.mutable.splice(idx, 1, [], arr);\\n            });\\n            indexIdsToAdd.forEach(([attr, value, id]) => {\\n                ops.mutable.push(id, nextIndexes[attr][value]);\\n            });\\n        } else {\\n            if (indexIdsToAdd.length) {\\n                nextIndexes = ops.batch.merge(\\n                    batchToken,\\n                    indexIdsToAdd.reduce(\\n                        (indexMap, [attr, value, id]) => {\\n                            indexMap[attr] = ops.batch.merge(\\n                                batchToken,\\n                                {\\n                                    [value]: ops.batch.push(\\n                                        batchToken,\\n                                        id,\\n                                        indexMap[attr][value] || []\\n                                    ),\\n                                },\\n                                indexMap[attr]\\n                            );\\n                            return indexMap;\\n                        },\\n                        { ...nextIndexes }\\n                    ),\\n                    nextIndexes\\n                );\\n            }\\n            if (indexIdsToDelete.length) {\\n                nextIndexes = ops.batch.merge(\\n                    batchToken,\\n                    indexIdsToDelete.reduce(\\n                        (indexMap, [attr, value, id]) => {\\n                            indexMap[attr] = ops.batch.merge(\\n                                batchToken,\\n                                {\\n                                    [value]: ops.batch.filter(\\n                                        batchToken,\\n                                        (rowId) => rowId !== id,\\n                                        indexMap[attr][value]\\n                                    ),\\n                                },\\n                                indexMap[attr]\\n                            );\\n                            return indexMap;\\n                        },\\n                        { ...nextIndexes }\\n                    ),\\n                    nextIndexes\\n                );\\n            }\\n        }\\n\\n        return ops.batch.merge(\\n            batchToken,\\n            {\\n                [this.mapName]: nextMap,\\n                indexes: nextIndexes,\\n            },\\n            branch\\n        );\\n    }\\n\\n    /**\\n     * Returns the data structure without rows `rows`.\\n     * @param  {Object} tx - transaction info\\n     * @param  {Object} branch - the data structure state\\n     * @param  {Object[]} rows - rows to update\\n     * @return {Object} the data structure without ids in `idsToDelete`.\\n     */\\n    delete(tx, branch, rows) {\\n        const { batchToken, withMutations } = tx;\\n\\n        const { arrName, mapName } = this;\\n        const arr = branch[arrName];\\n\\n        const idsToDelete = rows.map((row) => row[this.idAttribute]);\\n        if (withMutations) {\\n            idsToDelete.forEach((id) => {\\n                const idx = arr.indexOf(id);\\n                ops.mutable.splice(idx, 1, [], arr);\\n                ops.mutable.omit(id, branch[mapName]);\\n            });\\n            // delete ids from all indexes\\n            Object.values(branch.indexes).forEach((attrIndex) =>\\n                Object.values(attrIndex).forEach((valueIndex) =>\\n                    idsToDelete.forEach((id) => {\\n                        const idx = valueIndex.indexOf(id);\\n                        if (idx !== -1) {\\n                            ops.mutable.splice(idx, 1, [], valueIndex);\\n                        }\\n                    })\\n                )\\n            );\\n            return branch;\\n        }\\n\\n        const nextIndexes = ops.batch.merge(\\n            batchToken,\\n            Object.entries(branch.indexes).reduce(\\n                (indexMap, [attr, attrIndex]) => {\\n                    indexMap[attr] = ops.batch.merge(\\n                        batchToken,\\n                        Object.entries(attrIndex).reduce(\\n                            (attrIndexMap, [value, valueIndex]) => {\\n                                attrIndexMap[value] = ops.batch.filter(\\n                                    batchToken,\\n                                    (id) => !idsToDelete.includes(id),\\n                                    valueIndex\\n                                );\\n                                return attrIndexMap;\\n                            },\\n                            { ...indexMap[attr] }\\n                        ),\\n                        indexMap[attr]\\n                    );\\n                    return indexMap;\\n                },\\n                { ...branch.indexes }\\n            ),\\n            branch.indexes\\n        );\\n\\n        return ops.batch.merge(\\n            batchToken,\\n            {\\n                [arrName]: ops.batch.filter(\\n                    batchToken,\\n                    (id) => !idsToDelete.includes(id),\\n                    branch[arrName]\\n                ),\\n                [mapName]: ops.batch.omit(\\n                    batchToken,\\n                    idsToDelete,\\n                    branch[mapName]\\n                ),\\n                indexes: ops.batch.merge(\\n                    batchToken,\\n                    nextIndexes,\\n                    branch.indexes\\n                ),\\n            },\\n            branch\\n        );\\n    }\\n}\\n\\nexport default Table;\\n\",\"import ops from \\\"immutable-ops\\\";\\n\\nimport { CREATE, UPDATE, DELETE, SUCCESS, STATE_FLAG } from \\\"../constants\\\";\\n\\nimport Table from \\\"./Table\\\";\\n\\nconst BASE_EMPTY_STATE = {};\\nObject.defineProperty(BASE_EMPTY_STATE, STATE_FLAG, {\\n    enumerable: true,\\n    value: true,\\n});\\n\\n/** @private */\\nfunction replaceTableState(tableName, newTableState, tx, state) {\\n    const { batchToken, withMutations } = tx;\\n\\n    if (withMutations) {\\n        state[tableName] = newTableState;\\n        return state;\\n    }\\n\\n    return ops.batch.set(batchToken, tableName, newTableState, state);\\n}\\n\\n/** @private */\\nfunction query(tables, querySpec, state) {\\n    const { table: tableName, clauses } = querySpec;\\n    const table = tables[tableName];\\n    const rows = table.query(state[tableName], clauses);\\n    return {\\n        rows,\\n    };\\n}\\n\\n/** @private */\\nfunction update(tables, updateSpec, tx, state) {\\n    const { action, payload } = updateSpec;\\n\\n    let tableName;\\n    let nextTableState;\\n    let resultPayload;\\n\\n    if (action === CREATE) {\\n        ({ table: tableName } = updateSpec);\\n        const table = tables[tableName];\\n        const currTableState = state[tableName];\\n        const result = table.insert(tx, currTableState, payload);\\n        nextTableState = result.state;\\n        resultPayload = result.created;\\n    } else {\\n        const { query: querySpec } = updateSpec;\\n        ({ table: tableName } = querySpec);\\n        const { rows } = query(tables, querySpec, state);\\n\\n        const table = tables[tableName];\\n        const currTableState = state[tableName];\\n\\n        if (action === UPDATE) {\\n            nextTableState = table.update(tx, currTableState, rows, payload);\\n            // return updated rows\\n            resultPayload = query(tables, querySpec, state).rows;\\n        } else if (action === DELETE) {\\n            nextTableState = table.delete(tx, currTableState, rows);\\n            // return original rows that we just deleted\\n            resultPayload = rows;\\n        } else {\\n            throw new Error(`Database received unknown update type: ${action}`);\\n        }\\n    }\\n\\n    const nextDBState = replaceTableState(tableName, nextTableState, tx, state);\\n    return {\\n        status: SUCCESS,\\n        state: nextDBState,\\n        payload: resultPayload,\\n    };\\n}\\n\\n/**\\n * @memberof db\\n * @param {Object} schemaSpec\\n * @return Object database\\n */\\nexport function createDatabase(schemaSpec) {\\n    const { tables: tableSpecs } = schemaSpec;\\n    const tables = Object.entries(tableSpecs).reduce(\\n        (map, [tableName, tableSpec]) => ({\\n            ...map,\\n            [tableName]: new Table(tableSpec),\\n        }),\\n        {}\\n    );\\n\\n    const getEmptyState = () =>\\n        Object.entries(tables).reduce(\\n            (map, [tableName, table]) => ({\\n                ...map,\\n                [tableName]: table.getEmptyState(),\\n            }),\\n            BASE_EMPTY_STATE\\n        );\\n\\n    return {\\n        getEmptyState,\\n        query: query.bind(null, tables),\\n        update: update.bind(null, tables),\\n        // Used to inspect the schema.\\n        describe: (tableName) => tables[tableName],\\n    };\\n}\\n\\nexport default createDatabase;\\n\",\"import { ID_ARG_KEY_SELECTOR } from \\\"../constants\\\";\\n\\nexport default class SelectorSpec {\\n    constructor({ parent, orm }) {\\n        this._parent = parent;\\n        this._orm = orm;\\n        this.keySelector = ID_ARG_KEY_SELECTOR;\\n    }\\n\\n    get cachePath() {\\n        const basePath = this._parent ? this._parent.cachePath : [];\\n        return [...basePath, this.key];\\n    }\\n\\n    get orm() {\\n        return this._orm;\\n    }\\n\\n    get parent() {\\n        return this._parent;\\n    }\\n}\\n\",\"import SelectorSpec from \\\"./SelectorSpec\\\";\\n\\nexport default class ModelBasedSelectorSpec extends SelectorSpec {\\n    constructor({ model, ...other }) {\\n        super(other);\\n        this._model = model;\\n    }\\n\\n    get resultFunc() {\\n        return (session, idArg, ...other) => {\\n            const { [this._model.modelName]: ModelClass } = session;\\n            if (typeof idArg === \\\"undefined\\\") {\\n                return ModelClass.all()\\n                    .toModelArray()\\n                    .map((instance) =>\\n                        this.valueForInstance(instance, session, ...other)\\n                    );\\n            }\\n            if (Array.isArray(idArg)) {\\n                return idArg.map((id) =>\\n                    this.valueForInstance(\\n                        ModelClass.withId(id),\\n                        session,\\n                        ...other\\n                    )\\n                );\\n            }\\n            return this.valueForInstance(\\n                ModelClass.withId(idArg),\\n                session,\\n                ...other\\n            );\\n        };\\n    }\\n\\n    get model() {\\n        return this._model;\\n    }\\n}\\n\",\"export default function idArgSelector(state, idArg) {\\n    return idArg;\\n}\\n\",\"import ModelBasedSelectorSpec from \\\"./ModelBasedSelectorSpec\\\";\\nimport idArgSelector from \\\"./idArgSelector\\\";\\n\\nexport default class MapSelectorSpec extends ModelBasedSelectorSpec {\\n    constructor({ field, selector, ...other }) {\\n        super(other);\\n        this._field = field;\\n        this._selector = selector;\\n    }\\n\\n    createResultFunc(parentSelector) {\\n        const { idAttribute } = this._parent.toModel;\\n        return (state, ...other) => {\\n            /**\\n             * The parent selector should return a ref array\\n             * in case of a single ID being passed.\\n             * Otherwise it should return an array of ref arrays.\\n             */\\n            const parentResult = parentSelector(state, ...other);\\n            const idArg = idArgSelector(state, ...other);\\n            const single = (refArray) => {\\n                if (refArray === null) {\\n                    // an intermediate field could not be resolved\\n                    return null;\\n                }\\n                return refArray.map((ref) =>\\n                    this._selector(state, ref[idAttribute])\\n                );\\n            };\\n            if (typeof idArg === \\\"undefined\\\" || Array.isArray(idArg)) {\\n                return parentResult.map(single);\\n            }\\n            return single(parentResult);\\n        };\\n    }\\n\\n    get selector() {\\n        return this._selector;\\n    }\\n\\n    set selector(selector) {\\n        this._selector = selector;\\n    }\\n\\n    get key() {\\n        return this._selector;\\n    }\\n}\\n\",\"import SelectorSpec from \\\"./SelectorSpec\\\";\\nimport idArgSelector from \\\"./idArgSelector\\\";\\n\\nexport default class ModelSelectorSpec extends SelectorSpec {\\n    constructor({ model, ...other }) {\\n        super(other);\\n        this._model = model;\\n    }\\n\\n    get key() {\\n        return this._model.modelName;\\n    }\\n\\n    get dependencies() {\\n        return [this._orm, idArgSelector];\\n    }\\n\\n    get resultFunc() {\\n        return ({ [this._model.modelName]: ModelClass }, idArg) => {\\n            if (typeof idArg === \\\"undefined\\\") {\\n                return ModelClass.all().toRefArray();\\n            }\\n            if (Array.isArray(idArg)) {\\n                return idArg.map((id) => {\\n                    const instance = ModelClass.withId(id);\\n                    return instance ? instance.ref : null;\\n                });\\n            }\\n            const instance = ModelClass.withId(idArg);\\n            return instance ? instance.ref : null;\\n        };\\n    }\\n\\n    get model() {\\n        return this._model;\\n    }\\n}\\n\",\"import MapSelectorSpec from \\\"./MapSelectorSpec\\\";\\nimport ModelSelectorSpec from \\\"./ModelSelectorSpec\\\";\\nimport ModelBasedSelectorSpec from \\\"./ModelBasedSelectorSpec\\\";\\nimport idArgSelector from \\\"./idArgSelector\\\";\\n\\nimport QuerySet from \\\"../QuerySet\\\";\\nimport Model from \\\"../Model\\\";\\n\\nimport ForeignKey from \\\"../fields/ForeignKey\\\";\\nimport ManyToMany from \\\"../fields/ManyToMany\\\";\\n\\nexport default class FieldSelectorSpec extends ModelBasedSelectorSpec {\\n    constructor({ field, fieldModel, accessorName, isVirtual, ...other }) {\\n        super(other);\\n        this._field = field;\\n        this._fieldModel = fieldModel;\\n        this._accessorName = accessorName;\\n        this._isVirtual = isVirtual;\\n    }\\n\\n    get key() {\\n        return this._accessorName;\\n    }\\n\\n    get dependencies() {\\n        return [this._orm, idArgSelector];\\n    }\\n\\n    valueForInstance(instance, session) {\\n        if (!instance) {\\n            return null;\\n        }\\n        let value;\\n        if (this._parent instanceof ModelSelectorSpec) {\\n            /* orm.Model.field */\\n            value = instance[this._accessorName];\\n        } else {\\n            /* orm.Model.field1.field2..fieldN.field */\\n            const { [this._parent.toModelName]: ParentToModel } = session;\\n            const parentRef = this._parent.valueForInstance(instance, session);\\n            const parentInstance = parentRef\\n                ? new ParentToModel(parentRef)\\n                : null;\\n            value = parentInstance ? parentInstance[this._accessorName] : null;\\n        }\\n        if (value instanceof Model) {\\n            return value.ref;\\n        }\\n        if (value instanceof QuerySet) {\\n            return value.toRefArray();\\n        }\\n        return value;\\n    }\\n\\n    map(selector) {\\n        if (selector instanceof ModelSelectorSpec) {\\n            if (this.toModelName === selector.model.modelName) {\\n                throw new Error(\\n                    `Cannot select models in a \\\\`map()\\\\` call. If you just want the \\\\`${this._accessorName}\\\\` as a ref array then you can simply drop the \\\\`map()\\\\`. Otherwise make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`\\n                );\\n            } else {\\n                throw new Error(\\n                    `Cannot select \\\\`${selector.model.modelName}\\\\` models in this \\\\`map()\\\\` call. Make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`\\n                );\\n            }\\n        } else if (\\n            selector instanceof FieldSelectorSpec ||\\n            selector instanceof MapSelectorSpec\\n        ) {\\n            if (this.toModelName !== selector.model.modelName) {\\n                throw new Error(\\n                    `Cannot select fields of the \\\\`${selector.model.modelName}\\\\` model in this \\\\`map()\\\\` call. Make sure you're passing a field selector of the form \\\\`${this.toModelName}.<field>\\\\` or a custom selector instead.`\\n                );\\n            }\\n        } else if (\\n            !selector ||\\n            typeof selector !== \\\"function\\\" ||\\n            !selector.recomputations\\n        ) {\\n            throw new Error(\\n                `\\\\`map()\\\\` requires a selector as an input. Received: ${JSON.stringify(\\n                    selector\\n                )} of type ${typeof selector}`\\n            );\\n        }\\n        if (\\n            !(this._field instanceof ForeignKey) &&\\n            !(this._field instanceof ManyToMany)\\n        ) {\\n            throw new Error(\\\"Cannot map selectors for non-collection fields\\\");\\n        }\\n        return new MapSelectorSpec({\\n            parent: this,\\n            model: this._model,\\n            orm: this._orm,\\n            field: this._field,\\n            selector,\\n        });\\n    }\\n\\n    get toModelName() {\\n        return this._field.toModelName === \\\"this\\\"\\n            ? this._fieldModel.modelName\\n            : this._field.toModelName;\\n    }\\n\\n    get toModel() {\\n        const db = this._orm.getDatabase();\\n        return db.describe(this.toModelName);\\n    }\\n}\\n\",\"import ForeignKey from \\\"../fields/ForeignKey\\\";\\nimport ManyToMany from \\\"../fields/ManyToMany\\\";\\nimport RelationalField from \\\"../fields/RelationalField\\\";\\n\\nimport FieldSelectorSpec from \\\"./FieldSelectorSpec\\\";\\nimport ModelSelectorSpec from \\\"./ModelSelectorSpec\\\";\\n\\n/**\\n * @module selectors\\n * @private\\n */\\n\\nexport function createFieldSelectorSpec({\\n    parent,\\n    model,\\n    field,\\n    fieldModel,\\n    accessorName,\\n    orm,\\n    isVirtual,\\n}) {\\n    const fieldSelectorSpec = new FieldSelectorSpec({\\n        parent,\\n        model,\\n        field,\\n        fieldModel,\\n        accessorName,\\n        orm,\\n        isVirtual,\\n    });\\n    /* Do not even try to create field selectors below attributes. */\\n    if (!(field instanceof RelationalField)) {\\n        // \\\"orm.Author.name.publisher\\\" would be nonsense\\n        return fieldSelectorSpec;\\n    }\\n    /* Prevent field selectors below collections. */\\n    if (parent instanceof FieldSelectorSpec) {\\n        /* eslint-disable no-underscore-dangle */\\n        if (\\n            // \\\"orm.Author.books.publisher\\\" would be nonsense\\n            (parent._field instanceof ForeignKey && parent._isVirtual) ||\\n            // \\\"orm.Genre.books.publisher\\\" would be nonsense\\n            parent._field instanceof ManyToMany\\n        ) {\\n            throw new Error(\\n                `Cannot create a selector for \\\\`${parent._accessorName}.${accessorName}\\\\` because \\\\`${parent._accessorName}\\\\` is a collection field.`\\n            );\\n        }\\n    }\\n    const { toModelName } = field;\\n    const toModel = orm.get(\\n        toModelName === \\\"this\\\" ? model.modelName : toModelName\\n    );\\n    Object.entries(toModel.fields).forEach(\\n        ([relatedFieldName, relatedField]) => {\\n            const fieldAccessorName = relatedField.as || relatedFieldName;\\n            Object.defineProperty(fieldSelectorSpec, fieldAccessorName, {\\n                get: () =>\\n                    createFieldSelectorSpec({\\n                        parent: fieldSelectorSpec,\\n                        model,\\n                        fieldModel: toModel,\\n                        field: relatedField,\\n                        accessorName: fieldAccessorName,\\n                        orm,\\n                        isVirtual: false,\\n                    }),\\n            });\\n        }\\n    );\\n    Object.entries(toModel.virtualFields).forEach(\\n        ([relatedFieldName, relatedField]) => {\\n            const fieldAccessorName = relatedField.as || relatedFieldName;\\n            if (fieldSelectorSpec.hasOwnProperty(fieldAccessorName)) {\\n                return;\\n            }\\n            Object.defineProperty(fieldSelectorSpec, fieldAccessorName, {\\n                get: () =>\\n                    createFieldSelectorSpec({\\n                        parent: fieldSelectorSpec,\\n                        model,\\n                        fieldModel: toModel,\\n                        field: relatedField,\\n                        accessorName: fieldAccessorName,\\n                        orm,\\n                        isVirtual: true,\\n                    }),\\n            });\\n        }\\n    );\\n    return fieldSelectorSpec;\\n}\\n\\nexport function createModelSelectorSpec({ model, orm }) {\\n    const modelSelectorSpec = new ModelSelectorSpec({\\n        parent: null,\\n        orm,\\n        model,\\n    });\\n\\n    Object.entries(model.fields).forEach(([fieldName, field]) => {\\n        const fieldAccessorName = field.as || fieldName;\\n        Object.defineProperty(modelSelectorSpec, fieldAccessorName, {\\n            get: () =>\\n                createFieldSelectorSpec({\\n                    parent: modelSelectorSpec,\\n                    model,\\n                    fieldModel: model,\\n                    field,\\n                    accessorName: fieldAccessorName,\\n                    orm,\\n                    isVirtual: false,\\n                }),\\n        });\\n    });\\n\\n    Object.entries(model.virtualFields).forEach(([fieldName, field]) => {\\n        const fieldAccessorName = field.as || fieldName;\\n        if (modelSelectorSpec.hasOwnProperty(fieldAccessorName)) {\\n            return;\\n        }\\n        Object.defineProperty(modelSelectorSpec, fieldAccessorName, {\\n            get: () =>\\n                createFieldSelectorSpec({\\n                    parent: modelSelectorSpec,\\n                    model,\\n                    fieldModel: model,\\n                    field,\\n                    accessorName: fieldAccessorName,\\n                    orm,\\n                    isVirtual: true,\\n                }),\\n        });\\n    });\\n\\n    return modelSelectorSpec;\\n}\\n\",\"/* eslint-disable max-classes-per-file */\\nimport Session from \\\"./Session\\\";\\nimport Model from \\\"./Model\\\";\\nimport { createDatabase as defaultCreateDatabase } from \\\"./db\\\";\\nimport { attr } from \\\"./fields\\\";\\nimport Field from \\\"./fields/Field\\\";\\nimport ForeignKey from \\\"./fields/ForeignKey\\\";\\nimport ManyToMany from \\\"./fields/ManyToMany\\\";\\n\\nimport { createModelSelectorSpec } from \\\"./selectors\\\";\\n\\nimport {\\n    m2mName,\\n    attachQuerySetMethods,\\n    m2mToFieldName,\\n    m2mFromFieldName,\\n    warnDeprecated,\\n} from \\\"./utils\\\";\\n\\nconst ORM_DEFAULTS = {\\n    createDatabase: defaultCreateDatabase,\\n};\\n\\nconst RESERVED_TABLE_OPTIONS = [\\\"indexes\\\", \\\"meta\\\"];\\nconst isReservedTableOption = (word) => RESERVED_TABLE_OPTIONS.includes(word);\\n\\n/**\\n * ORM - the Object Relational Mapper.\\n *\\n * Use instances of this class to:\\n *\\n * - Register your {@link Model} classes using {@link ORM#register}\\n * - Get the empty state for the underlying database with {@link ORM#getEmptyState}\\n * - Start an immutable database session with {@link ORM#session}\\n * - Start a mutating database session with {@link ORM#mutableSession}\\n *\\n * Internally, this class handles generating a schema specification from models\\n * to the database.\\n */\\nclass ORM {\\n    /**\\n     * Creates a new ORM instance.\\n     *\\n     * @param {Object} [opts]\\n     * @param {Function} [opts.stateSelector] - function that given a Redux state tree\\n     *                                          will return the ORM state's subtree,\\n     *                                          e.g. `state => state.orm`\\n     *                                          (necessary if you want to use selectors)\\n     * @param {Function} [opts.createDatabase] - function that creates a database\\n     */\\n    constructor(opts) {\\n        const { createDatabase } = { ...ORM_DEFAULTS, ...(opts || {}) };\\n        this.createDatabase = createDatabase;\\n        this.registry = [];\\n        this.implicitThroughModels = [];\\n        this.installedFields = {};\\n        this.stateSelector = opts ? opts.stateSelector : null;\\n    }\\n\\n    /**\\n     * Registers a {@link Model} class to the ORM.\\n     *\\n     * If the model has declared any ManyToMany fields, their\\n     * through models will be generated and registered with\\n     * this call, unless a custom through model has been specified.\\n     *\\n     * @param  {...Model} models - a {@link Model} class to register\\n     * @return {undefined}\\n     */\\n    register(...models) {\\n        models.forEach((model) => {\\n            if (model.modelName === undefined) {\\n                throw new Error(\\n                    \\\"A model was passed that doesn't have a modelName set\\\"\\n                );\\n            }\\n\\n            model.invalidateClassCache();\\n\\n            this.registerManyToManyModelsFor(model);\\n            this.registry.push(model);\\n\\n            Object.defineProperty(this, model.modelName, {\\n                get: () => {\\n                    // make sure virtualFields are set up\\n                    this._setupModelPrototypes(this.registry);\\n\\n                    return createModelSelectorSpec({\\n                        model,\\n                        orm: this,\\n                    });\\n                },\\n            });\\n        });\\n    }\\n\\n    registerManyToManyModelsFor(model) {\\n        const { fields } = model;\\n        const thisModelName = model.modelName;\\n\\n        Object.entries(fields).forEach(([fieldName, fieldInstance]) => {\\n            if (!(fieldInstance instanceof ManyToMany)) {\\n                return;\\n            }\\n\\n            let toModelName;\\n            if (fieldInstance.toModelName === \\\"this\\\") {\\n                toModelName = thisModelName;\\n            } else {\\n                toModelName = fieldInstance.toModelName; // eslint-disable-line prefer-destructuring\\n            }\\n\\n            const selfReferencing = thisModelName === toModelName;\\n            const fromFieldName = m2mFromFieldName(thisModelName);\\n            const toFieldName = m2mToFieldName(toModelName);\\n\\n            if (fieldInstance.through) {\\n                if (selfReferencing && !fieldInstance.throughFields) {\\n                    throw new Error(\\n                        \\\"Self-referencing many-to-many relationship at \\\" +\\n                            `\\\"${thisModelName}.${fieldName}\\\" using custom ` +\\n                            `model \\\"${fieldInstance.through}\\\" has no ` +\\n                            \\\"throughFields key. Cannot determine which \\\" +\\n                            \\\"fields reference the instances partaking \\\" +\\n                            \\\"in the relationship.\\\"\\n                    );\\n                }\\n            } else {\\n                const Through = class ThroughModel extends Model {};\\n\\n                Through.modelName = m2mName(thisModelName, fieldName);\\n\\n                const PlainForeignKey = class PlainForeignKey extends ForeignKey {\\n                    get installsBackwardsVirtualField() {\\n                        return false;\\n                    }\\n\\n                    get installsBackwardsDescriptor() {\\n                        return false;\\n                    }\\n                };\\n                const ForeignKeyClass = selfReferencing\\n                    ? PlainForeignKey\\n                    : ForeignKey;\\n                Through.fields = {\\n                    id: attr(),\\n                    [fromFieldName]: new ForeignKeyClass(thisModelName),\\n                    [toFieldName]: new ForeignKeyClass(toModelName),\\n                };\\n\\n                Through.invalidateClassCache();\\n                this.implicitThroughModels.push(Through);\\n            }\\n        });\\n    }\\n\\n    /**\\n     * Gets a {@link Model} class by its name from the registry.\\n     * @param  {string} modelName - the name of the {@link Model} class to get\\n     * @throws If {@link Model} class is not found.\\n     * @return {Model} the {@link Model} class, if found\\n     */\\n    get(modelName) {\\n        const allModels = this.registry.concat(this.implicitThroughModels);\\n        const found = Object.values(allModels).find(\\n            (model) => model.modelName === modelName\\n        );\\n\\n        if (typeof found === \\\"undefined\\\") {\\n            throw new Error(`Did not find model ${modelName} from registry.`);\\n        }\\n        return found;\\n    }\\n\\n    getModelClasses() {\\n        this._setupModelPrototypes(this.registry);\\n        this._setupModelPrototypes(this.implicitThroughModels);\\n        return this.registry.concat(this.implicitThroughModels);\\n    }\\n\\n    generateSchemaSpec() {\\n        const models = this.getModelClasses();\\n        const tables = models.reduce((spec, modelClass) => {\\n            const tableName = modelClass.modelName;\\n            const tableSpec = modelClass.tableOptions();\\n            Object.keys(tableSpec)\\n                .filter(isReservedTableOption)\\n                .forEach((key) => {\\n                    throw new Error(\\n                        `Reserved keyword \\\\`${key}\\\\` used in ${tableName}.options.`\\n                    );\\n                });\\n            spec[tableName] = {\\n                fields: { ...modelClass.fields },\\n                ...tableSpec,\\n            };\\n            return spec;\\n        }, {});\\n        return { tables };\\n    }\\n\\n    getDatabase() {\\n        if (!this.db) {\\n            this.db = this.createDatabase(this.generateSchemaSpec());\\n        }\\n        return this.db;\\n    }\\n\\n    /**\\n     * Returns the empty database state.\\n     * @return {Object} the empty state\\n     */\\n    getEmptyState() {\\n        return this.getDatabase().getEmptyState();\\n    }\\n\\n    /**\\n     * Begins an immutable database session.\\n     *\\n     * @param  {Object} state  - the state the database manages\\n     * @return {Session} a new {@link Session} instance\\n     */\\n    session(state) {\\n        return new Session(this, this.getDatabase(), state);\\n    }\\n\\n    /**\\n     * Begins a mutable database session.\\n     *\\n     * @param  {Object} state  - the state the database manages\\n     * @return {Session} a new {@link Session} instance\\n     */\\n    mutableSession(state) {\\n        return new Session(this, this.getDatabase(), state, true);\\n    }\\n\\n    /**\\n     * @private\\n     */\\n    _setupModelPrototypes(models) {\\n        models\\n            .filter((model) => !model.isSetUp)\\n            .forEach((model) => {\\n                const { fields, modelName, querySetClass } = model;\\n                Object.entries(fields).forEach(([fieldName, field]) => {\\n                    if (!(field instanceof Field)) {\\n                        throw new Error(\\n                            `${modelName}.${fieldName} is of type \\\"${typeof field}\\\" ` +\\n                                \\\"but must be an instance of Field. Please use the \\\" +\\n                                \\\"`attr`, `fk`, `oneToOne` and `many` \\\" +\\n                                \\\"functions to define fields.\\\"\\n                        );\\n                    }\\n                    if (!this._isFieldInstalled(modelName, fieldName)) {\\n                        this._installField(field, fieldName, model);\\n                        this._setFieldInstalled(modelName, fieldName);\\n                    }\\n                });\\n                attachQuerySetMethods(model, querySetClass);\\n                model.isSetUp = true;\\n            });\\n    }\\n\\n    /**\\n     * @private\\n     */\\n    _isFieldInstalled(modelName, fieldName) {\\n        return this.installedFields.hasOwnProperty(modelName)\\n            ? !!this.installedFields[modelName][fieldName]\\n            : false;\\n    }\\n\\n    /**\\n     * @private\\n     */\\n    _setFieldInstalled(modelName, fieldName) {\\n        if (!this.installedFields.hasOwnProperty(modelName)) {\\n            this.installedFields[modelName] = {};\\n        }\\n        this.installedFields[modelName][fieldName] = true;\\n    }\\n\\n    /**\\n     * Installs a field on a model and its related models if necessary.\\n     * @private\\n     */\\n    _installField(field, fieldName, model) {\\n        const FieldInstaller = field.installerClass;\\n        new FieldInstaller({\\n            field,\\n            fieldName,\\n            model,\\n            orm: this,\\n        }).run();\\n    }\\n\\n    // DEPRECATED AND REMOVED METHODS\\n\\n    /**\\n     * @deprecated Use {@link ORM#mutableSession} instead.\\n     */\\n    withMutations(state) {\\n        warnDeprecated(\\n            \\\"`ORM.prototype.withMutations` has been deprecated. \\\" +\\n                \\\"Use `ORM.prototype.mutableSession` instead.\\\"\\n        );\\n        return this.mutableSession(state);\\n    }\\n\\n    /**\\n     * @deprecated Use {@link ORM#session} instead.\\n     */\\n    from(state) {\\n        warnDeprecated(\\n            \\\"`ORM.prototype.from` has been deprecated. \\\" +\\n                \\\"Use `ORM.prototype.session` instead.\\\"\\n        );\\n        return this.session(state);\\n    }\\n\\n    /**\\n     * @deprecated Use {@link ORM#getEmptyState} instead.\\n     */\\n    getDefaultState() {\\n        warnDeprecated(\\n            \\\"`ORM.prototype.getDefaultState` has been deprecated. Use \\\" +\\n                \\\"`ORM.prototype.getEmptyState` instead.\\\"\\n        );\\n        return this.getEmptyState();\\n    }\\n\\n    /**\\n     * @deprecated Define a Model class instead.\\n     */\\n    define() {\\n        throw new Error(\\n            \\\"`ORM.prototype.define` has been removed. Please define a Model class.\\\"\\n        );\\n    }\\n}\\n\\nexport function DeprecatedSchema() {\\n    throw new Error(\\n        \\\"Schema has been renamed to ORM. Please import ORM instead of Schema \\\" +\\n            \\\"from Redux-ORM.\\\"\\n    );\\n}\\n\\nexport { ORM };\\n\\nexport default ORM;\\n\",\"import { STATE_FLAG } from \\\"./constants\\\";\\n\\nconst defaultEqualityCheck = (a, b) => a === b;\\nexport const eqCheck = defaultEqualityCheck;\\n\\nconst isOrmState = (arg) =>\\n    arg && typeof arg === \\\"object\\\" && arg.hasOwnProperty(STATE_FLAG);\\n\\nconst argsAreEqual = (lastArgs, nextArgs, equalityCheck) =>\\n    nextArgs.every(\\n        (arg, index) =>\\n            (isOrmState(arg) && isOrmState(lastArgs[index])) ||\\n            equalityCheck(arg, lastArgs[index])\\n    );\\n\\nconst rowsAreEqual = (ids, rowsA, rowsB) =>\\n    ids.every((id) => rowsA[id] === rowsB[id]);\\n\\nconst accessedModelInstancesAreEqual = (previous, ormState, orm) => {\\n    const { accessedInstances } = previous;\\n\\n    return Object.entries(accessedInstances).every(([modelName, instances]) => {\\n        // if the entire table has not been changed, we have nothing to do\\n        if (previous.ormState[modelName] === ormState[modelName]) {\\n            return true;\\n        }\\n\\n        const { mapName } = orm.getDatabase().describe(modelName);\\n\\n        const { [mapName]: previousRows } = previous.ormState[modelName];\\n        const { [mapName]: rows } = ormState[modelName];\\n\\n        const accessedIds = Object.keys(instances);\\n        return rowsAreEqual(accessedIds, previousRows, rows);\\n    });\\n};\\n\\nconst accessedIndexesAreEqual = (previous, ormState) => {\\n    const { accessedIndexes } = previous;\\n\\n    return Object.entries(accessedIndexes).every(([modelName, indexes]) =>\\n        Object.entries(indexes).every(([column, values]) =>\\n            values.every(\\n                (value) =>\\n                    previous.ormState[modelName].indexes[column][value] ===\\n                    ormState[modelName].indexes[column][value]\\n            )\\n        )\\n    );\\n};\\n\\nconst fullTableScannedModelsAreEqual = (previous, ormState) =>\\n    previous.fullTableScannedModels.every(\\n        (modelName) => previous.ormState[modelName] === ormState[modelName]\\n    );\\n\\n/**\\n * A memoizer to use with redux-orm\\n * selectors. When the memoized function is first run,\\n * the memoizer will remember the models that are accessed\\n * during that function run.\\n *\\n * On subsequent runs, the memoizer will check if those\\n * models' states have changed compared to the previous run.\\n *\\n * Memoization algorithm operates like this:\\n *\\n * 1. Has the selector been run before? If not, go to 6.\\n *\\n * 2. If the selector has other input selectors in addition to the\\n *    ORM state selector, check their results for equality with the previous results.\\n *    If they aren't equal, go to 6.\\n *\\n * 3. Some filter queries may have required scanning entire tables during the last run.\\n *    If any of those tables have changed, go to 6.\\n *\\n * 4. Check which foreign key indexes the database has used to speed up queries\\n *    during the last run. If any have changed, go to 6.\\n *\\n * 5. Check which Model's instances the selector has accessed during the last run.\\n *    Check for equality with each of those states versus their states in the\\n *    previous ORM state. If all of them are equal, return the previous result.\\n *\\n * 6. Run the selector. Check the Session object used by the selector for\\n *    which Model's states were accessed, and merge them with the previously\\n *    saved information about accessed models (if-else branching can change\\n *    which models are accessed on different inputs). Save the ORM state and\\n *    other arguments the selector was called with, overriding previously\\n *    saved values. Save the selector result. Return the selector result.\\n *\\n * @private\\n * @param  {Function} func - function to memoize\\n * @param  {Function} argEqualityCheck - equality check function to use with normal\\n *                                       selector args\\n * @param  {ORM} orm - a redux-orm ORM instance\\n * @return {Function} `func` memoized.\\n */\\nexport function memoize(func, argEqualityCheck = defaultEqualityCheck, orm) {\\n    let previous = {\\n        /* Result of the previous function call */\\n        result: null,\\n        /* Arguments to the previous function call (excluding ORM state) */\\n        args: null,\\n        /**\\n         * Snapshot of the previous database.\\n         *\\n         * Lets us know how the tables looked like\\n         * during the previous function call.\\n         */\\n        ormState: null,\\n        /**\\n         * Names of models whose tables have been scanned completely\\n         * during previous function call (contains only model names)\\n         * Format example: ['Book']\\n         */\\n        fullTableScannedModels: [],\\n        /**\\n         * Map of which model instances have been accessed\\n         * during previous function call.\\n         * Contains only PKs of accessed instances.\\n         * Format example: { Book: { 1: true, 3: true } }\\n         */\\n        accessedInstances: {},\\n        /**\\n         * Map of which attribute indexes have been accessed\\n         * during previous function call.\\n         * Contains only attributes that were actually filtered on.\\n         * Author.withId(3).books would add 3 to the authorId index below.\\n         * Format example: { Book: { authorId: [1, 2], publisherId: [5] } }\\n         */\\n        accessedIndexes: {},\\n    };\\n\\n    return (...stateAndArgs) => {\\n        /**\\n         * The first argument to this function needs to be\\n         * the ORM's reducer state in the user's Redux store.\\n         */\\n        const [ormState, ...args] = stateAndArgs;\\n\\n        const selectorWasCalledBefore = Boolean(previous.args);\\n        if (\\n            selectorWasCalledBefore &&\\n            argsAreEqual(previous.args, args, argEqualityCheck) &&\\n            fullTableScannedModelsAreEqual(previous, ormState) &&\\n            accessedIndexesAreEqual(previous, ormState) &&\\n            accessedModelInstancesAreEqual(previous, ormState, orm)\\n        ) {\\n            /**\\n             * None of this selector's dependencies have changed\\n             * since the last time that we called it.\\n             */\\n            return previous.result;\\n        }\\n\\n        /**\\n         * Start a session so that the selector can access the database.\\n         * Make this session immutable. This way we can find out if\\n         * the operations that the selector performs are cacheable.\\n         */\\n        const session = orm.session(ormState);\\n        /* Replace all ORM state arguments by the session above */\\n        const argsWithSession = args.map((arg) =>\\n            isOrmState(arg) ? session : arg\\n        );\\n\\n        /* This is where we call the actual function */\\n        const result = func.apply(null, argsWithSession); // eslint-disable-line prefer-spread\\n\\n        /**\\n         * The metadata for the previous call are no longer valid.\\n         * Update cached values.\\n         */\\n        previous = {\\n            /* Arguments that were passed to the selector */\\n            args,\\n            /* Selector result */\\n            result,\\n            /* Redux state slice for session.state */\\n            ormState,\\n            /* Rows retrieved by resolved primary key */\\n            accessedInstances: session.accessedModelInstances,\\n            /* Foreign key indexes that were used to speed up queries */\\n            accessedIndexes: session.accessedIndexes,\\n            /* Tables that had to be scanned completely */\\n            fullTableScannedModels: session.fullTableScannedModels,\\n        };\\n\\n        return result;\\n    };\\n}\\n\",\"import { createSelectorCreator } from \\\"reselect\\\";\\nimport createCachedSelector, { FlatMapCache } from \\\"re-reselect\\\";\\n\\nimport { memoize } from \\\"./memoize\\\";\\n\\nimport { ORM } from \\\"./ORM\\\";\\nimport SelectorSpec from \\\"./selectors/SelectorSpec\\\";\\nimport MapSelectorSpec from \\\"./selectors/MapSelectorSpec\\\";\\n\\n/**\\n * @module redux\\n * @desc Provides functions for integration with Redux.\\n */\\n\\n/**\\n * Calls all models' reducers if they exist.\\n *\\n * @return {undefined}\\n * @global\\n */\\nexport function defaultUpdater(session, action) {\\n    session.sessionBoundModels.forEach((modelClass) => {\\n        if (typeof modelClass.reducer === \\\"function\\\") {\\n            // This calls this.applyUpdate to update this.state\\n            modelClass.reducer(action, modelClass, session);\\n        }\\n    });\\n}\\n\\n/**\\n * Call the returned function to pass actions to Redux-ORM.\\n *\\n * @global\\n *\\n * @param {ORM} orm - the ORM instance.\\n * @param {Function} [updater] - the function updating the ORM state based on the given action.\\n * @return {Function} reducer that will update the ORM state.\\n */\\nexport function createReducer(orm, updater = defaultUpdater) {\\n    return (state, action) => {\\n        const session = orm.session(state || orm.getEmptyState());\\n        updater(session, action);\\n        return session.state;\\n    };\\n}\\n\\n/**\\n * @private\\n * @param {SelectorSpec} spec\\n */\\nfunction createSelectorFromSpec(spec) {\\n    if (spec instanceof MapSelectorSpec) {\\n        const parentSelector = createSelectorFromSpec(spec.parent);\\n        return spec.createResultFunc(parentSelector);\\n    }\\n    return createCachedSelector(\\n        spec.dependencies,\\n        spec.resultFunc\\n    )({\\n        keySelector: spec.keySelector,\\n        cacheObject: new FlatMapCache(),\\n        selectorCreator: createSelector, // eslint-disable-line no-use-before-define\\n    });\\n}\\n\\n/**\\n * Tries to find ORM instance using the argument.\\n * @private\\n * @param {*} arg\\n */\\nfunction toORM(arg) {\\n    /* eslint-disable no-underscore-dangle */\\n    if (arg instanceof ORM) {\\n        return arg;\\n    }\\n    if (arg instanceof SelectorSpec) {\\n        return arg._orm;\\n    }\\n    return false;\\n}\\n\\nconst selectorCache = new Map();\\nconst SELECTOR_KEY = Symbol.for(\\\"REDUX_ORM_SELECTOR\\\");\\n\\n/**\\n * @private\\n * @param {function|ORM|SelectorSpec} arg\\n */\\nfunction toSelector(arg) {\\n    if (typeof arg === \\\"function\\\") {\\n        return arg;\\n    }\\n    if (arg instanceof ORM) {\\n        return arg.stateSelector;\\n    }\\n    if (arg instanceof MapSelectorSpec) {\\n        // the argument to map() needs to be callable\\n        arg.selector = toSelector(arg.selector);\\n    }\\n    if (arg instanceof SelectorSpec) {\\n        const { orm, cachePath } = arg;\\n        let level;\\n\\n        // the selector cache for the spec's ORM\\n        if (!selectorCache.has(orm)) {\\n            selectorCache.set(orm, new Map());\\n        }\\n        const ormSelectors = selectorCache.get(orm);\\n\\n        /**\\n         * Drill down into selector map by cachePath.\\n         *\\n         * The selector itself is stored under a special SELECTOR_KEY\\n         * so that we can store selectors below it as well.\\n         */\\n        level = ormSelectors;\\n        for (let i = 0; i < cachePath.length; ++i) {\\n            const storageKey = cachePath[i];\\n            if (!level.has(storageKey)) {\\n                level.set(storageKey, new Map());\\n            }\\n            level = level.get(storageKey);\\n        }\\n        if (level && level.has(SELECTOR_KEY)) {\\n            // Cache hit: the selector has been created before\\n            return level.get(SELECTOR_KEY);\\n        }\\n        // Cache miss: the selector needs to be created\\n        const selector = createSelectorFromSpec(arg);\\n        // Save the selector at the cachePath position\\n        level.set(SELECTOR_KEY, selector);\\n\\n        return selector;\\n    }\\n    throw new Error(\\n        `Failed to interpret selector argument: ${JSON.stringify(\\n            arg\\n        )} of type ${typeof arg}`\\n    );\\n}\\n\\n/**\\n * Returns a memoized selector based on passed arguments.\\n * This is similar to `reselect`'s `createSelector`,\\n * except you can also pass a single function to be memoized.\\n *\\n * If you pass multiple functions, the format will be the\\n * same as in `reselect`. The last argument is the selector\\n * function and the previous are input selectors.\\n *\\n * When you use this method to create a selector, the returned selector\\n * expects the whole `redux-orm` state branch as input. In the selector\\n * function that you pass as the last argument, any of the arguments\\n * you pass first will be considered selectors and mapped\\n * to their outputs, like in `reselect`.\\n *\\n * Here are some example selectors:\\n *\\n * ```javascript\\n * // orm is an instance of ORM\\n * // reduxState is the state of a Redux store\\n * const books = createSelector(orm.Book);\\n * books(reduxState) // array of book refs\\n *\\n * const bookAuthors = createSelector(orm.Book.authors);\\n * bookAuthors(reduxState) // two-dimensional array of author refs for each book\\n * ```\\n * Selectors can easily be applied to related models:\\n * ```javascript\\n * const bookAuthorNames = createSelector(\\n *     orm.Book.authors.map(orm.Author.name),\\n * );\\n * bookAuthorNames(reduxState, 8) // names of all authors of book with ID 8\\n * bookAuthorNames(reduxState, [8, 9]) // 2D array of names of all authors of books with IDs 8 and 9\\n * ```\\n * Also note that `orm.Author.name` did not need to be wrapped in another `createSelector` call,\\n * although that would be possible.\\n *\\n * For more complex calculations you can access\\n * entire session objects by passing an ORM instance.\\n * ```javascript\\n * const freshBananasCost = createSelector(\\n *     orm,\\n *     session => {\\n *        const banana = session.Product.get({\\n *            name: \\\"Banana\\\",\\n *        });\\n *        // amount of fresh bananas in shopping cart\\n *        const amount = session.ShoppingCart.filter({\\n *            product_id: banana.id,\\n *            is_fresh: true,\\n *        }).count();\\n *        return `USD ${amount * banana.price}`;\\n *     }\\n * );\\n * ```\\n *\\n * redux-orm uses a special memoization function to avoid recomputations.\\n *\\n * Everytime a selector runs, this function records which instances\\n * of your `Model`s were accessed.<br>\\n * On subsequent runs, the selector first checks if the previously\\n * accessed instances or `args` have changed in any way:\\n * <ul>\\n *     <li>If yes, the selector calls the function you passed to it.</li>\\n *     <li>If not, it just returns the previous result\\n *         (unless you call it for the first time).</li>\\n * </ul>\\n *\\n * This way you can use pure rendering in your React components\\n * for performance gains.\\n *\\n * @global\\n *\\n * @param  {...Function} args - zero or more input selectors\\n *                              and the selector function.\\n * @return {Function} memoized selector\\n */\\nexport function createSelector(...args) {\\n    if (!args.length) {\\n        throw new Error(\\\"Cannot create a selector without arguments.\\\");\\n    }\\n\\n    const resultArg = args.pop();\\n    const dependencies = Array.isArray(args[0]) ? args[0] : args;\\n\\n    const orm = dependencies.map(toORM).find(Boolean);\\n    const inputFuncs = dependencies.map(toSelector);\\n\\n    if (typeof resultArg === \\\"function\\\") {\\n        if (!orm) {\\n            throw new Error(\\n                \\\"Failed to resolve the current ORM database state. Please pass an ORM instance or an ORM selector as an argument to `createSelector()`.\\\"\\n            );\\n        } else if (!orm.stateSelector) {\\n            throw new Error(\\n                \\\"Failed to resolve the current ORM database state. Please pass an object to the ORM constructor that specifies a `stateSelector` function.\\\"\\n            );\\n        } else if (typeof orm.stateSelector !== \\\"function\\\") {\\n            throw new Error(\\n                `Failed to resolve the current ORM database state. Please pass a function when specifying the ORM's \\\\`stateSelector\\\\`. Received: ${JSON.stringify(\\n                    orm.stateSelector\\n                )} of type ${typeof orm.stateSelector}`\\n            );\\n        }\\n\\n        return createSelectorCreator(\\n            memoize,\\n            undefined,\\n            orm\\n        )([orm.stateSelector, ...inputFuncs], resultArg);\\n    }\\n\\n    if (resultArg instanceof ORM) {\\n        throw new Error(\\n            \\\"ORM instances cannot be the result function of selectors. You can access your models in the last function that you pass to `createSelector()`.\\\"\\n        );\\n    }\\n    if (inputFuncs.length) {\\n        console.warn(\\n            \\\"Your input selectors will be ignored: the passed result function does not require any input.\\\"\\n        );\\n    }\\n\\n    return toSelector(resultArg);\\n}\\n\",\"import QuerySet from \\\"./QuerySet\\\";\\nimport Model from \\\"./Model\\\";\\nimport { DeprecatedSchema, ORM } from \\\"./ORM\\\";\\nimport Session from \\\"./Session\\\";\\nimport { createReducer, createSelector } from \\\"./redux\\\";\\nimport ForeignKey from \\\"./fields/ForeignKey\\\";\\nimport ManyToMany from \\\"./fields/ManyToMany\\\";\\nimport OneToOne from \\\"./fields/OneToOne\\\";\\nimport Attribute from \\\"./fields/Attribute\\\";\\nimport { fk, many, oneToOne, attr } from \\\"./fields\\\";\\n\\nconst Schema = DeprecatedSchema;\\n\\nconst Backend = function RemovedBackend() {\\n    throw new Error(\\n        \\\"Having a custom Backend instance is now unsupported. \\\" +\\n            \\\"Documentation for database customization is upcoming, for now \\\" +\\n            \\\"please look at the db folder in the source.\\\"\\n    );\\n};\\n\\nexport {\\n    Attribute,\\n    QuerySet,\\n    Model,\\n    ORM,\\n    Schema,\\n    Backend,\\n    Session,\\n    ForeignKey,\\n    ManyToMany,\\n    OneToOne,\\n    fk,\\n    many,\\n    attr,\\n    oneToOne,\\n    createReducer,\\n    createSelector,\\n};\\n\\nexport default Model;\\n\"],\"sourceRoot\":\"\"}\n\\ No newline at end of file\ndiff --git a/node_modules/redux-orm/es/Model.js b/node_modules/redux-orm/es/Model.js\nindex 541a8d1..b630452 100644\n--- a/node_modules/redux-orm/es/Model.js\n+++ b/node_modules/redux-orm/es/Model.js\n@@ -274,7 +274,7 @@ var Model = /*#__PURE__*/function () {\n           var value = userProps[key];\n           props[key] = normalizeEntity(value);\n         } else if (field.getDefault) {\n-          props[key] = field.getDefault();\n+          props[key] = field.getDefault(userProps);\n         }\n       } else if (valuePassed) {\n         // Save for later processing\ndiff --git a/node_modules/redux-orm/es/redux-orm-tests.js b/node_modules/redux-orm/es/redux-orm-tests.js\nnew file mode 100644\nindex 0000000..a33fa22\n--- /dev/null\n+++ b/node_modules/redux-orm/es/redux-orm-tests.js\n@@ -0,0 +1,769 @@\n+import _inheritsLoose from \"@babel/runtime/helpers/inheritsLoose\";\n+import _defineProperty from \"@babel/runtime/helpers/defineProperty\";\n+\n+function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }\n+\n+function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }\n+\n+import { attr, createSelector as createOrmSelector, fk, many, Model, ORM } from \"./\";\n+\n+var Book = /*#__PURE__*/function (_Model) {\n+  _inheritsLoose(Book, _Model);\n+\n+  function Book() {\n+    return _Model.apply(this, arguments) || this;\n+  }\n+\n+  Book.reducer = function reducer(action, _Book) {\n+    switch (action.type) {\n+      case \"CREATE_BOOK\":\n+        _Book.create(action.payload);\n+\n+        break;\n+\n+      case \"DELETE_BOOK\":\n+        _Book.filter(function (book) {\n+          return book.title === action.payload.title;\n+        })[\"delete\"]();\n+\n+        break;\n+\n+      default:\n+        break;\n+    }\n+  };\n+\n+  return Book;\n+}(Model);\n+\n+_defineProperty(Book, \"modelName\", \"Book\");\n+\n+_defineProperty(Book, \"fields\", {\n+  title: attr(),\n+  coverArt: attr({\n+    getDefault: function getDefault() {\n+      return \"empty.png\";\n+    }\n+  }),\n+  publisher: fk(\"Publisher\", \"books\"),\n+  authors: many({\n+    to: \"Person\",\n+    relatedName: \"books\",\n+    through: \"Authorship\"\n+  })\n+});\n+\n+_defineProperty(Book, \"options\", {\n+  idAttribute: \"title\"\n+});\n+\n+var Person = /*#__PURE__*/function (_Model2) {\n+  _inheritsLoose(Person, _Model2);\n+\n+  function Person() {\n+    return _Model2.apply(this, arguments) || this;\n+  }\n+\n+  return Person;\n+}(Model);\n+\n+_defineProperty(Person, \"modelName\", \"Person\");\n+\n+_defineProperty(Person, \"fields\", {\n+  id: attr(),\n+  firstName: attr(),\n+  lastName: attr(),\n+  nationality: attr()\n+});\n+\n+var Authorship = /*#__PURE__*/function (_Model3) {\n+  _inheritsLoose(Authorship, _Model3);\n+\n+  function Authorship() {\n+    return _Model3.apply(this, arguments) || this;\n+  }\n+\n+  return Authorship;\n+}(Model);\n+\n+_defineProperty(Authorship, \"modelName\", \"Authorship\");\n+\n+_defineProperty(Authorship, \"fields\", {\n+  year: attr(),\n+  book: fk(\"Book\"),\n+  author: fk(\"Person\")\n+});\n+\n+var Publisher = /*#__PURE__*/function (_Model4) {\n+  _inheritsLoose(Publisher, _Model4);\n+\n+  function Publisher() {\n+    return _Model4.apply(this, arguments) || this;\n+  }\n+\n+  return Publisher;\n+}(Model);\n+\n+_defineProperty(Publisher, \"modelName\", \"Publisher\");\n+\n+_defineProperty(Publisher, \"fields\", {\n+  index: attr(),\n+  name: attr()\n+});\n+\n+_defineProperty(Publisher, \"options\", {\n+  idAttribute: \"index\"\n+});\n+\n+var schema = {\n+  Book: Book,\n+  Authorship: Authorship,\n+  Person: Person,\n+  Publisher: Publisher\n+};\n+\n+// create ORM instance and register { Book, Publisher, Person, Authorship } schema\n+var ormFixture = function ormFixture() {\n+  var orm = new ORM({\n+    stateSelector: function stateSelector(state) {\n+      return state.db;\n+    }\n+  });\n+  orm.register(Book, Authorship, Person, Publisher);\n+  return orm;\n+}; // create ORM instance and acquire new session\n+\n+\n+var sessionFixture = function sessionFixture() {\n+  var orm = ormFixture();\n+  return orm.session(orm.getEmptyState());\n+}; // argOptionalityAtModelCreation - inferred optionality of ModelType.create argument properties\n+\n+\n+(function () {\n+  var _sessionFixture = sessionFixture(),\n+      Book = _sessionFixture.Book,\n+      Publisher = _sessionFixture.Publisher;\n+  /**\r\n+   * 1.A. `number` Model identifiers are optional due to built-in incremental sequencing of numeric identifiers\r\n+   * @see {@link PublisherFields.index}\r\n+   */\n+\n+\n+  Publisher.create({\n+    name: \"P1\"\n+  });\n+  /**\r\n+   * 1.B. `string` identifiers are mandatory\r\n+   */\n+\n+  Book.create({\n+    publisher: 1,\n+    coverArt: \"foo.bmp\"\n+  }); // $ExpectError\n+\n+  /**\r\n+   * 2. non-relational fields with corresponding descriptors that contain defined `getDefault` callback: (`attr({ getDefault: () => 'empty.png' })`)\r\n+   * @see {@link Book#fields.coverArt}\r\n+   */\n+\n+  Book.create({\n+    title: \"B2\",\n+    publisher: 1\n+  });\n+  /**\r\n+   * 3. both attribute and relational fields where corresponding ModelFields interface property has optional (`?`) modifier\r\n+   * @see {@link BookFields.authors}\r\n+   */\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: 1,\n+    coverArt: \"foo.bmp\"\n+  });\n+})(); // argPropertyTypeRestrictionsOnCreate - ModelFields contribute to type constraints within ModelType.create arguments\n+\n+\n+(function () {\n+  var _sessionFixture2 = sessionFixture(),\n+      Book = _sessionFixture2.Book,\n+      Publisher = _sessionFixture2.Publisher,\n+      Person = _sessionFixture2.Person;\n+  /** Keys of declared model fields interface contribute strict requirements regarding corresponding property types */\n+\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: 1,\n+    coverArt: \"foo.png\",\n+    authors: [\"A1\"]\n+  });\n+  /* Incompatible property types: */\n+\n+  Book.create({\n+    title: 1,\n+    publisher: 1\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: \"P1\"\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: 1,\n+    coverArt: 4\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: 1,\n+    authors: {}\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: 1,\n+    authors: function authors() {\n+      return null;\n+    }\n+  }); // $ExpectError\n+\n+  /**\r\n+   * Properties associated to relational fields may be supplied with:\r\n+   *\r\n+   * - a primitive type matching id type of relation target\r\n+   * - Model/SessionBoundModel instance matching relation target\r\n+   *\r\n+   * In case of MutableQuerySets/many-to-many relationships, an array of union of above-mentioned types is accepted\r\n+   */\n+\n+  var authorModel = Person.create({\n+    id: \"A1\",\n+    firstName: \"A1\",\n+    lastName: \"A1\"\n+  });\n+  var publisherModel = Publisher.create({\n+    name: \"P1\"\n+  });\n+  Book.create({\n+    title: \"B1\",\n+    publisher: publisherModel,\n+    authors: [authorModel]\n+  });\n+  Book.create({\n+    title: \"B1\",\n+    publisher: publisherModel.index,\n+    authors: [authorModel, \"A1\", authorModel, authorModel.ref.id]\n+  });\n+  /** Id types are verified to match relation target */\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: authorModel\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: publisherModel.ref,\n+    authors: [publisherModel.ref, \"A1\"]\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: {\n+      index: \"P1 \"\n+    }\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: {\n+      index: 0\n+    },\n+    authors: [authorModel, true]\n+  }); // $ExpectError\n+})(); // argPropertyTypeRestrictionsOnUpsert - ModelFields contribute to type constraints within ModelType.create arguments\n+\n+\n+(function () {\n+  var _sessionFixture3 = sessionFixture(),\n+      Book = _sessionFixture3.Book,\n+      Publisher = _sessionFixture3.Publisher,\n+      Person = _sessionFixture3.Person;\n+  /** Upsert requires id to be provided */\n+\n+\n+  Book.upsert({\n+    publisher: 1\n+  }); // $ExpectError\n+  // $ExpectType SessionBoundModel<Book, Pick<{ title: string; publisher: number; }, never>> || SessionBoundModel<Book, CustomInstanceProps<Book, { title: string; publisher: number; }>>\n+\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: 1\n+  });\n+  /* Incompatible property types: */\n+\n+  Book.upsert({\n+    title: 4,\n+    publisher: \"P1\"\n+  }); // $ExpectError\n+\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: \"P1\"\n+  }); // $ExpectError\n+\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: 1,\n+    coverArt: 4\n+  }); // $ExpectError\n+\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: 1,\n+    authors: {}\n+  }); // $ExpectError\n+\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: 1,\n+    authors: function authors() {\n+      return null;\n+    }\n+  }); // $ExpectError\n+\n+  /**\r\n+   * Properties associated to relational fields may be supplied with:\r\n+   *\r\n+   * - a primitive type matching id type of relation target\r\n+   * - a Ref type derived from relation target\r\n+   * - Model/SessionBoundModel instance matching relation target\r\n+   * - a map containing {Idkey:IdType} entry, where IdKey/IdType are compatible with relation target id key:type signature\r\n+   *\r\n+   * In case of MutableQuerySets/many-to-many relationships, an array of union of above-mentioned types is accepted\r\n+   */\n+\n+  var authorModel = Person.upsert({\n+    id: \"A1\",\n+    firstName: \"A1\",\n+    lastName: \"A1\"\n+  });\n+  var publisherModel = Publisher.upsert({\n+    name: \"P1\",\n+    index: 1\n+  });\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: 1,\n+    authors: [authorModel]\n+  });\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: publisherModel,\n+    authors: [authorModel]\n+  });\n+  /** Id types are verified to match relation target */\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: authorModel\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: publisherModel.ref,\n+    authors: [publisherModel.ref, \"A1\"]\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: {\n+      index: \"P1 \"\n+    }\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: {\n+      index: 0\n+    },\n+    authors: [authorModel, true]\n+  }); // $ExpectError\n+})(); // restriction of allowed ORM.register args\n+\n+\n+(function () {\n+  var incompleteSchema = {\n+    Book: Book,\n+    Authorship: Authorship,\n+    Person: Person\n+  };\n+  var orm = new ORM();\n+  orm.register(Book, Authorship, Person, Publisher); // $ExpectError\n+})(); // inference of ORM branch state type\n+\n+\n+(function () {\n+  var emptyState = ormFixture().getEmptyState();\n+  var bookTableState = emptyState.Book; // $ExpectType TableState<typeof Book>\n+\n+  var bookItemsById = emptyState.Book.itemsById; // $ExpectType { readonly [K: string]: Ref<Book>; }\n+\n+  var authorshipMetaState = emptyState.Authorship.meta.maxId; // $ExpectType number\n+\n+  var bookMetaState = emptyState.Book.meta.maxId; // $ExpectType number | null\n+})(); // sessionInstanceExtendedWithNarrowedModelClasses - indexing session instance using registered Model.modelName returns narrowed Model class\n+\n+\n+(function () {\n+  var _sessionFixture4 = sessionFixture(),\n+      Book = _sessionFixture4.Book,\n+      Person = _sessionFixture4.Person,\n+      Publisher = _sessionFixture4.Publisher; // $ExpectType { Book: ModelType<Book>; Person: ModelType<Person>; Publisher: ModelType<Publisher>; }\n+\n+\n+  var sessionBoundModels = {\n+    Book: Book,\n+    Person: Person,\n+    Publisher: Publisher\n+  };\n+  return _objectSpread({}, sessionBoundModels);\n+})(); // IdKey and IdType mapped types support for valid identifier configurations\n+\n+\n+(function () {})(); // Model#create result retains custom properties supplied during call\n+\n+\n+(function () {\n+  var _sessionFixture5 = sessionFixture(),\n+      Book = _sessionFixture5.Book;\n+\n+  var basicBook = Book.create({\n+    title: \"book\",\n+    publisher: 1\n+  });\n+  // $ExpectType \"title\" | \"coverArt\" | \"publisher\" | \"authors\" || keyof BookFields\n+  var basicBookTitle = basicBook.title; // $ExpectType string\n+\n+  var authors = basicBook.authors; // $ExpectType MutableQuerySet<Person, {}>\n+\n+  var unknownPropertyError = basicBook.customProp; // $ExpectError\n+\n+  var customProp = {\n+    foo: 0,\n+    bar: true\n+  };\n+  var extendedBook = Book.create({\n+    title: \"extendedBook\",\n+    publisher: 1,\n+    customProp: customProp\n+  });\n+  // $ExpectType \"title\" | \"coverArt\" | \"publisher\" | \"authors\" | \"customProp\" || keyof BookFields | \"customProp\"\n+  var extendedBookTitle = extendedBook.title; // $ExpectType string\n+\n+  var instanceCustomProp = extendedBook.customProp; // $ExpectType { foo: number; bar: boolean; }\n+})(); // reducer API is intact\n+\n+\n+(function () {\n+  var orm = ormFixture();\n+  return function (state, action) {\n+    var session = orm.session(state);\n+    session.Book.create(action.payload);\n+    return session.state;\n+  };\n+})(); // QuerySet type is retained though query chain until terminated.\n+// Orders are optional, must conform to SortOrder type when present.\n+// QuerySet.orderBy overloads accept iteratees applicable to QuerySet's type only\n+// orderByArguments\n+\n+\n+(function () {\n+  var _sessionFixture6 = sessionFixture(),\n+      Book = _sessionFixture6.Book;\n+\n+  var booksQuerySet = Book.all(); // $ExpectType readonly Ref<Book>[]\n+\n+  booksQuerySet.orderBy(\"title\").orderBy(function (book) {\n+    return book.publisher;\n+  }, \"desc\").orderBy(function (book) {\n+    return book.title;\n+  }, false).orderBy(\"publisher\", \"asc\").orderBy(\"publisher\", true).toRefArray(); // $ExpectType readonly Ref<Book>[]\n+\n+  booksQuerySet.orderBy([\"title\"], [\"asc\"]).orderBy([\"publisher\", \"title\"], [true, \"desc\"]).orderBy([function (book) {\n+    return book.title;\n+  }], [\"desc\"]).orderBy([\"title\"]).orderBy([function (book) {\n+    return book.title;\n+  }, \"publisher\"], [\"desc\", false]).toRefArray();\n+  booksQuerySet.orderBy(\"notABookPropertyKey\"); // $ExpectError\n+\n+  booksQuerySet.orderBy([function (book) {\n+    return book.notABookPropertyKey;\n+  }], false); // $ExpectError\n+\n+  booksQuerySet.orderBy(\"title\", \"inc\"); // $ExpectError\n+\n+  booksQuerySet.orderBy(\"title\", 4); // $ExpectError\n+\n+  booksQuerySet.orderBy([\"notABookPropertyKey\"]); // $ExpectError\n+\n+  booksQuerySet.orderBy([function (book) {\n+    return book.notABookPropertyKey;\n+  }]); // $ExpectError\n+\n+  booksQuerySet.orderBy([\"title\"], [\"inc\"]); // $ExpectError\n+\n+  booksQuerySet.orderBy([\"title\"], [4]); // $ExpectError\n+})(); // selectors\n+\n+\n+(function () {\n+  // test fixture, use reselect.createSelector in production code\n+  var createSelector = function createSelector(param1Creator, combiner) {\n+    return function (state) {\n+      return combiner(param1Creator(state));\n+    };\n+  };\n+\n+  var orm = ormFixture();\n+  var ormSelector = createOrmSelector(orm, function (session) {\n+    return session.Book.all().toRefArray()[0];\n+  });\n+  var selector = createSelector(function (_ref) {\n+    var db = _ref.db;\n+    return db;\n+  }, ormSelector);\n+  createSelector(function (_ref2) {\n+    var db = _ref2.db;\n+    return db;\n+  }, ormSelector // $ExpectError\n+  );\n+  selector({\n+    db: orm.getEmptyState()\n+  }); // $ExpectType Ref<Book>\n+})(); // advanced selectors\n+\n+\n+(function () {\n+  var orm = ormFixture();\n+  var selector0 = createOrmSelector(orm, function (s) {\n+    return s.db;\n+  }, function (session) {\n+    return session.Book.first().ref;\n+  });\n+  var selector1 = createOrmSelector(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (session, title) {\n+    return session.Book.get({\n+      title: title\n+    }).ref;\n+  });\n+  var selector2 = createOrmSelector(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (session, id, title) {\n+    return session.Book.get({\n+      id: id,\n+      title: title\n+    }).ref;\n+  });\n+  var selector3 = createOrmSelector(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (session, id, title, id2) {\n+    return session.Book.get({\n+      id: id,\n+      title: title,\n+      id2: id2\n+    }).ref;\n+  });\n+  var selector4 = createOrmSelector(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (session, id, title, id2, title2) {\n+    return session.Book.get({\n+      id: id,\n+      title: title,\n+      id2: id2,\n+      title2: title2\n+    }).ref;\n+  });\n+  var selector5 = createOrmSelector(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (session) {\n+    return session.Book.get({\n+      title: arguments.length <= 2 ? undefined : arguments[2]\n+    }).ref;\n+  });\n+  var selector6 = createOrmSelector(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (session, id, title) {\n+    return session.Book.get({\n+      title: title\n+    }).ref;\n+  });\n+  var invalidSelector = createOrmSelector(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (session, foo, missingArg) {\n+    return foo;\n+  } // $ExpectError\n+  );\n+  var invalidSelector2 = createOrmSelector(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (session, foo) {\n+    return session.Book.withId(foo).ref;\n+  } // $ExpectError\n+  );\n+  var state = {\n+    db: orm.getEmptyState(),\n+    foo: 1,\n+    bar: \"foo\"\n+  };\n+  selector0(state); // $ExpectType Ref<Book>\n+\n+  selector1(state); // $ExpectType Ref<Book>\n+\n+  selector2(state); // $ExpectType Ref<Book>\n+\n+  selector3(state); // $ExpectType Ref<Book>\n+\n+  selector4(state); // $ExpectType Ref<Book>\n+\n+  selector5(state); // $ExpectType Ref<Book>\n+\n+  selector6(state); // $ExpectType Ref<Book>\n+})(); // redux-orm-types#7\n+\n+\n+(function () {\n+  var _sessionFixture7 = sessionFixture(),\n+      Book = _sessionFixture7.Book;\n+\n+  Book.exists({\n+    title: \"foo\"\n+  });\n+  Book.all().exists();\n+  Book.exists(); // $ExpectError\n+\n+  Book.exists(\"foo\"); // $ExpectError\n+\n+  Book.all().exists({}); // $ExpectError\n+})(); // redux-orm-types#8\n+\n+\n+(function () {\n+  var _sessionFixture8 = sessionFixture(),\n+      Book = _sessionFixture8.Book;\n+\n+  Book.all().toModelArray();\n+  Book.all().toRefArray();\n+  Book.toModelArray(); // $ExpectError\n+\n+  Book.toRefArray(); // $ExpectError\n+})(); // redux-orm-types#9\n+\n+\n+(function () {\n+  var _sessionFixture9 = sessionFixture(),\n+      Book = _sessionFixture9.Book,\n+      Person = _sessionFixture9.Person,\n+      Publisher = _sessionFixture9.Publisher;\n+\n+  var author = Person.create({\n+    id: \"1\",\n+    firstName: \"foo\",\n+    lastName: \"bar\",\n+    nationality: \"pl\"\n+  });\n+  var publisher = Publisher.create({\n+    name: \"foo\"\n+  });\n+  Book.create({\n+    title: \"foo\",\n+    publisher: 1\n+  });\n+  Book.create({\n+    title: \"foo\",\n+    publisher: 1,\n+    coverArt: \"bar\"\n+  });\n+  Book.create({\n+    title: \"foo\",\n+    publisher: publisher,\n+    coverArt: \"bar\",\n+    authors: [\"foo\", author]\n+  });\n+  Book.create({\n+    title: \"foo\",\n+    publisher: author\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"foo\",\n+    publisher: \"error\"\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"foo\",\n+    publisher: publisher,\n+    coverArt: \"bar\",\n+    authors: [3, author]\n+  }); // $ExpectError\n+})(); // redux-orm-types#18\n+\n+\n+(function () {\n+  return many({\n+    to: \"Bar\",\n+    relatedName: \"foos\",\n+    through: \"FooBar\",\n+    throughFields: [\"foo\", \"bar\"]\n+  });\n+})();\n\\ No newline at end of file\ndiff --git a/node_modules/redux-orm/lib/Model.js b/node_modules/redux-orm/lib/Model.js\nindex b2982a2..f707fea 100644\n--- a/node_modules/redux-orm/lib/Model.js\n+++ b/node_modules/redux-orm/lib/Model.js\n@@ -293,7 +293,7 @@ var Model = /*#__PURE__*/function () {\n           var value = userProps[key];\n           props[key] = (0, _utils.normalizeEntity)(value);\n         } else if (field.getDefault) {\n-          props[key] = field.getDefault();\n+          props[key] = field.getDefault(userProps);\n         }\n       } else if (valuePassed) {\n         // Save for later processing\ndiff --git a/node_modules/redux-orm/lib/redux-orm-tests.js b/node_modules/redux-orm/lib/redux-orm-tests.js\nnew file mode 100644\nindex 0000000..5863ac7\n--- /dev/null\n+++ b/node_modules/redux-orm/lib/redux-orm-tests.js\n@@ -0,0 +1,767 @@\n+\"use strict\";\n+\n+var _interopRequireDefault = require(\"@babel/runtime/helpers/interopRequireDefault\");\n+\n+var _inheritsLoose2 = _interopRequireDefault(require(\"@babel/runtime/helpers/inheritsLoose\"));\n+\n+var _defineProperty2 = _interopRequireDefault(require(\"@babel/runtime/helpers/defineProperty\"));\n+\n+var _ = require(\"./\");\n+\n+function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }\n+\n+function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2[\"default\"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }\n+\n+var Book = /*#__PURE__*/function (_Model) {\n+  (0, _inheritsLoose2[\"default\"])(Book, _Model);\n+\n+  function Book() {\n+    return _Model.apply(this, arguments) || this;\n+  }\n+\n+  Book.reducer = function reducer(action, _Book) {\n+    switch (action.type) {\n+      case \"CREATE_BOOK\":\n+        _Book.create(action.payload);\n+\n+        break;\n+\n+      case \"DELETE_BOOK\":\n+        _Book.filter(function (book) {\n+          return book.title === action.payload.title;\n+        })[\"delete\"]();\n+\n+        break;\n+\n+      default:\n+        break;\n+    }\n+  };\n+\n+  return Book;\n+}(_.Model);\n+\n+(0, _defineProperty2[\"default\"])(Book, \"modelName\", \"Book\");\n+(0, _defineProperty2[\"default\"])(Book, \"fields\", {\n+  title: (0, _.attr)(),\n+  coverArt: (0, _.attr)({\n+    getDefault: function getDefault() {\n+      return \"empty.png\";\n+    }\n+  }),\n+  publisher: (0, _.fk)(\"Publisher\", \"books\"),\n+  authors: (0, _.many)({\n+    to: \"Person\",\n+    relatedName: \"books\",\n+    through: \"Authorship\"\n+  })\n+});\n+(0, _defineProperty2[\"default\"])(Book, \"options\", {\n+  idAttribute: \"title\"\n+});\n+\n+var Person = /*#__PURE__*/function (_Model2) {\n+  (0, _inheritsLoose2[\"default\"])(Person, _Model2);\n+\n+  function Person() {\n+    return _Model2.apply(this, arguments) || this;\n+  }\n+\n+  return Person;\n+}(_.Model);\n+\n+(0, _defineProperty2[\"default\"])(Person, \"modelName\", \"Person\");\n+(0, _defineProperty2[\"default\"])(Person, \"fields\", {\n+  id: (0, _.attr)(),\n+  firstName: (0, _.attr)(),\n+  lastName: (0, _.attr)(),\n+  nationality: (0, _.attr)()\n+});\n+\n+var Authorship = /*#__PURE__*/function (_Model3) {\n+  (0, _inheritsLoose2[\"default\"])(Authorship, _Model3);\n+\n+  function Authorship() {\n+    return _Model3.apply(this, arguments) || this;\n+  }\n+\n+  return Authorship;\n+}(_.Model);\n+\n+(0, _defineProperty2[\"default\"])(Authorship, \"modelName\", \"Authorship\");\n+(0, _defineProperty2[\"default\"])(Authorship, \"fields\", {\n+  year: (0, _.attr)(),\n+  book: (0, _.fk)(\"Book\"),\n+  author: (0, _.fk)(\"Person\")\n+});\n+\n+var Publisher = /*#__PURE__*/function (_Model4) {\n+  (0, _inheritsLoose2[\"default\"])(Publisher, _Model4);\n+\n+  function Publisher() {\n+    return _Model4.apply(this, arguments) || this;\n+  }\n+\n+  return Publisher;\n+}(_.Model);\n+\n+(0, _defineProperty2[\"default\"])(Publisher, \"modelName\", \"Publisher\");\n+(0, _defineProperty2[\"default\"])(Publisher, \"fields\", {\n+  index: (0, _.attr)(),\n+  name: (0, _.attr)()\n+});\n+(0, _defineProperty2[\"default\"])(Publisher, \"options\", {\n+  idAttribute: \"index\"\n+});\n+var schema = {\n+  Book: Book,\n+  Authorship: Authorship,\n+  Person: Person,\n+  Publisher: Publisher\n+};\n+\n+// create ORM instance and register { Book, Publisher, Person, Authorship } schema\n+var ormFixture = function ormFixture() {\n+  var orm = new _.ORM({\n+    stateSelector: function stateSelector(state) {\n+      return state.db;\n+    }\n+  });\n+  orm.register(Book, Authorship, Person, Publisher);\n+  return orm;\n+}; // create ORM instance and acquire new session\n+\n+\n+var sessionFixture = function sessionFixture() {\n+  var orm = ormFixture();\n+  return orm.session(orm.getEmptyState());\n+}; // argOptionalityAtModelCreation - inferred optionality of ModelType.create argument properties\n+\n+\n+(function () {\n+  var _sessionFixture = sessionFixture(),\n+      Book = _sessionFixture.Book,\n+      Publisher = _sessionFixture.Publisher;\n+  /**\r\n+   * 1.A. `number` Model identifiers are optional due to built-in incremental sequencing of numeric identifiers\r\n+   * @see {@link PublisherFields.index}\r\n+   */\n+\n+\n+  Publisher.create({\n+    name: \"P1\"\n+  });\n+  /**\r\n+   * 1.B. `string` identifiers are mandatory\r\n+   */\n+\n+  Book.create({\n+    publisher: 1,\n+    coverArt: \"foo.bmp\"\n+  }); // $ExpectError\n+\n+  /**\r\n+   * 2. non-relational fields with corresponding descriptors that contain defined `getDefault` callback: (`attr({ getDefault: () => 'empty.png' })`)\r\n+   * @see {@link Book#fields.coverArt}\r\n+   */\n+\n+  Book.create({\n+    title: \"B2\",\n+    publisher: 1\n+  });\n+  /**\r\n+   * 3. both attribute and relational fields where corresponding ModelFields interface property has optional (`?`) modifier\r\n+   * @see {@link BookFields.authors}\r\n+   */\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: 1,\n+    coverArt: \"foo.bmp\"\n+  });\n+})(); // argPropertyTypeRestrictionsOnCreate - ModelFields contribute to type constraints within ModelType.create arguments\n+\n+\n+(function () {\n+  var _sessionFixture2 = sessionFixture(),\n+      Book = _sessionFixture2.Book,\n+      Publisher = _sessionFixture2.Publisher,\n+      Person = _sessionFixture2.Person;\n+  /** Keys of declared model fields interface contribute strict requirements regarding corresponding property types */\n+\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: 1,\n+    coverArt: \"foo.png\",\n+    authors: [\"A1\"]\n+  });\n+  /* Incompatible property types: */\n+\n+  Book.create({\n+    title: 1,\n+    publisher: 1\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: \"P1\"\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: 1,\n+    coverArt: 4\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: 1,\n+    authors: {}\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: 1,\n+    authors: function authors() {\n+      return null;\n+    }\n+  }); // $ExpectError\n+\n+  /**\r\n+   * Properties associated to relational fields may be supplied with:\r\n+   *\r\n+   * - a primitive type matching id type of relation target\r\n+   * - Model/SessionBoundModel instance matching relation target\r\n+   *\r\n+   * In case of MutableQuerySets/many-to-many relationships, an array of union of above-mentioned types is accepted\r\n+   */\n+\n+  var authorModel = Person.create({\n+    id: \"A1\",\n+    firstName: \"A1\",\n+    lastName: \"A1\"\n+  });\n+  var publisherModel = Publisher.create({\n+    name: \"P1\"\n+  });\n+  Book.create({\n+    title: \"B1\",\n+    publisher: publisherModel,\n+    authors: [authorModel]\n+  });\n+  Book.create({\n+    title: \"B1\",\n+    publisher: publisherModel.index,\n+    authors: [authorModel, \"A1\", authorModel, authorModel.ref.id]\n+  });\n+  /** Id types are verified to match relation target */\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: authorModel\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: publisherModel.ref,\n+    authors: [publisherModel.ref, \"A1\"]\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: {\n+      index: \"P1 \"\n+    }\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: {\n+      index: 0\n+    },\n+    authors: [authorModel, true]\n+  }); // $ExpectError\n+})(); // argPropertyTypeRestrictionsOnUpsert - ModelFields contribute to type constraints within ModelType.create arguments\n+\n+\n+(function () {\n+  var _sessionFixture3 = sessionFixture(),\n+      Book = _sessionFixture3.Book,\n+      Publisher = _sessionFixture3.Publisher,\n+      Person = _sessionFixture3.Person;\n+  /** Upsert requires id to be provided */\n+\n+\n+  Book.upsert({\n+    publisher: 1\n+  }); // $ExpectError\n+  // $ExpectType SessionBoundModel<Book, Pick<{ title: string; publisher: number; }, never>> || SessionBoundModel<Book, CustomInstanceProps<Book, { title: string; publisher: number; }>>\n+\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: 1\n+  });\n+  /* Incompatible property types: */\n+\n+  Book.upsert({\n+    title: 4,\n+    publisher: \"P1\"\n+  }); // $ExpectError\n+\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: \"P1\"\n+  }); // $ExpectError\n+\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: 1,\n+    coverArt: 4\n+  }); // $ExpectError\n+\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: 1,\n+    authors: {}\n+  }); // $ExpectError\n+\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: 1,\n+    authors: function authors() {\n+      return null;\n+    }\n+  }); // $ExpectError\n+\n+  /**\r\n+   * Properties associated to relational fields may be supplied with:\r\n+   *\r\n+   * - a primitive type matching id type of relation target\r\n+   * - a Ref type derived from relation target\r\n+   * - Model/SessionBoundModel instance matching relation target\r\n+   * - a map containing {Idkey:IdType} entry, where IdKey/IdType are compatible with relation target id key:type signature\r\n+   *\r\n+   * In case of MutableQuerySets/many-to-many relationships, an array of union of above-mentioned types is accepted\r\n+   */\n+\n+  var authorModel = Person.upsert({\n+    id: \"A1\",\n+    firstName: \"A1\",\n+    lastName: \"A1\"\n+  });\n+  var publisherModel = Publisher.upsert({\n+    name: \"P1\",\n+    index: 1\n+  });\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: 1,\n+    authors: [authorModel]\n+  });\n+  Book.upsert({\n+    title: \"B1\",\n+    publisher: publisherModel,\n+    authors: [authorModel]\n+  });\n+  /** Id types are verified to match relation target */\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: authorModel\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: publisherModel.ref,\n+    authors: [publisherModel.ref, \"A1\"]\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: {\n+      index: \"P1 \"\n+    }\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"B1\",\n+    publisher: {\n+      index: 0\n+    },\n+    authors: [authorModel, true]\n+  }); // $ExpectError\n+})(); // restriction of allowed ORM.register args\n+\n+\n+(function () {\n+  var incompleteSchema = {\n+    Book: Book,\n+    Authorship: Authorship,\n+    Person: Person\n+  };\n+  var orm = new _.ORM();\n+  orm.register(Book, Authorship, Person, Publisher); // $ExpectError\n+})(); // inference of ORM branch state type\n+\n+\n+(function () {\n+  var emptyState = ormFixture().getEmptyState();\n+  var bookTableState = emptyState.Book; // $ExpectType TableState<typeof Book>\n+\n+  var bookItemsById = emptyState.Book.itemsById; // $ExpectType { readonly [K: string]: Ref<Book>; }\n+\n+  var authorshipMetaState = emptyState.Authorship.meta.maxId; // $ExpectType number\n+\n+  var bookMetaState = emptyState.Book.meta.maxId; // $ExpectType number | null\n+})(); // sessionInstanceExtendedWithNarrowedModelClasses - indexing session instance using registered Model.modelName returns narrowed Model class\n+\n+\n+(function () {\n+  var _sessionFixture4 = sessionFixture(),\n+      Book = _sessionFixture4.Book,\n+      Person = _sessionFixture4.Person,\n+      Publisher = _sessionFixture4.Publisher; // $ExpectType { Book: ModelType<Book>; Person: ModelType<Person>; Publisher: ModelType<Publisher>; }\n+\n+\n+  var sessionBoundModels = {\n+    Book: Book,\n+    Person: Person,\n+    Publisher: Publisher\n+  };\n+  return _objectSpread({}, sessionBoundModels);\n+})(); // IdKey and IdType mapped types support for valid identifier configurations\n+\n+\n+(function () {})(); // Model#create result retains custom properties supplied during call\n+\n+\n+(function () {\n+  var _sessionFixture5 = sessionFixture(),\n+      Book = _sessionFixture5.Book;\n+\n+  var basicBook = Book.create({\n+    title: \"book\",\n+    publisher: 1\n+  });\n+  // $ExpectType \"title\" | \"coverArt\" | \"publisher\" | \"authors\" || keyof BookFields\n+  var basicBookTitle = basicBook.title; // $ExpectType string\n+\n+  var authors = basicBook.authors; // $ExpectType MutableQuerySet<Person, {}>\n+\n+  var unknownPropertyError = basicBook.customProp; // $ExpectError\n+\n+  var customProp = {\n+    foo: 0,\n+    bar: true\n+  };\n+  var extendedBook = Book.create({\n+    title: \"extendedBook\",\n+    publisher: 1,\n+    customProp: customProp\n+  });\n+  // $ExpectType \"title\" | \"coverArt\" | \"publisher\" | \"authors\" | \"customProp\" || keyof BookFields | \"customProp\"\n+  var extendedBookTitle = extendedBook.title; // $ExpectType string\n+\n+  var instanceCustomProp = extendedBook.customProp; // $ExpectType { foo: number; bar: boolean; }\n+})(); // reducer API is intact\n+\n+\n+(function () {\n+  var orm = ormFixture();\n+  return function (state, action) {\n+    var session = orm.session(state);\n+    session.Book.create(action.payload);\n+    return session.state;\n+  };\n+})(); // QuerySet type is retained though query chain until terminated.\n+// Orders are optional, must conform to SortOrder type when present.\n+// QuerySet.orderBy overloads accept iteratees applicable to QuerySet's type only\n+// orderByArguments\n+\n+\n+(function () {\n+  var _sessionFixture6 = sessionFixture(),\n+      Book = _sessionFixture6.Book;\n+\n+  var booksQuerySet = Book.all(); // $ExpectType readonly Ref<Book>[]\n+\n+  booksQuerySet.orderBy(\"title\").orderBy(function (book) {\n+    return book.publisher;\n+  }, \"desc\").orderBy(function (book) {\n+    return book.title;\n+  }, false).orderBy(\"publisher\", \"asc\").orderBy(\"publisher\", true).toRefArray(); // $ExpectType readonly Ref<Book>[]\n+\n+  booksQuerySet.orderBy([\"title\"], [\"asc\"]).orderBy([\"publisher\", \"title\"], [true, \"desc\"]).orderBy([function (book) {\n+    return book.title;\n+  }], [\"desc\"]).orderBy([\"title\"]).orderBy([function (book) {\n+    return book.title;\n+  }, \"publisher\"], [\"desc\", false]).toRefArray();\n+  booksQuerySet.orderBy(\"notABookPropertyKey\"); // $ExpectError\n+\n+  booksQuerySet.orderBy([function (book) {\n+    return book.notABookPropertyKey;\n+  }], false); // $ExpectError\n+\n+  booksQuerySet.orderBy(\"title\", \"inc\"); // $ExpectError\n+\n+  booksQuerySet.orderBy(\"title\", 4); // $ExpectError\n+\n+  booksQuerySet.orderBy([\"notABookPropertyKey\"]); // $ExpectError\n+\n+  booksQuerySet.orderBy([function (book) {\n+    return book.notABookPropertyKey;\n+  }]); // $ExpectError\n+\n+  booksQuerySet.orderBy([\"title\"], [\"inc\"]); // $ExpectError\n+\n+  booksQuerySet.orderBy([\"title\"], [4]); // $ExpectError\n+})(); // selectors\n+\n+\n+(function () {\n+  // test fixture, use reselect.createSelector in production code\n+  var createSelector = function createSelector(param1Creator, combiner) {\n+    return function (state) {\n+      return combiner(param1Creator(state));\n+    };\n+  };\n+\n+  var orm = ormFixture();\n+  var ormSelector = (0, _.createSelector)(orm, function (session) {\n+    return session.Book.all().toRefArray()[0];\n+  });\n+  var selector = createSelector(function (_ref) {\n+    var db = _ref.db;\n+    return db;\n+  }, ormSelector);\n+  createSelector(function (_ref2) {\n+    var db = _ref2.db;\n+    return db;\n+  }, ormSelector // $ExpectError\n+  );\n+  selector({\n+    db: orm.getEmptyState()\n+  }); // $ExpectType Ref<Book>\n+})(); // advanced selectors\n+\n+\n+(function () {\n+  var orm = ormFixture();\n+  var selector0 = (0, _.createSelector)(orm, function (s) {\n+    return s.db;\n+  }, function (session) {\n+    return session.Book.first().ref;\n+  });\n+  var selector1 = (0, _.createSelector)(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (session, title) {\n+    return session.Book.get({\n+      title: title\n+    }).ref;\n+  });\n+  var selector2 = (0, _.createSelector)(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (session, id, title) {\n+    return session.Book.get({\n+      id: id,\n+      title: title\n+    }).ref;\n+  });\n+  var selector3 = (0, _.createSelector)(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (session, id, title, id2) {\n+    return session.Book.get({\n+      id: id,\n+      title: title,\n+      id2: id2\n+    }).ref;\n+  });\n+  var selector4 = (0, _.createSelector)(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (session, id, title, id2, title2) {\n+    return session.Book.get({\n+      id: id,\n+      title: title,\n+      id2: id2,\n+      title2: title2\n+    }).ref;\n+  });\n+  var selector5 = (0, _.createSelector)(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (session) {\n+    return session.Book.get({\n+      title: arguments.length <= 2 ? undefined : arguments[2]\n+    }).ref;\n+  });\n+  var selector6 = (0, _.createSelector)(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (s) {\n+    return s.bar;\n+  }, function (session, id, title) {\n+    return session.Book.get({\n+      title: title\n+    }).ref;\n+  });\n+  var invalidSelector = (0, _.createSelector)(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (session, foo, missingArg) {\n+    return foo;\n+  } // $ExpectError\n+  );\n+  var invalidSelector2 = (0, _.createSelector)(orm, function (s) {\n+    return s.db;\n+  }, function (s) {\n+    return s.foo;\n+  }, function (session, foo) {\n+    return session.Book.withId(foo).ref;\n+  } // $ExpectError\n+  );\n+  var state = {\n+    db: orm.getEmptyState(),\n+    foo: 1,\n+    bar: \"foo\"\n+  };\n+  selector0(state); // $ExpectType Ref<Book>\n+\n+  selector1(state); // $ExpectType Ref<Book>\n+\n+  selector2(state); // $ExpectType Ref<Book>\n+\n+  selector3(state); // $ExpectType Ref<Book>\n+\n+  selector4(state); // $ExpectType Ref<Book>\n+\n+  selector5(state); // $ExpectType Ref<Book>\n+\n+  selector6(state); // $ExpectType Ref<Book>\n+})(); // redux-orm-types#7\n+\n+\n+(function () {\n+  var _sessionFixture7 = sessionFixture(),\n+      Book = _sessionFixture7.Book;\n+\n+  Book.exists({\n+    title: \"foo\"\n+  });\n+  Book.all().exists();\n+  Book.exists(); // $ExpectError\n+\n+  Book.exists(\"foo\"); // $ExpectError\n+\n+  Book.all().exists({}); // $ExpectError\n+})(); // redux-orm-types#8\n+\n+\n+(function () {\n+  var _sessionFixture8 = sessionFixture(),\n+      Book = _sessionFixture8.Book;\n+\n+  Book.all().toModelArray();\n+  Book.all().toRefArray();\n+  Book.toModelArray(); // $ExpectError\n+\n+  Book.toRefArray(); // $ExpectError\n+})(); // redux-orm-types#9\n+\n+\n+(function () {\n+  var _sessionFixture9 = sessionFixture(),\n+      Book = _sessionFixture9.Book,\n+      Person = _sessionFixture9.Person,\n+      Publisher = _sessionFixture9.Publisher;\n+\n+  var author = Person.create({\n+    id: \"1\",\n+    firstName: \"foo\",\n+    lastName: \"bar\",\n+    nationality: \"pl\"\n+  });\n+  var publisher = Publisher.create({\n+    name: \"foo\"\n+  });\n+  Book.create({\n+    title: \"foo\",\n+    publisher: 1\n+  });\n+  Book.create({\n+    title: \"foo\",\n+    publisher: 1,\n+    coverArt: \"bar\"\n+  });\n+  Book.create({\n+    title: \"foo\",\n+    publisher: publisher,\n+    coverArt: \"bar\",\n+    authors: [\"foo\", author]\n+  });\n+  Book.create({\n+    title: \"foo\",\n+    publisher: author\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"foo\",\n+    publisher: \"error\"\n+  }); // $ExpectError\n+\n+  Book.create({\n+    title: \"foo\",\n+    publisher: publisher,\n+    coverArt: \"bar\",\n+    authors: [3, author]\n+  }); // $ExpectError\n+})(); // redux-orm-types#18\n+\n+\n+(function () {\n+  return (0, _.many)({\n+    to: \"Bar\",\n+    relatedName: \"foos\",\n+    through: \"FooBar\",\n+    throughFields: [\"foo\", \"bar\"]\n+  });\n+})();\n\\ No newline at end of file\ndiff --git a/node_modules/redux-orm/src/Model.js b/node_modules/redux-orm/src/Model.js\nindex 20f66ef..6263d01 100644\n--- a/node_modules/redux-orm/src/Model.js\n+++ b/node_modules/redux-orm/src/Model.js\n@@ -71,7 +71,7 @@ const Model = class Model {\n         const propsObj = Object(props);\n         this._fields = { ...propsObj };\n \n-        Object.keys(propsObj).forEach(fieldName => {\n+        Object.keys(propsObj).forEach((fieldName) => {\n             // In this case, we got a prop that wasn't defined as a field.\n             // Assuming it's an arbitrary data field, making an instance-specific\n             // descriptor for it.\n@@ -80,7 +80,7 @@ const Model = class Model {\n             if (!(fieldName in this)) {\n                 Object.defineProperty(this, fieldName, {\n                     get: () => this._fields[fieldName],\n-                    set: value => this.set(fieldName, value),\n+                    set: (value) => this.set(fieldName, value),\n                     configurable: true,\n                     enumerable: true,\n                 });\n@@ -292,7 +292,7 @@ const Model = class Model {\n         const declaredFieldNames = Object.keys(this.fields);\n         const declaredVirtualFieldNames = Object.keys(this.virtualFields);\n \n-        declaredFieldNames.forEach(key => {\n+        declaredFieldNames.forEach((key) => {\n             const field = this.fields[key];\n             const valuePassed = userProps.hasOwnProperty(key);\n             if (!(field instanceof ManyToMany)) {\n@@ -300,7 +300,7 @@ const Model = class Model {\n                     const value = userProps[key];\n                     props[key] = normalizeEntity(value);\n                 } else if (field.getDefault) {\n-                    props[key] = field.getDefault();\n+                    props[key] = field.getDefault(userProps);\n                 }\n             } else if (valuePassed) {\n                 // Save for later processing\n@@ -320,7 +320,7 @@ const Model = class Model {\n         });\n \n         // add backward many-many if required\n-        declaredVirtualFieldNames.forEach(key => {\n+        declaredVirtualFieldNames.forEach((key) => {\n             if (!m2mRelations.hasOwnProperty(key)) {\n                 const field = this.virtualFields[key];\n                 if (\n@@ -527,12 +527,12 @@ const Model = class Model {\n         const className = ThisModel.modelName;\n         const fieldNames = Object.keys(ThisModel.fields);\n         const fields = fieldNames\n-            .map(fieldName => {\n+            .map((fieldName) => {\n                 const field = ThisModel.fields[fieldName];\n                 if (field instanceof ManyToMany) {\n                     const ids = this[fieldName]\n                         .toModelArray()\n-                        .map(model => model.getId());\n+                        .map((model) => model.getId());\n                     return `${fieldName}: [${ids.join(\", \")}]`;\n                 }\n                 const val = this._fields[fieldName];\n@@ -697,7 +697,7 @@ const Model = class Model {\n         const ThisModel = this.getClass();\n         const { fields, virtualFields, modelName } = ThisModel;\n \n-        Object.keys(relations).forEach(name => {\n+        Object.keys(relations).forEach((name) => {\n             const reverse = !fields.hasOwnProperty(name);\n             const field = virtualFields[name];\n             const values = relations[name];\n@@ -731,10 +731,10 @@ const Model = class Model {\n             }\n \n             const currentIds = ThroughModel.filter(\n-                through => through[fromField] === this[ThisModel.idAttribute]\n+                (through) => through[fromField] === this[ThisModel.idAttribute]\n             )\n                 .toRefArray()\n-                .map(ref => ref[toField]);\n+                .map((ref) => ref[toField]);\n \n             const diffActions = arrayDiffActions(currentIds, normalizedNewIds);\n \ndiff --git a/node_modules/redux-orm/src/ORM.js b/node_modules/redux-orm/src/ORM.js\nindex a0b00dd..5872107 100644\n--- a/node_modules/redux-orm/src/ORM.js\n+++ b/node_modules/redux-orm/src/ORM.js\n@@ -22,7 +22,7 @@ const ORM_DEFAULTS = {\n };\n \n const RESERVED_TABLE_OPTIONS = [\"indexes\", \"meta\"];\n-const isReservedTableOption = word => RESERVED_TABLE_OPTIONS.includes(word);\n+const isReservedTableOption = (word) => RESERVED_TABLE_OPTIONS.includes(word);\n \n /**\n  * ORM - the Object Relational Mapper.\n@@ -68,7 +68,7 @@ class ORM {\n      * @return {undefined}\n      */\n     register(...models) {\n-        models.forEach(model => {\n+        models.forEach((model) => {\n             if (model.modelName === undefined) {\n                 throw new Error(\n                     \"A model was passed that doesn't have a modelName set\"\n@@ -163,7 +163,7 @@ class ORM {\n     get(modelName) {\n         const allModels = this.registry.concat(this.implicitThroughModels);\n         const found = Object.values(allModels).find(\n-            model => model.modelName === modelName\n+            (model) => model.modelName === modelName\n         );\n \n         if (typeof found === \"undefined\") {\n@@ -185,7 +185,7 @@ class ORM {\n             const tableSpec = modelClass.tableOptions();\n             Object.keys(tableSpec)\n                 .filter(isReservedTableOption)\n-                .forEach(key => {\n+                .forEach((key) => {\n                     throw new Error(\n                         `Reserved keyword \\`${key}\\` used in ${tableName}.options.`\n                     );\n@@ -239,8 +239,8 @@ class ORM {\n      */\n     _setupModelPrototypes(models) {\n         models\n-            .filter(model => !model.isSetUp)\n-            .forEach(model => {\n+            .filter((model) => !model.isSetUp)\n+            .forEach((model) => {\n                 const { fields, modelName, querySetClass } = model;\n                 Object.entries(fields).forEach(([fieldName, field]) => {\n                     if (!(field instanceof Field)) {\ndiff --git a/node_modules/redux-orm/src/QuerySet.js b/node_modules/redux-orm/src/QuerySet.js\nindex 5dd357d..33bedbc 100644\n--- a/node_modules/redux-orm/src/QuerySet.js\n+++ b/node_modules/redux-orm/src/QuerySet.js\n@@ -75,7 +75,7 @@ const QuerySet = class QuerySet {\n      */\n     toModelArray() {\n         const { modelClass: ModelClass } = this;\n-        return this._evaluate().map(props => new ModelClass(props));\n+        return this._evaluate().map((props) => new ModelClass(props));\n     }\n \n     /**\n@@ -287,7 +287,7 @@ const QuerySet = class QuerySet {\n         const { session, modelName: table } = this.modelClass;\n \n         this.toModelArray().forEach(\n-            model => model._onDelete() // eslint-disable-line no-underscore-dangle\n+            (model) => model._onDelete() // eslint-disable-line no-underscore-dangle\n         );\n \n         session.applyUpdate({\ndiff --git a/node_modules/redux-orm/src/Session.js b/node_modules/redux-orm/src/Session.js\nindex ce40ba8..27e0707 100644\n--- a/node_modules/redux-orm/src/Session.js\n+++ b/node_modules/redux-orm/src/Session.js\n@@ -26,7 +26,7 @@ const Session = class Session {\n \n         this.models = schema.getModelClasses();\n \n-        this.sessionBoundModels = this.models.map(modelClass => {\n+        this.sessionBoundModels = this.models.map((modelClass) => {\n             function SessionBoundModel() {\n                 return Reflect.construct(\n                     modelClass,\n@@ -65,7 +65,7 @@ const Session = class Session {\n         if (!data.accessedInstances) {\n             data.accessedInstances = {};\n         }\n-        modelIds.forEach(id => {\n+        modelIds.forEach((id) => {\n             data.accessedInstances[id] = true;\n         });\n     }\n@@ -170,9 +170,9 @@ const Session = class Session {\n         const { rows } = result;\n \n         const { idAttribute } = this[table];\n-        const accessedIds = new Set(rows.map(row => row[idAttribute]));\n+        const accessedIds = new Set(rows.map((row) => row[idAttribute]));\n \n-        const anyClauseFilteredByPk = clauses.some(clause => {\n+        const anyClauseFilteredByPk = clauses.some((clause) => {\n             if (!clauseFiltersByAttribute(clause, idAttribute)) {\n                 return false;\n             }\n@@ -186,8 +186,8 @@ const Session = class Session {\n \n         const accessedIndexes = [];\n         const { indexes } = this.state[table];\n-        clauses.forEach(clause => {\n-            Object.keys(indexes).forEach(attr => {\n+        clauses.forEach((clause) => {\n+            Object.keys(indexes).forEach((attr) => {\n                 if (!clauseFiltersByAttribute(clause, attr)) {\n                     return;\n                 }\ndiff --git a/node_modules/redux-orm/src/db/Database.js b/node_modules/redux-orm/src/db/Database.js\nindex 25ad4c0..023f090 100644\n--- a/node_modules/redux-orm/src/db/Database.js\n+++ b/node_modules/redux-orm/src/db/Database.js\n@@ -105,7 +105,7 @@ export function createDatabase(schemaSpec) {\n         query: query.bind(null, tables),\n         update: update.bind(null, tables),\n         // Used to inspect the schema.\n-        describe: tableName => tables[tableName],\n+        describe: (tableName) => tables[tableName],\n     };\n }\n \ndiff --git a/node_modules/redux-orm/src/db/Table.js b/node_modules/redux-orm/src/db/Table.js\nindex 66670b2..9301f28 100644\n--- a/node_modules/redux-orm/src/db/Table.js\n+++ b/node_modules/redux-orm/src/db/Table.js\n@@ -63,7 +63,7 @@ function normalizeOrders(orders) {\n     if (orders === undefined) {\n         return undefined;\n     }\n-    const convert = order => {\n+    const convert = (order) => {\n         if ([\"desc\", false].includes(order)) {\n             return \"desc\";\n         }\n@@ -107,7 +107,7 @@ export class Table {\n \n     accessIds(branch, ids) {\n         const map = branch[this.mapName];\n-        return ids.map(id => map[id]);\n+        return ids.map((id) => map[id]);\n     }\n \n     idExists(branch, id) {\n@@ -144,8 +144,8 @@ export class Table {\n             [this.mapName]: {},\n         };\n         const attrIndexes = Object.keys(this.fields)\n-            .filter(attr => attr !== this.idAttribute)\n-            .filter(attr => this.fields[attr].index)\n+            .filter((attr) => attr !== this.idAttribute)\n+            .filter((attr) => this.fields[attr].index)\n             .reduce(\n                 (indexes, attr) => ({\n                     ...indexes,\n@@ -181,7 +181,7 @@ export class Table {\n \n         const { idAttribute } = this;\n \n-        const optimallyOrderedClauses = sortBy(clauses, clause => {\n+        const optimallyOrderedClauses = sortBy(clauses, (clause) => {\n             if (clauseFiltersByAttribute(clause, idAttribute)) {\n                 return 1;\n             }\n@@ -344,9 +344,10 @@ export class Table {\n \n         const indexesToAppendTo = Object.keys(workingState.indexes)\n             .filter(\n-                fkAttr => entry.hasOwnProperty(fkAttr) && entry[fkAttr] !== null\n+                (fkAttr) =>\n+                    entry.hasOwnProperty(fkAttr) && entry[fkAttr] !== null\n             )\n-            .map(fkAttr => [fkAttr, entry[fkAttr]]);\n+            .map((fkAttr) => [fkAttr, entry[fkAttr]]);\n \n         if (withMutations) {\n             ops.mutable.push(id, workingState[this.arrName]);\n@@ -427,7 +428,7 @@ export class Table {\n     update(tx, branch, rows, mergeObj) {\n         const { batchToken, withMutations } = tx;\n \n-        const mergeObjInto = row => {\n+        const mergeObjInto = (row) => {\n             const merge = withMutations\n                 ? ops.mutable.merge\n                 : ops.batch.merge(batchToken);\n@@ -436,7 +437,7 @@ export class Table {\n \n         const set = withMutations ? ops.mutable.set : ops.batch.set(batchToken);\n \n-        const indexedAttrs = Object.keys(branch.indexes).filter(attr =>\n+        const indexedAttrs = Object.keys(branch.indexes).filter((attr) =>\n             mergeObj.hasOwnProperty(attr)\n         );\n         const indexIdsToAdd = [];\n@@ -460,7 +461,7 @@ export class Table {\n             );\n             const id = result[this.idAttribute];\n             const nextRow = set(id, result, map);\n-            indexedAttrs.forEach(attr => {\n+            indexedAttrs.forEach((attr) => {\n                 const { [attr]: prevValue } = prevAttrValues;\n                 const { [attr]: nextValue } = nextAttrValues;\n                 if (prevValue === nextValue) {\n@@ -523,7 +524,7 @@ export class Table {\n                                 {\n                                     [value]: ops.batch.filter(\n                                         batchToken,\n-                                        rowId => rowId !== id,\n+                                        (rowId) => rowId !== id,\n                                         indexMap[attr][value]\n                                     ),\n                                 },\n@@ -561,17 +562,17 @@ export class Table {\n         const { arrName, mapName } = this;\n         const arr = branch[arrName];\n \n-        const idsToDelete = rows.map(row => row[this.idAttribute]);\n+        const idsToDelete = rows.map((row) => row[this.idAttribute]);\n         if (withMutations) {\n-            idsToDelete.forEach(id => {\n+            idsToDelete.forEach((id) => {\n                 const idx = arr.indexOf(id);\n                 ops.mutable.splice(idx, 1, [], arr);\n                 ops.mutable.omit(id, branch[mapName]);\n             });\n             // delete ids from all indexes\n-            Object.values(branch.indexes).forEach(attrIndex =>\n-                Object.values(attrIndex).forEach(valueIndex =>\n-                    idsToDelete.forEach(id => {\n+            Object.values(branch.indexes).forEach((attrIndex) =>\n+                Object.values(attrIndex).forEach((valueIndex) =>\n+                    idsToDelete.forEach((id) => {\n                         const idx = valueIndex.indexOf(id);\n                         if (idx !== -1) {\n                             ops.mutable.splice(idx, 1, [], valueIndex);\n@@ -592,7 +593,7 @@ export class Table {\n                             (attrIndexMap, [value, valueIndex]) => {\n                                 attrIndexMap[value] = ops.batch.filter(\n                                     batchToken,\n-                                    id => !idsToDelete.includes(id),\n+                                    (id) => !idsToDelete.includes(id),\n                                     valueIndex\n                                 );\n                                 return attrIndexMap;\n@@ -613,7 +614,7 @@ export class Table {\n             {\n                 [arrName]: ops.batch.filter(\n                     batchToken,\n-                    id => !idsToDelete.includes(id),\n+                    (id) => !idsToDelete.includes(id),\n                     branch[arrName]\n                 ),\n                 [mapName]: ops.batch.omit(\ndiff --git a/node_modules/redux-orm/src/descriptors.js b/node_modules/redux-orm/src/descriptors.js\nindex 6344e86..84fbc11 100644\n--- a/node_modules/redux-orm/src/descriptors.js\n+++ b/node_modules/redux-orm/src/descriptors.js\n@@ -166,14 +166,14 @@ function manyToManyDescriptor(\n              * referenced by any instance of the current model\n              */\n             const referencedOtherIds = new Set(\n-                throughQs.toRefArray().map(obj => obj[otherReferencingField])\n+                throughQs.toRefArray().map((obj) => obj[otherReferencingField])\n             );\n \n             /**\n              * selects all instances of other model that are referenced\n              * by any instance of the current model\n              */\n-            const qs = OtherModel.filter(otherModelInstance =>\n+            const qs = OtherModel.filter((otherModelInstance) =>\n                 referencedOtherIds.has(\n                     otherModelInstance[OtherModel.idAttribute]\n                 )\n@@ -190,21 +190,21 @@ function manyToManyDescriptor(\n             qs.add = function add(...entities) {\n                 const idsToAdd = new Set(entities.map(normalizeEntity));\n \n-                const existingQs = throughQs.filter(through =>\n+                const existingQs = throughQs.filter((through) =>\n                     idsToAdd.has(through[otherReferencingField])\n                 );\n \n                 if (existingQs.exists()) {\n                     const existingIds = existingQs\n                         .toRefArray()\n-                        .map(through => through[otherReferencingField]);\n+                        .map((through) => through[otherReferencingField]);\n \n                     throw new Error(\n                         `Tried to add already existing ${OtherModel.modelName} id(s) ${existingIds} to the ${ThisModel.modelName} instance with id ${thisId}`\n                     );\n                 }\n \n-                idsToAdd.forEach(id => {\n+                idsToAdd.forEach((id) => {\n                     ThroughModel.create({\n                         [otherReferencingField]: id,\n                         [thisReferencingField]: thisId,\n@@ -235,7 +235,7 @@ function manyToManyDescriptor(\n             qs.remove = function remove(...entities) {\n                 const idsToRemove = new Set(entities.map(normalizeEntity));\n \n-                const entitiesToDelete = throughQs.filter(through =>\n+                const entitiesToDelete = throughQs.filter((through) =>\n                     idsToRemove.has(through[otherReferencingField])\n                 );\n \n@@ -243,10 +243,10 @@ function manyToManyDescriptor(\n                     // Tried deleting non-existing entities.\n                     const entitiesToDeleteIds = entitiesToDelete\n                         .toRefArray()\n-                        .map(through => through[otherReferencingField]);\n+                        .map((through) => through[otherReferencingField]);\n \n                     const unexistingIds = [...idsToRemove].filter(\n-                        id => !entitiesToDeleteIds.includes(id)\n+                        (id) => !entitiesToDeleteIds.includes(id)\n                     );\n \n                     throw new Error(\ndiff --git a/node_modules/redux-orm/src/fields/ManyToMany.js b/node_modules/redux-orm/src/fields/ManyToMany.js\nindex 5b49ab1..c8b2747 100644\n--- a/node_modules/redux-orm/src/fields/ManyToMany.js\n+++ b/node_modules/redux-orm/src/fields/ManyToMany.js\n@@ -98,8 +98,8 @@ export class ManyToMany extends RelationalField {\n          * determine which field references which model\n          * and infer the directions from that\n          */\n-        const throughModelFieldReferencing = otherModel =>\n-            Object.keys(throughModel.fields).find(someFieldName =>\n+        const throughModelFieldReferencing = (otherModel) =>\n+            Object.keys(throughModel.fields).find((someFieldName) =>\n                 throughModel.fields[someFieldName].references(otherModel)\n             );\n \ndiff --git a/node_modules/redux-orm/src/memoize.js b/node_modules/redux-orm/src/memoize.js\nindex b9f7c86..f5ef592 100644\n--- a/node_modules/redux-orm/src/memoize.js\n+++ b/node_modules/redux-orm/src/memoize.js\n@@ -3,7 +3,7 @@ import { STATE_FLAG } from \"./constants\";\n const defaultEqualityCheck = (a, b) => a === b;\n export const eqCheck = defaultEqualityCheck;\n \n-const isOrmState = arg =>\n+const isOrmState = (arg) =>\n     arg && typeof arg === \"object\" && arg.hasOwnProperty(STATE_FLAG);\n \n const argsAreEqual = (lastArgs, nextArgs, equalityCheck) =>\n@@ -14,7 +14,7 @@ const argsAreEqual = (lastArgs, nextArgs, equalityCheck) =>\n     );\n \n const rowsAreEqual = (ids, rowsA, rowsB) =>\n-    ids.every(id => rowsA[id] === rowsB[id]);\n+    ids.every((id) => rowsA[id] === rowsB[id]);\n \n const accessedModelInstancesAreEqual = (previous, ormState, orm) => {\n     const { accessedInstances } = previous;\n@@ -41,7 +41,7 @@ const accessedIndexesAreEqual = (previous, ormState) => {\n     return Object.entries(accessedIndexes).every(([modelName, indexes]) =>\n         Object.entries(indexes).every(([column, values]) =>\n             values.every(\n-                value =>\n+                (value) =>\n                     previous.ormState[modelName].indexes[column][value] ===\n                     ormState[modelName].indexes[column][value]\n             )\n@@ -51,7 +51,7 @@ const accessedIndexesAreEqual = (previous, ormState) => {\n \n const fullTableScannedModelsAreEqual = (previous, ormState) =>\n     previous.fullTableScannedModels.every(\n-        modelName => previous.ormState[modelName] === ormState[modelName]\n+        (modelName) => previous.ormState[modelName] === ormState[modelName]\n     );\n \n /**\n@@ -160,7 +160,7 @@ export function memoize(func, argEqualityCheck = defaultEqualityCheck, orm) {\n          */\n         const session = orm.session(ormState);\n         /* Replace all ORM state arguments by the session above */\n-        const argsWithSession = args.map(arg =>\n+        const argsWithSession = args.map((arg) =>\n             isOrmState(arg) ? session : arg\n         );\n \ndiff --git a/node_modules/redux-orm/src/redux.js b/node_modules/redux-orm/src/redux.js\nindex 94a1b9e..603769d 100644\n--- a/node_modules/redux-orm/src/redux.js\n+++ b/node_modules/redux-orm/src/redux.js\n@@ -19,7 +19,7 @@ import MapSelectorSpec from \"./selectors/MapSelectorSpec\";\n  * @global\n  */\n export function defaultUpdater(session, action) {\n-    session.sessionBoundModels.forEach(modelClass => {\n+    session.sessionBoundModels.forEach((modelClass) => {\n         if (typeof modelClass.reducer === \"function\") {\n             // This calls this.applyUpdate to update this.state\n             modelClass.reducer(action, modelClass, session);\ndiff --git a/node_modules/redux-orm/src/selectors/MapSelectorSpec.js b/node_modules/redux-orm/src/selectors/MapSelectorSpec.js\nindex 095c9de..c971d12 100644\n--- a/node_modules/redux-orm/src/selectors/MapSelectorSpec.js\n+++ b/node_modules/redux-orm/src/selectors/MapSelectorSpec.js\n@@ -18,12 +18,12 @@ export default class MapSelectorSpec extends ModelBasedSelectorSpec {\n              */\n             const parentResult = parentSelector(state, ...other);\n             const idArg = idArgSelector(state, ...other);\n-            const single = refArray => {\n+            const single = (refArray) => {\n                 if (refArray === null) {\n                     // an intermediate field could not be resolved\n                     return null;\n                 }\n-                return refArray.map(ref =>\n+                return refArray.map((ref) =>\n                     this._selector(state, ref[idAttribute])\n                 );\n             };\ndiff --git a/node_modules/redux-orm/src/selectors/ModelBasedSelectorSpec.js b/node_modules/redux-orm/src/selectors/ModelBasedSelectorSpec.js\nindex 1c754d0..13b00f8 100644\n--- a/node_modules/redux-orm/src/selectors/ModelBasedSelectorSpec.js\n+++ b/node_modules/redux-orm/src/selectors/ModelBasedSelectorSpec.js\n@@ -12,12 +12,12 @@ export default class ModelBasedSelectorSpec extends SelectorSpec {\n             if (typeof idArg === \"undefined\") {\n                 return ModelClass.all()\n                     .toModelArray()\n-                    .map(instance =>\n+                    .map((instance) =>\n                         this.valueForInstance(instance, session, ...other)\n                     );\n             }\n             if (Array.isArray(idArg)) {\n-                return idArg.map(id =>\n+                return idArg.map((id) =>\n                     this.valueForInstance(\n                         ModelClass.withId(id),\n                         session,\ndiff --git a/node_modules/redux-orm/src/selectors/ModelSelectorSpec.js b/node_modules/redux-orm/src/selectors/ModelSelectorSpec.js\nindex 3609bfe..65800e6 100644\n--- a/node_modules/redux-orm/src/selectors/ModelSelectorSpec.js\n+++ b/node_modules/redux-orm/src/selectors/ModelSelectorSpec.js\n@@ -21,7 +21,7 @@ export default class ModelSelectorSpec extends SelectorSpec {\n                 return ModelClass.all().toRefArray();\n             }\n             if (Array.isArray(idArg)) {\n-                return idArg.map(id => {\n+                return idArg.map((id) => {\n                     const instance = ModelClass.withId(id);\n                     return instance ? instance.ref : null;\n                 });\ndiff --git a/node_modules/redux-orm/src/utils.js b/node_modules/redux-orm/src/utils.js\nindex da0436f..2066ee4 100644\n--- a/node_modules/redux-orm/src/utils.js\n+++ b/node_modules/redux-orm/src/utils.js\n@@ -99,7 +99,7 @@ function attachQuerySetMethods(modelClass, querySetClass) {\n     // There is no way to get a property descriptor for the whole prototype chain;\n     // only from an objects own properties. Therefore we traverse the whole prototype\n     // chain for querySet.\n-    forEachSuperClass(querySetClass, cls => {\n+    forEachSuperClass(querySetClass, (cls) => {\n         for (let i = 0; i < leftToDefine.length; i++) {\n             let defined = false;\n             const methodName = leftToDefine[i];\n@@ -175,9 +175,9 @@ function objectShallowEquals(a, b) {\n \n /** */\n function arrayDiffActions(sourceArr, targetArr) {\n-    const itemsInBoth = sourceArr.filter(item => targetArr.includes(item));\n-    const deleteItems = sourceArr.filter(item => !itemsInBoth.includes(item));\n-    const addItems = targetArr.filter(item => !itemsInBoth.includes(item));\n+    const itemsInBoth = sourceArr.filter((item) => targetArr.includes(item));\n+    const deleteItems = sourceArr.filter((item) => !itemsInBoth.includes(item));\n+    const addItems = targetArr.filter((item) => !itemsInBoth.includes(item));\n \n     if (deleteItems.length || addItems.length) {\n         return {\n"
  },
  {
    "path": "client/patches/sails.io.js+1.2.1.patch",
    "content": "diff --git a/node_modules/sails.io.js/sails.io.js b/node_modules/sails.io.js/sails.io.js\nindex 11694b5..bb5e594 100644\n--- a/node_modules/sails.io.js/sails.io.js\n+++ b/node_modules/sails.io.js/sails.io.js\n@@ -138,6 +138,15 @@\n     CONNECTION_METADATA_PARAMS.platform + '=' + SDK_INFO.platform + '&' +\n     CONNECTION_METADATA_PARAMS.language + '=' + SDK_INFO.language;\n \n+  var MANAGER_EVENT_NAMES = new Set([\n+    'error',\n+    'reconnect',\n+    'reconnect_attempt',\n+    'reconnect_error',\n+    'reconnect_failed',\n+    'ping'\n+  ]);\n+\n \n \n \n@@ -668,6 +677,7 @@\n         // Okay to change global headers while socket is connected\n         if (option == 'headers') {return;}\n         Object.defineProperty(self, option, {\n+          enumerable: true,\n           get: function() {\n             if (option == 'url') {\n               return _opts[option] || (self._raw && self._raw.io && self._raw.io.uri);\n@@ -986,7 +996,7 @@\n           consolog('====================================');\n         });\n \n-        self.on('reconnecting', function(numAttempts) {\n+        self.on('reconnect_attempt', function(numAttempts) {\n           consolog(\n             '\\n'+\n             '        Socket is trying to reconnect to '+(self.url ? self.url : 'Sails')+'...\\n'+\n@@ -1124,7 +1134,7 @@\n       // off to the self._raw for consumption\n       for (var evName in self.eventQueue) {\n         for (var i in self.eventQueue[evName]) {\n-          self._raw.on(evName, self.eventQueue[evName][i]);\n+          self._getEventTarget(evName).on(evName, self.eventQueue[evName][i]);\n         }\n       }\n \n@@ -1150,10 +1160,11 @@\n      * @return {SailsSocket}\n      */\n     SailsSocket.prototype.on = function (evName, fn){\n+      var target = this._getEventTarget(evName);\n \n       // Bind the event to the raw underlying socket if possible.\n-      if (this._raw) {\n-        this._raw.on(evName, fn);\n+      if (target) {\n+        target.on(evName, fn);\n         return this;\n       }\n \n@@ -1176,10 +1187,11 @@\n      * @return {SailsSocket}\n      */\n     SailsSocket.prototype.off = function (evName, fn){\n+      var target = this._getEventTarget(evName);\n \n       // Bind the event to the raw underlying socket if possible.\n-      if (this._raw) {\n-        this._raw.off(evName, fn);\n+      if (target) {\n+        target.off(evName, fn);\n         return this;\n       }\n \n@@ -1491,6 +1503,12 @@\n       throw new Error('`_request()` was a private API deprecated as of v0.11 of the sails.io.js client. Use `.request()` instead.');\n     };\n \n+    SailsSocket.prototype._getEventTarget = function (evName) {\n+      if (!this._raw) return null;\n+\n+      return MANAGER_EVENT_NAMES.has(evName) ? this._raw.io : this._raw;\n+    };\n+\n \n \n \n"
  },
  {
    "path": "client/patches/semantic-ui-react+2.1.5.patch",
    "content": "diff --git a/node_modules/semantic-ui-react/dist/es/lib/doesNodeContainClick.js b/node_modules/semantic-ui-react/dist/es/lib/doesNodeContainClick.js\nindex 6d06078..e22d4f0 100644\n--- a/node_modules/semantic-ui-react/dist/es/lib/doesNodeContainClick.js\n+++ b/node_modules/semantic-ui-react/dist/es/lib/doesNodeContainClick.js\n@@ -17,13 +17,7 @@ var doesNodeContainClick = function doesNodeContainClick(node, e) {\n   if (_some([e, node], _isNil)) return false; // if there is an e.target and it is in the document, use a simple node.contains() check\n \n   if (e.target) {\n-    _invoke(e.target, 'setAttribute', 'data-suir-click-target', true);\n-\n-    if (document.querySelector('[data-suir-click-target=true]')) {\n-      _invoke(e.target, 'removeAttribute', 'data-suir-click-target');\n-\n-      return node.contains(e.target);\n-    }\n+    return node.contains(e.target);\n   } // Below logic handles cases where the e.target is no longer in the document.\n   // The result of the click likely has removed the e.target node.\n   // Instead of node.contains(), we'll identify the click by X/Y position.\ndiff --git a/node_modules/semantic-ui-react/dist/es/modules/Dropdown/Dropdown.js b/node_modules/semantic-ui-react/dist/es/modules/Dropdown/Dropdown.js\nindex 1cc1bab..0e178ee 100644\n--- a/node_modules/semantic-ui-react/dist/es/modules/Dropdown/Dropdown.js\n+++ b/node_modules/semantic-ui-react/dist/es/modules/Dropdown/Dropdown.js\n@@ -342,7 +342,7 @@ var Dropdown = /*#__PURE__*/function (_Component) {\n         return;\n       }\n \n-      if (searchQuery.length >= minCharacters || minCharacters === 1) {\n+      if (searchQuery.length >= minCharacters || minCharacters === 0) {\n         _this.open(e);\n \n         return;\n@@ -480,7 +480,7 @@ var Dropdown = /*#__PURE__*/function (_Component) {\n       } // close search dropdown if search query is too small\n \n \n-      if (open && minCharacters !== 1 && newQuery.length < minCharacters) _this.close();\n+      if (open && minCharacters !== 0 && newQuery.length < minCharacters) _this.close();\n     };\n \n     _this.handleKeyDown = function (e) {\n@@ -1048,7 +1048,7 @@ var Dropdown = /*#__PURE__*/function (_Component) {\n \n     if (!prevState.focus && this.state.focus) {\n       if (!this.isMouseDown) {\n-        var openable = !search || search && minCharacters === 1 && !this.state.open;\n+        var openable = !search || search && minCharacters === 0 && !this.state.open;\n         if (openOnFocus && openable) this.open();\n       }\n     } else if (prevState.focus && !this.state.focus) {\n@@ -1436,7 +1436,7 @@ Dropdown.defaultProps = {\n   closeOnEscape: true,\n   deburr: false,\n   icon: 'dropdown',\n-  minCharacters: 1,\n+  minCharacters: 0,\n   noResultsMessage: 'No results found.',\n   openOnFocus: true,\n   renderLabel: renderItemContent,\ndiff --git a/node_modules/semantic-ui-react/dist/es/modules/Modal/Modal.js b/node_modules/semantic-ui-react/dist/es/modules/Modal/Modal.js\nindex a39f694..c9c616a 100644\n--- a/node_modules/semantic-ui-react/dist/es/modules/Modal/Modal.js\n+++ b/node_modules/semantic-ui-react/dist/es/modules/Modal/Modal.js\n@@ -20,6 +20,7 @@ import ModalDescription from './ModalDescription';\n import ModalDimmer from './ModalDimmer';\n import ModalHeader from './ModalHeader';\n import { canFit, getLegacyStyles, isLegacy } from './utils';\n+var IS_WINDOWS = navigator.platform.startsWith('Win');\n \n /**\n  * A modal displays content that temporarily blocks interactions with the main view of a site.\n@@ -41,6 +42,7 @@ var Modal = /*#__PURE__*/function (_Component) {\n     _this.ref = /*#__PURE__*/createRef();\n     _this.dimmerRef = /*#__PURE__*/createRef();\n     _this.latestDocumentMouseDownEvent = null;\n+    _this.latestWindowFocusTimeOnWindows = null;\n \n     _this.getMountNode = function () {\n       return isBrowser() ? _this.props.mountNode || document.body : null;\n@@ -68,11 +70,17 @@ var Modal = /*#__PURE__*/function (_Component) {\n       }));\n     };\n \n+    _this.handleWindowFocusOnWindows = function () {\n+      _this.latestWindowFocusTimeOnWindows = Date.now();\n+    };\n+\n     _this.handleDocumentMouseDown = function (e) {\n+      if (_this.latestWindowFocusTimeOnWindows && Date.now() - _this.latestWindowFocusTimeOnWindows < 50) return;\n       _this.latestDocumentMouseDownEvent = e;\n     };\n \n     _this.handleDocumentClick = function (e) {\n+      if (!_this.latestDocumentMouseDownEvent) return;\n       var closeOnDimmerClick = _this.props.closeOnDimmerClick;\n       var currentDocumentMouseDownEvent = _this.latestDocumentMouseDownEvent;\n       _this.latestDocumentMouseDownEvent = null;\n@@ -116,6 +124,13 @@ var Modal = /*#__PURE__*/function (_Component) {\n \n       _this.setPositionAndClassNames();\n \n+      if (IS_WINDOWS) {\n+        eventStack.sub('focus', _this.handleWindowFocusOnWindows, {\n+          pool: eventPool,\n+          target: window\n+        });\n+      }\n+\n       eventStack.sub('mousedown', _this.handleDocumentMouseDown, {\n         pool: eventPool,\n         target: _this.dimmerRef.current\n@@ -131,6 +146,14 @@ var Modal = /*#__PURE__*/function (_Component) {\n     _this.handlePortalUnmount = function (e) {\n       var eventPool = _this.props.eventPool;\n       cancelAnimationFrame(_this.animationRequestId);\n+\n+      if (IS_WINDOWS) {\n+        eventStack.unsub('focus', _this.handleWindowFocusOnWindows, {\n+          pool: eventPool,\n+          target: window\n+        });\n+      }\n+\n       eventStack.unsub('mousedown', _this.handleDocumentMouseDown, {\n         pool: eventPool,\n         target: _this.dimmerRef.current\ndiff --git a/node_modules/semantic-ui-react/src/lib/doesNodeContainClick.js b/node_modules/semantic-ui-react/src/lib/doesNodeContainClick.js\nindex d1ae271..43e1170 100644\n--- a/node_modules/semantic-ui-react/src/lib/doesNodeContainClick.js\n+++ b/node_modules/semantic-ui-react/src/lib/doesNodeContainClick.js\n@@ -14,12 +14,7 @@ const doesNodeContainClick = (node, e) => {\n \n   // if there is an e.target and it is in the document, use a simple node.contains() check\n   if (e.target) {\n-    _.invoke(e.target, 'setAttribute', 'data-suir-click-target', true)\n-\n-    if (document.querySelector('[data-suir-click-target=true]')) {\n-      _.invoke(e.target, 'removeAttribute', 'data-suir-click-target')\n-      return node.contains(e.target)\n-    }\n+    return node.contains(e.target)\n   }\n \n   // Below logic handles cases where the e.target is no longer in the document.\n"
  },
  {
    "path": "client/public/manifest.json",
    "content": "{\n  \"name\": \"PLANKA\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"type\": \"image/x-icon\"\n    },\n    {\n      \"src\": \"logo192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"logo512.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    }\n  ],\n  \"start_url\": \".\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#22252a\",\n  \"background_color\": \"#ffffff\"\n}\n"
  },
  {
    "path": "client/public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "client/src/actions/activities.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst fetchActivitiesInBoard = (boardId) => ({\n  type: ActionTypes.ACTIVITIES_IN_BOARD_FETCH,\n  payload: {\n    boardId,\n  },\n});\n\nfetchActivitiesInBoard.success = (boardId, activities, users) => ({\n  type: ActionTypes.ACTIVITIES_IN_BOARD_FETCH__SUCCESS,\n  payload: {\n    boardId,\n    activities,\n    users,\n  },\n});\n\nfetchActivitiesInBoard.failure = (boardId, error) => ({\n  type: ActionTypes.ACTIVITIES_IN_BOARD_FETCH__FAILURE,\n  payload: {\n    boardId,\n    error,\n  },\n});\n\nconst fetchActivitiesInCard = (cardId) => ({\n  type: ActionTypes.ACTIVITIES_IN_CARD_FETCH,\n  payload: {\n    cardId,\n  },\n});\n\nfetchActivitiesInCard.success = (cardId, activities, users) => ({\n  type: ActionTypes.ACTIVITIES_IN_CARD_FETCH__SUCCESS,\n  payload: {\n    cardId,\n    activities,\n    users,\n  },\n});\n\nfetchActivitiesInCard.failure = (cardId, error) => ({\n  type: ActionTypes.ACTIVITIES_IN_CARD_FETCH__FAILURE,\n  payload: {\n    cardId,\n    error,\n  },\n});\n\nconst handleActivityCreate = (activity) => ({\n  type: ActionTypes.ACTIVITY_CREATE_HANDLE,\n  payload: {\n    activity,\n  },\n});\n\nexport default {\n  fetchActivitiesInBoard,\n  fetchActivitiesInCard,\n  handleActivityCreate,\n};\n"
  },
  {
    "path": "client/src/actions/attachments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createAttachment = (attachment) => ({\n  type: ActionTypes.ATTACHMENT_CREATE,\n  payload: {\n    attachment,\n  },\n});\n\ncreateAttachment.success = (localId, attachment) => ({\n  type: ActionTypes.ATTACHMENT_CREATE__SUCCESS,\n  payload: {\n    localId,\n    attachment,\n  },\n});\n\ncreateAttachment.failure = (localId, error) => ({\n  type: ActionTypes.ATTACHMENT_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleAttachmentCreate = (attachment) => ({\n  type: ActionTypes.ATTACHMENT_CREATE_HANDLE,\n  payload: {\n    attachment,\n  },\n});\n\nconst updateAttachment = (id, data) => ({\n  type: ActionTypes.ATTACHMENT_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateAttachment.success = (attachment) => ({\n  type: ActionTypes.ATTACHMENT_UPDATE__SUCCESS,\n  payload: {\n    attachment,\n  },\n});\n\nupdateAttachment.failure = (id, error) => ({\n  type: ActionTypes.ATTACHMENT_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleAttachmentUpdate = (attachment) => ({\n  type: ActionTypes.ATTACHMENT_UPDATE_HANDLE,\n  payload: {\n    attachment,\n  },\n});\n\nconst deleteAttachment = (id) => ({\n  type: ActionTypes.ATTACHMENT_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteAttachment.success = (attachment) => ({\n  type: ActionTypes.ATTACHMENT_DELETE__SUCCESS,\n  payload: {\n    attachment,\n  },\n});\n\ndeleteAttachment.failure = (id, error) => ({\n  type: ActionTypes.ATTACHMENT_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleAttachmentDelete = (attachment) => ({\n  type: ActionTypes.ATTACHMENT_DELETE_HANDLE,\n  payload: {\n    attachment,\n  },\n});\n\nexport default {\n  createAttachment,\n  handleAttachmentCreate,\n  updateAttachment,\n  handleAttachmentUpdate,\n  deleteAttachment,\n  handleAttachmentDelete,\n};\n"
  },
  {
    "path": "client/src/actions/background-images.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createBackgroundImage = (backgroundImage) => ({\n  type: ActionTypes.BACKGROUND_IMAGE_CREATE,\n  payload: {\n    backgroundImage,\n  },\n});\n\ncreateBackgroundImage.success = (localId, backgroundImage) => ({\n  type: ActionTypes.BACKGROUND_IMAGE_CREATE__SUCCESS,\n  payload: {\n    localId,\n    backgroundImage,\n  },\n});\n\ncreateBackgroundImage.failure = (localId, error) => ({\n  type: ActionTypes.BACKGROUND_IMAGE_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleBackgroundImageCreate = (backgroundImage) => ({\n  type: ActionTypes.BACKGROUND_IMAGE_CREATE_HANDLE,\n  payload: {\n    backgroundImage,\n  },\n});\n\nconst deleteBackgroundImage = (id) => ({\n  type: ActionTypes.BACKGROUND_IMAGE_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteBackgroundImage.success = (backgroundImage) => ({\n  type: ActionTypes.BACKGROUND_IMAGE_DELETE__SUCCESS,\n  payload: {\n    backgroundImage,\n  },\n});\n\ndeleteBackgroundImage.failure = (id, error) => ({\n  type: ActionTypes.BACKGROUND_IMAGE_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleBackgroundImageDelete = (backgroundImage) => ({\n  type: ActionTypes.BACKGROUND_IMAGE_DELETE_HANDLE,\n  payload: {\n    backgroundImage,\n  },\n});\n\nexport default {\n  createBackgroundImage,\n  handleBackgroundImageCreate,\n  deleteBackgroundImage,\n  handleBackgroundImageDelete,\n};\n"
  },
  {
    "path": "client/src/actions/base-custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createBaseCustomFieldGroup = (baseCustomFieldGroup) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE,\n  payload: {\n    baseCustomFieldGroup,\n  },\n});\n\ncreateBaseCustomFieldGroup.success = (localId, baseCustomFieldGroup) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE__SUCCESS,\n  payload: {\n    localId,\n    baseCustomFieldGroup,\n  },\n});\n\ncreateBaseCustomFieldGroup.failure = (localId, error) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleBaseCustomFieldGroupCreate = (baseCustomFieldGroup) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE,\n  payload: {\n    baseCustomFieldGroup,\n  },\n});\n\nconst updateBaseCustomFieldGroup = (id, data) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateBaseCustomFieldGroup.success = (baseCustomFieldGroup) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE__SUCCESS,\n  payload: {\n    baseCustomFieldGroup,\n  },\n});\n\nupdateBaseCustomFieldGroup.failure = (id, error) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleBaseCustomFieldGroupUpdate = (baseCustomFieldGroup) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE,\n  payload: {\n    baseCustomFieldGroup,\n  },\n});\n\nconst deleteBaseCustomFieldGroup = (id) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteBaseCustomFieldGroup.success = (baseCustomFieldGroup) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE__SUCCESS,\n  payload: {\n    baseCustomFieldGroup,\n  },\n});\n\ndeleteBaseCustomFieldGroup.failure = (id, error) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleBaseCustomFieldGroupDelete = (baseCustomFieldGroup) => ({\n  type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE,\n  payload: {\n    baseCustomFieldGroup,\n  },\n});\n\nexport default {\n  createBaseCustomFieldGroup,\n  handleBaseCustomFieldGroupCreate,\n  updateBaseCustomFieldGroup,\n  handleBaseCustomFieldGroupUpdate,\n  deleteBaseCustomFieldGroup,\n  handleBaseCustomFieldGroupDelete,\n};\n"
  },
  {
    "path": "client/src/actions/board-memberships.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createBoardMembership = (boardMembership) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_CREATE,\n  payload: {\n    boardMembership,\n  },\n});\n\ncreateBoardMembership.success = (localId, boardMembership) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_CREATE__SUCCESS,\n  payload: {\n    localId,\n    boardMembership,\n  },\n});\n\ncreateBoardMembership.failure = (localId, error) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleBoardMembershipCreate = (\n  boardMembership,\n  isProjectAvailable,\n  project,\n  board,\n  users,\n  projectManagers,\n  backgroundImages,\n  baseCustomFieldGroups,\n  boards,\n  boardMemberships,\n  labels,\n  lists,\n  cards,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n  notificationsToDelete,\n  notificationServices,\n) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE,\n  payload: {\n    boardMembership,\n    isProjectAvailable,\n    project,\n    board,\n    users,\n    projectManagers,\n    backgroundImages,\n    baseCustomFieldGroups,\n    boards,\n    boardMemberships,\n    labels,\n    lists,\n    cards,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n    notificationsToDelete,\n    notificationServices,\n  },\n});\n\nconst updateBoardMembership = (id, data) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateBoardMembership.success = (boardMembership) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_UPDATE__SUCCESS,\n  payload: {\n    boardMembership,\n  },\n});\n\nupdateBoardMembership.failure = (id, error) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleBoardMembershipUpdate = (boardMembership) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_UPDATE_HANDLE,\n  payload: {\n    boardMembership,\n  },\n});\n\nconst deleteBoardMembership = (id, isCurrentUser) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_DELETE,\n  payload: {\n    id,\n    isCurrentUser,\n  },\n});\n\ndeleteBoardMembership.success = (boardMembership, isCurrentUser) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_DELETE__SUCCESS,\n  payload: {\n    boardMembership,\n    isCurrentUser,\n  },\n});\n\ndeleteBoardMembership.failure = (id, error) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleBoardMembershipDelete = (boardMembership, isCurrentUser) => ({\n  type: ActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE,\n  payload: {\n    boardMembership,\n    isCurrentUser,\n  },\n});\n\nexport default {\n  createBoardMembership,\n  handleBoardMembershipCreate,\n  updateBoardMembership,\n  handleBoardMembershipUpdate,\n  deleteBoardMembership,\n  handleBoardMembershipDelete,\n};\n"
  },
  {
    "path": "client/src/actions/boards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createBoard = (board) => ({\n  type: ActionTypes.BOARD_CREATE,\n  payload: {\n    board,\n  },\n});\n\ncreateBoard.success = (localId, board, boardMemberships) => ({\n  type: ActionTypes.BOARD_CREATE__SUCCESS,\n  payload: {\n    localId,\n    board,\n    boardMemberships,\n  },\n});\n\ncreateBoard.failure = (localId, error) => ({\n  type: ActionTypes.BOARD_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleBoardCreate = (board, boardMemberships) => ({\n  type: ActionTypes.BOARD_CREATE_HANDLE,\n  payload: {\n    board,\n    boardMemberships,\n  },\n});\n\nconst fetchBoard = (id) => ({\n  type: ActionTypes.BOARD_FETCH,\n  payload: {\n    id,\n  },\n});\n\nfetchBoard.success = (\n  board,\n  users,\n  projects,\n  boardMemberships,\n  labels,\n  lists,\n  cards,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n) => ({\n  type: ActionTypes.BOARD_FETCH__SUCCESS,\n  payload: {\n    board,\n    users,\n    projects,\n    boardMemberships,\n    labels,\n    lists,\n    cards,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n  },\n});\n\nfetchBoard.failure = (id, error) => ({\n  type: ActionTypes.BOARD_FETCH__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst updateBoard = (id, data) => ({\n  type: ActionTypes.BOARD_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateBoard.success = (board) => ({\n  type: ActionTypes.BOARD_UPDATE__SUCCESS,\n  payload: {\n    board,\n  },\n});\n\nupdateBoard.failure = (id, error) => ({\n  type: ActionTypes.BOARD_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleBoardUpdate = (board) => ({\n  type: ActionTypes.BOARD_UPDATE_HANDLE,\n  payload: {\n    board,\n  },\n});\n\nconst updateBoardContext = (id, value) => ({\n  type: ActionTypes.BOARD_CONTEXT_UPDATE,\n  payload: {\n    id,\n    value,\n  },\n});\n\nconst searchInBoard = (id, value, currentListId) => ({\n  type: ActionTypes.IN_BOARD_SEARCH,\n  payload: {\n    id,\n    value,\n    currentListId,\n  },\n});\n\nconst deleteBoard = (id) => ({\n  type: ActionTypes.BOARD_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteBoard.success = (board) => ({\n  type: ActionTypes.BOARD_DELETE__SUCCESS,\n  payload: {\n    board,\n  },\n});\n\ndeleteBoard.failure = (id, error) => ({\n  type: ActionTypes.BOARD_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleBoardDelete = (board) => ({\n  type: ActionTypes.BOARD_DELETE_HANDLE,\n  payload: {\n    board,\n  },\n});\n\nexport default {\n  createBoard,\n  handleBoardCreate,\n  fetchBoard,\n  updateBoard,\n  handleBoardUpdate,\n  updateBoardContext,\n  searchInBoard,\n  deleteBoard,\n  handleBoardDelete,\n};\n"
  },
  {
    "path": "client/src/actions/bootstrap.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst handleBootstrapUpdate = (bootstrap) => ({\n  type: ActionTypes.BOOTSTRAP_UPDATE_HANDLE,\n  payload: {\n    bootstrap,\n  },\n});\n\nexport default {\n  handleBootstrapUpdate,\n};\n"
  },
  {
    "path": "client/src/actions/cards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst fetchCards = (listId) => ({\n  type: ActionTypes.CARDS_FETCH,\n  payload: {\n    listId,\n  },\n});\n\nfetchCards.success = (\n  listId,\n  cards,\n  users,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n) => ({\n  type: ActionTypes.CARDS_FETCH__SUCCESS,\n  payload: {\n    listId,\n    cards,\n    users,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n  },\n});\n\nfetchCards.failure = (listId, error) => ({\n  type: ActionTypes.CARDS_FETCH__FAILURE,\n  payload: {\n    listId,\n    error,\n  },\n});\n\nconst handleCardsUpdate = (cards, activities) => ({\n  type: ActionTypes.CARDS_UPDATE_HANDLE,\n  payload: {\n    cards,\n    activities,\n  },\n});\n\nconst createCard = (card, autoOpen) => ({\n  type: ActionTypes.CARD_CREATE,\n  payload: {\n    card,\n    autoOpen,\n  },\n});\n\ncreateCard.success = (localId, card) => ({\n  type: ActionTypes.CARD_CREATE__SUCCESS,\n  payload: {\n    localId,\n    card,\n  },\n});\n\ncreateCard.failure = (localId, error) => ({\n  type: ActionTypes.CARD_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleCardCreate = (\n  card,\n  users,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n) => ({\n  type: ActionTypes.CARD_CREATE_HANDLE,\n  payload: {\n    card,\n    users,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n  },\n});\n\nconst updateCard = (id, data) => ({\n  type: ActionTypes.CARD_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateCard.success = (card) => ({\n  type: ActionTypes.CARD_UPDATE__SUCCESS,\n  payload: {\n    card,\n  },\n});\n\nupdateCard.failure = (id, error) => ({\n  type: ActionTypes.CARD_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleCardUpdate = (\n  card,\n  isFetched,\n  users,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n) => ({\n  type: ActionTypes.CARD_UPDATE_HANDLE,\n  payload: {\n    card,\n    isFetched,\n    users,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n  },\n});\n\nconst transferCard = (id, data) => ({\n  type: ActionTypes.CARD_TRANSFER,\n  payload: {\n    id,\n    data,\n  },\n});\n\ntransferCard.success = (\n  card,\n  users,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n) => ({\n  type: ActionTypes.CARD_TRANSFER__SUCCESS,\n  payload: {\n    card,\n    users,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n  },\n});\n\ntransferCard.failure = (\n  id,\n  error,\n  card,\n  users,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n) => ({\n  type: ActionTypes.CARD_TRANSFER__FAILURE,\n  payload: {\n    id,\n    error,\n    card,\n    users,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n  },\n});\n\nconst duplicateCard = (id, localId, data) => ({\n  type: ActionTypes.CARD_DUPLICATE,\n  payload: {\n    id,\n    localId,\n    data,\n  },\n});\n\nduplicateCard.success = (\n  localId,\n  card,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n) => ({\n  type: ActionTypes.CARD_DUPLICATE__SUCCESS,\n  payload: {\n    localId,\n    card,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n  },\n});\n\nduplicateCard.failure = (localId, error) => ({\n  type: ActionTypes.CARD_DUPLICATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst copyCard = (id) => ({\n  type: ActionTypes.CARD_COPY,\n  payload: {\n    id,\n  },\n});\n\nconst cutCard = (id) => ({\n  type: ActionTypes.CARD_CUT,\n  payload: {\n    id,\n  },\n});\n\nconst pasteCard = () => ({\n  type: ActionTypes.CARD_PASTE,\n  payload: {},\n});\n\nconst deleteCard = (id) => ({\n  type: ActionTypes.CARD_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteCard.success = (card) => ({\n  type: ActionTypes.CARD_DELETE__SUCCESS,\n  payload: {\n    card,\n  },\n});\n\ndeleteCard.failure = (id, error) => ({\n  type: ActionTypes.CARD_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleCardDelete = (card) => ({\n  type: ActionTypes.CARD_DELETE_HANDLE,\n  payload: {\n    card,\n  },\n});\n\nexport default {\n  fetchCards,\n  handleCardsUpdate,\n  createCard,\n  handleCardCreate,\n  updateCard,\n  handleCardUpdate,\n  transferCard,\n  duplicateCard,\n  copyCard,\n  cutCard,\n  pasteCard,\n  deleteCard,\n  handleCardDelete,\n};\n"
  },
  {
    "path": "client/src/actions/comments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst fetchComments = (cardId) => ({\n  type: ActionTypes.COMMENTS_FETCH,\n  payload: {\n    cardId,\n  },\n});\n\nfetchComments.success = (cardId, comments, users) => ({\n  type: ActionTypes.COMMENTS_FETCH__SUCCESS,\n  payload: {\n    cardId,\n    comments,\n    users,\n  },\n});\n\nfetchComments.failure = (cardId, error) => ({\n  type: ActionTypes.COMMENTS_FETCH__FAILURE,\n  payload: {\n    cardId,\n    error,\n  },\n});\n\nconst createComment = (comment) => ({\n  type: ActionTypes.COMMENT_CREATE,\n  payload: {\n    comment,\n  },\n});\n\ncreateComment.success = (localId, comment) => ({\n  type: ActionTypes.COMMENT_CREATE__SUCCESS,\n  payload: {\n    localId,\n    comment,\n  },\n});\n\ncreateComment.failure = (localId, error) => ({\n  type: ActionTypes.COMMENT_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleCommentCreate = (comment, users) => ({\n  type: ActionTypes.COMMENT_CREATE_HANDLE,\n  payload: {\n    comment,\n    users,\n  },\n});\n\nconst updateComment = (id, data) => ({\n  type: ActionTypes.COMMENT_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateComment.success = (comment) => ({\n  type: ActionTypes.COMMENT_UPDATE__SUCCESS,\n  payload: {\n    comment,\n  },\n});\n\nupdateComment.failure = (id, error) => ({\n  type: ActionTypes.COMMENT_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleCommentUpdate = (comment) => ({\n  type: ActionTypes.COMMENT_UPDATE_HANDLE,\n  payload: {\n    comment,\n  },\n});\n\nconst deleteComment = (id) => ({\n  type: ActionTypes.COMMENT_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteComment.success = (comment) => ({\n  type: ActionTypes.COMMENT_DELETE__SUCCESS,\n  payload: {\n    comment,\n  },\n});\n\ndeleteComment.failure = (id, error) => ({\n  type: ActionTypes.COMMENT_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleCommentDelete = (comment) => ({\n  type: ActionTypes.COMMENT_DELETE_HANDLE,\n  payload: {\n    comment,\n  },\n});\n\nexport default {\n  fetchComments,\n  createComment,\n  handleCommentCreate,\n  updateComment,\n  handleCommentUpdate,\n  deleteComment,\n  handleCommentDelete,\n};\n"
  },
  {
    "path": "client/src/actions/config.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst updateConfig = (data) => ({\n  type: ActionTypes.CONFIG_UPDATE,\n  payload: {\n    data,\n  },\n});\n\nupdateConfig.success = (config) => ({\n  type: ActionTypes.CONFIG_UPDATE__SUCCESS,\n  payload: {\n    config,\n  },\n});\n\nupdateConfig.failure = (error) => ({\n  type: ActionTypes.CONFIG_UPDATE__FAILURE,\n  payload: {\n    error,\n  },\n});\n\nconst handleConfigUpdate = (config) => ({\n  type: ActionTypes.CONFIG_UPDATE_HANDLE,\n  payload: {\n    config,\n  },\n});\n\nconst testSmtpConfig = () => ({\n  type: ActionTypes.SMTP_CONFIG_TEST,\n  payload: {},\n});\n\ntestSmtpConfig.success = (logs) => ({\n  type: ActionTypes.SMTP_CONFIG_TEST__SUCCESS,\n  payload: {\n    logs,\n  },\n});\n\ntestSmtpConfig.failure = (error) => ({\n  type: ActionTypes.SMTP_CONFIG_TEST__FAILURE,\n  payload: {\n    error,\n  },\n});\n\nexport default {\n  updateConfig,\n  handleConfigUpdate,\n  testSmtpConfig,\n};\n"
  },
  {
    "path": "client/src/actions/core.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst initializeCore = (\n  config,\n  user,\n  board,\n  webhooks,\n  users,\n  projects,\n  projectManagers,\n  backgroundImages,\n  baseCustomFieldGroups,\n  boards,\n  boardMemberships,\n  labels,\n  lists,\n  cards,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n  notifications,\n  notificationServices,\n) => ({\n  type: ActionTypes.CORE_INITIALIZE,\n  payload: {\n    config,\n    user,\n    board,\n    webhooks,\n    users,\n    projects,\n    projectManagers,\n    backgroundImages,\n    baseCustomFieldGroups,\n    boards,\n    boardMemberships,\n    labels,\n    lists,\n    cards,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n    notifications,\n    notificationServices,\n  },\n});\n\ninitializeCore.fetchBootstrap = (bootstrap) => ({\n  type: ActionTypes.CORE_INITIALIZE__BOOTSTRAP_FETCH,\n  payload: {\n    bootstrap,\n  },\n});\n\nconst toggleFavorites = (isEnabled) => ({\n  type: ActionTypes.FAVORITES_TOGGLE,\n  payload: {\n    isEnabled,\n  },\n});\n\nconst toggleEditMode = (isEnabled) => ({\n  type: ActionTypes.EDIT_MODE_TOGGLE,\n  payload: {\n    isEnabled,\n  },\n});\n\nconst updateHomeView = (value) => ({\n  type: ActionTypes.HOME_VIEW_UPDATE,\n  payload: {\n    value,\n  },\n});\n\nconst logout = () => ({\n  type: ActionTypes.LOGOUT,\n  payload: {},\n});\n\nlogout.revokeAccessToken = () => ({\n  type: ActionTypes.LOGOUT__ACCESS_TOKEN_REVOKE,\n  payload: {},\n});\n\nexport default {\n  initializeCore,\n  toggleFavorites,\n  toggleEditMode,\n  updateHomeView,\n  logout,\n};\n"
  },
  {
    "path": "client/src/actions/custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createCustomFieldGroup = (customFieldGroup) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE,\n  payload: {\n    customFieldGroup,\n  },\n});\n\ncreateCustomFieldGroup.success = (localId, customFieldGroup) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE__SUCCESS,\n  payload: {\n    localId,\n    customFieldGroup,\n  },\n});\n\ncreateCustomFieldGroup.failure = (localId, error) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleCustomFieldGroupCreate = (customFieldGroup) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE_HANDLE,\n  payload: {\n    customFieldGroup,\n  },\n});\n\nconst updateCustomFieldGroup = (id, data) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateCustomFieldGroup.success = (customFieldGroup) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE__SUCCESS,\n  payload: {\n    customFieldGroup,\n  },\n});\n\nupdateCustomFieldGroup.failure = (id, error) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleCustomFieldGroupUpdate = (customFieldGroup) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE_HANDLE,\n  payload: {\n    customFieldGroup,\n  },\n});\n\nconst deleteCustomFieldGroup = (id) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteCustomFieldGroup.success = (customFieldGroup) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE__SUCCESS,\n  payload: {\n    customFieldGroup,\n  },\n});\n\ndeleteCustomFieldGroup.failure = (id, error) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleCustomFieldGroupDelete = (customFieldGroup) => ({\n  type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE_HANDLE,\n  payload: {\n    customFieldGroup,\n  },\n});\n\nexport default {\n  createCustomFieldGroup,\n  handleCustomFieldGroupCreate,\n  updateCustomFieldGroup,\n  handleCustomFieldGroupUpdate,\n  deleteCustomFieldGroup,\n  handleCustomFieldGroupDelete,\n};\n"
  },
  {
    "path": "client/src/actions/custom-field-values.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst updateCustomFieldValue = (customFieldValue) => ({\n  type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE,\n  payload: {\n    customFieldValue,\n  },\n});\n\nupdateCustomFieldValue.success = (localId, customFieldValue) => ({\n  type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE__SUCCESS,\n  payload: {\n    localId,\n    customFieldValue,\n  },\n});\n\nupdateCustomFieldValue.failure = (localId, error) => ({\n  type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleCustomFieldValueUpdate = (customFieldValue) => ({\n  type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE_HANDLE,\n  payload: {\n    customFieldValue,\n  },\n});\n\nconst deleteCustomFieldValue = (id) => ({\n  type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteCustomFieldValue.success = (customFieldValue) => ({\n  type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE__SUCCESS,\n  payload: {\n    customFieldValue,\n  },\n});\n\ndeleteCustomFieldValue.failure = (id, error) => ({\n  type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleCustomFieldValueDelete = (customFieldValue) => ({\n  type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE_HANDLE,\n  payload: {\n    customFieldValue,\n  },\n});\n\nexport default {\n  updateCustomFieldValue,\n  handleCustomFieldValueUpdate,\n  deleteCustomFieldValue,\n  handleCustomFieldValueDelete,\n};\n"
  },
  {
    "path": "client/src/actions/custom-fields.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createCustomField = (customField) => ({\n  type: ActionTypes.CUSTOM_FIELD_CREATE,\n  payload: {\n    customField,\n  },\n});\n\ncreateCustomField.success = (localId, customField) => ({\n  type: ActionTypes.CUSTOM_FIELD_CREATE__SUCCESS,\n  payload: {\n    localId,\n    customField,\n  },\n});\n\ncreateCustomField.failure = (localId, error) => ({\n  type: ActionTypes.CUSTOM_FIELD_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleCustomFieldCreate = (customField) => ({\n  type: ActionTypes.CUSTOM_FIELD_CREATE_HANDLE,\n  payload: {\n    customField,\n  },\n});\n\nconst updateCustomField = (id, data) => ({\n  type: ActionTypes.CUSTOM_FIELD_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateCustomField.success = (customField) => ({\n  type: ActionTypes.CUSTOM_FIELD_UPDATE__SUCCESS,\n  payload: {\n    customField,\n  },\n});\n\nupdateCustomField.failure = (id, error) => ({\n  type: ActionTypes.CUSTOM_FIELD_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleCustomFieldUpdate = (customField) => ({\n  type: ActionTypes.CUSTOM_FIELD_UPDATE_HANDLE,\n  payload: {\n    customField,\n  },\n});\n\nconst deleteCustomField = (id) => ({\n  type: ActionTypes.CUSTOM_FIELD_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteCustomField.success = (customField) => ({\n  type: ActionTypes.CUSTOM_FIELD_DELETE__SUCCESS,\n  payload: {\n    customField,\n  },\n});\n\ndeleteCustomField.failure = (id, error) => ({\n  type: ActionTypes.CUSTOM_FIELD_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleCustomFieldDelete = (customField) => ({\n  type: ActionTypes.CUSTOM_FIELD_DELETE_HANDLE,\n  payload: {\n    customField,\n  },\n});\n\nexport default {\n  createCustomField,\n  handleCustomFieldCreate,\n  updateCustomField,\n  handleCustomFieldUpdate,\n  deleteCustomField,\n  handleCustomFieldDelete,\n};\n"
  },
  {
    "path": "client/src/actions/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport router from './router';\nimport socket from './socket';\nimport bootstrap from './bootstrap';\nimport login from './login';\nimport core from './core';\nimport modals from './modals';\nimport config from './config';\nimport webhooks from './webhooks';\nimport users from './users';\nimport projects from './projects';\nimport projectManagers from './project-managers';\nimport backgroundImages from './background-images';\nimport baseCustomFieldGroups from './base-custom-field-groups';\nimport boards from './boards';\nimport boardMemberships from './board-memberships';\nimport labels from './labels';\nimport lists from './lists';\nimport cards from './cards';\nimport taskLists from './task-lists';\nimport tasks from './tasks';\nimport attachments from './attachments';\nimport customFieldGroups from './custom-field-groups';\nimport customFields from './custom-fields';\nimport customFieldValues from './custom-field-values';\nimport comments from './comments';\nimport activities from './activities';\nimport notifications from './notifications';\nimport notificationServices from './notification-services';\n\nexport default {\n  ...router,\n  ...socket,\n  ...bootstrap,\n  ...login,\n  ...core,\n  ...modals,\n  ...config,\n  ...webhooks,\n  ...users,\n  ...projects,\n  ...projectManagers,\n  ...backgroundImages,\n  ...baseCustomFieldGroups,\n  ...boards,\n  ...boardMemberships,\n  ...labels,\n  ...lists,\n  ...cards,\n  ...taskLists,\n  ...tasks,\n  ...attachments,\n  ...customFieldGroups,\n  ...customFields,\n  ...customFieldValues,\n  ...comments,\n  ...activities,\n  ...notifications,\n  ...notificationServices,\n};\n"
  },
  {
    "path": "client/src/actions/labels.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createLabel = (label) => ({\n  type: ActionTypes.LABEL_CREATE,\n  payload: {\n    label,\n  },\n});\n\ncreateLabel.success = (localId, label) => ({\n  type: ActionTypes.LABEL_CREATE__SUCCESS,\n  payload: {\n    localId,\n    label,\n  },\n});\n\ncreateLabel.failure = (localId, error) => ({\n  type: ActionTypes.LABEL_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst createLabelFromCard = (cardId, label) => ({\n  type: ActionTypes.LABEL_FROM_CARD_CREATE,\n  payload: {\n    cardId,\n    label,\n  },\n});\n\ncreateLabelFromCard.success = (localId, label, cardLabel) => ({\n  type: ActionTypes.LABEL_FROM_CARD_CREATE__SUCCESS,\n  payload: {\n    localId,\n    label,\n    cardLabel,\n  },\n});\n\ncreateLabelFromCard.failure = (cardId, localId, error) => ({\n  type: ActionTypes.LABEL_FROM_CARD_CREATE__FAILURE,\n  payload: {\n    cardId,\n    localId,\n    error,\n  },\n});\n\nconst handleLabelCreate = (label) => ({\n  type: ActionTypes.LABEL_CREATE_HANDLE,\n  payload: {\n    label,\n  },\n});\n\nconst updateLabel = (id, data) => ({\n  type: ActionTypes.LABEL_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateLabel.success = (label) => ({\n  type: ActionTypes.LABEL_UPDATE__SUCCESS,\n  payload: {\n    label,\n  },\n});\n\nupdateLabel.failure = (id, error) => ({\n  type: ActionTypes.LABEL_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleLabelUpdate = (label) => ({\n  type: ActionTypes.LABEL_UPDATE_HANDLE,\n  payload: {\n    label,\n  },\n});\n\nconst deleteLabel = (id) => ({\n  type: ActionTypes.LABEL_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteLabel.success = (label) => ({\n  type: ActionTypes.LABEL_DELETE__SUCCESS,\n  payload: {\n    label,\n  },\n});\n\ndeleteLabel.failure = (id, error) => ({\n  type: ActionTypes.LABEL_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleLabelDelete = (label) => ({\n  type: ActionTypes.LABEL_DELETE_HANDLE,\n  payload: {\n    label,\n  },\n});\n\nconst addLabelToCard = (id, cardId) => ({\n  type: ActionTypes.LABEL_TO_CARD_ADD,\n  payload: {\n    id,\n    cardId,\n  },\n});\n\naddLabelToCard.success = (cardLabel) => ({\n  type: ActionTypes.LABEL_TO_CARD_ADD__SUCCESS,\n  payload: {\n    cardLabel,\n  },\n});\n\naddLabelToCard.failure = (id, cardId, error) => ({\n  type: ActionTypes.LABEL_TO_CARD_ADD__FAILURE,\n  payload: {\n    id,\n    cardId,\n    error,\n  },\n});\n\nconst handleLabelToCardAdd = (cardLabel) => ({\n  type: ActionTypes.LABEL_TO_CARD_ADD_HANDLE,\n  payload: {\n    cardLabel,\n  },\n});\n\nconst removeLabelFromCard = (id, cardId) => ({\n  type: ActionTypes.LABEL_FROM_CARD_REMOVE,\n  payload: {\n    id,\n    cardId,\n  },\n});\n\nremoveLabelFromCard.success = (cardLabel) => ({\n  type: ActionTypes.LABEL_FROM_CARD_REMOVE__SUCCESS,\n  payload: {\n    cardLabel,\n  },\n});\n\nremoveLabelFromCard.failure = (id, cardId, error) => ({\n  type: ActionTypes.LABEL_FROM_CARD_REMOVE__FAILURE,\n  payload: {\n    id,\n    cardId,\n    error,\n  },\n});\n\nconst handleLabelFromCardRemove = (cardLabel) => ({\n  type: ActionTypes.LABEL_FROM_CARD_REMOVE_HANDLE,\n  payload: {\n    cardLabel,\n  },\n});\n\nconst addLabelToBoardFilter = (id, boardId, currentListId) => ({\n  type: ActionTypes.LABEL_TO_BOARD_FILTER_ADD,\n  payload: {\n    id,\n    boardId,\n    currentListId,\n  },\n});\n\nconst removeLabelFromBoardFilter = (id, boardId, currentListId) => ({\n  type: ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE,\n  payload: {\n    id,\n    boardId,\n    currentListId,\n  },\n});\n\nexport default {\n  createLabel,\n  createLabelFromCard,\n  handleLabelCreate,\n  updateLabel,\n  handleLabelUpdate,\n  deleteLabel,\n  handleLabelDelete,\n  addLabelToCard,\n  handleLabelToCardAdd,\n  removeLabelFromCard,\n  handleLabelFromCardRemove,\n  addLabelToBoardFilter,\n  removeLabelFromBoardFilter,\n};\n"
  },
  {
    "path": "client/src/actions/lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createList = (list) => ({\n  type: ActionTypes.LIST_CREATE,\n  payload: {\n    list,\n  },\n});\n\ncreateList.success = (localId, list) => ({\n  type: ActionTypes.LIST_CREATE__SUCCESS,\n  payload: {\n    localId,\n    list,\n  },\n});\n\ncreateList.failure = (localId, error) => ({\n  type: ActionTypes.LIST_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleListCreate = (list) => ({\n  type: ActionTypes.LIST_CREATE_HANDLE,\n  payload: {\n    list,\n  },\n});\n\nconst updateList = (id, data) => ({\n  type: ActionTypes.LIST_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateList.success = (list) => ({\n  type: ActionTypes.LIST_UPDATE__SUCCESS,\n  payload: {\n    list,\n  },\n});\n\nupdateList.failure = (id, error) => ({\n  type: ActionTypes.LIST_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleListUpdate = (\n  list,\n  isFetched,\n  users,\n  cards,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n) => ({\n  type: ActionTypes.LIST_UPDATE_HANDLE,\n  payload: {\n    list,\n    isFetched,\n    users,\n    cards,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n  },\n});\n\nconst sortList = (id, data) => ({\n  type: ActionTypes.LIST_SORT,\n  payload: {\n    id,\n    data,\n  },\n});\n\nsortList.success = (list, cards) => ({\n  type: ActionTypes.LIST_SORT__SUCCESS,\n  payload: {\n    list,\n    cards,\n  },\n});\n\nsortList.failure = (id, error) => ({\n  type: ActionTypes.LIST_SORT__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst moveListCards = (id, nextId, cardIds) => ({\n  type: ActionTypes.LIST_CARDS_MOVE,\n  payload: {\n    id,\n    nextId,\n    cardIds,\n  },\n});\n\nmoveListCards.success = (list, cards, activities) => ({\n  type: ActionTypes.LIST_CARDS_MOVE__SUCCESS,\n  payload: {\n    list,\n    cards,\n    activities,\n  },\n});\n\nmoveListCards.failure = (id, error) => ({\n  type: ActionTypes.LIST_CARDS_MOVE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst clearList = (id) => ({\n  type: ActionTypes.LIST_CLEAR,\n  payload: {\n    id,\n  },\n});\n\nclearList.success = (list) => ({\n  type: ActionTypes.LIST_CLEAR__SUCCESS,\n  payload: {\n    list,\n  },\n});\n\nclearList.failure = (id, error) => ({\n  type: ActionTypes.LIST_CLEAR__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleListClear = (list) => ({\n  type: ActionTypes.LIST_CLEAR_HANDLE,\n  payload: {\n    list,\n  },\n});\n\nconst deleteList = (id, trashId, cardIds) => ({\n  type: ActionTypes.LIST_DELETE,\n  payload: {\n    id,\n    trashId,\n    cardIds,\n  },\n});\n\ndeleteList.success = (list, cards) => ({\n  type: ActionTypes.LIST_DELETE__SUCCESS,\n  payload: {\n    list,\n    cards,\n  },\n});\n\ndeleteList.failure = (id, error) => ({\n  type: ActionTypes.LIST_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleListDelete = (list, cards) => ({\n  type: ActionTypes.LIST_DELETE_HANDLE,\n  payload: {\n    list,\n    cards,\n  },\n});\n\nexport default {\n  createList,\n  handleListCreate,\n  updateList,\n  handleListUpdate,\n  sortList,\n  moveListCards,\n  clearList,\n  handleListClear,\n  deleteList,\n  handleListDelete,\n};\n"
  },
  {
    "path": "client/src/actions/login.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst initializeLogin = (bootstrap) => ({\n  type: ActionTypes.LOGIN_INITIALIZE,\n  payload: {\n    bootstrap,\n  },\n});\n\nconst authenticate = (data) => ({\n  type: ActionTypes.AUTHENTICATE,\n  payload: {\n    data,\n  },\n});\n\nauthenticate.success = (accessToken) => ({\n  type: ActionTypes.AUTHENTICATE__SUCCESS,\n  payload: {\n    accessToken,\n  },\n});\n\nauthenticate.failure = (error, terms) => ({\n  type: ActionTypes.AUTHENTICATE__FAILURE,\n  payload: {\n    error,\n    terms,\n  },\n});\n\nconst authenticateWithOidc = () => ({\n  type: ActionTypes.WITH_OIDC_AUTHENTICATE,\n  payload: {},\n});\n\nauthenticateWithOidc.success = (accessToken) => ({\n  type: ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS,\n  payload: {\n    accessToken,\n  },\n});\n\nauthenticateWithOidc.failure = (error, terms) => ({\n  type: ActionTypes.WITH_OIDC_AUTHENTICATE__FAILURE,\n  payload: {\n    error,\n    terms,\n  },\n});\n\nauthenticateWithOidc.debug = (logs) => ({\n  type: ActionTypes.WITH_OIDC_AUTHENTICATE__DEBUG,\n  payload: {\n    logs,\n  },\n});\n\nconst clearAuthenticateError = () => ({\n  type: ActionTypes.AUTHENTICATE_ERROR_CLEAR,\n  payload: {},\n});\n\nconst acceptTerms = (signature) => ({\n  type: ActionTypes.TERMS_ACCEPT,\n  payload: {\n    signature,\n  },\n});\n\nacceptTerms.success = (accessToken) => ({\n  type: ActionTypes.TERMS_ACCEPT__SUCCESS,\n  payload: {\n    accessToken,\n  },\n});\n\nacceptTerms.failure = (error) => ({\n  type: ActionTypes.TERMS_ACCEPT__FAILURE,\n  payload: {\n    error,\n  },\n});\n\nconst cancelTerms = () => ({\n  type: ActionTypes.TERMS_CANCEL,\n  payload: {},\n});\n\ncancelTerms.success = () => ({\n  type: ActionTypes.TERMS_CANCEL__SUCCESS,\n  payload: {},\n});\n\ncancelTerms.failure = (error) => ({\n  type: ActionTypes.TERMS_CANCEL__FAILURE,\n  payload: {\n    error,\n  },\n});\n\nconst updateTermsLanguage = (value) => ({\n  type: ActionTypes.TERMS_LANGUAGE_UPDATE,\n  payload: {\n    value,\n  },\n});\n\nupdateTermsLanguage.success = (terms) => ({\n  type: ActionTypes.TERMS_LANGUAGE_UPDATE__SUCCESS,\n  payload: {\n    terms,\n  },\n});\n\nupdateTermsLanguage.failure = (error) => ({\n  type: ActionTypes.TERMS_LANGUAGE_UPDATE__FAILURE,\n  payload: {\n    error,\n  },\n});\n\nexport default {\n  initializeLogin,\n  authenticate,\n  authenticateWithOidc,\n  clearAuthenticateError,\n  acceptTerms,\n  cancelTerms,\n  updateTermsLanguage,\n};\n"
  },
  {
    "path": "client/src/actions/modals.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst openModal = (type, params = {}) => ({\n  type: ActionTypes.MODAL_OPEN,\n  payload: {\n    type,\n    params,\n  },\n});\n\nconst closeModal = () => ({\n  type: ActionTypes.MODAL_CLOSE,\n  payload: {},\n});\n\nexport default {\n  openModal,\n  closeModal,\n};\n"
  },
  {
    "path": "client/src/actions/notification-services.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createNotificationService = (notificationService) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_CREATE,\n  payload: {\n    notificationService,\n  },\n});\n\ncreateNotificationService.success = (localId, notificationService) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_CREATE__SUCCESS,\n  payload: {\n    localId,\n    notificationService,\n  },\n});\n\ncreateNotificationService.failure = (localId, error) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleNotificationServiceCreate = (notificationService) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_CREATE_HANDLE,\n  payload: {\n    notificationService,\n  },\n});\n\nconst updateNotificationService = (id, data) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateNotificationService.success = (notificationService) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_UPDATE__SUCCESS,\n  payload: {\n    notificationService,\n  },\n});\n\nupdateNotificationService.failure = (id, error) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleNotificationServiceUpdate = (notificationService) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_UPDATE_HANDLE,\n  payload: {\n    notificationService,\n  },\n});\n\nconst testNotificationService = (id) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_TEST,\n  payload: {\n    id,\n  },\n});\n\ntestNotificationService.success = (notificationService) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_TEST__SUCCESS,\n  payload: {\n    notificationService,\n  },\n});\n\ntestNotificationService.failure = (id, error) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_TEST__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst deleteNotificationService = (id) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteNotificationService.success = (notificationService) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_DELETE__SUCCESS,\n  payload: {\n    notificationService,\n  },\n});\n\ndeleteNotificationService.failure = (id, error) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleNotificationServiceDelete = (notificationService) => ({\n  type: ActionTypes.NOTIFICATION_SERVICE_DELETE_HANDLE,\n  payload: {\n    notificationService,\n  },\n});\n\nexport default {\n  createNotificationService,\n  handleNotificationServiceCreate,\n  updateNotificationService,\n  handleNotificationServiceUpdate,\n  testNotificationService,\n  deleteNotificationService,\n  handleNotificationServiceDelete,\n};\n"
  },
  {
    "path": "client/src/actions/notifications.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst deleteAllNotifications = () => ({\n  type: ActionTypes.ALL_NOTIFICATIONS_DELETE,\n  payload: {},\n});\n\ndeleteAllNotifications.success = (notifications) => ({\n  type: ActionTypes.ALL_NOTIFICATIONS_DELETE__SUCCESS,\n  payload: {\n    notifications,\n  },\n});\n\ndeleteAllNotifications.failure = (error) => ({\n  type: ActionTypes.ALL_NOTIFICATIONS_DELETE__FAILURE,\n  payload: {\n    error,\n  },\n});\n\nconst handleNotificationCreate = (notification, users) => ({\n  type: ActionTypes.NOTIFICATION_CREATE_HANDLE,\n  payload: {\n    notification,\n    users,\n  },\n});\n\nconst deleteNotification = (id) => ({\n  type: ActionTypes.NOTIFICATION_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteNotification.success = (notification) => ({\n  type: ActionTypes.NOTIFICATION_DELETE__SUCCESS,\n  payload: {\n    notification,\n  },\n});\n\ndeleteNotification.failure = (id, error) => ({\n  type: ActionTypes.NOTIFICATION_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleNotificationDelete = (notification) => ({\n  type: ActionTypes.NOTIFICATION_DELETE_HANDLE,\n  payload: {\n    notification,\n  },\n});\n\nexport default {\n  deleteAllNotifications,\n  handleNotificationCreate,\n  deleteNotification,\n  handleNotificationDelete,\n};\n"
  },
  {
    "path": "client/src/actions/project-managers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createProjectManager = (projectManager) => ({\n  type: ActionTypes.PROJECT_MANAGER_CREATE,\n  payload: {\n    projectManager,\n  },\n});\n\ncreateProjectManager.success = (localId, projectManager) => ({\n  type: ActionTypes.PROJECT_MANAGER_CREATE__SUCCESS,\n  payload: {\n    localId,\n    projectManager,\n  },\n});\n\ncreateProjectManager.failure = (localId, error) => ({\n  type: ActionTypes.PROJECT_MANAGER_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleProjectManagerCreate = (\n  projectManager,\n  boardIds,\n  isCurrentUser,\n  isProjectAvailable,\n  project,\n  board,\n  users,\n  projectManagers,\n  backgroundImages,\n  baseCustomFieldGroups,\n  boards,\n  boardMemberships,\n  labels,\n  lists,\n  cards,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n  notificationsToDelete,\n  notificationServices,\n) => ({\n  type: ActionTypes.PROJECT_MANAGER_CREATE_HANDLE,\n  payload: {\n    projectManager,\n    boardIds,\n    isCurrentUser,\n    isProjectAvailable,\n    project,\n    board,\n    users,\n    projectManagers,\n    backgroundImages,\n    baseCustomFieldGroups,\n    boards,\n    boardMemberships,\n    labels,\n    lists,\n    cards,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n    notificationsToDelete,\n    notificationServices,\n  },\n});\n\nconst deleteProjectManager = (id) => ({\n  type: ActionTypes.PROJECT_MANAGER_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteProjectManager.success = (projectManager) => ({\n  type: ActionTypes.PROJECT_MANAGER_DELETE__SUCCESS,\n  payload: {\n    projectManager,\n  },\n});\n\ndeleteProjectManager.failure = (id, error) => ({\n  type: ActionTypes.PROJECT_MANAGER_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleProjectManagerDelete = (projectManager) => ({\n  type: ActionTypes.PROJECT_MANAGER_DELETE_HANDLE,\n  payload: {\n    projectManager,\n  },\n});\n\nexport default {\n  createProjectManager,\n  handleProjectManagerCreate,\n  deleteProjectManager,\n  handleProjectManagerDelete,\n};\n"
  },
  {
    "path": "client/src/actions/projects.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst searchProjects = (value) => ({\n  type: ActionTypes.PROJECTS_SEARCH,\n  payload: {\n    value,\n  },\n});\n\nconst updateProjectsOrder = (value) => ({\n  type: ActionTypes.PROJECTS_ORDER_UPDATE,\n  payload: {\n    value,\n  },\n});\n\nconst toggleHiddenProjects = (isVisible) => ({\n  type: ActionTypes.HIDDEN_PROJECTS_TOGGLE,\n  payload: {\n    isVisible,\n  },\n});\n\nconst createProject = (data) => ({\n  type: ActionTypes.PROJECT_CREATE,\n  payload: {\n    data,\n  },\n});\n\ncreateProject.success = (project, projectManagers) => ({\n  type: ActionTypes.PROJECT_CREATE__SUCCESS,\n  payload: {\n    project,\n    projectManagers,\n  },\n});\n\ncreateProject.failure = (error) => ({\n  type: ActionTypes.PROJECT_CREATE__FAILURE,\n  payload: {\n    error,\n  },\n});\n\nconst handleProjectCreate = (\n  project,\n  users,\n  projectManagers,\n  backgroundImages,\n  baseCustomFieldGroups,\n  boards,\n  boardMemberships,\n  customFields,\n  notificationServices,\n) => ({\n  type: ActionTypes.PROJECT_CREATE_HANDLE,\n  payload: {\n    project,\n    users,\n    projectManagers,\n    backgroundImages,\n    baseCustomFieldGroups,\n    boards,\n    boardMemberships,\n    customFields,\n    notificationServices,\n  },\n});\n\nconst updateProject = (id, data) => ({\n  type: ActionTypes.PROJECT_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateProject.success = (project) => ({\n  type: ActionTypes.PROJECT_UPDATE__SUCCESS,\n  payload: {\n    project,\n  },\n});\n\nupdateProject.failure = (id, error) => ({\n  type: ActionTypes.PROJECT_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleProjectUpdate = (\n  project,\n  boardIds,\n  isAvailable,\n  board,\n  users,\n  projectManagers,\n  backgroundImages,\n  baseCustomFieldGroups,\n  boards,\n  boardMemberships,\n  labels,\n  lists,\n  cards,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n  notificationsToDelete,\n  notificationServices,\n) => ({\n  type: ActionTypes.PROJECT_UPDATE_HANDLE,\n  payload: {\n    project,\n    boardIds,\n    isAvailable,\n    board,\n    users,\n    projectManagers,\n    backgroundImages,\n    baseCustomFieldGroups,\n    boards,\n    boardMemberships,\n    labels,\n    lists,\n    cards,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n    notificationsToDelete,\n    notificationServices,\n  },\n});\n\nconst deleteProject = (id) => ({\n  type: ActionTypes.PROJECT_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteProject.success = (project) => ({\n  type: ActionTypes.PROJECT_DELETE__SUCCESS,\n  payload: {\n    project,\n  },\n});\n\ndeleteProject.failure = (id, error) => ({\n  type: ActionTypes.PROJECT_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleProjectDelete = (project) => ({\n  type: ActionTypes.PROJECT_DELETE_HANDLE,\n  payload: {\n    project,\n  },\n});\n\nexport default {\n  searchProjects,\n  updateProjectsOrder,\n  toggleHiddenProjects,\n  createProject,\n  handleProjectCreate,\n  updateProject,\n  handleProjectUpdate,\n  deleteProject,\n  handleProjectDelete,\n};\n"
  },
  {
    "path": "client/src/actions/router.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst handleLocationChange = (\n  pathname,\n  currentBoardId,\n  currentCardId,\n  isEditModeEnabled,\n  board,\n  users,\n  projects,\n  boardMemberships,\n  labels,\n  lists,\n  cards,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n  notificationsToDelete,\n) => ({\n  type: ActionTypes.LOCATION_CHANGE_HANDLE,\n  payload: {\n    pathname,\n    currentBoardId,\n    currentCardId,\n    isEditModeEnabled,\n    board,\n    users,\n    projects,\n    boardMemberships,\n    labels,\n    lists,\n    cards,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n    notificationsToDelete,\n  },\n});\n\nhandleLocationChange.fetchContent = () => ({\n  type: ActionTypes.LOCATION_CHANGE_HANDLE__CONTENT_FETCH,\n  payload: {},\n});\n\nhandleLocationChange.fetchBoard = (id) => ({\n  type: ActionTypes.LOCATION_CHANGE_HANDLE__BOARD_FETCH,\n  payload: {\n    id,\n  },\n});\n\nexport default {\n  handleLocationChange,\n};\n"
  },
  {
    "path": "client/src/actions/socket.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst handleSocketDisconnect = () => ({\n  type: ActionTypes.SOCKET_DISCONNECT_HANDLE,\n  payload: {},\n});\n\nconst handleSocketReconnect = (\n  bootstrap,\n  config,\n  user,\n  board,\n  webhooks,\n  users,\n  projects,\n  projectManagers,\n  backgroundImages,\n  baseCustomFieldGroups,\n  boards,\n  boardMemberships,\n  labels,\n  lists,\n  cards,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n  notifications,\n  notificationServices,\n) => ({\n  type: ActionTypes.SOCKET_RECONNECT_HANDLE,\n  payload: {\n    bootstrap,\n    config,\n    user,\n    board,\n    webhooks,\n    users,\n    projects,\n    projectManagers,\n    backgroundImages,\n    baseCustomFieldGroups,\n    boards,\n    boardMemberships,\n    labels,\n    lists,\n    cards,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n    notifications,\n    notificationServices,\n  },\n});\n\nhandleSocketReconnect.fetchCore = (currentUserId, currentBoardId) => ({\n  type: ActionTypes.SOCKET_RECONNECT_HANDLE__CORE_FETCH,\n  payload: {\n    currentUserId,\n    currentBoardId,\n  },\n});\n\nexport default {\n  handleSocketDisconnect,\n  handleSocketReconnect,\n};\n"
  },
  {
    "path": "client/src/actions/task-lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createTaskList = (taskList) => ({\n  type: ActionTypes.TASK_LIST_CREATE,\n  payload: {\n    taskList,\n  },\n});\n\ncreateTaskList.success = (localId, taskList) => ({\n  type: ActionTypes.TASK_LIST_CREATE__SUCCESS,\n  payload: {\n    localId,\n    taskList,\n  },\n});\n\ncreateTaskList.failure = (localId, error) => ({\n  type: ActionTypes.TASK_LIST_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleTaskListCreate = (taskList) => ({\n  type: ActionTypes.TASK_LIST_CREATE_HANDLE,\n  payload: {\n    taskList,\n  },\n});\n\nconst updateTaskList = (id, data) => ({\n  type: ActionTypes.TASK_LIST_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateTaskList.success = (taskList) => ({\n  type: ActionTypes.TASK_LIST_UPDATE__SUCCESS,\n  payload: {\n    taskList,\n  },\n});\n\nupdateTaskList.failure = (id, error) => ({\n  type: ActionTypes.TASK_LIST_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleTaskListUpdate = (taskList) => ({\n  type: ActionTypes.TASK_LIST_UPDATE_HANDLE,\n  payload: {\n    taskList,\n  },\n});\n\nconst deleteTaskList = (id) => ({\n  type: ActionTypes.TASK_LIST_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteTaskList.success = (taskList) => ({\n  type: ActionTypes.TASK_LIST_DELETE__SUCCESS,\n  payload: {\n    taskList,\n  },\n});\n\ndeleteTaskList.failure = (id, error) => ({\n  type: ActionTypes.TASK_LIST_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleTaskListDelete = (taskList) => ({\n  type: ActionTypes.TASK_LIST_DELETE_HANDLE,\n  payload: {\n    taskList,\n  },\n});\n\nexport default {\n  createTaskList,\n  handleTaskListCreate,\n  updateTaskList,\n  handleTaskListUpdate,\n  deleteTaskList,\n  handleTaskListDelete,\n};\n"
  },
  {
    "path": "client/src/actions/tasks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createTask = (task) => ({\n  type: ActionTypes.TASK_CREATE,\n  payload: {\n    task,\n  },\n});\n\ncreateTask.success = (localId, task) => ({\n  type: ActionTypes.TASK_CREATE__SUCCESS,\n  payload: {\n    localId,\n    task,\n  },\n});\n\ncreateTask.failure = (localId, error) => ({\n  type: ActionTypes.TASK_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleTaskCreate = (task) => ({\n  type: ActionTypes.TASK_CREATE_HANDLE,\n  payload: {\n    task,\n  },\n});\n\nconst updateTask = (id, data) => ({\n  type: ActionTypes.TASK_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateTask.success = (task) => ({\n  type: ActionTypes.TASK_UPDATE__SUCCESS,\n  payload: {\n    task,\n  },\n});\n\nupdateTask.failure = (id, error) => ({\n  type: ActionTypes.TASK_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleTaskUpdate = (task) => ({\n  type: ActionTypes.TASK_UPDATE_HANDLE,\n  payload: {\n    task,\n  },\n});\n\nconst deleteTask = (id) => ({\n  type: ActionTypes.TASK_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteTask.success = (task) => ({\n  type: ActionTypes.TASK_DELETE__SUCCESS,\n  payload: {\n    task,\n  },\n});\n\ndeleteTask.failure = (id, error) => ({\n  type: ActionTypes.TASK_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleTaskDelete = (task) => ({\n  type: ActionTypes.TASK_DELETE_HANDLE,\n  payload: {\n    task,\n  },\n});\n\nexport default {\n  createTask,\n  handleTaskCreate,\n  updateTask,\n  handleTaskUpdate,\n  deleteTask,\n  handleTaskDelete,\n};\n"
  },
  {
    "path": "client/src/actions/users.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst handleUsersReset = (users) => ({\n  type: ActionTypes.USERS_RESET_HANDLE,\n  payload: {\n    users,\n  },\n});\n\nconst createUser = (data) => ({\n  type: ActionTypes.USER_CREATE,\n  payload: {\n    data,\n  },\n});\n\ncreateUser.success = (user) => ({\n  type: ActionTypes.USER_CREATE__SUCCESS,\n  payload: {\n    user,\n  },\n});\n\ncreateUser.failure = (error) => ({\n  type: ActionTypes.USER_CREATE__FAILURE,\n  payload: {\n    error,\n  },\n});\n\nconst handleUserCreate = (user) => ({\n  type: ActionTypes.USER_CREATE_HANDLE,\n  payload: {\n    user,\n  },\n});\n\nconst clearUserCreateError = () => ({\n  type: ActionTypes.USER_CREATE_ERROR_CLEAR,\n  payload: {},\n});\n\nconst updateUser = (id, data) => ({\n  type: ActionTypes.USER_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateUser.success = (user) => ({\n  type: ActionTypes.USER_UPDATE__SUCCESS,\n  payload: {\n    user,\n  },\n});\n\nupdateUser.failure = (id, error) => ({\n  type: ActionTypes.USER_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleUserUpdate = (\n  user,\n  projectIds,\n  boardIds,\n  bootstrap,\n  config,\n  board,\n  webhooks,\n  users,\n  projects,\n  projectManagers,\n  backgroundImages,\n  baseCustomFieldGroups,\n  boards,\n  boardMemberships,\n  labels,\n  lists,\n  cards,\n  cardMemberships,\n  cardLabels,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n  notificationsToDelete,\n  notificationServices,\n) => ({\n  type: ActionTypes.USER_UPDATE_HANDLE,\n  payload: {\n    user,\n    projectIds,\n    boardIds,\n    bootstrap,\n    config,\n    board,\n    webhooks,\n    users,\n    projects,\n    projectManagers,\n    backgroundImages,\n    baseCustomFieldGroups,\n    boards,\n    boardMemberships,\n    labels,\n    lists,\n    cards,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n    notificationsToDelete,\n    notificationServices,\n  },\n});\n\nconst updateUserEmail = (id, data) => ({\n  type: ActionTypes.USER_EMAIL_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateUserEmail.success = (user) => ({\n  type: ActionTypes.USER_EMAIL_UPDATE__SUCCESS,\n  payload: {\n    user,\n  },\n});\n\nupdateUserEmail.failure = (id, error) => ({\n  type: ActionTypes.USER_EMAIL_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst clearUserEmailUpdateError = (id) => ({\n  type: ActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR,\n  payload: {\n    id,\n  },\n});\n\nconst updateUserPassword = (id, data) => ({\n  type: ActionTypes.USER_PASSWORD_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateUserPassword.success = (user, accessToken) => ({\n  type: ActionTypes.USER_PASSWORD_UPDATE__SUCCESS,\n  payload: {\n    user,\n    accessToken,\n  },\n});\n\nupdateUserPassword.failure = (id, error) => ({\n  type: ActionTypes.USER_PASSWORD_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst clearUserPasswordUpdateError = (id) => ({\n  type: ActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR,\n  payload: {\n    id,\n  },\n});\n\nconst updateUserUsername = (id, data) => ({\n  type: ActionTypes.USER_USERNAME_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateUserUsername.success = (user) => ({\n  type: ActionTypes.USER_USERNAME_UPDATE__SUCCESS,\n  payload: {\n    user,\n  },\n});\n\nupdateUserUsername.failure = (id, error) => ({\n  type: ActionTypes.USER_USERNAME_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst clearUserUsernameUpdateError = (id) => ({\n  type: ActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR,\n  payload: {\n    id,\n  },\n});\n\nconst updateUserAvatar = (id) => ({\n  type: ActionTypes.USER_AVATAR_UPDATE,\n  payload: {\n    id,\n  },\n});\n\nupdateUserAvatar.success = (user) => ({\n  type: ActionTypes.USER_AVATAR_UPDATE__SUCCESS,\n  payload: {\n    user,\n  },\n});\n\nupdateUserAvatar.failure = (id, error) => ({\n  type: ActionTypes.USER_AVATAR_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst createUserApiKey = (id) => ({\n  type: ActionTypes.USER_API_KEY_CREATE,\n  payload: {\n    id,\n  },\n});\n\ncreateUserApiKey.success = (user, apiKey) => ({\n  type: ActionTypes.USER_API_KEY_CREATE__SUCCESS,\n  payload: {\n    user,\n    apiKey,\n  },\n});\n\ncreateUserApiKey.failure = (id, error) => ({\n  type: ActionTypes.USER_API_KEY_CREATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst deleteUserApiKey = (id) => ({\n  type: ActionTypes.USER_API_KEY_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteUserApiKey.success = (user) => ({\n  type: ActionTypes.USER_API_KEY_DELETE__SUCCESS,\n  payload: {\n    user,\n  },\n});\n\ndeleteUserApiKey.failure = (id, error) => ({\n  type: ActionTypes.USER_API_KEY_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst clearUserApiKeyValue = (id) => ({\n  type: ActionTypes.USER_API_KEY_VALUE_CLEAR,\n  payload: {\n    id,\n  },\n});\n\nconst deleteUser = (id) => ({\n  type: ActionTypes.USER_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteUser.success = (user) => ({\n  type: ActionTypes.USER_DELETE__SUCCESS,\n  payload: {\n    user,\n  },\n});\n\ndeleteUser.failure = (id, error) => ({\n  type: ActionTypes.USER_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleUserDelete = (user) => ({\n  type: ActionTypes.USER_DELETE_HANDLE,\n  payload: {\n    user,\n  },\n});\n\nconst addUserToCard = (id, cardId, isCurrent) => ({\n  type: ActionTypes.USER_TO_CARD_ADD,\n  payload: {\n    id,\n    cardId,\n    isCurrent,\n  },\n});\n\naddUserToCard.success = (cardMembership) => ({\n  type: ActionTypes.USER_TO_CARD_ADD__SUCCESS,\n  payload: {\n    cardMembership,\n  },\n});\n\naddUserToCard.failure = (id, cardId, error) => ({\n  type: ActionTypes.USER_TO_CARD_ADD__FAILURE,\n  payload: {\n    id,\n    cardId,\n    error,\n  },\n});\n\nconst handleUserToCardAdd = (cardMembership) => ({\n  type: ActionTypes.USER_TO_CARD_ADD_HANDLE,\n  payload: {\n    cardMembership,\n  },\n});\n\nconst removeUserFromCard = (id, cardId) => ({\n  type: ActionTypes.USER_FROM_CARD_REMOVE,\n  payload: {\n    id,\n    cardId,\n  },\n});\n\nremoveUserFromCard.success = (cardMembership) => ({\n  type: ActionTypes.USER_FROM_CARD_REMOVE__SUCCESS,\n  payload: {\n    cardMembership,\n  },\n});\n\nremoveUserFromCard.failure = (id, cardId, error) => ({\n  type: ActionTypes.USER_FROM_CARD_REMOVE__FAILURE,\n  payload: {\n    id,\n    cardId,\n    error,\n  },\n});\n\nconst handleUserFromCardRemove = (cardMembership) => ({\n  type: ActionTypes.USER_FROM_CARD_REMOVE_HANDLE,\n  payload: {\n    cardMembership,\n  },\n});\n\nconst addUserToBoardFilter = (id, boardId, replace, currentListId) => ({\n  type: ActionTypes.USER_TO_BOARD_FILTER_ADD,\n  payload: {\n    id,\n    boardId,\n    replace,\n    currentListId,\n  },\n});\n\nconst removeUserFromBoardFilter = (id, boardId, currentListId) => ({\n  type: ActionTypes.USER_FROM_BOARD_FILTER_REMOVE,\n  payload: {\n    id,\n    boardId,\n    currentListId,\n  },\n});\n\nexport default {\n  handleUsersReset,\n  createUser,\n  handleUserCreate,\n  clearUserCreateError,\n  updateUser,\n  handleUserUpdate,\n  updateUserEmail,\n  clearUserEmailUpdateError,\n  updateUserPassword,\n  clearUserPasswordUpdateError,\n  updateUserUsername,\n  clearUserUsernameUpdateError,\n  updateUserAvatar,\n  createUserApiKey,\n  deleteUserApiKey,\n  clearUserApiKeyValue,\n  deleteUser,\n  handleUserDelete,\n  addUserToCard,\n  handleUserToCardAdd,\n  removeUserFromCard,\n  handleUserFromCardRemove,\n  addUserToBoardFilter,\n  removeUserFromBoardFilter,\n};\n"
  },
  {
    "path": "client/src/actions/webhooks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst createWebhook = (webhook) => ({\n  type: ActionTypes.WEBHOOK_CREATE,\n  payload: {\n    webhook,\n  },\n});\n\ncreateWebhook.success = (localId, webhook) => ({\n  type: ActionTypes.WEBHOOK_CREATE__SUCCESS,\n  payload: {\n    localId,\n    webhook,\n  },\n});\n\ncreateWebhook.failure = (localId, error) => ({\n  type: ActionTypes.WEBHOOK_CREATE__FAILURE,\n  payload: {\n    localId,\n    error,\n  },\n});\n\nconst handleWebhookCreate = (webhook) => ({\n  type: ActionTypes.WEBHOOK_CREATE_HANDLE,\n  payload: {\n    webhook,\n  },\n});\n\nconst updateWebhook = (id, data) => ({\n  type: ActionTypes.WEBHOOK_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nupdateWebhook.success = (webhook) => ({\n  type: ActionTypes.WEBHOOK_UPDATE__SUCCESS,\n  payload: {\n    webhook,\n  },\n});\n\nupdateWebhook.failure = (id, error) => ({\n  type: ActionTypes.WEBHOOK_UPDATE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleWebhookUpdate = (webhook) => ({\n  type: ActionTypes.WEBHOOK_UPDATE_HANDLE,\n  payload: {\n    webhook,\n  },\n});\n\nconst deleteWebhook = (id) => ({\n  type: ActionTypes.WEBHOOK_DELETE,\n  payload: {\n    id,\n  },\n});\n\ndeleteWebhook.success = (webhook) => ({\n  type: ActionTypes.WEBHOOK_DELETE__SUCCESS,\n  payload: {\n    webhook,\n  },\n});\n\ndeleteWebhook.failure = (id, error) => ({\n  type: ActionTypes.WEBHOOK_DELETE__FAILURE,\n  payload: {\n    id,\n    error,\n  },\n});\n\nconst handleWebhookDelete = (webhook) => ({\n  type: ActionTypes.WEBHOOK_DELETE_HANDLE,\n  payload: {\n    webhook,\n  },\n});\n\nexport default {\n  createWebhook,\n  handleWebhookCreate,\n  updateWebhook,\n  handleWebhookUpdate,\n  deleteWebhook,\n  handleWebhookDelete,\n};\n"
  },
  {
    "path": "client/src/api/access-tokens.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport http from './http';\n\n/* Actions */\n\nconst createAccessToken = (data, headers) =>\n  http.post('/access-tokens?withHttpOnlyToken=true', data, headers);\n\nconst exchangeForAccessTokenWithOidc = (data, headers) =>\n  http.post('/access-tokens/exchange-with-oidc?withHttpOnlyToken=true', data, headers);\n\nconst debugOidc = (data, headers) => http.post('/access-tokens/debug-oidc', data, headers);\n\n// TODO: rename?\nconst acceptTerms = (data, headers) => http.post('/access-tokens/accept-terms', data, headers);\n\nconst revokePendingToken = (data, headers) =>\n  http.post('/access-tokens/revoke-pending-token', data, headers);\n\nconst deleteCurrentAccessToken = (headers) => http.delete('/access-tokens/me', undefined, headers);\n\nexport default {\n  createAccessToken,\n  exchangeForAccessTokenWithOidc,\n  debugOidc,\n  acceptTerms,\n  revokePendingToken,\n  deleteCurrentAccessToken,\n};\n"
  },
  {
    "path": "client/src/api/activities.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Transformers */\n\nexport const transformActivity = (activity) => ({\n  ...activity,\n  ...(activity.createdAt && {\n    createdAt: new Date(activity.createdAt),\n  }),\n});\n\n/* Actions */\n\nconst getBoardActivities = (boardId, data, headers) =>\n  socket.get(`/boards/${boardId}/actions`, data, headers).then((body) => ({\n    ...body,\n    items: body.items.map(transformActivity),\n  }));\n\nconst getCardActivities = (cardId, data, headers) =>\n  socket.get(`/cards/${cardId}/actions`, data, headers).then((body) => ({\n    ...body,\n    items: body.items.map(transformActivity),\n  }));\n\n/* Event handlers */\n\nconst makeHandleActivityCreate = (next) => (body) => {\n  next({\n    ...body,\n    item: transformActivity(body.item),\n  });\n};\n\nexport default {\n  getBoardActivities,\n  getCardActivities,\n  makeHandleActivityCreate,\n};\n"
  },
  {
    "path": "client/src/api/attachments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport http from './http';\nimport socket from './socket';\n\n/* Transformers */\n\nexport const transformAttachment = (attachment) => ({\n  ...attachment,\n  ...(attachment.createdAt && {\n    createdAt: new Date(attachment.createdAt),\n  }),\n});\n\n/* Actions */\n\nconst createAttachment = (cardId, data, headers) =>\n  socket.post(`/cards/${cardId}/attachments`, data, headers).then((body) => ({\n    ...body,\n    item: transformAttachment(body.item),\n  }));\n\nconst createAttachmentWithFile = (cardId, { file, ...data }, requestId, headers) =>\n  http\n    .post(\n      `/cards/${cardId}/attachments?requestId=${requestId}`,\n      {\n        ...data,\n        file,\n      },\n      headers,\n    )\n    .then((body) => ({\n      ...body,\n      item: transformAttachment(body.item),\n    }));\n\nconst updateAttachment = (id, data, headers) =>\n  socket.patch(`/attachments/${id}`, data, headers).then((body) => ({\n    ...body,\n    item: transformAttachment(body.item),\n  }));\n\nconst deleteAttachment = (id, headers) =>\n  socket.delete(`/attachments/${id}`, undefined, headers).then((body) => ({\n    ...body,\n    item: transformAttachment(body.item),\n  }));\n\n/* Event handlers */\n\nconst makeHandleAttachmentCreate = (next) => (body) => {\n  next({\n    ...body,\n    item: transformAttachment(body.item),\n  });\n};\n\nconst makeHandleAttachmentUpdate = makeHandleAttachmentCreate;\n\nconst makeHandleAttachmentDelete = makeHandleAttachmentCreate;\n\nexport default {\n  createAttachment,\n  createAttachmentWithFile,\n  updateAttachment,\n  deleteAttachment,\n  makeHandleAttachmentCreate,\n  makeHandleAttachmentUpdate,\n  makeHandleAttachmentDelete,\n};\n"
  },
  {
    "path": "client/src/api/background-images.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport http from './http';\nimport socket from './socket';\n\n/* Actions */\n\nconst createBackgroundImage = (projectId, { file, ...data }, requestId, headers) =>\n  http.post(\n    `/projects/${projectId}/background-images?requestId=${requestId}`,\n    {\n      ...data,\n      file,\n    },\n    headers,\n  );\n\nconst deleteBackgroundImage = (id, headers) =>\n  socket.delete(`/background-images/${id}`, undefined, headers);\n\nexport default {\n  createBackgroundImage,\n  deleteBackgroundImage,\n};\n"
  },
  {
    "path": "client/src/api/base-custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst createBaseCustomFieldGroup = (projectId, data, headers) =>\n  socket.post(`/projects/${projectId}/base-custom-field-groups`, data, headers);\n\nconst updateBaseCustomFieldGroup = (id, data, headers) =>\n  socket.patch(`/base-custom-field-groups/${id}`, data, headers);\n\nconst deleteBaseCustomFieldGroup = (id, headers) =>\n  socket.delete(`/base-custom-field-groups/${id}`, undefined, headers);\n\nexport default {\n  createBaseCustomFieldGroup,\n  updateBaseCustomFieldGroup,\n  deleteBaseCustomFieldGroup,\n};\n"
  },
  {
    "path": "client/src/api/board-memberships.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst createBoardMembership = (boardId, data, headers) =>\n  socket.post(`/boards/${boardId}/board-memberships`, data, headers);\n\nconst updateBoardMembership = (id, data, headers) =>\n  socket.patch(`/board-memberships/${id}`, data, headers);\n\nconst deleteBoardMembership = (id, headers) =>\n  socket.delete(`/board-memberships/${id}`, undefined, headers);\n\nexport default {\n  createBoardMembership,\n  updateBoardMembership,\n  deleteBoardMembership,\n};\n"
  },
  {
    "path": "client/src/api/boards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport http from './http';\nimport socket from './socket';\nimport { transformCard } from './cards';\nimport { transformAttachment } from './attachments';\n\n/* Actions */\n\nconst createBoard = (projectId, data, headers) =>\n  socket.post(`/projects/${projectId}/boards`, data, headers);\n\nconst createBoardWithImport = (projectId, data, requestId, headers) =>\n  http.post(`/projects/${projectId}/boards?requestId=${requestId}`, data, headers);\n\nconst getBoard = (id, subscribe, headers) =>\n  socket\n    .get(`/boards/${id}${subscribe ? '?subscribe=true' : ''}`, undefined, headers)\n    .then((body) => ({\n      ...body,\n      included: {\n        ...body.included,\n        cards: body.included.cards.map(transformCard),\n        attachments: body.included.attachments.map(transformAttachment),\n      },\n    }));\n\nconst updateBoard = (id, data, headers) => socket.patch(`/boards/${id}`, data, headers);\n\nconst deleteBoard = (id, headers) => socket.delete(`/boards/${id}`, undefined, headers);\n\nexport default {\n  createBoard,\n  createBoardWithImport,\n  getBoard,\n  updateBoard,\n  deleteBoard,\n};\n"
  },
  {
    "path": "client/src/api/bootstrap.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport http from './http';\n\n/* Actions */\n\nconst getBootstrap = (headers) => http.get('/bootstrap', undefined, headers);\n\nexport default {\n  getBootstrap,\n};\n"
  },
  {
    "path": "client/src/api/card-labels.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst createCardLabel = (cardId, data, headers) =>\n  socket.post(`/cards/${cardId}/card-labels`, data, headers);\n\nconst deleteCardLabel = (cardId, labelId, headers) =>\n  socket.delete(`/cards/${cardId}/card-labels/labelId:${labelId}`, undefined, headers);\n\nexport default {\n  createCardLabel,\n  deleteCardLabel,\n};\n"
  },
  {
    "path": "client/src/api/card-memberships.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst createCardMembership = (cardId, data, headers) =>\n  socket.post(`/cards/${cardId}/card-memberships`, data, headers);\n\nconst deleteCardMembership = (cardId, userId, headers) =>\n  socket.delete(`/cards/${cardId}/card-memberships/userId:${userId}`, undefined, headers);\n\nexport default {\n  createCardMembership,\n  deleteCardMembership,\n};\n"
  },
  {
    "path": "client/src/api/cards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport omit from 'lodash/omit';\n\nimport socket from './socket';\nimport { transformAttachment } from './attachments';\nimport { transformActivity } from './activities';\nimport { transformNotification } from './notifications';\n\n/* Transformers */\n\nexport const transformCard = (card) => ({\n  ...card,\n  ...(card.dueDate && {\n    dueDate: new Date(card.dueDate),\n  }),\n  ...(card.stopwatch && {\n    stopwatch: {\n      ...card.stopwatch,\n      ...(card.stopwatch.startedAt && {\n        startedAt: new Date(card.stopwatch.startedAt),\n      }),\n    },\n  }),\n  ...(card.createdAt && {\n    createdAt: new Date(card.createdAt),\n  }),\n  ...(card.listChangedAt && {\n    listChangedAt: new Date(card.listChangedAt),\n  }),\n});\n\nexport const transformCardData = (data) => ({\n  ...data,\n  ...(data.dueDate && {\n    dueDate: data.dueDate.toISOString(),\n  }),\n  ...(data.stopwatch && {\n    stopwatch: {\n      ...data.stopwatch,\n      ...(data.stopwatch.startedAt && {\n        startedAt: data.stopwatch.startedAt.toISOString(),\n      }),\n    },\n  }),\n});\n\n/* Actions */\n\nconst getCards = (listId, data, headers) =>\n  socket.get(`/lists/${listId}/cards`, data, headers).then((body) => ({\n    ...body,\n    items: body.items.map(transformCard),\n    included: {\n      ...body.included,\n      attachments: body.included.attachments.map(transformAttachment),\n    },\n  }));\n\nconst createCard = (listId, data, headers) =>\n  socket.post(`/lists/${listId}/cards`, transformCardData(data), headers).then((body) => ({\n    ...body,\n    item: transformCard(body.item),\n  }));\n\nconst getCard = (id, headers) =>\n  socket.get(`/cards/${id}`, undefined, headers).then((body) => ({\n    ...body,\n    item: transformCard(body.item),\n    included: {\n      ...body.included,\n      attachments: body.included.attachments.map(transformAttachment),\n    },\n  }));\n\nconst updateCard = (id, data, headers) =>\n  socket.patch(`/cards/${id}`, transformCardData(data), headers).then((body) => ({\n    ...body,\n    item: transformCard(body.item),\n  }));\n\nconst duplicateCard = (id, data, headers) =>\n  socket.post(`/cards/${id}/duplicate`, data, headers).then((body) => ({\n    ...body,\n    item: transformCard(body.item),\n    included: {\n      ...body.included,\n      attachments: body.included.attachments.map(transformAttachment),\n    },\n  }));\n\nconst readCardNotifications = (id, headers) =>\n  socket.post(`/cards/${id}/read-notifications`, undefined, headers).then((body) => ({\n    ...body,\n    item: transformCard(body.item),\n    included: {\n      ...body.included,\n      notifications: body.included.notifications.map(transformNotification),\n    },\n  }));\n\nconst deleteCard = (id, headers) =>\n  socket.delete(`/cards/${id}`, undefined, headers).then((body) => ({\n    ...body,\n    item: transformCard(body.item),\n  }));\n\n/* Event handlers */\n\nconst makeHandleCardsUpdate = (next) => (body) => {\n  next({\n    ...body,\n    items: body.items.map(transformCard),\n    included: body.included && {\n      ...omit(body.included, 'actions'),\n      activities: body.included.actions.map(transformActivity),\n    },\n  });\n};\n\nconst makeHandleCardCreate = (next) => (body) => {\n  next({\n    ...body,\n    item: transformCard(body.item),\n  });\n};\n\nconst makeHandleCardUpdate = makeHandleCardCreate;\n\nconst makeHandleCardDelete = makeHandleCardUpdate;\n\nexport default {\n  getCards,\n  createCard,\n  getCard,\n  updateCard,\n  duplicateCard,\n  readCardNotifications,\n  deleteCard,\n  makeHandleCardsUpdate,\n  makeHandleCardCreate,\n  makeHandleCardUpdate,\n  makeHandleCardDelete,\n};\n"
  },
  {
    "path": "client/src/api/comments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Transformers */\n\nexport const transformComment = (comment) => ({\n  ...comment,\n  ...(comment.createdAt && {\n    createdAt: new Date(comment.createdAt),\n  }),\n});\n\n/* Actions */\n\nconst getComments = (cardId, data, headers) =>\n  socket.get(`/cards/${cardId}/comments`, data, headers).then((body) => ({\n    ...body,\n    items: body.items.map(transformComment),\n  }));\n\nconst createComment = (cardId, data, headers) =>\n  socket.post(`/cards/${cardId}/comments`, data, headers).then((body) => ({\n    ...body,\n    item: transformComment(body.item),\n  }));\n\nconst updateComment = (id, data, headers) =>\n  socket.patch(`/comments/${id}`, data, headers).then((body) => ({\n    ...body,\n    item: transformComment(body.item),\n  }));\n\nconst deleteComment = (id, headers) =>\n  socket.delete(`/comments/${id}`, undefined, headers).then((body) => ({\n    ...body,\n    item: transformComment(body.item),\n  }));\n\n/* Event handlers */\n\nconst makeHandleCommentCreate = (next) => (body) => {\n  next({\n    ...body,\n    item: transformComment(body.item),\n  });\n};\n\nconst makeHandleCommentUpdate = makeHandleCommentCreate;\n\nconst makeHandleCommentDelete = makeHandleCommentUpdate;\n\nexport default {\n  getComments,\n  createComment,\n  updateComment,\n  deleteComment,\n  makeHandleCommentCreate,\n  makeHandleCommentUpdate,\n  makeHandleCommentDelete,\n};\n"
  },
  {
    "path": "client/src/api/config.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst getConfig = (headers) => socket.get('/config', undefined, headers);\n\nconst updateConfig = (data, headers) => socket.patch('/config', data, headers);\n\nconst testSmtpConfig = (headers) => socket.post('/config/test-smtp', undefined, headers);\n\nexport default {\n  getConfig,\n  updateConfig,\n  testSmtpConfig,\n};\n"
  },
  {
    "path": "client/src/api/custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst createBoardCustomFieldGroup = (cardId, data, headers) =>\n  socket.post(`/boards/${cardId}/custom-field-groups`, data, headers);\n\nconst createCardCustomFieldGroup = (cardId, data, headers) =>\n  socket.post(`/cards/${cardId}/custom-field-groups`, data, headers);\n\nconst getCustomFieldGroup = (id, headers) =>\n  socket.get(`/custom-field-groups/${id}`, undefined, headers);\n\nconst updateCustomFieldGroup = (id, data, headers) =>\n  socket.patch(`/custom-field-groups/${id}`, data, headers);\n\nconst deleteCustomFieldGroup = (id, headers) =>\n  socket.delete(`/custom-field-groups/${id}`, undefined, headers);\n\nexport default {\n  createBoardCustomFieldGroup,\n  createCardCustomFieldGroup,\n  getCustomFieldGroup,\n  updateCustomFieldGroup,\n  deleteCustomFieldGroup,\n};\n"
  },
  {
    "path": "client/src/api/custom-field-values.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst updateCustomFieldValue = (cardId, customFieldGroupId, customFieldId, data, headers) =>\n  socket.patch(\n    `/cards/${cardId}/custom-field-values/customFieldGroupId:${customFieldGroupId}:customFieldId:${customFieldId}`,\n    data,\n    headers,\n  );\n\nconst deleteCustomFieldValue = (cardId, customFieldGroupId, customFieldId, headers) =>\n  socket.delete(\n    `/cards/${cardId}/custom-field-values/customFieldGroupId:${customFieldGroupId}:customFieldId:${customFieldId}`,\n    undefined,\n    headers,\n  );\n\nexport default {\n  updateCustomFieldValue,\n  deleteCustomFieldValue,\n};\n"
  },
  {
    "path": "client/src/api/custom-fields.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst createCustomFieldInBaseGroup = (baseCustomFieldGroupId, data, headers) =>\n  socket.post(`/base-custom-field-groups/${baseCustomFieldGroupId}/custom-fields`, data, headers);\n\nconst createCustomFieldInGroup = (customFieldGroupId, data, headers) =>\n  socket.post(`/custom-field-groups/${customFieldGroupId}/custom-fields`, data, headers);\n\nconst updateCustomField = (id, data, headers) =>\n  socket.patch(`/custom-fields/${id}`, data, headers);\n\nconst deleteCustomField = (id, headers) =>\n  socket.delete(`/custom-fields/${id}`, undefined, headers);\n\nexport default {\n  createCustomFieldInBaseGroup,\n  createCustomFieldInGroup,\n  updateCustomField,\n  deleteCustomField,\n};\n"
  },
  {
    "path": "client/src/api/http.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Config from '../constants/Config';\n\nconst http = {};\n\n// TODO: add all methods\n['GET', 'POST', 'DELETE'].forEach((method) => {\n  http[method.toLowerCase()] = (url, data, headers) => {\n    const formData =\n      data &&\n      Object.keys(data).reduce((result, key) => {\n        result.append(key, data[key]);\n\n        return result;\n      }, new FormData());\n\n    return fetch(`${Config.BASE_PATH}/api${url}`, {\n      method,\n      headers,\n      body: formData,\n      credentials: 'include',\n    })\n      .then((response) =>\n        response.json().then((body) => ({\n          body,\n          isError: response.status !== 200,\n        })),\n      )\n      .then(({ body, isError }) => {\n        if (isError) {\n          throw body;\n        }\n\n        return body;\n      });\n  };\n});\n\nexport default http;\n"
  },
  {
    "path": "client/src/api/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport http from './http';\nimport socket from './socket';\nimport bootstrap from './bootstrap';\nimport terms from './terms';\nimport accessTokens from './access-tokens';\nimport config from './config';\nimport webhooks from './webhooks';\nimport users from './users';\nimport projects from './projects';\nimport projectManagers from './project-managers';\nimport backgroundImages from './background-images';\nimport baseCustomFieldGroups from './base-custom-field-groups';\nimport boards from './boards';\nimport boardMemberships from './board-memberships';\nimport labels from './labels';\nimport lists from './lists';\nimport cards from './cards';\nimport cardMemberships from './card-memberships';\nimport cardLabels from './card-labels';\nimport taskLists from './task-lists';\nimport tasks from './tasks';\nimport attachments from './attachments';\nimport customFieldGroups from './custom-field-groups';\nimport customFields from './custom-fields';\nimport customFieldValues from './custom-field-values';\nimport comments from './comments';\nimport activities from './activities';\nimport notifications from './notifications';\nimport notificationServices from './notification-services';\n\nexport { http, socket };\n\nexport default {\n  ...bootstrap,\n  ...terms,\n  ...accessTokens,\n  ...config,\n  ...webhooks,\n  ...users,\n  ...projects,\n  ...projectManagers,\n  ...backgroundImages,\n  ...baseCustomFieldGroups,\n  ...boards,\n  ...boardMemberships,\n  ...labels,\n  ...lists,\n  ...cards,\n  ...cardMemberships,\n  ...cardLabels,\n  ...taskLists,\n  ...tasks,\n  ...attachments,\n  ...customFieldGroups,\n  ...customFields,\n  ...customFieldValues,\n  ...comments,\n  ...activities,\n  ...notifications,\n  ...notificationServices,\n};\n"
  },
  {
    "path": "client/src/api/labels.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst createLabel = (boardId, data, headers) =>\n  socket.post(`/boards/${boardId}/labels`, data, headers);\n\nconst updateLabel = (id, data, headers) => socket.patch(`/labels/${id}`, data, headers);\n\nconst deleteLabel = (id, headers) => socket.delete(`/labels/${id}`, undefined, headers);\n\nexport default {\n  createLabel,\n  updateLabel,\n  deleteLabel,\n};\n"
  },
  {
    "path": "client/src/api/lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport omit from 'lodash/omit';\n\nimport socket from './socket';\nimport { transformCard } from './cards';\nimport { transformAttachment } from './attachments';\nimport { transformActivity } from './activities';\n\n/* Actions */\n\nconst createList = (boardId, data, headers) =>\n  socket.post(`/boards/${boardId}/lists`, data, headers);\n\nconst getList = (id, headers) =>\n  socket.get(`/lists/${id}`, undefined, headers).then((body) => ({\n    ...body,\n    included: {\n      ...body.included,\n      cards: body.included.cards.map(transformCard),\n      attachments: body.included.attachments.map(transformAttachment),\n    },\n  }));\n\nconst updateList = (id, data, headers) => socket.patch(`/lists/${id}`, data, headers);\n\nconst sortList = (id, data, headers) =>\n  socket.post(`/lists/${id}/sort`, data, headers).then((body) => ({\n    ...body,\n    included: {\n      ...body.included,\n      cards: body.included.cards.map(transformCard),\n    },\n  }));\n\nconst moveListCards = (id, data, headers) =>\n  socket.post(`/lists/${id}/move-cards`, data, headers).then((body) => ({\n    ...body,\n    included: {\n      ...omit(body.included, 'actions'),\n      cards: body.included.cards.map(transformCard),\n      activities: body.included.actions.map(transformActivity),\n    },\n  }));\n\nconst clearList = (id, headers) => socket.post(`/lists/${id}/clear`, undefined, headers);\n\nconst deleteList = (id, headers) =>\n  socket.delete(`/lists/${id}`, undefined, headers).then((body) => ({\n    ...body,\n    included: {\n      ...body.included,\n      cards: body.included.cards.map(transformCard),\n    },\n  }));\n\n/* Event handlers */\n\nconst makeHandleListDelete = (next) => (body) => {\n  next({\n    ...body,\n    included: {\n      ...body.included,\n      cards: body.included.cards.map(transformCard),\n    },\n  });\n};\n\nexport default {\n  createList,\n  getList,\n  updateList,\n  sortList,\n  moveListCards,\n  clearList,\n  deleteList,\n  makeHandleListDelete,\n};\n"
  },
  {
    "path": "client/src/api/notification-services.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst createUserNotificationService = (userId, data, headers) =>\n  socket.post(`/users/${userId}/notification-services`, data, headers);\n\nconst createBoardNotificationService = (boardId, data, headers) =>\n  socket.post(`/boards/${boardId}/notification-services`, data, headers);\n\nconst updateNotificationService = (id, data, headers) =>\n  socket.patch(`/notification-services/${id}`, data, headers);\n\nconst testNotificationService = (id, headers) =>\n  socket.post(`/notification-services/${id}/test`, undefined, headers);\n\nconst deleteNotificationService = (id, headers) =>\n  socket.delete(`/notification-services/${id}`, undefined, headers);\n\nexport default {\n  createUserNotificationService,\n  createBoardNotificationService,\n  updateNotificationService,\n  testNotificationService,\n  deleteNotificationService,\n};\n"
  },
  {
    "path": "client/src/api/notifications.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport omit from 'lodash/omit';\n\nimport socket from './socket';\n\n/* Transformers */\n\nexport const transformNotification = (notification) => ({\n  ...(notification.actionId\n    ? {\n        ...omit(notification, 'actionId'),\n        activityId: notification.actionId,\n      }\n    : notification),\n  ...(notification.createdAt && {\n    createdAt: new Date(notification.createdAt),\n  }),\n});\n\n/* Actions */\n\nconst getNotifications = (headers) =>\n  socket.get('/notifications', undefined, headers).then((body) => ({\n    ...body,\n    items: body.items.map(transformNotification),\n  }));\n\n/* const getNotification = (id, headers) =>\n  socket.get(`/notifications/${id}`, undefined, headers).then((body) => ({\n    ...body,\n    item: transformNotification(body.item),\n    included: {\n      users: body.included.users.map(transformUser),\n    },\n  })); */\n\nconst updateNotification = (id, data, headers) =>\n  socket.patch(`/notifications/${id}`, data, headers).then((body) => ({\n    ...body,\n    item: transformNotification(body.item),\n  }));\n\nconst readAllNotifications = (headers) =>\n  socket.post('/notifications/read-all', undefined, headers).then((body) => ({\n    ...body,\n    items: body.items.map(transformNotification),\n  }));\n\n/* Event handlers */\n\nconst makeHandleNotificationCreate = (next) => (body) => {\n  next({\n    ...body,\n    item: transformNotification(body.item),\n  });\n};\n\nconst makeHandleNotificationUpdate = makeHandleNotificationCreate;\n\nexport default {\n  getNotifications,\n  // getNotification,\n  updateNotification,\n  readAllNotifications,\n  makeHandleNotificationCreate,\n  makeHandleNotificationUpdate,\n};\n"
  },
  {
    "path": "client/src/api/project-managers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst createProjectManager = (projectId, data, headers) =>\n  socket.post(`/projects/${projectId}/project-managers`, data, headers);\n\nconst deleteProjectManager = (id, headers) =>\n  socket.delete(`/project-managers/${id}`, undefined, headers);\n\nexport default {\n  createProjectManager,\n  deleteProjectManager,\n};\n"
  },
  {
    "path": "client/src/api/projects.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst getProjects = (headers) => socket.get('/projects', undefined, headers);\n\nconst createProject = (data, headers) => socket.post('/projects', data, headers);\n\nconst getProject = (id, headers) => socket.get(`/projects/${id}`, undefined, headers);\n\nconst updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers);\n\nconst deleteProject = (id, headers) => socket.delete(`/projects/${id}`, undefined, headers);\n\nexport default {\n  getProjects,\n  createProject,\n  getProject,\n  updateProject,\n  deleteProject,\n};\n"
  },
  {
    "path": "client/src/api/socket.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socketIOClient from 'socket.io-client';\nimport sailsIOClient from 'sails.io.js';\n\nimport Config from '../constants/Config';\n\nconst io = sailsIOClient(socketIOClient);\n\nio.sails.path = `${Config.BASE_PATH}/socket.io`;\nio.sails.autoConnect = false;\nio.sails.reconnection = true;\nio.sails.useCORSRouteToGetCookie = false;\nio.sails.environment = import.meta.env.MODE;\n\nconst { socket } = io;\n\nsocket.connect = socket._connect; // eslint-disable-line no-underscore-dangle\n\n['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {\n  socket[method.toLowerCase()] = (url, data, headers) =>\n    new Promise((resolve, reject) => {\n      socket.request(\n        {\n          method,\n          data,\n          headers,\n          url: `/api${url}`,\n        },\n        (body, { error }) => {\n          if (body instanceof Error || error) {\n            reject(body);\n          } else {\n            resolve(body);\n          }\n        },\n      );\n    });\n});\n\nexport default socket;\n"
  },
  {
    "path": "client/src/api/task-lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst createTaskList = (cardId, data, headers) =>\n  socket.post(`/cards/${cardId}/task-lists`, data, headers);\n\nconst getTaskList = (id, headers) => socket.get(`/task-lists/${id}`, undefined, headers);\n\nconst updateTaskList = (id, data, headers) => socket.patch(`/task-lists/${id}`, data, headers);\n\nconst deleteTaskList = (id, headers) => socket.delete(`/task-lists/${id}`, undefined, headers);\n\nexport default {\n  createTaskList,\n  getTaskList,\n  updateTaskList,\n  deleteTaskList,\n};\n"
  },
  {
    "path": "client/src/api/tasks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst createTask = (taskListId, data, headers) =>\n  socket.post(`/task-lists/${taskListId}/tasks`, data, headers);\n\nconst updateTask = (id, data, headers) => socket.patch(`/tasks/${id}`, data, headers);\n\nconst deleteTask = (id, headers) => socket.delete(`/tasks/${id}`, undefined, headers);\n\nexport default {\n  createTask,\n  updateTask,\n  deleteTask,\n};\n"
  },
  {
    "path": "client/src/api/terms.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport http from './http';\n\n/* Actions */\n\nconst getTerms = (language, headers) =>\n  http.get(`/terms${language ? `?language=${language}` : ''}`, undefined, headers);\n\nexport default {\n  getTerms,\n};\n"
  },
  {
    "path": "client/src/api/users.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport http from './http';\nimport socket from './socket';\n\n/* Actions */\n\nconst getUsers = (headers) => socket.get('/users', undefined, headers);\n\nconst createUser = (data, headers) => socket.post('/users', data, headers);\n\n/* const getUser = (id, headers) =>\n  socket.get(`/users/${id}`, undefined, headers).then((body) => ({\n    ...body,\n    item: transformUser(body.item),\n  })); */\n\nconst getCurrentUser = (subscribe, headers) =>\n  socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers);\n\nconst updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers);\n\nconst updateUserEmail = (id, data, headers) => socket.patch(`/users/${id}/email`, data, headers);\n\nconst updateUserPassword = (id, data, headers) =>\n  socket.patch(`/users/${id}/password`, data, headers);\n\nconst updateUserUsername = (id, data, headers) =>\n  socket.patch(`/users/${id}/username`, data, headers);\n\nconst updateUserAvatar = (id, data, headers) => http.post(`/users/${id}/avatar`, data, headers);\n\nconst createUserApiKey = (userId, headers) =>\n  socket.post(`/users/${userId}/api-key`, undefined, headers);\n\nconst deleteUser = (id, headers) => socket.delete(`/users/${id}`, undefined, headers);\n\nexport default {\n  getUsers,\n  createUser,\n  // getUser,\n  getCurrentUser,\n  updateUser,\n  updateUserEmail,\n  updateUserPassword,\n  updateUserUsername,\n  updateUserAvatar,\n  createUserApiKey,\n  deleteUser,\n};\n"
  },
  {
    "path": "client/src/api/webhooks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\n\n/* Actions */\n\nconst getWebhooks = (headers) => socket.get('/webhooks', undefined, headers);\n\nconst createWebhook = (data, headers) => socket.post('/webhooks', data, headers);\n\nconst updateWebhook = (id, data, headers) => socket.patch(`/webhooks/${id}`, data, headers);\n\nconst deleteWebhook = (id, headers) => socket.delete(`/webhooks/${id}`, undefined, headers);\n\nexport default {\n  getWebhooks,\n  createWebhook,\n  updateWebhook,\n  deleteWebhook,\n};\n"
  },
  {
    "path": "client/src/assets/docs/whats-new.md",
    "content": "# [2.1.0] - 2026-03-19\n\n### Added\n\n* Support running under subpath\n* Add ability to display card ages\n* Allow exposing Swagger specification\n* Configurable HTTP timeout for OIDC\n\n## [2.0.3] - 2026-03-01\n\n### Fixed\n\n* Improve security by ensuring the outgoing proxy is not accessible from outside\n\n## [2.0.2] - 2026-02-23\n\n### Fixed\n\n* Prevent dropzone from overflowing content\n* Update Gravatar hash algorithm\n* Improve backup and restore scripts\n* Improve installation on Windows and containerized environments\n\n## [2.0.1] - 2026-02-17\n\n### Fixed\n\n* Improve connection reliability after the app is idle\n* Allow loading custom End User Terms of Service\n\n## [2.0.0] - 2026-02-11\n\n### Added\n\n* Mention users in comments\n* Add download button for file attachments\n* Enable strikethrough for cards in closed lists\n* Expand card descriptions\n* Enable copy-to-clipboard for custom fields\n* Include task assignees in member filters\n* Link tasks to cards\n* Open card actions menu on right-click\n* Hide completed tasks\n* Add dedicated button to make projects private\n* Track navigation paths when switching cards\n* Support OAuth callbacks for OIDC\n* Display legal requirements in the app\n* Track storage usage\n* Move lists between boards\n* Restore toggleable due dates\n* Add Gravatar support for avatars\n* Add board setting to expand task lists by default\n* Configure and test SMTP via UI\n* Add API key authentication\n* Add create-board button on the open-board screen\n* Support object-path mapping for OIDC attributes\n* Add basic keyboard shortcuts for cards\n* Enable copy/cut cards with keyboard shortcuts\n* Enhance card actions menu with separators and action bar\n* Display last updates in the About modal\n* Allow unlinking SSO from user accounts\n* Apply color to entire lists instead of only card bottoms\n\n### Changed\n\n* Move webhooks configuration to UI\n* Parse dates as UTC without relying on TZ environment variable\n* Move About and Terms into a separate modal\n* Move infrequent card actions to a more-actions menu\n* Improve error page display\n* Enable favorites panel by default\n* Improve login page appearance\n* Enhance Markdown editor (colors, quote borders, disable fuzzy links)\n* Improve PDF viewer compatibility across browsers\n* Update background color for own comments\n* Improve browser caching for public files and attachments\n* Optimize and parallelize image processing tasks\n* Re-stream static files from S3 with protected access\n* Unify file storage directory\n* Configure proxy for outgoing traffic to prevent SSRF\n\n### Fixed\n\n* Prevent editors from deleting other comments\n* Handle escape actions in mentions input correctly\n* Prevent text overflow in activities\n* Prevent deactivated users from receiving notifications\n* Preserve newlines in markdown with mentions\n* Fix app crash when boards are added before their projects\n* Enable spellcheck on all textareas\n* Fix multiple UI, toolbar, and popup styling issues\n* Limit attachment gallery content to prevent layout issues\n* Correct translations for client, server, and Markdown editor\n* Fix minor UI issues\n\n---\n\n## [2.0.0-rc.4] - 2025-09-04\n\n### Fixed\n\n* Prevent vulnerability where maliciously renamed file attachments could execute JavaScript in the gallery UI\n\n---\n\n## [2.0.0-rc.3] - 2025-05-28\n\n### Added\n\n* Notify users when they are added to a card\n* Emphasize cards in colored and closed lists\n* Track board activity log changes\n* Display total number of comments on cards\n* Add CSV attachment viewer\n* Log actions when a user is removed from a card\n* Log actions when task completion status changes\n* Support Docker secrets\n\n### Changed\n\n* Improve notifications popup appearance\n* Improve card content rendering and styling\n* Limit attachment content display for clarity\n* Increase maximum length of OIDC code challenge\n\n### Fixed\n\n* Fix disabled cards display\n* Correct translations for client, server, and Markdown editor\n* Fix minor UI issues\n\n---\n\n## [2.0.0-rc.2] - 2025-05-10\n\n### Added\n\n* Add global user roles and improve user management\n* Enable user deactivation\n* Support private and shared projects\n* Search projects by name and project groups\n* Add favorite projects with favorites panel\n* Add project descriptions and background image gallery\n* Add list types: Closed, Archive, Trash\n* Add board views: List and Grid\n* Add new Markdown editor\n* Link attachments (attach URLs)\n* Enable quick filter by current user\n* Add board settings modal\n* Subscribe to entire boards\n* Assign users to tasks\n* Support multiple task lists\n* Add more label colors\n* Always display card creator option\n* Show notification badge for board tabs\n* Display message about new version availability\n\n### Changed\n\n* Restrict access to users based on global roles\n* Limit email visibility\n* Make projects page responsive\n* Redesign card appearance\n* Show edit buttons only when needed\n* Use time-ago format for dates\n* Highlight recent cards\n* Improve attachment viewers and syntax highlighting\n* Restyle comments\n* Restyle login page\n* Enable user auto-subscription when commenting\n* Navigate to adjacent cards using arrow keys\n* Open same-site links in current tab\n* Improve card deletion workflow\n* Archive all cards in a closed list with one button\n* Confirm deletion actions\n* Close only active elements when clicking outside\n\n### Fixed\n\n* Prevent deleting the last project manager\n* Prevent deleting projects with existing boards\n"
  },
  {
    "path": "client/src/components/activities/BoardActivitiesModal/BoardActivitiesModal.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { useInView } from 'react-intersection-observer';\nimport { Comment, Loader } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useClosableModal } from '../../../hooks';\nimport Item from './Item';\n\nimport styles from './BoardActivitiesModal.module.scss';\n\nconst BoardActivitiesModal = React.memo(() => {\n  const activityIds = useSelector(selectors.selectActivityIdsForCurrentBoard);\n\n  const { isActivitiesFetching, isAllActivitiesFetched } = useSelector(\n    selectors.selectCurrentBoard,\n  );\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleClose = useCallback(() => {\n    dispatch(entryActions.closeModal());\n  }, [dispatch]);\n\n  const [inViewRef] = useInView({\n    threshold: 1,\n    onChange: (inView) => {\n      if (inView) {\n        dispatch(entryActions.fetchActivitiesInCurrentBoard());\n      }\n    },\n  });\n\n  const [ClosableModal] = useClosableModal();\n\n  return (\n    <ClosableModal closeIcon size=\"small\" centered={false} onClose={handleClose}>\n      <ClosableModal.Header>\n        {t('common.boardActions', {\n          context: 'title',\n        })}\n      </ClosableModal.Header>\n      <ClosableModal.Content>\n        <div className={styles.itemsWrapper}>\n          <Comment.Group className={styles.items}>\n            {activityIds.map((activityId) => (\n              <Item key={activityId} id={activityId} />\n            ))}\n          </Comment.Group>\n        </div>\n        {isActivitiesFetching !== undefined && isAllActivitiesFetched !== undefined && (\n          <div className={styles.loaderWrapper}>\n            {isActivitiesFetching ? (\n              <Loader active inverted inline=\"centered\" size=\"small\" />\n            ) : (\n              !isAllActivitiesFetched && <div ref={inViewRef} />\n            )}\n          </div>\n        )}\n      </ClosableModal.Content>\n    </ClosableModal>\n  );\n});\n\nexport default BoardActivitiesModal;\n"
  },
  {
    "path": "client/src/components/activities/BoardActivitiesModal/BoardActivitiesModal.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .items {\n    max-width: none;\n  }\n\n  .itemsWrapper {\n    margin-top: 12px;\n  }\n\n  .loaderWrapper {\n    margin-top: 10px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/activities/BoardActivitiesModal/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { useTranslation, Trans } from 'react-i18next';\nimport { Link } from 'react-router';\nimport { Comment } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport { isUserStatic } from '../../../utils/record-helpers';\nimport Paths from '../../../constants/Paths';\nimport { ActivityTypes } from '../../../constants/Enums';\nimport TimeAgo from '../../common/TimeAgo';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id }) => {\n  const selectActivityById = useMemo(() => selectors.makeSelectActivityById(), []);\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n\n  const activity = useSelector((state) => selectActivityById(state, id));\n  const user = useSelector((state) => selectUserById(state, activity.userId));\n  const card = useSelector((state) => selectCardById(state, activity.cardId));\n\n  const [t] = useTranslation();\n\n  const userName = isUserStatic(user)\n    ? t(`common.${user.name}`, {\n        context: 'title',\n      })\n    : user.name;\n\n  const cardName = card ? card.name : activity.data.card.name;\n\n  let contentNode;\n  switch (activity.type) {\n    case ActivityTypes.CREATE_CARD: {\n      const { list } = activity.data;\n      const listName = list.name || t(`common.${list.type}`);\n\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userAddedCardToList\"\n          values={{\n            user: userName,\n            card: cardName,\n            list: listName,\n          }}\n        >\n          <span className={styles.author}>{userName}</span>\n          {' added '}\n          <Link to={Paths.CARDS.replace(':id', activity.cardId)}>{cardName}</Link>\n          {' to '}\n          {listName}\n        </Trans>\n      );\n\n      break;\n    }\n    case ActivityTypes.MOVE_CARD: {\n      const { fromList, toList } = activity.data;\n\n      const fromListName = fromList.name || t(`common.${fromList.type}`);\n      const toListName = toList.name || t(`common.${toList.type}`);\n\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userMovedCardFromListToList\"\n          values={{\n            user: userName,\n            card: cardName,\n            fromList: fromListName,\n            toList: toListName,\n          }}\n        >\n          <span className={styles.author}>{userName}</span>\n          {' moved '}\n          <Link to={Paths.CARDS.replace(':id', activity.cardId)}>{cardName}</Link>\n          {' from '}\n          {fromListName}\n          {' to '}\n          {toListName}\n        </Trans>\n      );\n\n      break;\n    }\n    case ActivityTypes.ADD_MEMBER_TO_CARD:\n      contentNode =\n        user.id === activity.data.user.id ? (\n          <Trans\n            i18nKey=\"common.userJoinedCard\"\n            values={{\n              user: userName,\n              card: cardName,\n            }}\n          >\n            <span className={styles.author}>{userName}</span>\n            {' joined '}\n            <Link to={Paths.CARDS.replace(':id', activity.cardId)}>{cardName}</Link>\n          </Trans>\n        ) : (\n          <Trans\n            i18nKey=\"common.userAddedUserToCard\"\n            values={{\n              actorUser: userName,\n              addedUser: activity.data.user.name,\n              card: cardName,\n            }}\n          >\n            <span className={styles.author}>{userName}</span>\n            {' added '}\n            {activity.data.user.name}\n            {' to '}\n            <Link to={Paths.CARDS.replace(':id', activity.cardId)}>{cardName}</Link>\n          </Trans>\n        );\n\n      break;\n    case ActivityTypes.REMOVE_MEMBER_FROM_CARD:\n      contentNode =\n        user.id === activity.data.user.id ? (\n          <Trans\n            i18nKey=\"common.userLeftCard\"\n            values={{\n              user: userName,\n              card: cardName,\n            }}\n          >\n            <span className={styles.author}>{userName}</span>\n            {' left '}\n            <Link to={Paths.CARDS.replace(':id', activity.cardId)}>{cardName}</Link>\n          </Trans>\n        ) : (\n          <Trans\n            i18nKey=\"common.userRemovedUserFromCard\"\n            values={{\n              actorUser: userName,\n              removedUser: activity.data.user.name,\n              card: cardName,\n            }}\n          >\n            <span className={styles.author}>{userName}</span>\n            {' removed '}\n            {activity.data.user.name}\n            {' from '}\n            <Link to={Paths.CARDS.replace(':id', activity.cardId)}>{cardName}</Link>\n          </Trans>\n        );\n\n      break;\n    case ActivityTypes.COMPLETE_TASK:\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userCompletedTaskOnCard\"\n          values={{\n            user: userName,\n            task: activity.data.task.name,\n            card: cardName,\n          }}\n        >\n          <span className={styles.author}>{userName}</span>\n          {' completed '}\n          {activity.data.task.name}\n          {' on '}\n          <Link to={Paths.CARDS.replace(':id', activity.cardId)}>{cardName}</Link>\n        </Trans>\n      );\n\n      break;\n    case ActivityTypes.UNCOMPLETE_TASK:\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userMarkedTaskIncompleteOnCard\"\n          values={{\n            user: userName,\n            task: activity.data.task.name,\n            card: cardName,\n          }}\n        >\n          <span className={styles.author}>{userName}</span>\n          {' marked '}\n          {activity.data.task.name}\n          {' incomplete on '}\n          <Link to={Paths.CARDS.replace(':id', activity.cardId)}>{cardName}</Link>\n        </Trans>\n      );\n\n      break;\n    default:\n      contentNode = null;\n  }\n\n  return (\n    <Comment>\n      <span className={styles.user}>\n        <UserAvatar id={activity.userId} />\n      </span>\n      <div className={styles.content}>\n        <div>{contentNode}</div>\n        <span className={styles.date}>\n          <TimeAgo date={activity.createdAt} />\n        </span>\n      </div>\n    </Comment>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/activities/BoardActivitiesModal/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .author {\n    color: #17394d;\n    font-weight: bold;\n  }\n\n  .content {\n    border-bottom: 1px solid #092d4221;\n    display: inline-block;\n    line-height: 20px;\n    padding-bottom: 14px;\n    vertical-align: top;\n    width: calc(100% - 40px);\n    word-wrap: break-word;\n  }\n\n  .date {\n    color: #6b808c;\n    font-size: 12px;\n  }\n\n  .user {\n    display: inline-block;\n    padding: 4px 8px 0 0;\n    vertical-align: top;\n  }\n}\n"
  },
  {
    "path": "client/src/components/activities/BoardActivitiesModal/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport BoardActivitiesModal from './BoardActivitiesModal';\n\nexport default BoardActivitiesModal;\n"
  },
  {
    "path": "client/src/components/activities/CardActivities/CardActivities.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useInView } from 'react-intersection-observer';\nimport { Comment, Loader } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport Item from './Item';\n\nimport styles from './CardActivities.module.scss';\n\nconst CardActivities = React.memo(() => {\n  const activityIds = useSelector(selectors.selectActivityIdsForCurrentCard);\n  const { isActivitiesFetching, isAllActivitiesFetched } = useSelector(selectors.selectCurrentCard);\n\n  const dispatch = useDispatch();\n\n  const [inViewRef] = useInView({\n    threshold: 1,\n    onChange: (inView) => {\n      if (inView) {\n        dispatch(entryActions.fetchActivitiesInCurrentCard());\n      }\n    },\n  });\n\n  return (\n    <>\n      <div className={styles.itemsWrapper}>\n        <Comment.Group className={styles.items}>\n          {activityIds.map((activityId) => (\n            <Item key={activityId} id={activityId} />\n          ))}\n        </Comment.Group>\n      </div>\n      {isActivitiesFetching !== undefined && isAllActivitiesFetched !== undefined && (\n        <div className={styles.loaderWrapper}>\n          {isActivitiesFetching ? (\n            <Loader active inverted inline=\"centered\" size=\"small\" />\n          ) : (\n            !isAllActivitiesFetched && <div ref={inViewRef} />\n          )}\n        </div>\n      )}\n    </>\n  );\n});\n\nexport default CardActivities;\n"
  },
  {
    "path": "client/src/components/activities/CardActivities/CardActivities.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .items {\n    max-width: none;\n  }\n\n  .itemsWrapper {\n    margin-left: -40px;\n    margin-top: 12px;\n  }\n\n  .loaderWrapper {\n    margin-top: 10px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/activities/CardActivities/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { useTranslation, Trans } from 'react-i18next';\nimport { Comment } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport { isUserStatic } from '../../../utils/record-helpers';\nimport { ActivityTypes } from '../../../constants/Enums';\nimport TimeAgo from '../../common/TimeAgo';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id }) => {\n  const selectActivityById = useMemo(() => selectors.makeSelectActivityById(), []);\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const activity = useSelector((state) => selectActivityById(state, id));\n  const user = useSelector((state) => selectUserById(state, activity.userId));\n\n  const [t] = useTranslation();\n\n  const userName = isUserStatic(user)\n    ? t(`common.${user.name}`, {\n        context: 'title',\n      })\n    : user.name;\n\n  let contentNode;\n  switch (activity.type) {\n    case ActivityTypes.CREATE_CARD: {\n      const { list } = activity.data;\n      const listName = list.name || t(`common.${list.type}`);\n\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userAddedThisCardToList\"\n          values={{\n            user: userName,\n            list: listName,\n          }}\n        >\n          <span className={styles.author}>{userName}</span>\n          {' added this card to '}\n          {listName}\n        </Trans>\n      );\n\n      break;\n    }\n    case ActivityTypes.MOVE_CARD: {\n      const { fromList, toList } = activity.data;\n\n      const fromListName = fromList.name || t(`common.${fromList.type}`);\n      const toListName = toList.name || t(`common.${toList.type}`);\n\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userMovedThisCardFromListToList\"\n          values={{\n            user: userName,\n            fromList: fromListName,\n            toList: toListName,\n          }}\n        >\n          <span className={styles.author}>{userName}</span>\n          {' moved this card from '}\n          {fromListName}\n          {' to '}\n          {toListName}\n        </Trans>\n      );\n\n      break;\n    }\n    case ActivityTypes.ADD_MEMBER_TO_CARD:\n      contentNode =\n        user.id === activity.data.user.id ? (\n          <Trans\n            i18nKey=\"common.userJoinedThisCard\"\n            values={{\n              user: userName,\n            }}\n          >\n            <span className={styles.author}>{userName}</span>\n            {' joined this card'}\n          </Trans>\n        ) : (\n          <Trans\n            i18nKey=\"common.userAddedUserToThisCard\"\n            values={{\n              actorUser: userName,\n              addedUser: activity.data.user.name,\n            }}\n          >\n            <span className={styles.author}>{userName}</span>\n            {' added '}\n            {activity.data.user.name}\n            {' to this card'}\n          </Trans>\n        );\n\n      break;\n    case ActivityTypes.REMOVE_MEMBER_FROM_CARD:\n      contentNode =\n        user.id === activity.data.user.id ? (\n          <Trans\n            i18nKey=\"common.userLeftThisCard\"\n            values={{\n              user: userName,\n            }}\n          >\n            <span className={styles.author}>{userName}</span>\n            {' left this card'}\n          </Trans>\n        ) : (\n          <Trans\n            i18nKey=\"common.userRemovedUserFromThisCard\"\n            values={{\n              actorUser: userName,\n              removedUser: activity.data.user.name,\n            }}\n          >\n            <span className={styles.author}>{userName}</span>\n            {' removed '}\n            {activity.data.user.name}\n            {' from this card'}\n          </Trans>\n        );\n\n      break;\n    case ActivityTypes.COMPLETE_TASK:\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userCompletedTaskOnThisCard\"\n          values={{\n            user: userName,\n            task: activity.data.task.name,\n          }}\n        >\n          <span className={styles.author}>{userName}</span>\n          {' completed '}\n          {activity.data.task.name}\n          {' on this card'}\n        </Trans>\n      );\n\n      break;\n    case ActivityTypes.UNCOMPLETE_TASK:\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userMarkedTaskIncompleteOnThisCard\"\n          values={{\n            user: userName,\n            task: activity.data.task.name,\n          }}\n        >\n          <span className={styles.author}>{userName}</span>\n          {' marked '}\n          {activity.data.task.name}\n          {' incomplete on this card'}\n        </Trans>\n      );\n\n      break;\n    default:\n      contentNode = null;\n  }\n\n  return (\n    <Comment>\n      <span className={styles.user}>\n        <UserAvatar id={activity.userId} />\n      </span>\n      <div className={styles.content}>\n        <div>{contentNode}</div>\n        <span className={styles.date}>\n          <TimeAgo date={activity.createdAt} />\n        </span>\n      </div>\n    </Comment>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/activities/CardActivities/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .author {\n    color: #17394d;\n    font-weight: bold;\n  }\n\n  .content {\n    border-bottom: 1px solid #092d4221;\n    display: inline-block;\n    line-height: 20px;\n    padding-bottom: 14px;\n    vertical-align: top;\n    width: calc(100% - 40px);\n    word-wrap: break-word;\n  }\n\n  .date {\n    color: #6b808c;\n    font-size: 12px;\n  }\n\n  .user {\n    display: inline-block;\n    padding: 4px 8px 0 0;\n    vertical-align: top;\n  }\n}\n"
  },
  {
    "path": "client/src/components/activities/CardActivities/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport CardActivities from './CardActivities';\n\nexport default CardActivities;\n"
  },
  {
    "path": "client/src/components/attachments/AddAttachmentStep/AddAttachmentStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Menu } from 'semantic-ui-react';\nimport { FilePicker, Popup } from '../../../lib/custom-ui';\n\nimport entryActions from '../../../entry-actions';\nimport { AttachmentTypes } from '../../../constants/Enums';\n\nimport styles from './AddAttachmentStep.module.scss';\n\nconst AddAttachmentStep = React.memo(({ onClose }) => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleFilesSelect = useCallback(\n    (files) => {\n      files.forEach((file) => {\n        dispatch(\n          entryActions.createAttachmentInCurrentCard({\n            file,\n            type: AttachmentTypes.FILE,\n            name: file.name,\n          }),\n        );\n      });\n\n      onClose();\n    },\n    [onClose, dispatch],\n  );\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.addAttachment', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Menu secondary vertical className={styles.menu}>\n          <FilePicker multiple onSelect={handleFilesSelect}>\n            <Menu.Item className={styles.menuItem}>\n              <Icon name=\"computer\" className={styles.menuItemIcon} />\n              {t('common.fromComputer', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          </FilePicker>\n        </Menu>\n        <hr className={styles.divider} />\n        <div className={styles.tip}>\n          {t('common.pressPasteShortcutToAddAttachmentFromClipboard')}\n        </div>\n      </Popup.Content>\n    </>\n  );\n});\n\nAddAttachmentStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default AddAttachmentStep;\n"
  },
  {
    "path": "client/src/components/attachments/AddAttachmentStep/AddAttachmentStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .divider {\n    background: #eee;\n    border: 0;\n    height: 1px;\n    margin-bottom: 8px;\n  }\n\n  .menu {\n    margin: 0 -12px -5px;\n    width: calc(100% + 24px);\n  }\n\n  .menuItem {\n    margin: 0;\n    padding-left: 14px;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.5em 0 0;\n  }\n\n  .tip {\n    opacity: 0.5;\n  }\n}\n"
  },
  {
    "path": "client/src/components/attachments/AddAttachmentStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AddAttachmentStep from './AddAttachmentStep';\n\nexport default AddAttachmentStep;\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/Attachments.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useContext } from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Gallery } from 'react-photoswipe-gallery';\nimport { Button } from 'semantic-ui-react';\nimport { useToggle } from '../../../lib/hooks';\n\nimport selectors from '../../../selectors';\nimport { ClosableContext } from '../../../contexts';\nimport Item from './Item';\n\nimport styles from './Attachments.module.scss';\n\nconst INITIALLY_VISIBLE = 4;\n\nconst Attachments = React.memo(({ hideImagesWhenNotAllVisible }) => {\n  const attachments = useSelector(selectors.selectAttachmentsForCurrentCard);\n\n  const [t] = useTranslation();\n  const [isAllVisible, toggleAllVisible] = useToggle();\n  const [activateClosable, deactivateClosable] = useContext(ClosableContext);\n\n  const handleBeforeGalleryOpen = useCallback(\n    (gallery) => {\n      activateClosable();\n\n      gallery.on('destroy', () => {\n        deactivateClosable();\n      });\n    },\n    [activateClosable, deactivateClosable],\n  );\n\n  const handleToggleAllVisibleClick = useCallback(() => {\n    toggleAllVisible();\n  }, [toggleAllVisible]);\n\n  let visibleTotal = 0;\n\n  const itemsNode = attachments.map((attachment) => {\n    let isVisible = false;\n    if (isAllVisible || visibleTotal < INITIALLY_VISIBLE) {\n      if (\n        isAllVisible ||\n        !hideImagesWhenNotAllVisible ||\n        !attachment.data ||\n        !attachment.data.image\n      ) {\n        visibleTotal += 1;\n        isVisible = true;\n      }\n    }\n\n    return <Item key={attachment.id} id={attachment.id} isVisible={isVisible} />;\n  });\n\n  const hiddenTotal = attachments.length - visibleTotal;\n\n  return (\n    <>\n      <Gallery\n        withCaption\n        withDownloadButton\n        options={{\n          wheelToZoom: true,\n          showHideAnimationType: 'none',\n          closeTitle: '',\n          zoomTitle: '',\n          arrowPrevTitle: '',\n          arrowNextTitle: '',\n          errorMsg: '',\n          paddingFn: (viewportSize) => {\n            const paddingX = viewportSize.x / 20;\n            const paddingY = viewportSize.y / 20;\n\n            return {\n              top: paddingX,\n              bottom: paddingX,\n              left: paddingY,\n              right: paddingY,\n            };\n          },\n        }}\n        onBeforeOpen={handleBeforeGalleryOpen}\n      >\n        {itemsNode}\n      </Gallery>\n      {(isAllVisible ? attachments.length > hiddenTotal : hiddenTotal > 0) && (\n        <Button\n          fluid\n          content={\n            isAllVisible\n              ? t('action.showFewerAttachments')\n              : t('action.showAllAttachments', {\n                  hidden: hiddenTotal,\n                })\n          }\n          className={styles.toggleButton}\n          onClick={handleToggleAllVisibleClick}\n        />\n      )}\n    </>\n  );\n});\n\nAttachments.propTypes = {\n  hideImagesWhenNotAllVisible: PropTypes.bool,\n};\n\nAttachments.defaultProps = {\n  hideImagesWhenNotAllVisible: false,\n};\n\nexport default Attachments;\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/Attachments.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .toggleButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/ContentViewer.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useEffect, useMemo, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport Frame from 'react-frame-component';\nimport { Loader } from 'semantic-ui-react';\nimport syntaxHighlighter from '../../../lib/syntax-highlighter';\n\nimport Markdown from '../../common/Markdown';\n\nimport styles from './ContentViewer.module.scss';\n\nconst Languages = {\n  PLAINTEXT: 'plaintext',\n  MARKDOWN: 'markdown',\n};\n\nconst ContentViewer = React.memo(({ src, filename, className }) => {\n  const [content, setContent] = useState(null);\n\n  const frameStyles = useMemo(\n    () => [\n      ...Array.from(document.styleSheets).flatMap((styleSheet) =>\n        Array.from(styleSheet.cssRules).map((cssRule) => cssRule.cssText),\n      ),\n      'body{background:rgb(248,248,248);min-width:fit-content;overflow-x:visible}',\n      '.frame-content{padding:40px}',\n      '.frame-content>pre{margin:0}',\n      '.hljs{padding:0}',\n      '::-webkit-scrollbar{height:10px}',\n    ],\n    [],\n  );\n\n  const languages = useMemo(\n    () => syntaxHighlighter.detectLanguagesByFilename(filename),\n    [filename],\n  );\n\n  useEffect(() => {\n    async function fetchFile() {\n      await syntaxHighlighter.loadLanguages(languages);\n\n      let response;\n      try {\n        response = await fetch(src, {\n          credentials: 'include',\n        });\n      } catch {\n        return;\n      }\n\n      const text = await response.text();\n      setContent(text);\n    }\n\n    fetchFile();\n  }, [src, languages]);\n\n  if (content === null) {\n    return <Loader active size=\"big\" />;\n  }\n\n  let contentNode;\n  if (languages.includes(Languages.PLAINTEXT)) {\n    contentNode = (\n      <pre>\n        <code>{content}</code>\n      </pre>\n    );\n  } else if (languages.includes(Languages.MARKDOWN)) {\n    contentNode = <Markdown>{content}</Markdown>;\n  } else {\n    const hljsResult = syntaxHighlighter.highlight(content, languages);\n\n    contentNode = (\n      <pre>\n        <code\n          // eslint-disable-next-line react/no-danger\n          dangerouslySetInnerHTML={{ __html: hljsResult.value }}\n          className={`hljs language-${hljsResult.language}`}\n        />\n      </pre>\n    );\n  }\n\n  return (\n    <Frame\n      head={<style>{frameStyles.join('')}</style>}\n      className={classNames(styles.wrapper, className)}\n    >\n      {contentNode}\n    </Frame>\n  );\n});\n\nContentViewer.propTypes = {\n  src: PropTypes.string.isRequired,\n  filename: PropTypes.string.isRequired,\n  className: PropTypes.string,\n};\n\nContentViewer.defaultProps = {\n  className: undefined,\n};\n\nexport default ContentViewer;\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/ContentViewer.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    background: #fff;\n    border: 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/CsvViewer.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport Papa from 'papaparse';\nimport Frame from 'react-frame-component';\nimport { Loader, Pagination, Table } from 'semantic-ui-react';\n\nimport styles from './CsvViewer.module.scss';\n\nconst ROWS_PER_PAGE = 50;\n\nconst CsvViewer = React.memo(({ src, className }) => {\n  const [rows, setRows] = useState(null);\n  const [currentPage, setCurrentPage] = useState(1);\n\n  const frameStyles = useMemo(\n    () => [\n      ...Array.from(document.styleSheets).flatMap((styleSheet) =>\n        Array.from(styleSheet.cssRules).map((cssRule) => cssRule.cssText),\n      ),\n      'body{background:rgb(248,248,248);min-width:fit-content;overflow-x:visible}',\n      '.frame-content{padding:40px}',\n      '.frame-content>pre{margin:0}',\n      '.hljs{padding:0}',\n      '::-webkit-scrollbar{height:10px}',\n      '.ui.pagination.menu{display:flex;justify-content:center;margin-top:20px;padding:10px 0}',\n    ],\n    [],\n  );\n\n  const handlePageChange = useCallback((_, { activePage }) => {\n    setCurrentPage(activePage);\n  }, []);\n\n  useEffect(() => {\n    async function fetchFile() {\n      try {\n        const response = await fetch(src, {\n          credentials: 'include',\n        });\n\n        const text = await response.text();\n\n        Papa.parse(text, {\n          skipEmptyLines: true,\n          complete: ({ data }) => {\n            setRows(data);\n          },\n        });\n      } catch {\n        /* empty */\n      }\n    }\n\n    fetchFile();\n  }, [src]);\n\n  if (rows === null) {\n    return <Loader active size=\"big\" />;\n  }\n\n  const startIndex = (currentPage - 1) * ROWS_PER_PAGE;\n  const endIndex = startIndex + ROWS_PER_PAGE;\n  const currentRows = rows.slice(startIndex, endIndex);\n  const totalPages = Math.ceil(rows.length / ROWS_PER_PAGE);\n\n  return (\n    <Frame\n      head={<style>{frameStyles.join('')}</style>}\n      className={classNames(styles.wrapper, className)}\n    >\n      <div>\n        <Table celled compact>\n          <Table.Header>\n            <Table.Row>\n              {rows[0].map((cell) => (\n                <Table.HeaderCell key={cell}>{cell}</Table.HeaderCell>\n              ))}\n            </Table.Row>\n          </Table.Header>\n          <Table.Body>\n            {currentRows.slice(1).map((row) => (\n              <Table.Row key={row}>\n                {row.map((cell) => (\n                  <Table.Cell key={cell}>{cell}</Table.Cell>\n                ))}\n              </Table.Row>\n            ))}\n          </Table.Body>\n        </Table>\n      </div>\n      {totalPages > 1 && (\n        <Pagination\n          secondary\n          pointing\n          totalPages={totalPages}\n          activePage={currentPage}\n          firstItem={null}\n          lastItem={null}\n          onPageChange={handlePageChange}\n        />\n      )}\n    </Frame>\n  );\n});\n\nCsvViewer.propTypes = {\n  src: PropTypes.string.isRequired,\n  className: PropTypes.string,\n};\n\nCsvViewer.defaultProps = {\n  className: undefined,\n};\n\nexport default CsvViewer;\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/CsvViewer.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    background: #fff;\n    border: 0;\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n  }\n}\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/EditStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef, useSteps } from '../../../hooks';\nimport ConfirmationStep from '../../common/ConfirmationStep';\n\nimport styles from './EditStep.module.scss';\n\nconst StepTypes = {\n  DELETE: 'DELETE',\n};\n\nconst EditStep = React.memo(({ attachmentId, onClose }) => {\n  const selectAttachmentById = useMemo(() => selectors.makeSelectAttachmentById(), []);\n\n  const attachment = useSelector((state) => selectAttachmentById(state, attachmentId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultData = useMemo(\n    () => ({\n      name: attachment.name,\n    }),\n    [attachment.name],\n  );\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: '',\n    ...defaultData,\n  }));\n\n  const [step, openStep, handleBack] = useSteps();\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n    };\n\n    if (!cleanData.name) {\n      nameFieldRef.current.select();\n      return;\n    }\n\n    if (!dequal(cleanData, defaultData)) {\n      dispatch(entryActions.updateAttachment(attachmentId, cleanData));\n    }\n\n    onClose();\n  }, [attachmentId, onClose, dispatch, defaultData, data, nameFieldRef]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteAttachment(attachmentId));\n  }, [attachmentId, dispatch]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  useEffect(() => {\n    nameFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [nameFieldRef]);\n\n  if (step && step.type === StepTypes.DELETE) {\n    return (\n      <ConfirmationStep\n        title=\"common.deleteAttachment\"\n        content=\"common.areYouSureYouWantToDeleteThisAttachment\"\n        buttonContent=\"action.deleteAttachment\"\n        onConfirm={handleDeleteConfirm}\n        onBack={handleBack}\n      />\n    );\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.editAttachment', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.title')}</div>\n          <Input\n            fluid\n            ref={handleNameFieldRef}\n            name=\"name\"\n            value={data.name}\n            maxLength={128}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          <Button positive content={t('action.save')} />\n        </Form>\n        <Button\n          content={t('action.delete')}\n          className={styles.deleteButton}\n          onClick={handleDeleteClick}\n        />\n      </Popup.Content>\n    </>\n  );\n});\n\nEditStep.propTypes = {\n  attachmentId: PropTypes.string.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default EditStep;\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/EditStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .deleteButton {\n    bottom: 12px;\n    box-shadow: 0 1px 0 #cbcccc;\n    position: absolute;\n    right: 9px;\n  }\n\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Item as GalleryItem } from 'react-photoswipe-gallery';\n\nimport selectors from '../../../selectors';\nimport Config from '../../../constants/Config';\nimport Encodings from '../../../constants/Encodings';\nimport { AttachmentTypes } from '../../../constants/Enums';\nimport ItemContent from './ItemContent';\nimport ContentViewer from './ContentViewer';\nimport PdfViewer from './PdfViewer';\nimport CsvViewer from './CsvViewer';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id, isVisible }) => {\n  const selectAttachmentById = useMemo(() => selectors.makeSelectAttachmentById(), []);\n\n  const attachment = useSelector((state) => selectAttachmentById(state, id));\n\n  const [t] = useTranslation();\n\n  if (!attachment.isPersisted) {\n    return <ItemContent id={id} />;\n  }\n\n  let galleryItemProps;\n  if (attachment.type === AttachmentTypes.FILE) {\n    if (attachment.data.image) {\n      galleryItemProps = attachment.data.image;\n    } else {\n      let content;\n      switch (attachment.data.mimeType) {\n        case 'application/pdf':\n          content = (\n            <PdfViewer\n              src={attachment.data.url}\n              className={classNames(styles.content, styles.contentViewer)}\n            />\n          );\n\n          break;\n        case 'audio/mpeg':\n        case 'audio/wav':\n        case 'audio/ogg':\n        case 'audio/opus':\n        case 'audio/mp4':\n        case 'audio/x-aac':\n          content = (\n            // eslint-disable-next-line jsx-a11y/media-has-caption\n            <audio controls src={attachment.data.url} className={styles.content} />\n          );\n\n          break;\n        case 'text/csv':\n          content = (\n            <CsvViewer\n              src={attachment.data.url}\n              className={classNames(styles.content, styles.contentViewer)}\n            />\n          );\n\n          break;\n        case 'video/mp4':\n        case 'video/ogg':\n        case 'video/webm':\n          content = (\n            // eslint-disable-next-line jsx-a11y/media-has-caption\n            <video controls src={attachment.data.url} className={styles.content} />\n          );\n\n          break;\n        default:\n          if (attachment.data.encoding === Encodings.UTF8) {\n            if (attachment.data.size <= Config.MAX_SIZE_TO_DISPLAY_CONTENT) {\n              content = (\n                <ContentViewer\n                  src={attachment.data.url}\n                  filename={attachment.data.filename}\n                  className={classNames(styles.content, styles.contentViewer)}\n                />\n              );\n            } else {\n              content = (\n                <span className={classNames(styles.content, styles.contentError)}>\n                  {t('common.contentOfThisAttachmentIsTooBigToDisplay')}\n                </span>\n              );\n            }\n          } else {\n            content = (\n              <span className={classNames(styles.content, styles.contentError)}>\n                {t('common.thereIsNoPreviewAvailableForThisAttachment')}\n              </span>\n            );\n          }\n      }\n\n      galleryItemProps = {\n        content,\n      };\n    }\n  } else if (attachment.type === AttachmentTypes.LINK) {\n    galleryItemProps = {\n      content: (\n        <span className={classNames(styles.content, styles.contentError)}>\n          {t('common.thereIsNoPreviewAvailableForThisAttachment')}\n        </span>\n      ),\n    };\n  }\n\n  return (\n    <GalleryItem\n      {...galleryItemProps} // eslint-disable-line react/jsx-props-no-spreading\n      original={attachment.data.url}\n      caption={attachment.name}\n    >\n      {({ ref, open }) =>\n        isVisible ? (\n          <ItemContent\n            ref={ref}\n            id={id}\n            onOpen={attachment.type === AttachmentTypes.FILE ? open : undefined}\n          />\n        ) : (\n          <span ref={ref} />\n        )\n      }\n    </GalleryItem>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n  isVisible: PropTypes.bool.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .content {\n    bottom: 0;\n    left: 0;\n    margin: auto;\n    max-height: 90%;\n    max-width: 90%;\n    position: absolute;\n    right: 0;\n    top: 0;\n  }\n\n  .contentViewer {\n    height: 90%;\n    width: MIN(90%, 1120px); // https://github.com/sass/node-sass/issues/2815\n  }\n\n  .contentError {\n    align-items: center;\n    color: #fff;\n    display: flex;\n    font-size: 20px;\n    font-weight: bold;\n    height: fit-content;\n    justify-content: center;\n    line-height: 1.2;\n    text-align: center;\n    width: fit-content;\n  }\n}\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/ItemContent.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Icon, Label, Loader } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { usePopupInClosableContext } from '../../../hooks';\nimport { isListArchiveOrTrash } from '../../../utils/record-helpers';\nimport { AttachmentTypes, BoardMembershipRoles } from '../../../constants/Enums';\nimport EditStep from './EditStep';\nimport Favicon from '../../common/Favicon';\nimport TimeAgo from '../../common/TimeAgo';\n\nimport styles from './ItemContent.module.scss';\n\nconst ItemContent = React.forwardRef(({ id, onOpen }, ref) => {\n  const selectAttachmentById = useMemo(() => selectors.makeSelectAttachmentById(), []);\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const attachment = useSelector((state) => selectAttachmentById(state, id));\n\n  const isCover = useSelector(\n    (state) => id === selectors.selectCurrentCard(state).coverAttachmentId,\n  );\n\n  const canEdit = useSelector((state) => {\n    const { listId } = selectors.selectCurrentCard(state);\n    const list = selectListById(state, listId);\n\n    if (isListArchiveOrTrash(list)) {\n      return false;\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleClick = useCallback(() => {\n    if (onOpen) {\n      onOpen();\n    } else {\n      window.open(attachment.data.url, '_blank');\n    }\n  }, [onOpen, attachment.data]);\n\n  const handleDownloadClick = useCallback(\n    (event) => {\n      event.stopPropagation();\n\n      const linkElement = document.createElement('a');\n      linkElement.href = attachment.data.url;\n      linkElement.download = attachment.data.filename;\n      linkElement.target = '_blank';\n      linkElement.click();\n    },\n    [attachment.data],\n  );\n\n  const handleToggleCoverClick = useCallback(\n    (event) => {\n      event.stopPropagation();\n\n      dispatch(\n        entryActions.updateCurrentCard({\n          coverAttachmentId: isCover ? null : id,\n        }),\n      );\n    },\n    [id, isCover, dispatch],\n  );\n\n  const EditPopup = usePopupInClosableContext(EditStep);\n\n  if (!attachment.isPersisted) {\n    return (\n      <div className={classNames(styles.wrapper, styles.wrapperSubmitting)}>\n        <Loader inverted />\n      </div>\n    );\n  }\n\n  return (\n    /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                jsx-a11y/no-static-element-interactions */\n    <div ref={ref} className={styles.wrapper} onClick={handleClick}>\n      <div\n        className={styles.thumbnail}\n        style={{\n          background:\n            attachment.type === AttachmentTypes.FILE &&\n            attachment.data.image &&\n            `url(\"${attachment.data.thumbnailUrls.outside360}\") center / cover`,\n        }}\n      >\n        {attachment.type === AttachmentTypes.FILE &&\n          (attachment.data.image ? (\n            isCover && (\n              <Label\n                corner=\"left\"\n                size=\"mini\"\n                icon={{\n                  name: 'checkmark',\n                  color: 'grey',\n                  inverted: true,\n                }}\n                className={styles.thumbnailLabel}\n              />\n            )\n          ) : (\n            <span className={styles.thumbnailExtension}>{attachment.data.extension || '-'}</span>\n          ))}\n        {attachment.type === AttachmentTypes.LINK && <Favicon url={attachment.data.faviconUrl} />}\n      </div>\n      <div className={styles.details}>\n        <span className={styles.name}>{attachment.name}</span>\n        <span className={styles.information}>\n          <TimeAgo date={attachment.createdAt} />\n        </span>\n        {attachment.type === AttachmentTypes.FILE && (\n          <span className={styles.options}>\n            <button type=\"button\" className={styles.option} onClick={handleDownloadClick}>\n              <Icon name=\"download\" size=\"small\" className={styles.optionIcon} />\n              <span className={styles.optionText}>\n                {t('action.download', {\n                  context: 'title',\n                })}\n              </span>\n            </button>\n            {attachment.data.image && canEdit && (\n              <button type=\"button\" className={styles.option} onClick={handleToggleCoverClick}>\n                <Icon\n                  name=\"window maximize outline\"\n                  flipped=\"vertically\"\n                  size=\"small\"\n                  className={styles.optionIcon}\n                />\n                <span className={styles.optionText}>\n                  {isCover\n                    ? t('action.removeCover', {\n                        context: 'title',\n                      })\n                    : t('action.makeCover', {\n                        context: 'title',\n                      })}\n                </span>\n              </button>\n            )}\n          </span>\n        )}\n      </div>\n      {canEdit && (\n        <EditPopup attachmentId={id}>\n          <Button className={styles.editButton}>\n            <Icon fitted name=\"pencil\" size=\"small\" />\n          </Button>\n        </EditPopup>\n      )}\n    </div>\n  );\n});\n\nItemContent.propTypes = {\n  id: PropTypes.string.isRequired,\n  onOpen: PropTypes.func,\n};\n\nItemContent.defaultProps = {\n  onOpen: undefined,\n};\n\nexport default React.memo(ItemContent);\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/ItemContent.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .details {\n    box-sizing: border-box;\n    padding: 6px 32px 6px 128px;\n    margin: 0;\n    min-height: 80px;\n  }\n\n  .editButton {\n    background: transparent;\n    box-shadow: none;\n    line-height: 28px;\n    margin: 0;\n    min-height: auto;\n    opacity: 0;\n    padding: 0;\n    position: absolute;\n    right: 4px;\n    top: 4px;\n    width: 28px;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n    }\n  }\n\n  .information {\n    display: block;\n    color: #6b808c;\n    line-height: 20px;\n    margin-bottom: 6px;\n  }\n\n  .name {\n    color: #17394d;\n    font-size: 14px;\n    font-weight: bold;\n    line-height: 20px;\n    word-wrap: break-word;\n  }\n\n  .option {\n    background: none;\n    border: none;\n    color: #6b808c;\n    cursor: pointer;\n    outline: none;\n    padding: 0;\n\n    &:not(:last-child) {\n      margin-right: 10px;\n    }\n\n    &:hover {\n      color: #172b4d;\n    }\n  }\n\n  .optionIcon {\n    margin-right: 6px;\n  }\n\n  .optionText {\n    text-decoration: underline;\n  }\n\n  .options {\n    display: block;\n    color: #6b808c;\n    line-height: 20px;\n  }\n\n  .thumbnail {\n    align-items: center;\n    background: rgba(9, 30, 66, 0.04);\n    border-radius: 3px;\n    display: flex;\n    height: 80px;\n    justify-content: center;\n    margin-top: -40px;\n    position: absolute;\n    top: 50%;\n    width: 112px;\n  }\n\n  .thumbnailExtension {\n    color: #5e6c84;\n    display: block;\n    font-size: 18px;\n    font-weight: bold;\n    overflow: hidden;\n    padding: 0 20px 0 20px;\n    text-overflow: ellipsis;\n    text-transform: uppercase;\n  }\n\n  .thumbnailLabel {\n    border-color: rgba(29, 46, 63, 0.8);\n\n    i {\n      cursor: inherit;\n    }\n  }\n\n  .wrapper {\n    cursor: pointer;\n    margin-bottom: 8px;\n    min-height: 80px;\n    position: relative;\n\n    &:hover {\n      .details {\n        background: rgba(9, 30, 66, 0.04);\n      }\n\n      .editButton {\n        opacity: 1;\n      }\n    }\n  }\n\n  .wrapperSubmitting {\n    background: rgba(9, 30, 66, 0.04);\n  }\n}\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/PdfViewer.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\n\nimport styles from './PdfViewer.module.scss';\n\nconst PdfViewer = React.memo(({ src, className }) => (\n  // eslint-disable-next-line jsx-a11y/iframe-has-title\n  <iframe src={src} type=\"application/pdf\" className={classNames(styles.wrapper, className)} />\n));\n\nPdfViewer.propTypes = {\n  src: PropTypes.string.isRequired,\n  className: PropTypes.string,\n};\n\nPdfViewer.defaultProps = {\n  className: undefined,\n};\n\nexport default PdfViewer;\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/PdfViewer.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    border: 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/attachments/Attachments/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Attachments from './Attachments';\n\nexport default Attachments;\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/AddBaseCustomFieldGroupStep/AddBaseCustomFieldGroupStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\n\nimport styles from './AddBaseCustomFieldGroupStep.module.scss';\n\nconst AddBaseCustomFieldGroupStep = React.memo(({ onClose }) => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const [data, handleFieldChange] = useForm({\n    name: '',\n  });\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n    };\n\n    if (!cleanData.name) {\n      nameFieldRef.current.select();\n      return;\n    }\n\n    dispatch(entryActions.createBaseCustomFieldGroupInCurrentProject(cleanData));\n    onClose();\n  }, [onClose, dispatch, data, nameFieldRef]);\n\n  useEffect(() => {\n    nameFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [nameFieldRef]);\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.createCustomFieldGroup', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.title')}</div>\n          <Input\n            fluid\n            ref={handleNameFieldRef}\n            name=\"name\"\n            value={data.name}\n            maxLength={128}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          <Button positive content={t('action.createCustomFieldGroup')} />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nAddBaseCustomFieldGroupStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default AddBaseCustomFieldGroupStep;\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/AddBaseCustomFieldGroupStep/AddBaseCustomFieldGroupStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/AddBaseCustomFieldGroupStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AddBaseCustomFieldGroupStep from './AddBaseCustomFieldGroupStep';\n\nexport default AddBaseCustomFieldGroupStep;\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupChip/BaseCustomFieldGroupChip.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\n\nimport styles from './BaseCustomFieldGroupChip.module.scss';\n\nconst Sizes = {\n  TINY: 'tiny',\n  SMALL: 'small',\n  MEDIUM: 'medium',\n};\n\nconst BaseCustomFieldGroupChip = React.memo(({ id, size, onClick }) => {\n  const selectBaseCustomFieldGroupById = useMemo(\n    () => selectors.makeSelectBaseCustomFieldGroupById(),\n    [],\n  );\n\n  const baseCustomFieldGroup = useSelector((state) => selectBaseCustomFieldGroupById(state, id));\n\n  const contentNode = (\n    <span\n      title={baseCustomFieldGroup.name}\n      className={classNames(\n        styles.wrapper,\n        styles[`wrapper${upperFirst(size)}`],\n        onClick && styles.wrapperHoverable,\n      )}\n    >\n      {baseCustomFieldGroup.name}\n    </span>\n  );\n\n  return onClick ? (\n    <button\n      type=\"button\"\n      disabled={!baseCustomFieldGroup.isPersisted}\n      className={styles.button}\n      onClick={onClick}\n    >\n      {contentNode}\n    </button>\n  ) : (\n    contentNode\n  );\n});\n\nBaseCustomFieldGroupChip.propTypes = {\n  id: PropTypes.string.isRequired,\n  size: PropTypes.oneOf(Object.values(Sizes)),\n  onClick: PropTypes.func,\n};\n\nBaseCustomFieldGroupChip.defaultProps = {\n  size: Sizes.MEDIUM,\n  onClick: undefined,\n};\n\nexport default BaseCustomFieldGroupChip;\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupChip/BaseCustomFieldGroupChip.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    background: transparent;\n    border: none;\n    cursor: pointer;\n    display: inline-block;\n    max-width: 100%;\n    outline: none;\n    padding: 0;\n  }\n\n  .wrapper {\n    background: #dce0e4;\n    border-radius: 3px;\n    color: #6a808b;\n    display: inline-block;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    transition: background 0.3s ease;\n    vertical-align: top;\n    white-space: nowrap;\n  }\n\n  .wrapperHoverable:hover {\n    background: #d2d8dc;\n    color: #17394d;\n  }\n\n  /* Sizes */\n\n  .wrapperTiny {\n    font-size: 12px;\n    line-height: 20px;\n    max-width: 176px;\n    padding: 0px 6px;\n  }\n\n  .wrapperSmall {\n    font-size: 12px;\n    line-height: 20px;\n    max-width: 176px;\n    padding: 2px 8px;\n  }\n\n  .wrapperMedium {\n    line-height: 20px;\n    max-width: 230px;\n    padding: 6px 12px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupChip/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport BaseCustomFieldGroupChip from './BaseCustomFieldGroupChip';\n\nexport default BaseCustomFieldGroupChip;\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/BaseCustomFieldGroupStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { DragDropContext, Droppable } from 'react-beautiful-dnd';\nimport { Button } from 'semantic-ui-react';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useField, useNestedRef, useSteps } from '../../../hooks';\nimport DroppableTypes from '../../../constants/DroppableTypes';\nimport EditStep from './EditStep';\nimport CustomField from './CustomField';\nimport CustomFieldAddStep from './CustomFieldAddStep';\nimport CustomFieldEditStep from './CustomFieldEditStep';\nimport ConfirmationStep from '../../common/ConfirmationStep';\n\nimport styles from './BaseCustomFieldGroupStep.module.scss';\n\nconst StepTypes = {\n  EDIT: 'EDIT',\n  DELETE: 'DELETE',\n  ADD_CUSTOM_FIELD: 'ADD_CUSTOM_FIELD',\n  EDIT_CUSTOM_FIELD: 'EDIT_CUSTOM_FIELD',\n};\n\nconst BaseCustomFieldGroupStep = React.memo(({ id, onBack, onClose }) => {\n  const selectBaseCustomFieldGroupById = useMemo(\n    () => selectors.makeSelectBaseCustomFieldGroupById(),\n    [],\n  );\n\n  const selectCustomFieldsByBaseGroupId = useMemo(\n    () => selectors.makeSelectCustomFieldsByBaseGroupId(),\n    [],\n  );\n\n  const baseCustomFieldGroupName = useSelector(\n    (state) => selectBaseCustomFieldGroupById(state, id).name,\n  );\n\n  const customFields = useSelector((state) => selectCustomFieldsByBaseGroupId(state, id));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n  const [search, handleSearchChange] = useField('');\n  const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);\n\n  const filteredCustomFields = useMemo(\n    () =>\n      customFields.filter((customField) => customField.name.toLowerCase().includes(cleanSearch)),\n    [customFields, cleanSearch],\n  );\n\n  const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteBaseCustomFieldGroup(id));\n  }, [id, dispatch]);\n\n  const handleCustomFieldDragEnd = useCallback(\n    ({ draggableId, source, destination }) => {\n      if (!destination || source.index === destination.index) {\n        return;\n      }\n\n      dispatch(entryActions.moveCustomField(draggableId, destination.index));\n    },\n    [dispatch],\n  );\n\n  const handleEditClick = useCallback(() => {\n    openStep(StepTypes.EDIT);\n  }, [openStep]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  const handleCustomFieldAddClick = useCallback(() => {\n    openStep(StepTypes.ADD_CUSTOM_FIELD);\n  }, [openStep]);\n\n  const handleCustomFieldEdit = useCallback(\n    (customFieldId) => {\n      openStep(StepTypes.EDIT_CUSTOM_FIELD, {\n        id: customFieldId,\n      });\n    },\n    [openStep],\n  );\n\n  useEffect(() => {\n    searchFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [searchFieldRef]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.EDIT:\n        return <EditStep baseCustomFieldGroupId={id} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.DELETE:\n        return (\n          <ConfirmationStep\n            title=\"common.deleteCustomFieldGroup\"\n            content=\"common.areYouSureYouWantToDeleteThisCustomFieldGroup\"\n            buttonContent=\"action.deleteCustomFieldGroup\"\n            typeValue={baseCustomFieldGroupName}\n            typeContent=\"common.typeTitleToConfirm\"\n            onConfirm={handleDeleteConfirm}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.ADD_CUSTOM_FIELD:\n        return (\n          <CustomFieldAddStep\n            baseCustomFieldGroupId={id}\n            // TODO: memoize?\n            defaultData={{\n              name: search,\n            }}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.EDIT_CUSTOM_FIELD: {\n        const currentCustomField = customFields.find(\n          (customField) => customField.id === step.params.id,\n        );\n\n        if (currentCustomField) {\n          return <CustomFieldEditStep id={currentCustomField.id} onBack={handleBack} />;\n        }\n\n        openStep(null);\n\n        break;\n      }\n      default:\n    }\n  }\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.customFieldGroup', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Input\n          fluid\n          ref={handleSearchFieldRef}\n          value={search}\n          placeholder={t('common.searchCustomFields')}\n          maxLength={128}\n          icon=\"search\"\n          onChange={handleSearchChange}\n        />\n        {filteredCustomFields.length > 0 && (\n          <DragDropContext onDragEnd={handleCustomFieldDragEnd}>\n            <Droppable droppableId=\"customFields\" type={DroppableTypes.CUSTOM_FIELD}>\n              {({ innerRef, droppableProps, placeholder }) => (\n                <div\n                  {...droppableProps} // eslint-disable-line react/jsx-props-no-spreading\n                  ref={innerRef}\n                  className={styles.items}\n                >\n                  {filteredCustomFields.map((customField, index) => (\n                    <CustomField\n                      key={customField.id}\n                      id={customField.id}\n                      index={index}\n                      onEdit={handleCustomFieldEdit}\n                    />\n                  ))}\n                  {placeholder}\n                </div>\n              )}\n            </Droppable>\n            <Droppable droppableId=\"customFields:hack\" type={DroppableTypes.CUSTOM_FIELD}>\n              {({ innerRef, droppableProps, placeholder }) => (\n                <div\n                  {...droppableProps} // eslint-disable-line react/jsx-props-no-spreading\n                  ref={innerRef}\n                  className={styles.droppableHack}\n                >\n                  {placeholder}\n                </div>\n              )}\n            </Droppable>\n          </DragDropContext>\n        )}\n        <Button\n          fluid\n          content={t('action.addCustomField')}\n          className={styles.actionButton}\n          onClick={handleCustomFieldAddClick}\n        />\n        <Button\n          fluid\n          content={t('action.editGroup')}\n          className={styles.actionButton}\n          onClick={handleEditClick}\n        />\n        <Button\n          fluid\n          content={t('action.deleteGroup')}\n          className={styles.actionButton}\n          onClick={handleDeleteClick}\n        />\n      </Popup.Content>\n    </>\n  );\n});\n\nBaseCustomFieldGroupStep.propTypes = {\n  id: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nBaseCustomFieldGroupStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default BaseCustomFieldGroupStep;\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/BaseCustomFieldGroupStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actionButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: background 0.3s ease;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .droppableHack {\n    display: none;\n    position: fixed;\n  }\n\n  .items {\n    margin-top: 8px;\n    max-height: 60vh;\n    overflow-x: hidden;\n    overflow-y: auto;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &::-webkit-scrollbar {\n      width: 9px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background-clip: padding-box;\n      border-left: 0.25em transparent solid;\n      border-radius: 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomField.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport ReactDOM from 'react-dom';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { Draggable } from 'react-beautiful-dnd';\nimport { Button, Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\n\nimport styles from './CustomField.module.scss';\n\nconst CustomField = React.memo(({ id, index, onEdit }) => {\n  const selectCustomFieldById = useMemo(() => selectors.makeSelectCustomFieldById(), []);\n\n  const customField = useSelector((state) => selectCustomFieldById(state, id));\n\n  const handleEditClick = useCallback(() => {\n    onEdit(id);\n  }, [id, onEdit]);\n\n  return (\n    <Draggable draggableId={id} index={index} isDragDisabled={!customField.isPersisted}>\n      {({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {\n        const contentNode = (\n          // eslint-disable-next-line react/jsx-props-no-spreading\n          <div {...draggableProps} ref={innerRef} className={styles.wrapper}>\n            <span\n              {...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading\n              className={styles.name}\n            >\n              {customField.showOnFrontOfCard && <Icon name=\"pin\" className={styles.nameIcon} />}\n              {customField.name}\n            </span>\n            <Button\n              icon=\"pencil\"\n              size=\"small\"\n              floated=\"right\"\n              disabled={!customField.isPersisted}\n              className={styles.editButton}\n              onClick={handleEditClick}\n            />\n          </div>\n        );\n\n        return isDragging ? ReactDOM.createPortal(contentNode, document.body) : contentNode;\n      }}\n    </Draggable>\n  );\n});\n\nCustomField.propTypes = {\n  id: PropTypes.string.isRequired,\n  index: PropTypes.number.isRequired,\n  onEdit: PropTypes.func.isRequired,\n};\n\nexport default CustomField;\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomField.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .editButton {\n    background: transparent;\n    box-shadow: none;\n    flex: 0 0 auto;\n    font-weight: normal;\n    padding: 8px 10px;\n    text-decoration: underline;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .name {\n    background: rgba(9, 30, 66, 0.04);\n    border-radius: 3px;\n    color: #17394d;\n    flex: 1 1 auto;\n    font-size: 14px;\n    overflow: hidden;\n    padding: 8px 32px 8px 10px;\n    position: relative;\n    text-overflow: ellipsis;\n  }\n\n  .nameIcon {\n    color: rgba(9, 30, 66, 0.24);\n    font-size: 12px;\n    margin: 0 8px 0 0;\n    width: 14px;\n  }\n\n  .wrapper {\n    display: flex;\n    margin-bottom: 4px;\n    max-width: 280px;\n    white-space: nowrap;\n  }\n}\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomFieldAddStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport entryActions from '../../../entry-actions';\nimport { useForm } from '../../../hooks';\nimport CustomFieldEditor from './CustomFieldEditor';\n\nimport styles from './CustomFieldAddStep.module.scss';\n\nconst CustomFieldAddStep = React.memo(({ baseCustomFieldGroupId, defaultData, onBack }) => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: '',\n    showOnFrontOfCard: false,\n    ...defaultData,\n  }));\n\n  const customFieldEditorRef = useRef(null);\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim() || null,\n    };\n\n    if (!cleanData.name) {\n      customFieldEditorRef.current.selectNameField();\n      return;\n    }\n\n    dispatch(entryActions.createCustomFieldInBaseGroup(baseCustomFieldGroupId, cleanData));\n    onBack();\n  }, [baseCustomFieldGroupId, onBack, dispatch, data]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.addCustomField', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <CustomFieldEditor\n            ref={customFieldEditorRef}\n            data={data}\n            onFieldChange={handleFieldChange}\n          />\n          <Button positive content={t('action.addCustomField')} className={styles.submitButton} />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nCustomFieldAddStep.propTypes = {\n  baseCustomFieldGroupId: PropTypes.string.isRequired,\n  defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  onBack: PropTypes.func.isRequired,\n};\n\nexport default CustomFieldAddStep;\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomFieldAddStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .submitButton {\n    margin-top: 12px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomFieldEditStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport React, { useCallback, useMemo, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useSteps } from '../../../hooks';\nimport CustomFieldEditor from './CustomFieldEditor';\nimport ConfirmationStep from '../../common/ConfirmationStep';\n\nimport styles from './CustomFieldEditStep.module.scss';\n\nconst StepTypes = {\n  DELETE: 'DELETE',\n};\n\nconst CustomFieldEditStep = React.memo(({ id, onBack }) => {\n  const selectCustomFieldById = useMemo(() => selectors.makeSelectCustomFieldById(), []);\n\n  const customField = useSelector((state) => selectCustomFieldById(state, id));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultData = useMemo(\n    () => ({\n      name: customField.name,\n      showOnFrontOfCard: customField.showOnFrontOfCard,\n    }),\n    [customField.name, customField.showOnFrontOfCard],\n  );\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: '',\n    showOnFrontOfCard: false,\n    ...defaultData,\n  }));\n\n  const [step, openStep, handleBack] = useSteps();\n\n  const customFieldEditorRef = useRef(null);\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim() || null,\n    };\n\n    if (!cleanData.name) {\n      customFieldEditorRef.current.selectNameField();\n      return;\n    }\n\n    if (!dequal(cleanData, defaultData)) {\n      dispatch(entryActions.updateCustomField(id, cleanData));\n    }\n\n    onBack();\n  }, [id, onBack, dispatch, defaultData, data]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteCustomField(id));\n    onBack();\n  }, [id, onBack, dispatch]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  if (step && step.type === StepTypes.DELETE) {\n    return (\n      <ConfirmationStep\n        title=\"common.deleteCustomField\"\n        content=\"common.areYouSureYouWantToDeleteThisCustomField\"\n        buttonContent=\"action.deleteCustomField\"\n        onConfirm={handleDeleteConfirm}\n        onBack={handleBack}\n      />\n    );\n  }\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editCustomField', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <CustomFieldEditor\n            ref={customFieldEditorRef}\n            data={data}\n            onFieldChange={handleFieldChange}\n          />\n          <Button positive content={t('action.save')} className={styles.submitButton} />\n        </Form>\n        <Button\n          content={t('action.delete')}\n          className={styles.deleteButton}\n          onClick={handleDeleteClick}\n        />\n      </Popup.Content>\n    </>\n  );\n});\n\nCustomFieldEditStep.propTypes = {\n  id: PropTypes.string.isRequired,\n  onBack: PropTypes.func.isRequired,\n};\n\nexport default CustomFieldEditStep;\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomFieldEditStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .deleteButton {\n    bottom: 12px;\n    box-shadow: 0 1px 0 #cbcccc;\n    position: absolute;\n    right: 9px;\n  }\n\n  .submitButton {\n    margin-top: 12px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomFieldEditor.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useImperativeHandle } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { Radio } from 'semantic-ui-react';\nimport { Input } from '../../../lib/custom-ui';\n\nimport { useNestedRef } from '../../../hooks';\n\nimport styles from './CustomFieldEditor.module.scss';\n\nconst CustomFieldEditor = React.forwardRef(({ data, onFieldChange }, ref) => {\n  const [t] = useTranslation();\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const selectNameField = useCallback(() => {\n    nameFieldRef.current.select();\n  }, [nameFieldRef]);\n\n  useImperativeHandle(\n    ref,\n    () => ({\n      selectNameField,\n    }),\n    [selectNameField],\n  );\n\n  useEffect(() => {\n    nameFieldRef.current.focus();\n  }, [nameFieldRef]);\n\n  return (\n    <>\n      <div className={styles.text}>{t('common.title')}</div>\n      <Input\n        fluid\n        ref={handleNameFieldRef}\n        name=\"name\"\n        value={data.name}\n        maxLength={128}\n        className={styles.fieldName}\n        onChange={onFieldChange}\n      />\n      <Radio\n        toggle\n        name=\"showOnFrontOfCard\"\n        checked={data.showOnFrontOfCard}\n        label={t('common.showOnFrontOfCard')}\n        className={classNames(styles.field, styles.fieldRadio)}\n        onChange={onFieldChange}\n      />\n    </>\n  );\n});\n\nCustomFieldEditor.propTypes = {\n  data: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  onFieldChange: PropTypes.func.isRequired,\n};\n\nexport default React.memo(CustomFieldEditor);\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomFieldEditor.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .fieldName {\n    margin-bottom: 16px;\n  }\n\n  .fieldRadio {\n    width: 100%;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/EditStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\n\nimport styles from './EditStep.module.scss';\n\nconst EditStep = React.memo(({ baseCustomFieldGroupId, onBack, onClose }) => {\n  const selectBaseCustomFieldGroupById = useMemo(\n    () => selectors.makeSelectBaseCustomFieldGroupById(),\n    [],\n  );\n\n  const baseCustomFieldGroup = useSelector((state) =>\n    selectBaseCustomFieldGroupById(state, baseCustomFieldGroupId),\n  );\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultData = useMemo(\n    () => ({\n      name: baseCustomFieldGroup.name,\n    }),\n    [baseCustomFieldGroup.name],\n  );\n\n  const [data, handleFieldChange] = useForm({\n    name: '',\n    ...defaultData,\n  });\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n    };\n\n    if (!cleanData.name) {\n      nameFieldRef.current.select();\n      return;\n    }\n\n    if (!dequal(cleanData, defaultData)) {\n      dispatch(entryActions.updateBaseCustomFieldGroup(baseCustomFieldGroupId, cleanData));\n    }\n\n    onClose();\n  }, [baseCustomFieldGroupId, onClose, dispatch, defaultData, data, nameFieldRef]);\n\n  useEffect(() => {\n    nameFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [nameFieldRef]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editCustomFieldGroup', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.title')}</div>\n          <Input\n            fluid\n            ref={handleNameFieldRef}\n            name=\"name\"\n            value={data.name}\n            maxLength={128}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          <Button positive content={t('action.save')} />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nEditStep.propTypes = {\n  baseCustomFieldGroupId: PropTypes.string.isRequired,\n  onBack: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default EditStep;\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/EditStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport BaseCustomFieldGroupStep from './BaseCustomFieldGroupStep';\n\nexport default BaseCustomFieldGroupStep;\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/ActionsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Icon } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useSteps } from '../../../hooks';\nimport SelectPermissionsStep from './SelectPermissionsStep';\nimport ConfirmationStep from '../../common/ConfirmationStep';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './ActionsStep.module.scss';\n\nconst StepTypes = {\n  EDIT_PERMISSIONS: 'EDIT_PERMISSIONS',\n  DELETE: 'DELETE',\n};\n\nconst ActionsStep = React.memo(({ boardMembershipId, title, onBack, onClose }) => {\n  const selectBoardMembershipById = useMemo(() => selectors.makeSelectBoardMembershipById(), []);\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const boardMembership = useSelector((state) =>\n    selectBoardMembershipById(state, boardMembershipId),\n  );\n\n  const user = useSelector((state) => selectUserById(state, boardMembership.userId));\n\n  const isCurrentUser = useSelector(\n    (state) => boardMembership.userId === selectors.selectCurrentUserId(state),\n  );\n\n  const canEdit = useSelector(selectors.selectIsCurrentUserManagerForCurrentProject);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n\n  const handleRoleSelect = useCallback(\n    (data) => {\n      dispatch(entryActions.updateBoardMembership(boardMembershipId, data));\n    },\n    [boardMembershipId, dispatch],\n  );\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteBoardMembership(boardMembershipId));\n    onClose();\n  }, [boardMembershipId, onClose, dispatch]);\n\n  const handleFilterClick = useCallback(() => {\n    dispatch(entryActions.addUserToFilterInCurrentBoard(boardMembership.userId, true));\n    onClose();\n  }, [onClose, boardMembership.userId, dispatch]);\n\n  const handleEditPermissionsClick = useCallback(() => {\n    openStep(StepTypes.EDIT_PERMISSIONS);\n  }, [openStep]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.EDIT_PERMISSIONS:\n        return (\n          <SelectPermissionsStep\n            boardMembership={boardMembership}\n            title=\"common.editPermissions\"\n            buttonContent=\"action.save\"\n            onSelect={handleRoleSelect}\n            onBack={handleBack}\n            onClose={onClose}\n          />\n        );\n      case StepTypes.DELETE:\n        return (\n          <ConfirmationStep\n            title={isCurrentUser ? `common.leaveBoard` : 'common.removeMember'}\n            content={\n              isCurrentUser\n                ? `common.areYouSureYouWantToLeaveBoard`\n                : `common.areYouSureYouWantToRemoveThisMemberFromBoard`\n            }\n            buttonContent={isCurrentUser ? `action.leaveBoard` : 'action.removeMember'}\n            onConfirm={handleDeleteConfirm}\n            onBack={handleBack}\n          />\n        );\n      default:\n    }\n\n    openStep(null);\n  }\n\n  const contentNode = (\n    <>\n      <div className={styles.userWrapper}>\n        <span className={styles.user}>\n          <UserAvatar id={boardMembership.userId} size=\"large\" />\n        </span>\n        <span className={styles.content}>\n          <div className={styles.name}>{user.name}</div>\n          {user.username && <div className={styles.username}>@{user.username}</div>}\n        </span>\n      </div>\n      {user.phone && (\n        <div className={styles.information}>\n          <Icon name=\"phone\" className={styles.informationIcon} />\n          {user.phone}\n        </div>\n      )}\n      {user.organization && (\n        <div className={styles.information}>\n          <Icon name=\"building\" className={styles.informationIcon} />\n          {user.organization}\n        </div>\n      )}\n      <Button\n        basic\n        content={t('action.showCardsWithThisUser')}\n        icon=\"filter\"\n        size=\"tiny\"\n        onClick={handleFilterClick}\n      />\n      {(isCurrentUser || canEdit) && (\n        <>\n          <hr className={styles.divider} />\n          {canEdit && (\n            <Button\n              fluid\n              content={t('action.editPermissions')}\n              className={styles.button}\n              onClick={handleEditPermissionsClick}\n            />\n          )}\n          {isCurrentUser ? (\n            <Button\n              fluid\n              content={t(`action.leaveBoard`)}\n              className={styles.button}\n              onClick={handleDeleteClick}\n            />\n          ) : (\n            <Button\n              fluid\n              content={t(`action.removeFromBoard`)}\n              className={styles.button}\n              onClick={handleDeleteClick}\n            />\n          )}\n        </>\n      )}\n    </>\n  );\n\n  return onBack ? (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t(title, {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>{contentNode}</Popup.Content>\n    </>\n  ) : (\n    contentNode\n  );\n});\n\nActionsStep.propTypes = {\n  boardMembershipId: PropTypes.string.isRequired,\n  title: PropTypes.string,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nActionsStep.defaultProps = {\n  title: 'common.memberActions',\n  onBack: undefined,\n};\n\nexport default ActionsStep;\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/ActionsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .content {\n    display: inline-block;\n    width: calc(100% - 44px);\n  }\n\n  .divider {\n    background: #eee;\n    border: 0;\n    height: 1px;\n  }\n\n  .information {\n    color: #888888;\n    margin-bottom: 8px;\n  }\n\n  .informationIcon {\n    margin-right: 6px;\n    opacity: 0.45;\n  }\n\n  .name {\n    color: #212121;\n    font-size: 16px;\n    font-weight: bold;\n    line-height: 1.2;\n    padding: 9px 28px 0 2px;\n  }\n\n  .userWrapper {\n    margin-bottom: 8px;\n    min-height: 49px;\n  }\n\n  .user {\n    display: inline-block;\n    padding-right: 8px;\n    padding-top: 10px;\n    vertical-align: top;\n  }\n\n  .username {\n    color: #888888;\n    font-size: 14px;\n    line-height: 1.2;\n    padding: 2px 0 2px 2px;\n    word-wrap: break-word;\n  }\n}\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/AddStep/AddStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Input, Popup } from '../../../../lib/custom-ui';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { useField, useNestedRef, useSteps } from '../../../../hooks';\nimport { isUserAdminOrProjectOwner } from '../../../../utils/record-helpers';\nimport User from './User';\nimport SelectPermissionsStep from '../SelectPermissionsStep';\n\nimport styles from './AddStep.module.scss';\n\nconst StepTypes = {\n  SELECT_PERMISSIONS: 'SELECT_PERMISSIONS',\n};\n\nconst AddStep = React.memo(({ onClose }) => {\n  const users = useSelector((state) => {\n    const user = selectors.selectCurrentUser(state);\n\n    if (!isUserAdminOrProjectOwner(user)) {\n      return [user];\n    }\n\n    return selectors.selectActiveUsers(state);\n  });\n\n  const currentUserIds = useSelector(selectors.selectMemberUserIdsForCurrentBoard);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n  const [search, handleSearchChange] = useField('');\n  const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);\n\n  const filteredUsers = useMemo(\n    () =>\n      users.filter(\n        (user) =>\n          user.name.toLowerCase().includes(cleanSearch) ||\n          (user.username && user.username.includes(cleanSearch)),\n      ),\n    [users, cleanSearch],\n  );\n\n  const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');\n\n  const handleRoleSelect = useCallback(\n    (data) => {\n      dispatch(\n        entryActions.createMembershipInCurrentBoard({\n          ...data,\n          userId: step.params.userId,\n        }),\n      );\n\n      onClose();\n    },\n    [onClose, dispatch, step],\n  );\n\n  const handleUserSelect = useCallback(\n    (userId) => {\n      openStep(StepTypes.SELECT_PERMISSIONS, {\n        userId,\n      });\n    },\n    [openStep],\n  );\n\n  useEffect(() => {\n    searchFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [searchFieldRef]);\n\n  if (step && step.type === StepTypes.SELECT_PERMISSIONS) {\n    const currentUser = users.find((user) => user.id === step.params.userId);\n\n    if (currentUser) {\n      return (\n        <SelectPermissionsStep\n          buttonContent=\"action.addMember\"\n          onSelect={handleRoleSelect}\n          onBack={handleBack}\n          onClose={onClose}\n        />\n      );\n    }\n\n    openStep(null);\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.addMember', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Input\n          fluid\n          ref={handleSearchFieldRef}\n          value={search}\n          placeholder={t('common.searchUsers')}\n          maxLength={128}\n          icon=\"search\"\n          onChange={handleSearchChange}\n        />\n        {filteredUsers.length > 0 && (\n          <div className={styles.users}>\n            {filteredUsers.map((user) => (\n              <User\n                key={user.id}\n                id={user.id}\n                isActive={currentUserIds.includes(user.id)}\n                onSelect={handleUserSelect}\n              />\n            ))}\n          </div>\n        )}\n      </Popup.Content>\n    </>\n  );\n});\n\nAddStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default AddStep;\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/AddStep/AddStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .users {\n    margin-top: 8px;\n    max-height: 60vh;\n    overflow-x: hidden;\n    overflow-y: auto;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &::-webkit-scrollbar {\n      width: 9px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background-clip: padding-box;\n      border-left: 0.25em transparent solid;\n      border-radius: 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/AddStep/User.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../../selectors';\nimport UserAvatar from '../../../users/UserAvatar';\n\nimport styles from './User.module.scss';\n\nconst User = React.memo(({ id, isActive, onSelect }) => {\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const user = useSelector((state) => selectUserById(state, id));\n\n  const handleClick = useCallback(() => {\n    onSelect(id);\n  }, [id, onSelect]);\n\n  return (\n    <button type=\"button\" disabled={isActive} className={styles.menuItem} onClick={handleClick}>\n      <span className={styles.user}>\n        <UserAvatar id={id} />\n      </span>\n      <div className={classNames(styles.menuItemText, isActive && styles.menuItemTextActive)}>\n        {user.name}\n      </div>\n    </button>\n  );\n});\n\nUser.propTypes = {\n  id: PropTypes.string.isRequired,\n  isActive: PropTypes.bool.isRequired,\n  onSelect: PropTypes.func.isRequired,\n};\n\nexport default User;\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/AddStep/User.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menuItem {\n    background: transparent;\n    border: none;\n    border-radius: 0.28571429rem;\n    display: block;\n    margin: 0;\n    outline: 0;\n    overflow: hidden;\n    padding: 4px;\n    text-align: left;\n    width: 100%;\n\n    &:enabled {\n      cursor: pointer;\n\n      &:hover {\n        background: rgba(0, 0, 0, 0.05);\n      }\n    }\n  }\n\n  .menuItemText {\n    display: inline-block;\n    line-height: 32px;\n    position: relative;\n    width: calc(100% - 40px);\n  }\n\n  .menuItemTextActive:before {\n    bottom: 2px;\n    color: #798d99;\n    content: \"Г\";\n    font-size: 18px;\n    font-weight: normal;\n    line-height: 36px;\n    position: absolute;\n    right: 2px;\n    text-align: center;\n    transform: rotate(-135deg);\n    width: 36px;\n  }\n\n  .user {\n    display: inline-block;\n    line-height: 32px;\n    padding-right: 8px;\n    width: 40px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/AddStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AddStep from './AddStep';\n\nexport default AddStep;\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/BoardMemberships.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport groupBy from 'lodash/groupBy';\nimport React, { useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { Button } from 'semantic-ui-react';\nimport { usePopup } from '../../../lib/popup';\n\nimport selectors from '../../../selectors';\nimport { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\nimport Group from './Group';\nimport AddStep from './AddStep';\n\nimport styles from './BoardMemberships.module.scss';\n\nconst BoardMemberships = React.memo(() => {\n  const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);\n\n  const canAdd = useSelector((state) => {\n    const user = selectors.selectCurrentUser(state);\n\n    if (!isUserAdminOrProjectOwner(user)) {\n      return !selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    }\n\n    return selectors.selectIsCurrentUserManagerForCurrentProject(state);\n  });\n\n  const boardMembershipsByRole = useMemo(\n    () => groupBy(boardMemberships, 'role'),\n    [boardMemberships],\n  );\n\n  const AddPopup = usePopup(AddStep);\n\n  return (\n    <>\n      {boardMemberships.length > 0 && (\n        <div className={classNames(styles.segment, styles.groups)}>\n          {[BoardMembershipRoles.EDITOR, BoardMembershipRoles.VIEWER].map(\n            (role) =>\n              boardMembershipsByRole[role] && (\n                <Group\n                  key={role}\n                  items={boardMembershipsByRole[role]}\n                  role={role}\n                  groupsTotal={Object.keys(boardMembershipsByRole).length}\n                />\n              ),\n          )}\n        </div>\n      )}\n      {canAdd && (\n        <AddPopup>\n          <Button icon=\"add user\" className={classNames(styles.segment, styles.addButton)} />\n        </AddPopup>\n      )}\n    </>\n  );\n});\n\nexport default BoardMemberships;\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/BoardMemberships.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .addButton {\n    background: rgba(0, 0, 0, 0.24);\n    border-radius: 50%;\n    box-shadow: none;\n    color: #fff;\n    line-height: 36px;\n    margin: 0;\n    padding: 0;\n    transition: all 0.1s ease 0s;\n    vertical-align: top;\n    width: 36px;\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.32);\n    }\n  }\n\n  .groups {\n    display: flex;\n  }\n\n  .segment:not(:last-child) {\n    margin-right: 10px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/Group.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport { Icon, Button } from 'semantic-ui-react';\nimport { usePopup } from '../../../lib/popup';\n\nimport { BoardMembershipRoleIcons } from '../../../constants/Icons';\nimport GroupItemsStep from './GroupItemsStep';\nimport ActionsStep from './ActionsStep';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './Group.module.scss';\n\nconst MAX_MEMBERS = 6;\n\nconst Group = React.memo(({ items, role, groupsTotal }) => {\n  const GroupItemsPopup = usePopup(GroupItemsStep);\n  const ActionsPopup = usePopup(ActionsStep);\n\n  let visibleTotal = MAX_MEMBERS - groupsTotal;\n  let hiddenTotal = items.length - visibleTotal;\n\n  if (hiddenTotal === 1) {\n    visibleTotal += 1;\n    hiddenTotal -= 1;\n  }\n\n  return (\n    <div className={styles.wrapper}>\n      <Icon name={BoardMembershipRoleIcons[role]} className={styles.icon} />\n      {items.slice(0, visibleTotal).map((item) => (\n        <span key={item.id} className={styles.user}>\n          <ActionsPopup boardMembershipId={item.id}>\n            <UserAvatar id={item.user.id} size=\"large\" isDisabled={!item.isPersisted} />\n          </ActionsPopup>\n        </span>\n      ))}\n      {hiddenTotal > 0 && (\n        <GroupItemsPopup items={items} title={`common.${role}s`}>\n          <Button className={styles.othersButton}>+{hiddenTotal < 99 ? hiddenTotal : 99}</Button>\n        </GroupItemsPopup>\n      )}\n    </div>\n  );\n});\n\nGroup.propTypes = {\n  items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types\n  role: PropTypes.string.isRequired,\n  groupsTotal: PropTypes.number.isRequired,\n};\n\nexport default Group;\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/Group.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .icon {\n    color: rgba(255, 255, 255, 0.64);\n    line-height: 1;\n  }\n\n  .othersButton {\n    background: rgba(0, 0, 0, 0.24);\n    border-radius: 50%;\n    box-shadow: none;\n    color: #fff;\n    line-height: 36px;\n    margin: 0;\n    padding: 0;\n    transition: all 0.1s ease 0s;\n    vertical-align: top;\n    width: 36px;\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.32);\n    }\n  }\n\n  .user {\n    line-height: 1;\n    margin: 0 -4px 0 0;\n  }\n\n  .wrapper {\n    align-items: center;\n    background: rgba(0, 0, 0, 0.08);\n    border-radius: 24px;\n    display: flex;\n    line-height: 34px;\n    padding: 5px 13px 5px 10px;\n\n    &:not(:last-child) {\n      margin-right: 10px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/GroupItemsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\n\nimport { useSteps } from '../../../hooks';\nimport ActionsStep from './ActionsStep';\nimport PureBoardMembershipsStep from '../PureBoardMembershipsStep';\n\nconst StepTypes = {\n  SELECT: 'SELECT',\n};\n\nconst GroupItemsStep = React.memo(({ items, title, onClose }) => {\n  const [step, openStep, handleBack] = useSteps();\n\n  const handleUserClick = useCallback(\n    (userId) => {\n      openStep(StepTypes.SELECT, {\n        userId,\n      });\n    },\n    [openStep],\n  );\n\n  if (step && step.type === StepTypes.SELECT) {\n    const currentItem = items.find((item) => item.userId === step.params.userId);\n\n    if (currentItem) {\n      return (\n        <ActionsStep boardMembershipId={currentItem.id} onBack={handleBack} onClose={onClose} />\n      );\n    }\n\n    openStep(null);\n  }\n\n  return <PureBoardMembershipsStep items={items} title={title} onUserSelect={handleUserClick} />;\n});\n\nGroupItemsStep.propTypes = {\n  items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types\n  title: PropTypes.string.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default GroupItemsStep;\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/SelectPermissionsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport omit from 'lodash/omit';\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Icon, Menu, Radio, Segment } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport { useForm } from '../../../hooks';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\nimport { BoardMembershipRoleIcons } from '../../../constants/Icons';\n\nimport styles from './SelectPermissionsStep.module.scss';\n\nconst DESCRIPTION_BY_ROLE = {\n  [BoardMembershipRoles.EDITOR]: 'common.canEditBoardLayoutAndAssignMembersToCards',\n  [BoardMembershipRoles.VIEWER]: 'common.canOnlyViewBoard',\n};\n\nconst SelectPermissionsStep = React.memo(\n  ({ boardMembership, title, buttonContent, onSelect, onBack, onClose }) => {\n    const [t] = useTranslation();\n\n    const defaultData = useMemo(\n      () =>\n        boardMembership && {\n          role: boardMembership.role,\n          canComment: boardMembership.canComment,\n        },\n      [boardMembership],\n    );\n\n    const [data, handleFieldChange, setData] = useForm(() => ({\n      role: BoardMembershipRoles.EDITOR,\n      canComment: null,\n      ...defaultData,\n    }));\n\n    const handleSubmit = useCallback(() => {\n      if (!dequal(data, defaultData)) {\n        onSelect(data.role === BoardMembershipRoles.EDITOR ? omit(data, 'canComment') : data);\n      }\n\n      onClose();\n    }, [defaultData, onSelect, onClose, data]);\n\n    const handleSelectRoleClick = useCallback(\n      (_, { value: role }) => {\n        setData((prevData) => ({\n          ...prevData,\n          role,\n          canComment: role === BoardMembershipRoles.EDITOR ? null : !!prevData.canComment,\n        }));\n      },\n      [setData],\n    );\n\n    return (\n      <>\n        <Popup.Header onBack={onBack}>\n          {t(title, {\n            context: 'title',\n          })}\n        </Popup.Header>\n        <Popup.Content>\n          <Form onSubmit={handleSubmit}>\n            <Menu secondary vertical className={styles.menu}>\n              {[BoardMembershipRoles.EDITOR, BoardMembershipRoles.VIEWER].map((role) => (\n                <Menu.Item\n                  key={role}\n                  value={role}\n                  active={role === data.role}\n                  className={styles.menuItem}\n                  onClick={handleSelectRoleClick}\n                >\n                  <Icon name={BoardMembershipRoleIcons[role]} className={styles.menuItemIcon} />\n                  <div className={styles.menuItemTitle}>{t(`common.${role}`)}</div>\n                  <p className={styles.menuItemDescription}>{t(DESCRIPTION_BY_ROLE[role])}</p>\n                </Menu.Item>\n              ))}\n            </Menu>\n            {data.role !== BoardMembershipRoles.EDITOR && (\n              <Segment basic className={styles.settings}>\n                <Radio\n                  toggle\n                  name=\"canComment\"\n                  checked={data.canComment}\n                  label={t('common.canComment')}\n                  className={styles.fieldRadio}\n                  onChange={handleFieldChange}\n                />\n              </Segment>\n            )}\n            <Button positive content={t(buttonContent)} />\n          </Form>\n        </Popup.Content>\n      </>\n    );\n  },\n);\n\nSelectPermissionsStep.propTypes = {\n  boardMembership: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  title: PropTypes.string,\n  buttonContent: PropTypes.string,\n  onSelect: PropTypes.func.isRequired,\n  onBack: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nSelectPermissionsStep.defaultProps = {\n  boardMembership: undefined,\n  title: 'common.selectPermissions',\n  buttonContent: 'action.selectPermissions',\n};\n\nexport default SelectPermissionsStep;\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/SelectPermissionsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .fieldRadio {\n    margin-bottom: 16px;\n    width: 100%;\n\n    &:last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  .menu {\n    margin: 0 auto 8px;\n    width: 100%;\n  }\n\n  .menuItem:last-child {\n    margin-bottom: 0;\n  }\n\n  .menuItemDescription {\n    opacity: 0.5;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.35714286em 0 0;\n  }\n\n  .menuItemTitle {\n    margin-bottom: 8px;\n  }\n\n  .settings {\n    margin: 0 0 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMemberships/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport BoardMemberships from './BoardMemberships';\n\nexport default BoardMemberships;\n"
  },
  {
    "path": "client/src/components/board-memberships/BoardMembershipsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../selectors';\nimport PureBoardMembershipsStep from './PureBoardMembershipsStep';\n\nconst BoardMembershipsStep = React.memo(\n  ({\n    currentUserIds,\n    title,\n    clearButtonContent,\n    onUserSelect,\n    onUserDeselect,\n    onClear,\n    onBack,\n  }) => {\n    const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);\n\n    return (\n      <PureBoardMembershipsStep\n        items={boardMemberships}\n        currentUserIds={currentUserIds}\n        title={title}\n        clearButtonContent={clearButtonContent}\n        onUserSelect={onUserSelect}\n        onUserDeselect={onUserDeselect}\n        onClear={onClear}\n        onBack={onBack}\n      />\n    );\n  },\n);\n\nBoardMembershipsStep.propTypes = {\n  currentUserIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types\n  title: PropTypes.string,\n  clearButtonContent: PropTypes.string,\n  onUserSelect: PropTypes.func.isRequired,\n  onUserDeselect: PropTypes.func.isRequired,\n  onClear: PropTypes.func,\n  onBack: PropTypes.func,\n};\n\nBoardMembershipsStep.defaultProps = {\n  title: undefined,\n  clearButtonContent: undefined,\n  onClear: undefined,\n  onBack: undefined,\n};\n\nexport default BoardMembershipsStep;\n"
  },
  {
    "path": "client/src/components/board-memberships/PureBoardMembershipsStep/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { Menu } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id, isActive, onUserSelect, onUserDeselect }) => {\n  const selectBoardMembershipById = useMemo(() => selectors.makeSelectBoardMembershipById(), []);\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const boardMembership = useSelector((state) => selectBoardMembershipById(state, id));\n  const user = useSelector((state) => selectUserById(state, boardMembership.userId));\n\n  const handleToggleClick = useCallback(() => {\n    if (isActive) {\n      if (onUserDeselect) {\n        onUserDeselect(boardMembership.userId);\n      }\n    } else {\n      onUserSelect(boardMembership.userId);\n    }\n  }, [isActive, onUserSelect, onUserDeselect, boardMembership.userId]);\n\n  return (\n    <Menu.Item\n      active={isActive}\n      disabled={!boardMembership.isPersisted}\n      className={classNames(styles.menuItem, isActive && styles.menuItemActive)}\n      onClick={handleToggleClick}\n    >\n      <span className={styles.user}>\n        <UserAvatar id={boardMembership.userId} />\n      </span>\n      <div className={classNames(styles.menuItemText, isActive && styles.menuItemTextActive)}>\n        {user.name}\n      </div>\n    </Menu.Item>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n  isActive: PropTypes.bool.isRequired,\n  onUserSelect: PropTypes.func.isRequired,\n  onUserDeselect: PropTypes.func,\n};\n\nItem.defaultProps = {\n  onUserDeselect: undefined,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/board-memberships/PureBoardMembershipsStep/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menuItem {\n    display: block;\n    margin: 0;\n    overflow: hidden;\n    padding: 4px;\n  }\n\n  .menuItemActive {\n    background: transparent;\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.05);\n    }\n  }\n\n  .menuItemText {\n    display: inline-block;\n    line-height: 32px;\n    width: calc(100% - 40px);\n  }\n\n  .menuItemTextActive:before {\n    bottom: 2px;\n    color: #798d99;\n    content: \"Г\";\n    font-size: 18px;\n    font-weight: normal;\n    line-height: 36px;\n    position: absolute;\n    right: 2px;\n    text-align: center;\n    transform: rotate(-135deg);\n    width: 36px;\n  }\n\n  .user {\n    display: inline-block;\n    line-height: 32px;\n    padding-right: 8px;\n    width: 40px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/board-memberships/PureBoardMembershipsStep/PureBoardMembershipsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport orderBy from 'lodash/orderBy';\nimport React, { useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Menu } from 'semantic-ui-react';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport { useField, useNestedRef } from '../../../hooks';\nimport Item from './Item';\n\nimport styles from './PureBoardMembershipsStep.module.scss';\n\nconst PureBoardMembershipsStep = React.memo(\n  ({\n    items,\n    currentUserIds,\n    title,\n    clearButtonContent,\n    onUserSelect,\n    onUserDeselect,\n    onClear,\n    onBack,\n  }) => {\n    const [t] = useTranslation();\n    const [search, handleSearchChange] = useField('');\n    const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);\n\n    const filteredItems = useMemo(\n      () =>\n        orderBy(\n          items.filter(\n            ({ user }) =>\n              user.name.toLowerCase().includes(cleanSearch) ||\n              (user.username && user.username.includes(cleanSearch)),\n          ),\n          ({ user }) => user.name.toLowerCase(),\n        ),\n      [items, cleanSearch],\n    );\n\n    const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');\n\n    useEffect(() => {\n      searchFieldRef.current.focus({\n        preventScroll: true,\n      });\n    }, [searchFieldRef]);\n\n    return (\n      <>\n        <Popup.Header onBack={onBack}>\n          {t(title, {\n            context: 'title',\n          })}\n        </Popup.Header>\n        <Popup.Content>\n          <Input\n            fluid\n            ref={handleSearchFieldRef}\n            value={search}\n            placeholder={t('common.searchMembers')}\n            maxLength={128}\n            icon=\"search\"\n            onChange={handleSearchChange}\n          />\n          {filteredItems.length > 0 && (\n            <Menu secondary vertical className={styles.menu}>\n              {filteredItems.map((boardMembership) => (\n                <Item\n                  key={boardMembership.id}\n                  id={boardMembership.id}\n                  isActive={currentUserIds.includes(boardMembership.user.id)}\n                  onUserSelect={onUserSelect}\n                  onUserDeselect={onUserDeselect}\n                />\n              ))}\n            </Menu>\n          )}\n          {currentUserIds.length > 0 && onClear && (\n            <Button\n              fluid\n              content={t(clearButtonContent)}\n              className={styles.clearButton}\n              onClick={onClear}\n            />\n          )}\n        </Popup.Content>\n      </>\n    );\n  },\n);\n\nPureBoardMembershipsStep.propTypes = {\n  /* eslint-disable react/forbid-prop-types */\n  items: PropTypes.array.isRequired,\n  currentUserIds: PropTypes.array,\n  /* eslint-enable react/forbid-prop-types */\n  title: PropTypes.string,\n  clearButtonContent: PropTypes.string,\n  onUserSelect: PropTypes.func.isRequired,\n  onUserDeselect: PropTypes.func,\n  onClear: PropTypes.func,\n  onBack: PropTypes.func,\n};\n\nPureBoardMembershipsStep.defaultProps = {\n  currentUserIds: [],\n  title: 'common.members',\n  clearButtonContent: 'action.clear',\n  onUserDeselect: undefined,\n  onClear: undefined,\n  onBack: undefined,\n};\n\nexport default PureBoardMembershipsStep;\n"
  },
  {
    "path": "client/src/components/board-memberships/PureBoardMembershipsStep/PureBoardMembershipsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .clearButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: background 0.3s ease;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .menu {\n    margin: 8px auto 0;\n    max-height: 60vh;\n    overflow-x: hidden;\n    overflow-y: auto;\n    width: 100%;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &::-webkit-scrollbar {\n      width: 9px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background-clip: padding-box;\n      border-left: 0.25em transparent solid;\n      border-radius: 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/board-memberships/PureBoardMembershipsStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport PureBoardMembershipsStep from './PureBoardMembershipsStep';\n\nexport default PureBoardMembershipsStep;\n"
  },
  {
    "path": "client/src/components/boards/AddBoardStep/AddBoardStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Icon } from 'semantic-ui-react';\nimport { useDidUpdate, useToggle } from '../../../lib/hooks';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef, useSteps } from '../../../hooks';\nimport ImportStep from './ImportStep';\n\nimport styles from './AddBoardStep.module.scss';\n\nconst StepTypes = {\n  IMPORT: 'IMPORT',\n};\n\nconst AddBoardStep = React.memo(({ onClose }) => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const [data, handleFieldChange, setData] = useForm({\n    name: '',\n    import: null,\n  });\n\n  const [step, openStep, handleBack] = useSteps();\n  const [focusNameFieldState, focusNameField] = useToggle();\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n    };\n\n    if (!cleanData.name) {\n      nameFieldRef.current.select();\n      return;\n    }\n\n    dispatch(entryActions.createBoardInCurrentProject(cleanData));\n    onClose();\n  }, [onClose, dispatch, data, nameFieldRef]);\n\n  const handleImportSelect = useCallback(\n    (nextImport) => {\n      setData((prevData) => ({\n        ...prevData,\n        import: nextImport,\n      }));\n    },\n    [setData],\n  );\n\n  const handleImportBack = useCallback(() => {\n    handleBack();\n    focusNameField();\n  }, [handleBack, focusNameField]);\n\n  const handleImportClick = useCallback(() => {\n    openStep(StepTypes.IMPORT);\n  }, [openStep]);\n\n  useEffect(() => {\n    nameFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [nameFieldRef]);\n\n  useDidUpdate(() => {\n    nameFieldRef.current.focus();\n  }, [focusNameFieldState]);\n\n  if (step && step.type === StepTypes.IMPORT) {\n    return <ImportStep onSelect={handleImportSelect} onBack={handleImportBack} />;\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.createBoard', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.title')}</div>\n          <Input\n            fluid\n            ref={handleNameFieldRef}\n            name=\"name\"\n            value={data.name}\n            maxLength={128}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          <div className={styles.controls}>\n            <Button positive content={t('action.createBoard')} className={styles.button} />\n            <Button\n              type=\"button\"\n              className={classNames(styles.button, styles.importButton)}\n              onClick={handleImportClick}\n            >\n              <Icon\n                name={data.import ? data.import.type : 'arrow down'}\n                className={styles.importButtonIcon}\n              />\n              {data.import ? data.import.file.name : t('action.import')}\n            </Button>\n          </div>\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nAddBoardStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default AddBoardStep;\n"
  },
  {
    "path": "client/src/components/boards/AddBoardStep/AddBoardStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    white-space: nowrap;\n  }\n\n  .controls {\n    display: flex;\n    max-width: 280px;\n\n    @media only screen and (width < 768px) {\n      max-width: 226px;\n    }\n  }\n\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .importButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-left: auto;\n    margin-right: 0;\n    overflow: hidden;\n    text-align: left;\n    text-decoration: underline;\n    text-overflow: ellipsis;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .importButtonIcon {\n    text-decoration: none;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/AddBoardStep/ImportStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Button } from 'semantic-ui-react';\nimport { FilePicker, Popup } from '../../../lib/custom-ui';\n\nimport styles from './ImportStep.module.scss';\n\nconst ImportStep = React.memo(({ onSelect, onBack }) => {\n  const [t] = useTranslation();\n\n  const handleFileSelect = useCallback(\n    (type, file) => {\n      onSelect({\n        type,\n        file,\n      });\n\n      onBack();\n    },\n    [onSelect, onBack],\n  );\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.importBoard', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <FilePicker accept=\".json\" onSelect={(file) => handleFileSelect('trello', file)}>\n          <Button fluid content={t('common.fromTrello')} icon=\"trello\" className={styles.button} />\n        </FilePicker>\n      </Popup.Content>\n    </>\n  );\n});\n\nImportStep.propTypes = {\n  onSelect: PropTypes.func.isRequired,\n  onBack: PropTypes.func.isRequired,\n};\n\nexport default ImportStep;\n"
  },
  {
    "path": "client/src/components/boards/AddBoardStep/ImportStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: left;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/AddBoardStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AddBoardStep from './AddBoardStep';\n\nexport default AddBoardStep;\n"
  },
  {
    "path": "client/src/components/boards/Board/Board.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport ModalTypes from '../../../constants/ModalTypes';\nimport { BoardContexts, BoardViews } from '../../../constants/Enums';\nimport KanbanContent from './KanbanContent';\nimport FiniteContent from './FiniteContent';\nimport EndlessContent from './EndlessContent';\nimport ShortcutsProvider from './ShortcutsProvider';\nimport CardModal from '../../cards/CardModal';\nimport BoardActivitiesModal from '../../activities/BoardActivitiesModal';\n\nconst Board = React.memo(() => {\n  const board = useSelector(selectors.selectCurrentBoard);\n  const modal = useSelector(selectors.selectCurrentModal);\n  const isCardModalOpened = useSelector((state) => !!selectors.selectPath(state).cardId);\n\n  let Content;\n  if (board.view === BoardViews.KANBAN) {\n    Content = KanbanContent;\n  } else {\n    switch (board.context) {\n      case BoardContexts.BOARD:\n        Content = FiniteContent;\n\n        break;\n      case BoardContexts.ARCHIVE:\n      case BoardContexts.TRASH:\n        Content = EndlessContent;\n\n        break;\n      default:\n    }\n  }\n\n  let modalNode = null;\n  if (isCardModalOpened) {\n    modalNode = <CardModal />;\n  } else if (modal) {\n    switch (modal.type) {\n      case ModalTypes.BOARD_ACTIVITIES:\n        modalNode = <BoardActivitiesModal />;\n\n        break;\n      default:\n    }\n  }\n\n  return (\n    <>\n      <ShortcutsProvider>\n        <Content />\n      </ShortcutsProvider>\n      {modalNode}\n    </>\n  );\n});\n\nexport default Board;\n"
  },
  {
    "path": "client/src/components/boards/Board/EndlessContent.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { BoardViews } from '../../../constants/Enums';\nimport GridView from './GridView';\nimport ListView from './ListView';\n\nconst EndlessContent = React.memo(() => {\n  const board = useSelector(selectors.selectCurrentBoard);\n  const { isCardsFetching, isAllCardsFetched } = useSelector(selectors.selectCurrentList);\n  const cardIds = useSelector(selectors.selectFilteredCardIdsForCurrentList);\n\n  const dispatch = useDispatch();\n\n  const handleCardsFetch = useCallback(() => {\n    dispatch(entryActions.fetchCardsInCurrentList());\n  }, [dispatch]);\n\n  const handleCardCreate = useCallback(\n    (data, autoOpen) => {\n      dispatch(entryActions.createCardInCurrentList(data, autoOpen));\n    },\n    [dispatch],\n  );\n\n  const handleCardPaste = useCallback(() => {\n    dispatch(entryActions.pasteCardInCurrentList());\n  }, [dispatch]);\n\n  const viewProps = {\n    cardIds,\n    isCardsFetching,\n    isAllCardsFetched,\n    onCardsFetch: handleCardsFetch,\n    onCardCreate: handleCardCreate,\n    onCardPaste: handleCardPaste,\n  };\n\n  let View;\n  switch (board.view) {\n    case BoardViews.GRID:\n      View = GridView;\n\n      break;\n    case BoardViews.LIST:\n      View = ListView;\n\n      break;\n    default:\n  }\n\n  return <View {...viewProps} />; // eslint-disable-line react/jsx-props-no-spreading\n});\n\nexport default EndlessContent;\n"
  },
  {
    "path": "client/src/components/boards/Board/FiniteContent.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { BoardViews } from '../../../constants/Enums';\nimport GridView from './GridView';\nimport ListView from './ListView';\n\nconst FiniteContent = React.memo(() => {\n  const board = useSelector(selectors.selectCurrentBoard);\n  const cardIds = useSelector(selectors.selectFilteredCardIdsForCurrentBoard);\n  const canAddCard = useSelector((state) => !!selectors.selectFirstKanbanListId(state));\n\n  const dispatch = useDispatch();\n\n  const handleCardCreate = useCallback(\n    (data, autoOpen) => {\n      dispatch(entryActions.createCardInCurrentContext(data, undefined, autoOpen));\n    },\n    [dispatch],\n  );\n\n  const handleCardPaste = useCallback(() => {\n    dispatch(entryActions.pasteCardInCurrentContext());\n  }, [dispatch]);\n\n  let View;\n  switch (board.view) {\n    case BoardViews.GRID:\n      View = GridView;\n\n      break;\n    case BoardViews.LIST:\n      View = ListView;\n\n      break;\n    default:\n  }\n\n  return (\n    <View\n      cardIds={cardIds}\n      onCardCreate={canAddCard ? handleCardCreate : undefined}\n      onCardPaste={canAddCard ? handleCardPaste : undefined}\n    />\n  );\n});\n\nexport default FiniteContent;\n"
  },
  {
    "path": "client/src/components/boards/Board/GridView.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { shallowEqual, useSelector } from 'react-redux';\nimport { useInView } from 'react-intersection-observer';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Icon, Loader } from 'semantic-ui-react';\nimport { useWindowWidth } from '../../../lib/hooks';\nimport { Masonry } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\nimport Card from '../../cards/Card';\nimport AddCard from '../../cards/AddCard';\nimport PlusMathIcon from '../../../assets/images/plus-math-icon.svg?react';\n\nimport styles from './GridView.module.scss';\n\nconst GridView = React.memo(\n  ({ cardIds, isCardsFetching, isAllCardsFetched, onCardsFetch, onCardCreate, onCardPaste }) => {\n    const clipboard = useSelector(selectors.selectClipboard);\n\n    const { canAddCard, canPasteCard } = useSelector((state) => {\n      const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n      const isEditor = !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n\n      return {\n        canAddCard: isEditor,\n        canPasteCard: isEditor,\n      };\n    }, shallowEqual);\n\n    const [t] = useTranslation();\n    const [isAddCardOpened, setIsAddCardOpened] = useState(false);\n    const windowWidth = useWindowWidth();\n\n    const [inViewRef] = useInView({\n      threshold: 1,\n      onChange: (inView) => {\n        if (inView && onCardsFetch) {\n          onCardsFetch();\n        }\n      },\n    });\n\n    const handleAddCardClick = useCallback(() => {\n      setIsAddCardOpened(true);\n    }, []);\n\n    const handleAddCardClose = useCallback(() => {\n      setIsAddCardOpened(false);\n    }, []);\n\n    const columns = Math.floor(windowWidth / 300); // TODO: move to constant?\n\n    return (\n      <div className={styles.wrapper}>\n        <Masonry columns={columns} spacing={20}>\n          {canAddCard &&\n            (onCardCreate && isAddCardOpened ? (\n              <div className={styles.card}>\n                <AddCard onCreate={onCardCreate} onClose={handleAddCardClose} />\n              </div>\n            ) : (\n              <div>\n                <div className={styles.addCardButtonWrapper}>\n                  <Button\n                    type=\"button\"\n                    disabled={!onCardCreate}\n                    className={styles.addCardButton}\n                    onClick={handleAddCardClick}\n                  >\n                    <PlusMathIcon className={styles.addCardButtonIcon} />\n                    <span className={styles.addCardButtonText}>\n                      {onCardCreate ? t('action.addCard') : t('common.atLeastOneListMustBePresent')}\n                    </span>\n                  </Button>\n                  {onCardPaste && clipboard && canPasteCard && (\n                    <Button\n                      type=\"button\"\n                      disabled={!onCardCreate}\n                      className={classNames(styles.addCardButton, styles.paste)}\n                      onClick={onCardPaste}\n                    >\n                      <Icon fitted name=\"paste\" />\n                    </Button>\n                  )}\n                </div>\n              </div>\n            ))}\n          {cardIds.map((cardId) => (\n            <div key={cardId} className={styles.card}>\n              <Card id={cardId} />\n            </div>\n          ))}\n        </Masonry>\n        {isCardsFetching !== undefined && isAllCardsFetched !== undefined && (\n          <div className={styles.loaderWrapper}>\n            {isCardsFetching ? (\n              <Loader active inverted inline=\"centered\" size=\"small\" />\n            ) : (\n              !isAllCardsFetched && <div ref={inViewRef} />\n            )}\n          </div>\n        )}\n      </div>\n    );\n  },\n);\n\nGridView.propTypes = {\n  cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types\n  isCardsFetching: PropTypes.bool,\n  isAllCardsFetched: PropTypes.bool,\n  onCardsFetch: PropTypes.func,\n  onCardCreate: PropTypes.func,\n  onCardPaste: PropTypes.func,\n};\n\nGridView.defaultProps = {\n  isCardsFetching: undefined,\n  isAllCardsFetched: undefined,\n  onCardsFetch: undefined,\n  onCardCreate: undefined,\n  onCardPaste: undefined,\n};\n\nexport default GridView;\n"
  },
  {
    "path": "client/src/components/boards/Board/GridView.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .addCardButton {\n    background: rgba(0, 0, 0, 0.24);\n    border: none;\n    border-radius: 3px;\n    color: rgba(255, 255, 255, 0.72);\n    cursor: pointer;\n    fill: rgba(255, 255, 255, 0.72);\n    flex: 1;\n    font-weight: normal;\n    height: 42px;\n    margin: 0;\n    min-height: 42px;\n    padding: 11px;\n    text-align: left;\n    transition: background 85ms ease-in, opacity 40ms ease-in,\n      border-color 85ms ease-in;\n\n    &:active {\n      outline: none;\n    }\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.32);\n    }\n\n    &.paste {\n      flex: 0 0 auto;\n    }\n  }\n\n  .addCardButtonIcon {\n    height: 20px;\n    padding: 0.64px;\n    width: 20px;\n  }\n\n  .addCardButtonText {\n    display: inline-block;\n    font-size: 14px;\n    line-height: 20px;\n    vertical-align: top;\n  }\n\n  .addCardButtonWrapper {\n    display: flex;\n    gap: 6px;\n  }\n\n  .card {\n    background: rgba(223, 227, 230, 0.8);\n    border-radius: 3px;\n    padding: 4px;\n  }\n\n  .loaderWrapper {\n    margin: 40px 0;\n  }\n\n  .wrapper {\n    overflow-y: scroll;\n    padding-left: 20px;\n    width: 100%;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.08);\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/Board/KanbanContent/AddList.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Icon, Input } from 'semantic-ui-react';\nimport { useClickAwayListener, useDidUpdate, useToggle } from '../../../../lib/hooks';\nimport { usePopup } from '../../../../lib/popup';\n\nimport entryActions from '../../../../entry-actions';\nimport { useClosable, useForm, useNestedRef } from '../../../../hooks';\nimport { ListTypes } from '../../../../constants/Enums';\nimport { ListTypeIcons } from '../../../../constants/Icons';\nimport SelectListTypeStep from '../../../lists/SelectListTypeStep';\n\nimport styles from './AddList.module.scss';\n\nconst DEFAULT_DATA = {\n  name: '',\n  type: ListTypes.ACTIVE,\n};\n\nconst AddList = React.memo(({ onClose }) => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);\n  const [focusNameFieldState, focusNameField] = useToggle();\n  const [isClosableActiveRef, activateClosable, deactivateClosable] = useClosable();\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n  const [submitButtonRef, handleSubmitButtonRef] = useNestedRef();\n  const [selectTypeButtonRef, handleSelectTypeButtonRef] = useNestedRef();\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n    };\n\n    if (!cleanData.name) {\n      nameFieldRef.current.select();\n      return;\n    }\n\n    dispatch(entryActions.createListInCurrentBoard(cleanData));\n    setData(DEFAULT_DATA);\n    focusNameField();\n  }, [dispatch, data, setData, focusNameField, nameFieldRef]);\n\n  const handleTypeSelect = useCallback(\n    (type) => {\n      setData((prevData) => ({\n        ...prevData,\n        type,\n      }));\n    },\n    [setData],\n  );\n\n  const handleFieldKeyDown = useCallback(\n    (event) => {\n      if (event.key === 'Escape') {\n        onClose();\n      }\n    },\n    [onClose],\n  );\n\n  const handleSelectTypeClose = useCallback(() => {\n    deactivateClosable();\n    nameFieldRef.current.focus();\n  }, [deactivateClosable, nameFieldRef]);\n\n  const handleAwayClick = useCallback(() => {\n    if (isClosableActiveRef.current) {\n      return;\n    }\n\n    onClose();\n  }, [onClose, isClosableActiveRef]);\n\n  const handleClickAwayCancel = useCallback(() => {\n    nameFieldRef.current.focus();\n  }, [nameFieldRef]);\n\n  const clickAwayProps = useClickAwayListener(\n    [nameFieldRef, submitButtonRef, selectTypeButtonRef],\n    handleAwayClick,\n    handleClickAwayCancel,\n  );\n\n  useEffect(() => {\n    nameFieldRef.current.focus();\n  }, [nameFieldRef]);\n\n  useDidUpdate(() => {\n    nameFieldRef.current.focus();\n  }, [focusNameFieldState]);\n\n  const SelectListTypePopup = usePopup(SelectListTypeStep, {\n    onOpen: activateClosable,\n    onClose: handleSelectTypeClose,\n  });\n\n  return (\n    <Form className={styles.wrapper} onSubmit={handleSubmit}>\n      <Input\n        {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n        ref={handleNameFieldRef}\n        name=\"name\"\n        value={data.name}\n        placeholder={t('common.enterListTitle')}\n        maxLength={128}\n        className={styles.field}\n        onKeyDown={handleFieldKeyDown}\n        onChange={handleFieldChange}\n      />\n      <div className={styles.controls}>\n        <Button\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          positive\n          ref={handleSubmitButtonRef}\n          content={t('action.addList')}\n          className={styles.button}\n        />\n        <SelectListTypePopup defaultValue={data.type} onSelect={handleTypeSelect}>\n          <Button\n            {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n            ref={handleSelectTypeButtonRef}\n            type=\"button\"\n            className={classNames(styles.button, styles.selectTypeButton)}\n          >\n            <Icon name={ListTypeIcons[data.type]} className={styles.selectTypeButtonIcon} />\n            {t(`common.${data.type}`)}\n          </Button>\n        </SelectListTypePopup>\n      </div>\n    </Form>\n  );\n});\n\nAddList.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default AddList;\n"
  },
  {
    "path": "client/src/components/boards/Board/KanbanContent/AddList.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    min-height: 30px;\n    white-space: nowrap;\n  }\n\n  .controls {\n    display: flex;\n    margin-top: 4px;\n  }\n\n  .field {\n    border: none;\n    border-radius: 3px;\n    box-shadow: 0 1px 0 #ccc;\n    color: #333;\n    outline: none;\n    overflow: hidden;\n    width: 100%;\n\n    &:focus {\n      border-color: #298fca;\n      box-shadow: 0 0 2px #298fca;\n    }\n  }\n\n  .selectTypeButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-left: auto;\n    margin-right: 0;\n    overflow: hidden;\n    text-align: left;\n    text-decoration: underline;\n    text-overflow: ellipsis;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .selectTypeButtonIcon {\n    text-decoration: none;\n  }\n\n  .wrapper {\n    background: #e2e4e6;\n    border-radius: 3px;\n    padding: 4px;\n    transition: opacity 40ms ease-in;\n    width: 272px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/Board/KanbanContent/KanbanContent.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { DragDropContext, Droppable } from 'react-beautiful-dnd';\nimport { useDidUpdate } from '../../../../lib/hooks';\nimport { closePopup } from '../../../../lib/popup';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport parseDndId from '../../../../utils/parse-dnd-id';\nimport DroppableTypes from '../../../../constants/DroppableTypes';\nimport { BoardMembershipRoles } from '../../../../constants/Enums';\nimport AddList from './AddList';\nimport List from '../../../lists/List';\nimport PlusMathIcon from '../../../../assets/images/plus-math-icon.svg?react';\n\nimport styles from './KanbanContent.module.scss';\nimport globalStyles from '../../../../styles.module.scss';\n\nconst KanbanContent = React.memo(() => {\n  const listIds = useSelector(selectors.selectKanbanListIdsForCurrentBoard);\n\n  const canAddList = useSelector((state) => {\n    const isEditModeEnabled = selectors.selectIsEditModeEnabled(state); // TODO: move out?\n\n    if (!isEditModeEnabled) {\n      return isEditModeEnabled;\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [isAddListOpened, setIsAddListOpened] = useState(false);\n\n  const wrapperRef = useRef(null);\n  const prevPositionRef = useRef(null);\n\n  const handleDragStart = useCallback(() => {\n    document.body.classList.add(globalStyles.dragging);\n    closePopup();\n  }, []);\n\n  const handleDragEnd = useCallback(\n    ({ draggableId, type, source, destination }) => {\n      document.body.classList.remove(globalStyles.dragging);\n\n      if (!destination) {\n        return;\n      }\n\n      if (source.droppableId === destination.droppableId && source.index === destination.index) {\n        return;\n      }\n\n      const id = parseDndId(draggableId);\n\n      switch (type) {\n        case DroppableTypes.LIST:\n          dispatch(entryActions.moveList(id, destination.index));\n\n          break;\n        case DroppableTypes.CARD:\n          dispatch(\n            entryActions.moveCard(id, parseDndId(destination.droppableId), destination.index),\n          );\n\n          break;\n        default:\n      }\n    },\n    [dispatch],\n  );\n\n  const handleAddListClick = useCallback(() => {\n    setIsAddListOpened(true);\n  }, []);\n\n  const handleAddListClose = useCallback(() => {\n    setIsAddListOpened(false);\n  }, []);\n\n  const handleMouseDown = useCallback((event) => {\n    // If button is defined and not equal to 0 (left click)\n    if (event.button) {\n      return;\n    }\n\n    if (event.target !== wrapperRef.current && !event.target.dataset.dragScroller) {\n      return;\n    }\n\n    prevPositionRef.current = event.clientX;\n\n    window.getSelection().removeAllRanges();\n    document.body.classList.add(globalStyles.dragScrolling);\n  }, []);\n\n  const handleWindowMouseMove = useCallback((event) => {\n    if (prevPositionRef.current === null) {\n      return;\n    }\n\n    event.preventDefault();\n\n    window.scrollBy({\n      left: prevPositionRef.current - event.clientX,\n    });\n\n    prevPositionRef.current = event.clientX;\n  }, []);\n\n  const handleWindowMouseRelease = useCallback(() => {\n    if (prevPositionRef.current === null) {\n      return;\n    }\n\n    prevPositionRef.current = null;\n    document.body.classList.remove(globalStyles.dragScrolling);\n  }, []);\n\n  useEffect(() => {\n    window.addEventListener('mousemove', handleWindowMouseMove);\n\n    window.addEventListener('mouseup', handleWindowMouseRelease);\n    window.addEventListener('blur', handleWindowMouseRelease);\n    window.addEventListener('contextmenu', handleWindowMouseRelease);\n\n    return () => {\n      window.removeEventListener('mousemove', handleWindowMouseMove);\n\n      window.removeEventListener('mouseup', handleWindowMouseRelease);\n      window.removeEventListener('blur', handleWindowMouseRelease);\n      window.removeEventListener('contextmenu', handleWindowMouseRelease);\n    };\n  }, [handleWindowMouseMove, handleWindowMouseRelease]);\n\n  useDidUpdate(() => {\n    if (isAddListOpened) {\n      window.scroll(document.body.scrollWidth, 0);\n    }\n  }, [listIds, isAddListOpened]);\n\n  return (\n    // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n    <div ref={wrapperRef} className={styles.wrapper} onMouseDown={handleMouseDown}>\n      <div>\n        <DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>\n          <Droppable droppableId=\"board\" type={DroppableTypes.LIST} direction=\"horizontal\">\n            {({ innerRef, droppableProps, placeholder }) => (\n              <div\n                {...droppableProps} // eslint-disable-line react/jsx-props-no-spreading\n                data-drag-scroller\n                ref={innerRef}\n                className={styles.lists}\n              >\n                {listIds.map((listId, index) => (\n                  <List key={listId} id={listId} index={index} />\n                ))}\n                {placeholder}\n                {canAddList && (\n                  <div data-drag-scroller className={styles.list}>\n                    {isAddListOpened ? (\n                      <AddList onClose={handleAddListClose} />\n                    ) : (\n                      <button\n                        type=\"button\"\n                        className={styles.addListButton}\n                        onClick={handleAddListClick}\n                      >\n                        <PlusMathIcon className={styles.addListButtonIcon} />\n                        <span className={styles.addListButtonText}>\n                          {listIds.length > 0 ? t('action.addAnotherList') : t('action.addList')}\n                        </span>\n                      </button>\n                    )}\n                  </div>\n                )}\n              </div>\n            )}\n          </Droppable>\n        </DragDropContext>\n      </div>\n    </div>\n  );\n});\n\nexport default KanbanContent;\n"
  },
  {
    "path": "client/src/components/boards/Board/KanbanContent/KanbanContent.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .addListButton {\n    background: rgba(0, 0, 0, 0.24);\n    border: none;\n    border-radius: 3px;\n    color: rgba(255, 255, 255, 0.72);\n    cursor: pointer;\n    display: block;\n    fill: rgba(255, 255, 255, 0.72);\n    font-weight: normal;\n    height: 42px;\n    padding: 11px;\n    text-align: left;\n    transition: background 85ms ease-in, opacity 40ms ease-in,\n      border-color 85ms ease-in;\n    width: 100%;\n\n    &:active {\n      outline: none;\n    }\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.32);\n    }\n  }\n\n  .addListButtonIcon {\n    height: 20px;\n    padding: 0.64px;\n    width: 20px;\n  }\n\n  .addListButtonText {\n    display: inline-block;\n    font-size: 14px;\n    line-height: 20px;\n    vertical-align: top;\n  }\n\n  .list {\n    margin-right: 8px;\n    width: 272px;\n  }\n\n  .lists {\n    display: inline-flex;\n    height: 100%;\n    min-width: 100%;\n  }\n\n  .wrapper {\n    padding: 0 12px 0 20px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/Board/KanbanContent/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport KanbanContent from './KanbanContent';\n\nexport default KanbanContent;\n"
  },
  {
    "path": "client/src/components/boards/Board/ListView.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { shallowEqual, useSelector } from 'react-redux';\nimport { useInView } from 'react-intersection-observer';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Icon, Loader } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\nimport Card from '../../cards/Card';\nimport AddCard from '../../cards/AddCard';\nimport PlusMathIcon from '../../../assets/images/plus-math-icon.svg?react';\n\nimport styles from './ListView.module.scss';\n\nconst ListView = React.memo(\n  ({ cardIds, isCardsFetching, isAllCardsFetched, onCardsFetch, onCardCreate, onCardPaste }) => {\n    const clipboard = useSelector(selectors.selectClipboard);\n\n    const { canAddCard, canPasteCard } = useSelector((state) => {\n      const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n      const isEditor = !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n\n      return {\n        canAddCard: isEditor,\n        canPasteCard: isEditor,\n      };\n    }, shallowEqual);\n\n    const [t] = useTranslation();\n    const [isAddCardOpened, setIsAddCardOpened] = useState(false);\n\n    const [inViewRef] = useInView({\n      threshold: 1,\n      onChange: (inView) => {\n        if (inView && onCardsFetch) {\n          onCardsFetch();\n        }\n      },\n    });\n\n    const handleAddCardClick = useCallback(() => {\n      setIsAddCardOpened(true);\n    }, []);\n\n    const handleAddCardClose = useCallback(() => {\n      setIsAddCardOpened(false);\n    }, []);\n\n    return (\n      <div className={styles.wrapper}>\n        {canAddCard &&\n          (onCardCreate && isAddCardOpened ? (\n            <div className={styles.segment}>\n              <AddCard onCreate={onCardCreate} onClose={handleAddCardClose} />\n            </div>\n          ) : (\n            <div className={styles.addCardButtonWrapper}>\n              <Button\n                type=\"button\"\n                disabled={!onCardCreate}\n                className={styles.addCardButton}\n                onClick={handleAddCardClick}\n              >\n                <PlusMathIcon className={styles.addCardButtonIcon} />\n                <span className={styles.addCardButtonText}>\n                  {onCardCreate ? t('action.addCard') : t('common.atLeastOneListMustBePresent')}\n                </span>\n              </Button>\n              {onCardPaste && clipboard && canPasteCard && (\n                <Button\n                  type=\"button\"\n                  disabled={!onCardCreate}\n                  className={classNames(styles.addCardButton, styles.paste)}\n                  onClick={onCardPaste}\n                >\n                  <Icon fitted name=\"paste\" />\n                </Button>\n              )}\n            </div>\n          ))}\n        {cardIds.length > 0 && (\n          <div className={classNames(styles.segment, styles.cards)}>\n            {cardIds.map((cardId, cardIndex) => (\n              <div key={cardId} className={styles.card}>\n                <Card isInline id={cardId} index={cardIndex} />\n              </div>\n            ))}\n          </div>\n        )}\n        {isCardsFetching !== undefined && isAllCardsFetched !== undefined && (\n          <div className={styles.loaderWrapper}>\n            {isCardsFetching ? (\n              <Loader active inverted inline=\"centered\" size=\"small\" />\n            ) : (\n              !isAllCardsFetched && <div ref={inViewRef} />\n            )}\n          </div>\n        )}\n      </div>\n    );\n  },\n);\n\nListView.propTypes = {\n  cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types\n  isCardsFetching: PropTypes.bool,\n  isAllCardsFetched: PropTypes.bool,\n  onCardsFetch: PropTypes.func,\n  onCardCreate: PropTypes.func,\n  onCardPaste: PropTypes.func,\n};\n\nListView.defaultProps = {\n  isCardsFetching: undefined,\n  isAllCardsFetched: undefined,\n  onCardsFetch: undefined,\n  onCardCreate: undefined,\n  onCardPaste: undefined,\n};\n\nexport default ListView;\n"
  },
  {
    "path": "client/src/components/boards/Board/ListView.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .addCardButton {\n    background: rgba(0, 0, 0, 0.24);\n    border: none;\n    border-radius: 3px;\n    color: rgba(255, 255, 255, 0.72);\n    cursor: pointer;\n    display: block;\n    fill: rgba(255, 255, 255, 0.72);\n    flex: 1;\n    font-weight: normal;\n    height: 42px;\n    margin: 0;\n    padding: 11px;\n    text-align: left;\n    transition: background 85ms ease-in, opacity 40ms ease-in,\n      border-color 85ms ease-in;\n\n    &:active {\n      outline: none;\n    }\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.32);\n    }\n\n    &.paste {\n      flex: 0 0 auto;\n    }\n  }\n\n  .addCardButtonIcon {\n    height: 20px;\n    padding: 0.64px;\n    width: 20px;\n  }\n\n  .addCardButtonText {\n    display: inline-block;\n    font-size: 14px;\n    line-height: 20px;\n    vertical-align: top;\n  }\n\n  .addCardButtonWrapper {\n    display: flex;\n    gap: 6px;\n    margin-bottom: 12px;\n  }\n\n  .card {\n    margin-bottom: 6px;\n  }\n\n  .cards {\n    overflow: hidden;\n    padding-bottom: 0 !important; // TODO: hack?\n  }\n\n  .loaderWrapper {\n    margin: 24px 0;\n  }\n\n  .segment {\n    background: rgba(223, 227, 230, 0.8);\n    border-radius: 3px;\n    margin-bottom: 12px;\n    padding: 6px;\n  }\n\n  .wrapper {\n    overflow-x: hidden;\n    overflow-y: scroll;\n    padding: 0 20px;\n    width: 100%;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.08);\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/Board/ShortcutsProvider.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { push } from '../../../lib/redux-router';\nimport { useDidUpdate } from '../../../lib/hooks';\nimport { closePopup } from '../../../lib/popup';\n\nimport store from '../../../store';\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { isListArchiveOrTrash } from '../../../utils/record-helpers';\nimport { isActiveTextElement } from '../../../utils/element-helpers';\nimport { isModifierKeyPressed } from '../../../utils/event-helpers';\nimport { BoardShortcutsContext } from '../../../contexts';\nimport Paths from '../../../constants/Paths';\nimport {\n  BoardContexts,\n  BoardMembershipRoles,\n  BoardViews,\n  ListTypes,\n} from '../../../constants/Enums';\nimport CardActionsStep from '../../cards/CardActionsStep';\n\nconst canCopyCard = (isManager, boardMembership) => {\n  if (isManager) {\n    return true;\n  }\n\n  return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n};\n\nconst canCutCard = (boardMembership) =>\n  !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n\nconst canPasteCard = (boardMembership) =>\n  !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n\nconst canEditCardName = (boardMembership, list) => {\n  if (isListArchiveOrTrash(list)) {\n    return false;\n  }\n\n  return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n};\n\nconst canArchiveCard = (boardMembership, list) => {\n  if (list.type === ListTypes.ARCHIVE) {\n    return false;\n  }\n\n  return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n};\n\nconst canUseCardMembers = (boardMembership, list) => {\n  if (isListArchiveOrTrash(list)) {\n    return false;\n  }\n\n  return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n};\n\nconst canUseCardLabels = (boardMembership, list) => {\n  if (isListArchiveOrTrash(list)) {\n    return false;\n  }\n\n  return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n};\n\nconst ShortcutsProvider = React.memo(({ children }) => {\n  const { cardId, boardId } = useSelector(selectors.selectPath);\n\n  const dispatch = useDispatch();\n\n  const selectedListRef = useRef(null);\n  const selectedCardRef = useRef(null);\n\n  const handleListMouseEnter = useCallback((id, onPaste) => {\n    selectedListRef.current = {\n      id,\n      onPaste,\n    };\n  }, []);\n\n  const handleListMouseLeave = useCallback(() => {\n    selectedListRef.current = null;\n  }, []);\n\n  const handleCardMouseEnter = useCallback((id, editName, openActions) => {\n    selectedCardRef.current = {\n      id,\n      editName,\n      openActions,\n    };\n  }, []);\n\n  const handleCardMouseLeave = useCallback(() => {\n    selectedCardRef.current = null;\n  }, []);\n\n  const contextValue = useMemo(\n    () => [handleListMouseEnter, handleListMouseLeave, handleCardMouseEnter, handleCardMouseLeave],\n    [handleListMouseEnter, handleListMouseLeave, handleCardMouseEnter, handleCardMouseLeave],\n  );\n\n  useDidUpdate(() => {\n    selectedListRef.current = null;\n    selectedCardRef.current = null;\n  }, [cardId, boardId]);\n\n  useEffect(() => {\n    const handleCardCopy = (event) => {\n      if (!selectedCardRef.current) {\n        return;\n      }\n\n      const state = store.getState();\n      const card = selectors.selectCardById(state, selectedCardRef.current.id);\n\n      if (!card || !card.isPersisted) {\n        return;\n      }\n\n      const isManager = selectors.selectIsCurrentUserManagerForCurrentProject(state);\n      const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n\n      if (!canCopyCard(isManager, boardMembership)) {\n        return;\n      }\n\n      event.preventDefault();\n      dispatch(entryActions.copyCard(card.id));\n    };\n\n    const handleCardCut = (event) => {\n      if (!selectedCardRef.current) {\n        return;\n      }\n\n      const state = store.getState();\n      const card = selectors.selectCardById(state, selectedCardRef.current.id);\n\n      if (!card || !card.isPersisted) {\n        return;\n      }\n\n      const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n\n      if (!canCutCard(boardMembership)) {\n        return;\n      }\n\n      event.preventDefault();\n      dispatch(entryActions.cutCard(card.id));\n    };\n\n    const handleCardPaste = (event) => {\n      const state = store.getState();\n      const clipboard = selectors.selectClipboard(state);\n\n      if (!clipboard) {\n        return;\n      }\n\n      const board = selectors.selectCurrentBoard(state);\n\n      let listId;\n      if (board.context === BoardContexts.BOARD) {\n        if (board.view === BoardViews.KANBAN) {\n          listId = selectedListRef.current?.id;\n        } else {\n          listId = selectors.selectFirstKanbanListId(state);\n        }\n      } else {\n        listId = selectors.selectCurrentListId(state);\n      }\n\n      if (!listId) {\n        return;\n      }\n\n      const list = selectors.selectListById(state, listId);\n\n      if (!list || !list.isPersisted) {\n        return;\n      }\n\n      const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n\n      if (!canPasteCard(boardMembership)) {\n        return;\n      }\n\n      event.preventDefault();\n      dispatch(entryActions.pasteCard(list.id));\n\n      if (selectedListRef.current) {\n        selectedListRef.current.onPaste();\n      }\n    };\n\n    const handleCardOpen = (event) => {\n      if (!selectedCardRef.current) {\n        return;\n      }\n\n      const state = store.getState();\n      const card = selectors.selectCardById(state, selectedCardRef.current.id);\n\n      if (!card || !card.isPersisted) {\n        return;\n      }\n\n      event.preventDefault();\n\n      closePopup();\n      dispatch(push(Paths.CARDS.replace(':id', card.id)));\n    };\n\n    const handleCardNameEdit = (event) => {\n      if (!selectedCardRef.current) {\n        return;\n      }\n\n      const state = store.getState();\n      const card = selectors.selectCardById(state, selectedCardRef.current.id);\n\n      if (!card || !card.isPersisted) {\n        return;\n      }\n\n      const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n      const list = selectors.selectListById(state, card.listId);\n\n      if (!canEditCardName(boardMembership, list)) {\n        return;\n      }\n\n      event.preventDefault();\n      selectedCardRef.current.editName();\n    };\n\n    const handleCardArchive = (event) => {\n      if (!selectedCardRef.current) {\n        return;\n      }\n\n      const state = store.getState();\n      const card = selectors.selectCardById(state, selectedCardRef.current.id);\n\n      if (!card || !card.isPersisted) {\n        return;\n      }\n\n      const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n      const list = selectors.selectListById(state, card.listId);\n\n      if (!canArchiveCard(boardMembership, list)) {\n        return;\n      }\n\n      event.preventDefault();\n      selectedCardRef.current.openActions(CardActionsStep.StepTypes.ARCHIVE);\n    };\n\n    const handleCardMembers = (event) => {\n      if (!selectedCardRef.current) {\n        return;\n      }\n\n      const state = store.getState();\n      const card = selectors.selectCardById(state, selectedCardRef.current.id);\n\n      if (!card || !card.isPersisted) {\n        return;\n      }\n\n      const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n      const list = selectors.selectListById(state, card.listId);\n\n      if (!canUseCardMembers(boardMembership, list)) {\n        return;\n      }\n\n      event.preventDefault();\n      selectedCardRef.current.openActions(CardActionsStep.StepTypes.MEMBERS);\n    };\n\n    const handleCardLabels = (event) => {\n      if (!selectedCardRef.current) {\n        return;\n      }\n\n      const state = store.getState();\n      const card = selectors.selectCardById(state, selectedCardRef.current.id);\n\n      if (!card || !card.isPersisted) {\n        return;\n      }\n\n      const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n      const list = selectors.selectListById(state, card.listId);\n\n      if (!canUseCardLabels(boardMembership, list)) {\n        return;\n      }\n\n      event.preventDefault();\n      selectedCardRef.current.openActions(CardActionsStep.StepTypes.LABELS);\n    };\n\n    const handleLabelToCardAdd = (event) => {\n      if (!selectedCardRef.current) {\n        return;\n      }\n\n      const state = store.getState();\n      const card = selectors.selectCardById(state, selectedCardRef.current.id);\n\n      if (!card || !card.isPersisted) {\n        return;\n      }\n\n      const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n      const list = selectors.selectListById(state, card.listId);\n\n      if (!canUseCardLabels(boardMembership, list)) {\n        return;\n      }\n\n      const index = event.code === 'Digit0' ? 10 : parseInt(event.code.slice(-1), 10) - 1;\n      const label = selectors.selectLabelsForCurrentBoard(state)[index];\n\n      if (!label) {\n        return;\n      }\n\n      event.preventDefault();\n      const labelIds = selectors.selectLabelIdsByCardId(state, card.id);\n\n      if (labelIds.includes(label.id)) {\n        dispatch(entryActions.removeLabelFromCard(label.id, card.id));\n      } else {\n        dispatch(entryActions.addLabelToCard(label.id, card.id));\n      }\n    };\n\n    const handleKeyDown = (event) => {\n      if (isActiveTextElement(event.target)) {\n        return;\n      }\n\n      if (isModifierKeyPressed(event)) {\n        switch (event.key) {\n          case 'c':\n            handleCardCopy(event);\n\n            break;\n          case 'x':\n            handleCardCut(event);\n\n            break;\n          case 'v':\n            handleCardPaste(event);\n\n            break;\n          default:\n        }\n\n        return;\n      }\n\n      switch (event.code) {\n        case 'KeyE':\n        case 'Enter':\n          handleCardOpen(event);\n\n          break;\n        case 'KeyL':\n          handleCardLabels(event);\n\n          break;\n        case 'KeyM':\n          handleCardMembers(event);\n\n          break;\n        case 'KeyT':\n          handleCardNameEdit(event);\n\n          break;\n        case 'KeyV':\n          handleCardArchive(event);\n\n          break;\n        case 'Digit1':\n        case 'Digit2':\n        case 'Digit3':\n        case 'Digit4':\n        case 'Digit5':\n        case 'Digit6':\n        case 'Digit7':\n        case 'Digit8':\n        case 'Digit9':\n        case 'Digit0':\n          handleLabelToCardAdd(event);\n\n          break;\n        default:\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown);\n\n    return () => {\n      window.removeEventListener('keydown', handleKeyDown);\n    };\n  }, [dispatch]);\n\n  return (\n    <BoardShortcutsContext.Provider value={contextValue}>{children}</BoardShortcutsContext.Provider>\n  );\n});\n\nShortcutsProvider.propTypes = {\n  children: PropTypes.element.isRequired,\n};\n\nexport default ShortcutsProvider;\n"
  },
  {
    "path": "client/src/components/boards/Board/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Board from './Board';\n\nexport default Board;\n"
  },
  {
    "path": "client/src/components/boards/BoardActions/BoardActions.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport { BoardContexts } from '../../../constants/Enums';\nimport { BoardContextIcons } from '../../../constants/Icons';\nimport Filters from './Filters';\nimport RightSide from './RightSide';\nimport BoardMemberships from '../../board-memberships/BoardMemberships';\n\nimport styles from './BoardActions.module.scss';\n\nconst BoardActions = React.memo(() => {\n  const boardContext = useSelector((state) => selectors.selectCurrentBoard(state).context);\n\n  const withContextTitle = boardContext !== BoardContexts.BOARD;\n\n  const withMemberships = useSelector((state) => {\n    if (withContextTitle) {\n      return false;\n    }\n\n    const boardMemberships = selectors.selectMembershipsForCurrentBoard(state);\n\n    if (boardMemberships.length > 0) {\n      return true;\n    }\n\n    return selectors.selectIsCurrentUserManagerForCurrentProject(state);\n  });\n\n  const [t] = useTranslation();\n\n  return (\n    <div className={styles.wrapper}>\n      <div className={styles.actions}>\n        {withContextTitle && (\n          <div className={styles.action}>\n            <div className={styles.contextTitle}>\n              <Icon name={BoardContextIcons[boardContext]} className={styles.contextTitleIcon} />\n              {t(`common.${boardContext}`)}\n            </div>\n          </div>\n        )}\n        {withMemberships && (\n          <div className={styles.action}>\n            <BoardMemberships />\n          </div>\n        )}\n        <div className={styles.action}>\n          <Filters />\n        </div>\n        <div className={classNames(styles.action, styles.actionRightSide)}>\n          <RightSide />\n        </div>\n      </div>\n    </div>\n  );\n});\n\nexport default BoardActions;\n"
  },
  {
    "path": "client/src/components/boards/BoardActions/BoardActions.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .action {\n    align-items: center;\n    display: flex;\n    flex: 0 0 auto;\n\n    &:first-child {\n      padding-left: 20px;\n    }\n\n    &:last-child {\n      padding-right: 20px;\n    }\n\n    &:not(:last-child) {\n      margin-right: 20px;\n    }\n  }\n\n  .actionRightSide {\n    margin-left: auto;\n  }\n\n  .actions {\n    display: flex;\n    height: 46px;\n  }\n\n  .contextTitle {\n    background: rgba(0, 0, 0, 0.08);\n    border-radius: 3px;\n    color: #fff;\n    line-height: 34px;\n    padding: 2px 14px;\n  }\n\n  .contextTitleIcon {\n    margin-right: 8px;\n  }\n\n  .wrapper {\n    overflow-x: auto;\n    overflow-y: hidden;\n    -ms-overflow-style: none;\n    padding: 15px 0;\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/BoardActions/Filters.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport debounce from 'lodash/debounce';\nimport React, { useCallback, useMemo, useState } from 'react';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon } from 'semantic-ui-react';\nimport { useDidUpdate } from '../../../lib/hooks';\nimport { usePopup } from '../../../lib/popup';\nimport { Input } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useNestedRef } from '../../../hooks';\nimport UserAvatar from '../../users/UserAvatar';\nimport BoardMembershipsStep from '../../board-memberships/BoardMembershipsStep';\nimport LabelChip from '../../labels/LabelChip';\nimport LabelsStep from '../../labels/LabelsStep';\n\nimport styles from './Filters.module.scss';\n\nconst Filters = React.memo(() => {\n  const board = useSelector(selectors.selectCurrentBoard);\n  const userIds = useSelector(selectors.selectFilterUserIdsForCurrentBoard);\n  const labelIds = useSelector(selectors.selectFilterLabelIdsForCurrentBoard);\n  const currentUserId = useSelector(selectors.selectCurrentUserId);\n\n  const withCurrentUserSelector = useSelector(\n    (state) => !!selectors.selectCurrentUserMembershipForCurrentBoard(state),\n  );\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [search, setSearch] = useState(board.search);\n  const [isSearchFocused, setIsSearchFocused] = useState(false);\n\n  const debouncedSearch = useMemo(\n    () =>\n      debounce((nextSearch) => {\n        dispatch(entryActions.searchInCurrentBoard(nextSearch));\n      }, 400),\n    [dispatch],\n  );\n\n  const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');\n\n  const cancelSearch = useCallback(() => {\n    debouncedSearch.cancel();\n    setSearch('');\n    dispatch(entryActions.searchInCurrentBoard(''));\n    searchFieldRef.current.blur();\n  }, [dispatch, debouncedSearch, searchFieldRef]);\n\n  const handleUserSelect = useCallback(\n    (userId) => {\n      dispatch(entryActions.addUserToFilterInCurrentBoard(userId));\n    },\n    [dispatch],\n  );\n\n  const handleCurrentUserSelect = useCallback(() => {\n    dispatch(entryActions.addUserToFilterInCurrentBoard(currentUserId));\n  }, [currentUserId, dispatch]);\n\n  const handleUserDeselect = useCallback(\n    (userId) => {\n      dispatch(entryActions.removeUserFromFilterInCurrentBoard(userId));\n    },\n    [dispatch],\n  );\n\n  const handleUserClick = useCallback(\n    ({\n      currentTarget: {\n        dataset: { id: userId },\n      },\n    }) => {\n      dispatch(entryActions.removeUserFromFilterInCurrentBoard(userId));\n    },\n    [dispatch],\n  );\n\n  const handleLabelSelect = useCallback(\n    (labelId) => {\n      dispatch(entryActions.addLabelToFilterInCurrentBoard(labelId));\n    },\n    [dispatch],\n  );\n\n  const handleLabelDeselect = useCallback(\n    (labelId) => {\n      dispatch(entryActions.removeLabelFromFilterInCurrentBoard(labelId));\n    },\n    [dispatch],\n  );\n\n  const handleLabelClick = useCallback(\n    ({\n      currentTarget: {\n        dataset: { id: labelId },\n      },\n    }) => {\n      dispatch(entryActions.removeLabelFromFilterInCurrentBoard(labelId));\n    },\n    [dispatch],\n  );\n\n  const handleSearchChange = useCallback(\n    (_, { value }) => {\n      setSearch(value);\n      debouncedSearch(value);\n    },\n    [debouncedSearch],\n  );\n\n  const handleSearchFocus = useCallback(() => {\n    setIsSearchFocused(true);\n  }, []);\n\n  const handleSearchKeyDown = useCallback(\n    (event) => {\n      if (event.key === 'Escape') {\n        cancelSearch();\n      }\n    },\n    [cancelSearch],\n  );\n\n  const handleSearchBlur = useCallback(() => {\n    setIsSearchFocused(false);\n  }, []);\n\n  const handleCancelSearchClick = useCallback(() => {\n    cancelSearch();\n  }, [cancelSearch]);\n\n  useDidUpdate(() => {\n    setSearch(board.search);\n  }, [board.search]);\n\n  const BoardMembershipsPopup = usePopup(BoardMembershipsStep);\n  const LabelsPopup = usePopup(LabelsStep);\n\n  const isSearchActive = search || isSearchFocused;\n\n  return (\n    <>\n      <span className={styles.filter}>\n        <BoardMembershipsPopup\n          currentUserIds={userIds}\n          title=\"common.filterByMembers\"\n          onUserSelect={handleUserSelect}\n          onUserDeselect={handleUserDeselect}\n        >\n          <button type=\"button\" className={styles.filterButton}>\n            <span className={styles.filterTitle}>{`${t('common.members')}:`}</span>\n            {userIds.length === 0 && <span className={styles.filterLabel}>{t('common.all')}</span>}\n          </button>\n        </BoardMembershipsPopup>\n        {userIds.length === 0 && withCurrentUserSelector && (\n          <button type=\"button\" className={styles.filterButton} onClick={handleCurrentUserSelect}>\n            <span className={styles.filterLabel}>\n              <Icon fitted name=\"target\" className={styles.filterLabelIcon} />\n            </span>\n          </button>\n        )}\n        {userIds.map((userId) => (\n          <span key={userId} className={styles.filterItem}>\n            <UserAvatar id={userId} size=\"tiny\" onClick={handleUserClick} />\n          </span>\n        ))}\n      </span>\n      <span className={styles.filter}>\n        <LabelsPopup\n          currentIds={labelIds}\n          title=\"common.filterByLabels\"\n          onSelect={handleLabelSelect}\n          onDeselect={handleLabelDeselect}\n        >\n          <button type=\"button\" className={styles.filterButton}>\n            <span className={styles.filterTitle}>{`${t('common.labels')}:`}</span>\n            {labelIds.length === 0 && <span className={styles.filterLabel}>{t('common.all')}</span>}\n          </button>\n        </LabelsPopup>\n        {labelIds.map((labelId) => (\n          <span key={labelId} className={styles.filterItem}>\n            <LabelChip id={labelId} size=\"small\" onClick={handleLabelClick} />\n          </span>\n        ))}\n      </span>\n      <span className={styles.filter}>\n        <Input\n          ref={handleSearchFieldRef}\n          value={search}\n          placeholder={t('common.searchCards')}\n          maxLength={128}\n          icon={\n            isSearchActive ? (\n              <Icon link name=\"cancel\" onClick={handleCancelSearchClick} />\n            ) : (\n              'search'\n            )\n          }\n          className={classNames(styles.search, !isSearchActive && styles.searchInactive)}\n          onFocus={handleSearchFocus}\n          onKeyDown={handleSearchKeyDown}\n          onChange={handleSearchChange}\n          onBlur={handleSearchBlur}\n        />\n      </span>\n    </>\n  );\n});\n\nexport default Filters;\n"
  },
  {
    "path": "client/src/components/boards/BoardActions/Filters.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .filter {\n    margin-right: 10px;\n  }\n\n  .filterButton {\n    background: transparent;\n    border: none;\n    cursor: pointer;\n    display: inline-block;\n    outline: none;\n    padding: 0;\n\n    &:not(:first-of-type) {\n      margin-left: 6px;\n    }\n  }\n\n  .filterItem {\n    display: inline-block;\n    font-size: 0;\n    line-height: 0;\n    margin-right: 4px;\n    max-width: 190px;\n    vertical-align: top;\n  }\n\n  .filterLabel {\n    background: rgba(0, 0, 0, 0.24);\n    border-radius: 3px;\n    color: #fff;\n    display: inline-block;\n    font-size: 12px;\n    line-height: 20px;\n    padding: 2px 8px;\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.32);\n    }\n  }\n\n  .filterLabelIcon {\n    line-height: 1;\n  }\n\n  .filterTitle {\n    border-radius: 3px;\n    color: #fff;\n    display: inline-block;\n    font-size: 12px;\n    line-height: 20px;\n    padding: 2px 12px;\n  }\n\n  .search {\n    height: 30px;\n    margin: 0 12px;\n    transition: width 0.2s ease;\n    width: 280px;\n\n    @media only screen and (width < 768px) {\n      width: 220px;\n    }\n\n    input {\n      font-size: 13px;\n    }\n  }\n\n  .searchInactive {\n    color: #fff;\n    height: 24px;\n    width: 220px;\n\n    input {\n      background: rgba(0, 0, 0, 0.24);\n      border: none;\n      color: #fff !important;\n      font-size: 12px;\n\n      &::placeholder {\n        color: #fff;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/BoardActions/RightSide/ActionsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { shallowEqual, useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../../lib/custom-ui';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { useSteps } from '../../../../hooks';\nimport { BoardContexts, BoardMembershipRoles } from '../../../../constants/Enums';\nimport { BoardContextIcons } from '../../../../constants/Icons';\nimport ConfirmationStep from '../../../common/ConfirmationStep';\nimport CustomFieldGroupsStep from '../../../custom-field-groups/CustomFieldGroupsStep';\n\nimport styles from './ActionsStep.module.scss';\n\nconst StepTypes = {\n  CUSTOM_FIELD_GROUPS: 'CUSTOM_FIELD_GROUPS',\n  EMPTY_TRASH: 'EMPTY_TRASH',\n};\n\nconst ActionsStep = React.memo(({ onClose }) => {\n  const board = useSelector(selectors.selectCurrentBoard);\n\n  const { withSubscribe, withCustomFieldGroups, withTrashEmptier } = useSelector((state) => {\n    const isManager = selectors.selectIsCurrentUserManagerForCurrentProject(state);\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n\n    let isMember = false;\n    let isEditor = false;\n\n    if (boardMembership) {\n      isMember = true;\n      isEditor = boardMembership.role === BoardMembershipRoles.EDITOR;\n    }\n\n    return {\n      withSubscribe: isMember, // TODO: rename?\n      withCustomFieldGroups: isEditor,\n      withTrashEmptier: board.context === BoardContexts.TRASH && (isManager || isEditor),\n    };\n  }, shallowEqual);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n\n  const handleToggleSubscriptionClick = useCallback(() => {\n    dispatch(\n      entryActions.updateCurrentBoard({\n        isSubscribed: !board.isSubscribed,\n      }),\n    );\n\n    onClose();\n  }, [onClose, board.isSubscribed, dispatch]);\n\n  const handleSelectContextClick = useCallback(\n    (_, { value: context }) => {\n      dispatch(entryActions.updateContextInCurrentBoard(context));\n      onClose();\n    },\n    [onClose, dispatch],\n  );\n\n  const handleActivitiesClick = useCallback(() => {\n    dispatch(entryActions.openBoardActivitiesModal());\n    onClose();\n  }, [onClose, dispatch]);\n\n  const handleEmptyTrashConfirm = useCallback(() => {\n    dispatch(entryActions.clearTrashListInCurrentBoard());\n    onClose();\n  }, [onClose, dispatch]);\n\n  const handleCustomFieldsClick = useCallback(() => {\n    openStep(StepTypes.CUSTOM_FIELD_GROUPS);\n  }, [openStep]);\n\n  const handleEmptyTrashClick = useCallback(() => {\n    openStep(StepTypes.EMPTY_TRASH);\n  }, [openStep]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.CUSTOM_FIELD_GROUPS:\n        return <CustomFieldGroupsStep onBack={handleBack} onClose={onClose} />;\n      case StepTypes.EMPTY_TRASH:\n        return (\n          <ConfirmationStep\n            title=\"common.emptyTrash\"\n            content=\"common.areYouSureYouWantToEmptyTrash\"\n            buttonContent=\"action.emptyTrash\"\n            onConfirm={handleEmptyTrashConfirm}\n            onBack={handleBack}\n          />\n        );\n      default:\n    }\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.boardActions', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Menu secondary vertical className={styles.menu}>\n          {withSubscribe && (\n            <Menu.Item className={styles.menuItem} onClick={handleToggleSubscriptionClick}>\n              <Icon\n                name={board.isSubscribed ? 'bell slash outline' : 'bell outline'}\n                className={styles.menuItemIcon}\n              />\n              {t(board.isSubscribed ? 'action.unsubscribe' : 'action.subscribe', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {withCustomFieldGroups && (\n            <Menu.Item className={styles.menuItem} onClick={handleCustomFieldsClick}>\n              <Icon name=\"sticky note outline\" className={styles.menuItemIcon} />\n              {t('common.customFields', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          <Menu.Item className={styles.menuItem} onClick={handleActivitiesClick}>\n            <Icon name=\"list ul\" className={styles.menuItemIcon} />\n            {t('common.actions', {\n              context: 'title',\n            })}\n          </Menu.Item>\n          {withTrashEmptier && (\n            <>\n              <hr className={styles.divider} />\n              <Menu.Item className={styles.menuItem} onClick={handleEmptyTrashClick}>\n                <Icon name=\"trash alternate outline\" className={styles.menuItemIcon} />\n                {t('action.emptyTrash', {\n                  context: 'title',\n                })}\n              </Menu.Item>\n            </>\n          )}\n          <>\n            <hr className={styles.divider} />\n            {[BoardContexts.BOARD, BoardContexts.ARCHIVE, BoardContexts.TRASH].map((context) => (\n              <Menu.Item\n                key={context}\n                value={context}\n                active={context === board.context}\n                className={styles.menuItem}\n                onClick={handleSelectContextClick}\n              >\n                <Icon name={BoardContextIcons[context]} className={styles.menuItemIcon} />\n                {t(`common.${context}`)}\n              </Menu.Item>\n            ))}\n          </>\n        </Menu>\n      </Popup.Content>\n    </>\n  );\n});\n\nActionsStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default ActionsStep;\n"
  },
  {
    "path": "client/src/components/boards/BoardActions/RightSide/ActionsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .divider {\n    background: #eee;\n    border: 0;\n    height: 1px;\n    margin-bottom: 8px;\n  }\n\n  .menu {\n    margin: 0 -12px -5px;\n    width: calc(100% + 24px);\n  }\n\n  .menuItem {\n    margin: 0;\n    padding-left: 14px;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.5em 0 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/BoardActions/RightSide/RightSide.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Icon } from 'semantic-ui-react';\nimport { usePopup } from '../../../../lib/popup';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { BoardContexts, BoardViews } from '../../../../constants/Enums';\nimport { BoardViewIcons } from '../../../../constants/Icons';\nimport ActionsStep from './ActionsStep';\n\nimport styles from './RightSide.module.scss';\n\nconst RightSide = React.memo(() => {\n  const board = useSelector(selectors.selectCurrentBoard);\n\n  const dispatch = useDispatch();\n\n  const handleSelectViewClick = useCallback(\n    ({ currentTarget: { value: view } }) => {\n      dispatch(entryActions.updateViewInCurrentBoard(view));\n    },\n    [dispatch],\n  );\n\n  const ActionsPopup = usePopup(ActionsStep);\n\n  const views = [BoardViews.GRID, BoardViews.LIST];\n  if (board.context === BoardContexts.BOARD) {\n    views.unshift(BoardViews.KANBAN);\n  }\n\n  return (\n    <>\n      <div className={styles.action}>\n        <div className={styles.buttonGroup}>\n          {views.map((view) => (\n            <button\n              key={view}\n              type=\"button\"\n              value={view}\n              disabled={view === board.view}\n              className={styles.button}\n              onClick={handleSelectViewClick}\n            >\n              <Icon fitted name={BoardViewIcons[view]} />\n            </button>\n          ))}\n        </div>\n      </div>\n      <div className={styles.action}>\n        <ActionsPopup>\n          <button type=\"button\" className={styles.button}>\n            <Icon fitted name=\"ellipsis vertical\" />\n          </button>\n        </ActionsPopup>\n      </div>\n    </>\n  );\n});\n\nexport default RightSide;\n"
  },
  {
    "path": "client/src/components/boards/BoardActions/RightSide/RightSide.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .action:not(:last-child) {\n    margin-right: 10px;\n  }\n\n  .button {\n    background: rgba(0, 0, 0, 0.24);\n    border: none;\n    border-radius: 3px;\n    color: #fff;\n    cursor: pointer;\n    display: inline-block;\n    font-size: 13px;\n    line-height: 20px;\n    outline: none;\n    padding: 5px 12px;\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.32);\n    }\n  }\n\n  .buttonGroup {\n    .button {\n      border-radius: 0;\n\n      &:enabled {\n        background: rgba(0, 0, 0, 0.08);\n        color: rgba(255, 255, 255, 0.72);\n\n        &:hover {\n          background: rgba(0, 0, 0, 0.12);\n          color: #fff;\n        }\n      }\n\n      &:first-child {\n        border-top-left-radius: 3px;\n        border-bottom-left-radius: 3px;\n      }\n\n      &:last-child {\n        border-top-right-radius: 3px;\n        border-bottom-right-radius: 3px;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/BoardActions/RightSide/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport RightSide from './RightSide';\n\nexport default RightSide;\n"
  },
  {
    "path": "client/src/components/boards/BoardActions/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport BoardActions from './BoardActions';\n\nexport default BoardActions;\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/BoardSettingsModal.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useClosableModal } from '../../../hooks';\nimport GeneralPane from './GeneralPane';\nimport PreferencesPane from './PreferencesPane';\nimport NotificationsPane from './NotificationsPane';\n\nconst BoardSettingsModal = React.memo(() => {\n  const openPreferences = useSelector(\n    (state) => selectors.selectCurrentModal(state).params.openPreferences,\n  );\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleClose = useCallback(() => {\n    dispatch(entryActions.closeModal());\n  }, [dispatch]);\n\n  const [ClosableModal] = useClosableModal();\n\n  const panes = [\n    {\n      menuItem: t('common.general', {\n        context: 'title',\n      }),\n      render: () => <GeneralPane />,\n    },\n    {\n      menuItem: t('common.preferences', {\n        context: 'title',\n      }),\n      render: () => <PreferencesPane />,\n    },\n    {\n      menuItem: t('common.notifications', {\n        context: 'title',\n      }),\n      render: () => <NotificationsPane />,\n    },\n  ];\n\n  return (\n    <ClosableModal closeIcon size=\"small\" centered={false} onClose={handleClose}>\n      <ClosableModal.Content>\n        <Tab\n          menu={{\n            secondary: true,\n            pointing: true,\n          }}\n          panes={panes}\n          defaultActiveIndex={openPreferences ? 1 : undefined}\n        />\n      </ClosableModal.Content>\n    </ClosableModal>\n  );\n});\n\nexport default BoardSettingsModal;\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/GeneralPane/EditInformation.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport React, { useCallback, useMemo } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Input } from 'semantic-ui-react';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../../hooks';\n\nimport styles from './EditInformation.module.scss';\n\nconst EditInformation = React.memo(() => {\n  const selectBoardById = useMemo(() => selectors.makeSelectBoardById(), []);\n\n  const boardId = useSelector((state) => selectors.selectCurrentModal(state).params.id);\n  const board = useSelector((state) => selectBoardById(state, boardId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultData = useMemo(\n    () => ({\n      name: board.name,\n    }),\n    [board.name],\n  );\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: '',\n    ...defaultData,\n  }));\n\n  const cleanData = useMemo(\n    () => ({\n      ...data,\n      name: data.name.trim(),\n    }),\n    [data],\n  );\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const handleSubmit = useCallback(() => {\n    if (!cleanData.name) {\n      nameFieldRef.current.select();\n      return;\n    }\n\n    dispatch(entryActions.updateBoard(boardId, cleanData));\n  }, [boardId, dispatch, cleanData, nameFieldRef]);\n\n  return (\n    <Form onSubmit={handleSubmit}>\n      <div className={styles.text}>{t('common.title')}</div>\n      <Input\n        fluid\n        ref={handleNameFieldRef}\n        name=\"name\"\n        value={data.name}\n        maxLength={128}\n        className={styles.field}\n        onChange={handleFieldChange}\n      />\n      <Button positive disabled={dequal(cleanData, defaultData)} content={t('action.save')} />\n    </Form>\n  );\n});\n\nexport default EditInformation;\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/GeneralPane/EditInformation.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/GeneralPane/GeneralPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Divider, Header, Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { usePopupInClosableContext } from '../../../../hooks';\nimport EditInformation from './EditInformation';\nimport ConfirmationStep from '../../../common/ConfirmationStep';\n\nimport styles from './GeneralPane.module.scss';\n\nconst GeneralPane = React.memo(() => {\n  const selectBoardById = useMemo(() => selectors.makeSelectBoardById(), []);\n\n  const boardId = useSelector((state) => selectors.selectCurrentModal(state).params.id);\n  const board = useSelector((state) => selectBoardById(state, boardId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteBoard(boardId));\n  }, [boardId, dispatch]);\n\n  const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <EditInformation />\n      <Divider horizontal section>\n        <Header as=\"h4\">\n          {t('common.dangerZone', {\n            context: 'title',\n          })}\n        </Header>\n      </Divider>\n      <div className={styles.action}>\n        <ConfirmationPopup\n          title=\"common.deleteBoard\"\n          content=\"common.areYouSureYouWantToDeleteThisBoard\"\n          buttonContent=\"action.deleteBoard\"\n          typeValue={board.name}\n          typeContent=\"common.typeTitleToConfirm\"\n          onConfirm={handleDeleteConfirm}\n        >\n          <Button className={styles.actionButton}>\n            {t(`action.deleteBoard`, {\n              context: 'title',\n            })}\n          </Button>\n        </ConfirmationPopup>\n      </div>\n    </Tab.Pane>\n  );\n});\n\nexport default GeneralPane;\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/GeneralPane/GeneralPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .action {\n    border: none;\n    border-radius: 0.28571429rem;\n    display: inline-block;\n    height: 36px;\n    overflow: hidden;\n    position: relative;\n    transition: background 0.3s ease;\n    width: 100%;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .actionButton {\n    background: transparent;\n    color: #6b808c;\n    font-weight: normal;\n    height: 36px;\n    line-height: 24px;\n    padding: 6px 12px;\n    text-align: left;\n    text-decoration: underline;\n    width: 100%;\n  }\n\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/GeneralPane/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport GeneralPane from './GeneralPane';\n\nexport default GeneralPane;\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/NotificationsPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport NotificationServices from '../../notification-services/NotificationServices';\n\nimport styles from './NotificationsPane.module.scss';\n\nconst NotificationsPane = React.memo(() => {\n  const selectNotificationServiceIdsByBoardId = useMemo(\n    () => selectors.makeSelectNotificationServiceIdsByBoardId(),\n    [],\n  );\n\n  const boardId = useSelector((state) => selectors.selectCurrentModal(state).params.id);\n\n  const notificationServiceIds = useSelector((state) =>\n    selectNotificationServiceIdsByBoardId(state, boardId),\n  );\n\n  const dispatch = useDispatch();\n\n  const handleCreate = useCallback(\n    (data) => {\n      dispatch(entryActions.createNotificationServiceInBoard(boardId, data));\n    },\n    [boardId, dispatch],\n  );\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <NotificationServices ids={notificationServiceIds} onCreate={handleCreate} />\n    </Tab.Pane>\n  );\n});\n\nexport default NotificationsPane;\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/NotificationsPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/PreferencesPane/DefaultCardType.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Radio, Segment } from 'semantic-ui-react';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport SelectCardType from '../../../cards/SelectCardType';\n\nimport styles from './DefaultCardType.module.scss';\n\nconst DefaultCardType = React.memo(() => {\n  const selectBoardById = useMemo(() => selectors.makeSelectBoardById(), []);\n\n  const boardId = useSelector((state) => selectors.selectCurrentModal(state).params.id);\n  const board = useSelector((state) => selectBoardById(state, boardId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleSelect = useCallback(\n    (defaultCardType) => {\n      dispatch(\n        entryActions.updateBoard(boardId, {\n          defaultCardType,\n        }),\n      );\n    },\n    [boardId, dispatch],\n  );\n\n  const handleToggleChange = useCallback(\n    (_, { name: fieldName, checked }) => {\n      dispatch(\n        entryActions.updateBoard(boardId, {\n          [fieldName]: checked,\n        }),\n      );\n    },\n    [boardId, dispatch],\n  );\n\n  return (\n    <>\n      <SelectCardType value={board.defaultCardType} onSelect={handleSelect} />\n      <Segment basic className={styles.settings}>\n        <Radio\n          toggle\n          name=\"limitCardTypesToDefaultOne\"\n          checked={board.limitCardTypesToDefaultOne}\n          label={t('common.limitCardTypesToDefaultOne')}\n          className={styles.radio}\n          onChange={handleToggleChange}\n        />\n      </Segment>\n    </>\n  );\n});\n\nexport default DefaultCardType;\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/PreferencesPane/DefaultCardType.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .radio {\n    margin-bottom: 16px;\n    width: 100%;\n\n    &:last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  .settings {\n    margin: 0 0 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/PreferencesPane/DefaultView.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Menu } from 'semantic-ui-react';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { BoardViews } from '../../../../constants/Enums';\nimport { BoardViewIcons } from '../../../../constants/Icons';\n\nimport styles from './DefaultView.module.scss';\n\nconst DESCRIPTION_BY_VIEW = {\n  [BoardViews.KANBAN]: 'common.visualTaskManagementWithLists',\n  [BoardViews.GRID]: 'common.dynamicAndUnevenlySpacedLayout',\n  [BoardViews.LIST]: 'common.sequentialDisplayOfCards',\n};\n\nconst DefaultView = React.memo(() => {\n  const selectBoardById = useMemo(() => selectors.makeSelectBoardById(), []);\n\n  const boardId = useSelector((state) => selectors.selectCurrentModal(state).params.id);\n  const board = useSelector((state) => selectBoardById(state, boardId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleSelectClick = useCallback(\n    (_, { value: defaultView }) => {\n      dispatch(\n        entryActions.updateBoard(boardId, {\n          defaultView,\n        }),\n      );\n    },\n    [boardId, dispatch],\n  );\n\n  return (\n    <Menu secondary vertical className={styles.menu}>\n      {[BoardViews.KANBAN, BoardViews.GRID, BoardViews.LIST].map((view) => (\n        <Menu.Item\n          key={view}\n          value={view}\n          active={view === board.defaultView}\n          className={styles.menuItem}\n          onClick={handleSelectClick}\n        >\n          <Icon name={BoardViewIcons[view]} className={styles.menuItemIcon} />\n          <div className={styles.menuItemTitle}>{t(`common.${view}`)}</div>\n          <p className={styles.menuItemDescription}>{t(DESCRIPTION_BY_VIEW[view])}</p>\n        </Menu.Item>\n      ))}\n    </Menu>\n  );\n});\n\nexport default DefaultView;\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/PreferencesPane/DefaultView.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menu {\n    margin: 0 auto 8px;\n    width: 100%;\n  }\n\n  .menuItem:last-child {\n    margin-bottom: 0;\n  }\n\n  .menuItemDescription {\n    opacity: 0.5;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.35714286em 0 0;\n  }\n\n  .menuItemTitle {\n    margin-bottom: 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/PreferencesPane/Others.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Radio, Segment } from 'semantic-ui-react';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\n\nimport styles from './Others.module.scss';\n\nconst Others = React.memo(() => {\n  const selectBoardById = useMemo(() => selectors.makeSelectBoardById(), []);\n\n  const boardId = useSelector((state) => selectors.selectCurrentModal(state).params.id);\n  const board = useSelector((state) => selectBoardById(state, boardId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleChange = useCallback(\n    (_, { name: fieldName, checked }) => {\n      dispatch(\n        entryActions.updateBoard(boardId, {\n          [fieldName]: checked,\n        }),\n      );\n    },\n    [boardId, dispatch],\n  );\n\n  return (\n    <Segment basic>\n      <Radio\n        toggle\n        name=\"alwaysDisplayCardCreator\"\n        checked={board.alwaysDisplayCardCreator}\n        label={t('common.alwaysDisplayCardCreator')}\n        className={styles.radio}\n        onChange={handleChange}\n      />\n      <Radio\n        toggle\n        name=\"displayCardAges\"\n        checked={board.displayCardAges}\n        label={t('common.displayCardAges')}\n        className={styles.radio}\n        onChange={handleChange}\n      />\n      <Radio\n        toggle\n        name=\"expandTaskListsByDefault\"\n        checked={board.expandTaskListsByDefault}\n        label={t('common.expandTaskListsByDefault')}\n        className={styles.radio}\n        onChange={handleChange}\n      />\n    </Segment>\n  );\n});\n\nexport default Others;\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/PreferencesPane/Others.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .radio {\n    margin-bottom: 16px;\n    width: 100%;\n\n    &:last-child {\n      margin-bottom: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/PreferencesPane/PreferencesPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { Divider, Header, Tab } from 'semantic-ui-react';\n\nimport DefaultView from './DefaultView';\nimport DefaultCardType from './DefaultCardType';\nimport Others from './Others';\n\nimport styles from './PreferencesPane.module.scss';\n\nconst PreferencesPane = React.memo(() => {\n  const [t] = useTranslation();\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <Divider horizontal className={styles.firstDivider}>\n        <Header as=\"h4\">\n          {t('common.defaultView', {\n            context: 'title',\n          })}\n        </Header>\n      </Divider>\n      <DefaultView />\n      <Divider horizontal>\n        <Header as=\"h4\">\n          {t('common.defaultCardType', {\n            context: 'title',\n          })}\n        </Header>\n      </Divider>\n      <DefaultCardType />\n      <Divider horizontal>\n        <Header as=\"h4\">\n          {t('common.others', {\n            context: 'title',\n          })}\n        </Header>\n      </Divider>\n      <Others />\n    </Tab.Pane>\n  );\n});\n\nexport default PreferencesPane;\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/PreferencesPane/PreferencesPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .firstDivider {\n    margin-top: 0;\n  }\n\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/PreferencesPane/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport PreferencesPane from './PreferencesPane';\n\nexport default PreferencesPane;\n"
  },
  {
    "path": "client/src/components/boards/BoardSettingsModal/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport BoardSettingsModal from './BoardSettingsModal';\n\nexport default BoardSettingsModal;\n"
  },
  {
    "path": "client/src/components/boards/Boards/Boards.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useRef } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { DragDropContext, Droppable } from 'react-beautiful-dnd';\nimport { Button } from 'semantic-ui-react';\nimport { closePopup, usePopup } from '../../../lib/popup';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport DroppableTypes from '../../../constants/DroppableTypes';\nimport Item from './Item';\nimport AddBoardStep from '../AddBoardStep';\n\nimport styles from './Boards.module.scss';\nimport globalStyles from '../../../styles.module.scss';\n\nconst Boards = React.memo(() => {\n  const boardIds = useSelector(selectors.selectBoardIdsForCurrentProject);\n\n  const canAdd = useSelector((state) => {\n    const isEditModeEnabled = selectors.selectIsEditModeEnabled(state); // TODO: move out?\n\n    if (!isEditModeEnabled) {\n      return isEditModeEnabled;\n    }\n\n    return selectors.selectIsCurrentUserManagerForCurrentProject(state);\n  });\n\n  const dispatch = useDispatch();\n\n  const tabsWrapperRef = useRef(null);\n\n  const handleDragStart = useCallback(() => {\n    document.body.classList.add(globalStyles.dragging);\n    closePopup();\n  }, []);\n\n  const handleDragEnd = useCallback(\n    ({ draggableId, source, destination }) => {\n      document.body.classList.remove(globalStyles.dragging);\n\n      if (!destination || source.index === destination.index) {\n        return;\n      }\n\n      dispatch(entryActions.moveBoard(draggableId, destination.index));\n    },\n    [dispatch],\n  );\n\n  const handleWheel = useCallback(({ deltaY }) => {\n    tabsWrapperRef.current.scrollBy({\n      left: deltaY,\n    });\n  }, []);\n\n  const AddBoardPopup = usePopup(AddBoardStep);\n\n  return (\n    <div className={styles.wrapper} onWheel={handleWheel}>\n      <div ref={tabsWrapperRef} className={styles.tabsWrapper}>\n        <DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>\n          <Droppable droppableId=\"boards\" type={DroppableTypes.BOARD} direction=\"horizontal\">\n            {({ innerRef, droppableProps, placeholder }) => (\n              // eslint-disable-next-line react/jsx-props-no-spreading\n              <div {...droppableProps} ref={innerRef} className={styles.tabs}>\n                {boardIds.map((boardId, index) => (\n                  <Item key={boardId} id={boardId} index={index} />\n                ))}\n                {placeholder}\n                {canAdd && (\n                  <AddBoardPopup>\n                    <Button icon=\"plus\" className={styles.addButton} />\n                  </AddBoardPopup>\n                )}\n              </div>\n            )}\n          </Droppable>\n        </DragDropContext>\n      </div>\n    </div>\n  );\n});\n\nexport default Boards;\n"
  },
  {
    "path": "client/src/components/boards/Boards/Boards.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .addButton {\n    background: transparent;\n    color: #fff;\n    margin-right: 0;\n    vertical-align: top;\n\n    &:hover {\n      background: rgba(34, 36, 38, 0.3);\n    }\n  }\n\n  .tabs {\n    display: flex;\n    height: 38px;\n    flex: 0 0 auto;\n    white-space: nowrap;\n  }\n\n  .tabsWrapper {\n    display: flex;\n    flex: 0 0 auto;\n    height: 56px;\n    overflow-x: auto;\n    overflow-y: hidden;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &:hover {\n      height: 38px;\n    }\n\n    &::-webkit-scrollbar {\n      height: 5px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      border-radius: 3px;\n    }\n  }\n\n  .wrapper {\n    border-bottom: 1px solid rgba(0, 0, 0, 0.24);\n    display: flex;\n    flex: 1 1 auto;\n    flex-direction: column;\n    height: 38px;\n    overflow: hidden;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/Boards/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Link } from 'react-router';\nimport { Draggable } from 'react-beautiful-dnd';\nimport { Button, Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport Paths from '../../../constants/Paths';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id, index }) => {\n  const selectBoardById = useMemo(() => selectors.makeSelectBoardById(), []);\n\n  const selectNotificationsTotalByBoardId = useMemo(\n    () => selectors.makeSelectNotificationsTotalByBoardId(),\n    [],\n  );\n\n  const board = useSelector((state) => selectBoardById(state, id));\n  const notificationsTotal = useSelector((state) => selectNotificationsTotalByBoardId(state, id));\n  const isActive = useSelector((state) => id === selectors.selectPath(state).boardId);\n\n  const canEdit = useSelector((state) => {\n    const isEditModeEnabled = selectors.selectIsEditModeEnabled(state); // TODO: move out?\n\n    if (!isEditModeEnabled) {\n      return isEditModeEnabled;\n    }\n\n    return selectors.selectIsCurrentUserManagerForCurrentProject(state);\n  });\n\n  const dispatch = useDispatch();\n\n  const handleEditClick = useCallback(() => {\n    dispatch(entryActions.openBoardSettingsModal(id));\n  }, [id, dispatch]);\n\n  return (\n    <Draggable draggableId={id} index={index} isDragDisabled={!board.isPersisted || !canEdit}>\n      {({ innerRef, draggableProps, dragHandleProps }) => (\n        // eslint-disable-next-line react/jsx-props-no-spreading\n        <div {...draggableProps} {...dragHandleProps} ref={innerRef} className={styles.wrapper}>\n          <div className={classNames(styles.tab, isActive && styles.tabActive)}>\n            {board.isPersisted ? (\n              <>\n                <Link\n                  to={Paths.BOARDS.replace(':id', id)}\n                  title={board.name}\n                  className={styles.link}\n                >\n                  {notificationsTotal > 0 && (\n                    <span className={styles.notifications}>{notificationsTotal}</span>\n                  )}\n                  <span className={styles.name}>{board.name}</span>\n                </Link>\n                {canEdit && (\n                  <Button className={styles.editButton} onClick={handleEditClick}>\n                    <Icon fitted name=\"pencil\" size=\"small\" />\n                  </Button>\n                )}\n              </>\n            ) : (\n              <span className={classNames(styles.name, styles.link)}>{board.name}</span>\n            )}\n          </div>\n        </div>\n      )}\n    </Draggable>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n  index: PropTypes.number.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/boards/Boards/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .editButton {\n    background: transparent;\n    box-shadow: none;\n    color: #fff;\n    line-height: 32px;\n    margin-right: 0;\n    opacity: 0;\n    padding: 0;\n    position: absolute;\n    right: 2px;\n    top: 2px;\n    width: 32px;\n\n    &:hover {\n      background: rgba(255, 255, 255, 0.08);\n    }\n  }\n\n  .link {\n    cursor: pointer;\n    display: block;\n    line-height: 20px;\n    padding: 10px 34px 6px 14px;\n    text-overflow: ellipsis;\n    max-width: 400px;\n    overflow: hidden;\n  }\n\n  .name {\n    color: rgba(255, 255, 255, 0.72);\n  }\n\n  .notifications {\n    background: #eb5a46;\n    border: none;\n    border-radius: 3px;\n    color: #fff;\n    display: inline-block;\n    font-size: 10px;\n    line-height: 18px;\n    margin-right: 6px;\n    outline: none;\n    padding: 0px 6px;\n    transition: background 0.3s ease;\n    vertical-align: text-bottom;\n  }\n\n  .tab {\n    border-radius: 3px 3px 0 0;\n    min-width: 100px;\n    position: relative;\n    transition: all 0.1s ease;\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.12);\n\n      .name {\n        color: #fff;\n      }\n\n      .editButton {\n        opacity: 1;\n      }\n    }\n  }\n\n  .tabActive {\n    background: rgba(0, 0, 0, 0.24);\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.32);\n    }\n\n    .name {\n      color: #fff;\n      font-weight: bold;\n    }\n  }\n\n  .wrapper {\n    display: flex;\n    flex: 0 0 auto;\n  }\n}\n"
  },
  {
    "path": "client/src/components/boards/Boards/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Boards from './Boards';\n\nexport default Boards;\n"
  },
  {
    "path": "client/src/components/cards/AddCard/AddCard.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport TextareaAutosize from 'react-textarea-autosize';\nimport { Button, Form, Icon, TextArea } from 'semantic-ui-react';\nimport { useClickAwayListener, useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';\nimport { usePopup } from '../../../lib/popup';\n\nimport selectors from '../../../selectors';\nimport { useClosable, useForm, useNestedRef } from '../../../hooks';\nimport { isModifierKeyPressed } from '../../../utils/event-helpers';\nimport { CardTypeIcons } from '../../../constants/Icons';\nimport SelectCardTypeStep from '../SelectCardTypeStep';\n\nimport styles from './AddCard.module.scss';\n\nconst DEFAULT_DATA = {\n  name: '',\n};\n\nconst AddCard = React.memo(({ isOpened, className, onCreate, onClose }) => {\n  const { defaultCardType: defaultType, limitCardTypesToDefaultOne: limitTypesToDefaultOne } =\n    useSelector(selectors.selectCurrentBoard);\n\n  const [t] = useTranslation();\n  const prevDefaultType = usePrevious(defaultType);\n\n  const [data, handleFieldChange, setData] = useForm(() => ({\n    ...DEFAULT_DATA,\n    type: defaultType,\n  }));\n\n  const [focusNameFieldState, focusNameField] = useToggle();\n  const [isClosableActiveRef, activateClosable, deactivateClosable] = useClosable();\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef();\n  const [submitButtonRef, handleSubmitButtonRef] = useNestedRef();\n  const [selectTypeButtonRef, handleSelectTypeButtonRef] = useNestedRef();\n\n  const submit = useCallback(\n    (autoOpen) => {\n      const cleanData = {\n        ...data,\n        name: data.name.trim(),\n      };\n\n      if (!cleanData.name) {\n        nameFieldRef.current.select();\n        return;\n      }\n\n      onCreate(cleanData, autoOpen);\n\n      setData({\n        ...DEFAULT_DATA,\n        type: defaultType,\n      });\n\n      if (autoOpen) {\n        onClose();\n      } else {\n        focusNameField();\n      }\n    },\n    [onCreate, onClose, defaultType, data, setData, focusNameField, nameFieldRef],\n  );\n\n  const handleSubmit = useCallback(() => {\n    submit();\n  }, [submit]);\n\n  const handleTypeSelect = useCallback(\n    (type) => {\n      setData((prevData) => ({\n        ...prevData,\n        type,\n      }));\n    },\n    [setData],\n  );\n\n  const handleFieldKeyDown = useCallback(\n    (event) => {\n      switch (event.key) {\n        case 'Enter':\n          event.preventDefault();\n          submit(isModifierKeyPressed(event));\n\n          break;\n        case 'Escape':\n          onClose();\n\n          break;\n        default:\n      }\n    },\n    [onClose, submit],\n  );\n\n  const handleSelectTypeClose = useCallback(() => {\n    deactivateClosable();\n    nameFieldRef.current.focus();\n  }, [deactivateClosable, nameFieldRef]);\n\n  const handleAwayClick = useCallback(() => {\n    if (!isOpened || isClosableActiveRef.current) {\n      return;\n    }\n\n    onClose();\n  }, [isOpened, onClose, isClosableActiveRef]);\n\n  const handleClickAwayCancel = useCallback(() => {\n    nameFieldRef.current.focus();\n  }, [nameFieldRef]);\n\n  const clickAwayProps = useClickAwayListener(\n    [nameFieldRef, submitButtonRef, selectTypeButtonRef],\n    handleAwayClick,\n    handleClickAwayCancel,\n  );\n\n  useEffect(() => {\n    if (isOpened) {\n      nameFieldRef.current.focus();\n    }\n  }, [isOpened, nameFieldRef]);\n\n  useEffect(() => {\n    if (!isOpened && defaultType !== prevDefaultType) {\n      setData((prevData) => ({\n        ...prevData,\n        type: defaultType,\n      }));\n    }\n  }, [isOpened, defaultType, prevDefaultType, setData]);\n\n  useDidUpdate(() => {\n    nameFieldRef.current.focus();\n  }, [focusNameFieldState]);\n\n  const SelectCardTypePopup = usePopup(SelectCardTypeStep, {\n    onOpen: activateClosable,\n    onClose: handleSelectTypeClose,\n  });\n\n  return (\n    <Form\n      className={classNames(className, !isOpened && styles.wrapperClosed)}\n      onSubmit={handleSubmit}\n    >\n      <div className={styles.fieldWrapper}>\n        <TextArea\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          ref={handleNameFieldRef}\n          as={TextareaAutosize}\n          name=\"name\"\n          value={data.name}\n          placeholder={t('common.enterCardTitle')}\n          maxLength={1024}\n          minRows={3}\n          className={styles.field}\n          onKeyDown={handleFieldKeyDown}\n          onChange={handleFieldChange}\n        />\n      </div>\n      <div className={styles.controls}>\n        <Button\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          positive\n          ref={handleSubmitButtonRef}\n          content={t('action.addCard')}\n          className={styles.button}\n        />\n        <SelectCardTypePopup defaultValue={data.type} onSelect={handleTypeSelect}>\n          <Button\n            {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n            ref={handleSelectTypeButtonRef}\n            type=\"button\"\n            disabled={limitTypesToDefaultOne}\n            className={classNames(styles.button, styles.selectTypeButton)}\n          >\n            <Icon name={CardTypeIcons[data.type]} className={styles.selectTypeButtonIcon} />\n            {t(`common.${data.type}`)}\n          </Button>\n        </SelectCardTypePopup>\n      </div>\n    </Form>\n  );\n});\n\nAddCard.propTypes = {\n  isOpened: PropTypes.bool,\n  className: PropTypes.string,\n  onCreate: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nAddCard.defaultProps = {\n  isOpened: true,\n  className: undefined,\n};\n\nexport default AddCard;\n"
  },
  {
    "path": "client/src/components/cards/AddCard/AddCard.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    white-space: nowrap;\n  }\n\n  .controls {\n    display: flex;\n  }\n\n  .field {\n    border: none;\n    margin-bottom: 4px;\n    outline: none;\n    overflow: hidden;\n    padding: 0;\n    resize: none;\n    width: 100%;\n  }\n\n  .fieldWrapper {\n    background: #fff;\n    border-radius: 3px;\n    box-shadow: 0 1px 0 #ccc;\n    margin-bottom: 8px;\n    min-height: 20px;\n    padding: 6px 8px 2px;\n  }\n\n  .selectTypeButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-left: auto;\n    margin-right: 0;\n    overflow: hidden;\n    text-align: left;\n    text-decoration: underline;\n    text-overflow: ellipsis;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .selectTypeButtonIcon {\n    text-decoration: none;\n  }\n\n  .wrapperClosed {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/AddCard/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AddCard from './AddCard';\n\nexport default AddCard;\n"
  },
  {
    "path": "client/src/components/cards/ArchiveCardsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch } from 'react-redux';\n\nimport entryActions from '../../entry-actions';\nimport ConfirmationStep from '../common/ConfirmationStep';\n\nconst ArchiveCardsStep = React.memo(({ listId, onBack, onClose }) => {\n  const dispatch = useDispatch();\n\n  const handleConfirm = useCallback(() => {\n    dispatch(entryActions.moveListCardsToArchiveList(listId));\n    onClose();\n  }, [listId, onClose, dispatch]);\n\n  return (\n    <ConfirmationStep\n      title=\"common.archiveCards\"\n      content=\"common.areYouSureYouWantToArchiveCards\"\n      buttonContent=\"action.archiveCards\"\n      onConfirm={handleConfirm}\n      onBack={onBack}\n    />\n  );\n});\n\nArchiveCardsStep.propTypes = {\n  listId: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nArchiveCardsStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default ArchiveCardsStep;\n"
  },
  {
    "path": "client/src/components/cards/Card/Card.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useContext, useMemo, useRef, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Button, Icon } from 'semantic-ui-react';\nimport { push } from '../../../lib/redux-router';\nimport { closePopup, usePopup } from '../../../lib/popup';\n\nimport selectors from '../../../selectors';\nimport { BoardShortcutsContext } from '../../../contexts';\nimport Paths from '../../../constants/Paths';\nimport ClipboardTypes from '../../../constants/ClipboardTypes';\nimport { BoardMembershipRoles, CardTypes } from '../../../constants/Enums';\nimport ProjectContent from './ProjectContent';\nimport StoryContent from './StoryContent';\nimport InlineContent from './InlineContent';\nimport EditName from './EditName';\nimport CardActionsStep from '../CardActionsStep';\n\nimport styles from './Card.module.scss';\n\nconst Card = React.memo(({ id, isInline }) => {\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n  const selectIsCardWithIdRecent = useMemo(() => selectors.makeSelectIsCardWithIdRecent(), []);\n\n  const card = useSelector((state) => selectCardById(state, id));\n\n  const isHighlightedAsRecent = useSelector((state) => {\n    const { turnOffRecentCardHighlighting } = selectors.selectCurrentUser(state);\n\n    if (turnOffRecentCardHighlighting) {\n      return false;\n    }\n\n    return selectIsCardWithIdRecent(state, id);\n  });\n\n  const isCut = useSelector((state) => {\n    const clipboard = selectors.selectClipboard(state);\n    return clipboard && clipboard.type === ClipboardTypes.CUT && card.id === clipboard.cardId;\n  });\n\n  const canUseActions = useSelector((state) => {\n    const isManager = selectors.selectIsCurrentUserManagerForCurrentProject(state);\n\n    if (isManager) {\n      return true;\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  const dispatch = useDispatch();\n  const [isEditNameOpened, setIsEditNameOpened] = useState(false);\n  const [, , handleCardMouseEnter, handleCardMouseLeave] = useContext(BoardShortcutsContext);\n\n  const actionsPopupRef = useRef(null);\n\n  const handleClick = useCallback(() => {\n    if (document.activeElement) {\n      document.activeElement.blur();\n    }\n\n    dispatch(push(Paths.CARDS.replace(':id', id)));\n  }, [id, dispatch]);\n\n  const handleMouseEnter = useCallback(() => {\n    handleCardMouseEnter(\n      id,\n      () => {\n        setIsEditNameOpened(true);\n      },\n      (step) => {\n        closePopup();\n\n        actionsPopupRef.current.open({\n          defaultStep: step,\n        });\n      },\n    );\n  }, [id, handleCardMouseEnter]);\n\n  const handleContextMenu = useCallback((event) => {\n    if (!actionsPopupRef.current) {\n      return;\n    }\n\n    event.preventDefault();\n\n    closePopup();\n    actionsPopupRef.current.open();\n  }, []);\n\n  const handleNameEdit = useCallback(() => {\n    setIsEditNameOpened(true);\n  }, []);\n\n  const handleEditNameClose = useCallback(() => {\n    setIsEditNameOpened(false);\n  }, []);\n\n  const CardActionsPopup = usePopup(CardActionsStep);\n\n  if (isEditNameOpened) {\n    return <EditName cardId={id} onClose={handleEditNameClose} />;\n  }\n\n  let Content;\n  if (isInline) {\n    Content = InlineContent;\n  } else {\n    switch (card.type) {\n      case CardTypes.PROJECT:\n        Content = ProjectContent;\n\n        break;\n      case CardTypes.STORY:\n        Content = StoryContent;\n\n        break;\n      default:\n    }\n  }\n\n  return (\n    <div\n      className={classNames(styles.wrapper, isHighlightedAsRecent && styles.wrapperRecent, 'card')}\n    >\n      {card.isPersisted ? (\n        <>\n          {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                       jsx-a11y/no-static-element-interactions */}\n          <div\n            className={classNames(\n              styles.content,\n              card.isClosed && styles.contentDisabled,\n              isCut && styles.contentCut,\n            )}\n            onMouseEnter={handleMouseEnter}\n            onMouseLeave={handleCardMouseLeave}\n            onClick={handleClick}\n            onContextMenu={handleContextMenu}\n          >\n            <Content cardId={id} />\n          </div>\n          {canUseActions && (\n            <CardActionsPopup ref={actionsPopupRef} cardId={id} onNameEdit={handleNameEdit}>\n              <Button className={styles.actionsButton}>\n                <Icon fitted name=\"pencil\" size=\"small\" />\n              </Button>\n            </CardActionsPopup>\n          )}\n        </>\n      ) : (\n        <span className={classNames(styles.content, card.isClosed && styles.contentDisabled)}>\n          <Content cardId={id} />\n        </span>\n      )}\n    </div>\n  );\n});\n\nCard.propTypes = {\n  id: PropTypes.string.isRequired,\n  isInline: PropTypes.bool,\n};\n\nCard.defaultProps = {\n  isInline: false,\n};\n\nexport default Card;\n"
  },
  {
    "path": "client/src/components/cards/Card/Card.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actionsButton {\n    background: none;\n    box-shadow: none;\n    border-radius: 3px;\n    box-sizing: content-box;\n    color: #798d99;\n    display: inline-block;\n    margin: 0;\n    min-height: auto;\n    opacity: 0;\n    outline: none;\n    padding: 4px;\n    position: absolute;\n    right: 2px;\n    top: 2px;\n    transition: background 85ms ease;\n    width: 20px;\n    z-index: 1000;\n\n    &:hover {\n      background: #ebeef0;\n      color: #516b7a;\n    }\n  }\n\n  .content {\n    cursor: pointer;\n\n    &:after {\n      clear: both;\n      content: \"\";\n      display: table;\n    }\n  }\n\n  .contentCut {\n    opacity: 0.5;\n  }\n\n  .contentDisabled {\n    filter: saturate(0.5);\n    opacity: 0.64;\n  }\n\n  .wrapper {\n    background: #fff;\n    border-radius: 3px;\n    box-shadow: 0 1px 0 #ccc;\n    overflow: hidden;\n    position: relative;\n\n    &:hover {\n      background: #f5f6f7;\n      border-bottom-color: rgba(9, 30, 66, 0.25);\n\n      .actionsButton {\n        opacity: 1;\n      }\n    }\n  }\n\n  .wrapperRecent:not(:hover) {\n    overflow: hidden;\n\n    &:after {\n      animation: slide 4s infinite 2s;\n      background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.64) 50%, rgba(128, 186, 232, 0) 99%, rgba(125, 185, 232, 0) 100%);\n      content: '';\n      height: 100%;\n      position: absolute;\n      top: 0;\n      transform: translateX(100%);\n      width: 100%;\n\n      @keyframes slide {\n        0% {\n          transform: translateX(-100%);\n        }\n\n        50% {\n          transform: translateX(100%);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/Card/EditName.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport TextareaAutosize from 'react-textarea-autosize';\nimport { Button, Form, TextArea } from 'semantic-ui-react';\nimport { useClickAwayListener } from '../../../lib/hooks';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useField, useNestedRef } from '../../../hooks';\nimport { focusEnd } from '../../../utils/element-helpers';\n\nimport styles from './EditName.module.scss';\n\nconst EditName = React.memo(({ cardId, onClose }) => {\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n\n  const defaultValue = useSelector((state) => selectCardById(state, cardId).name);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [value, handleFieldChange] = useField(defaultValue);\n\n  const [fieldRef, handleFieldRef] = useNestedRef();\n  const [buttonRef, handleButtonRef] = useNestedRef();\n\n  const submit = useCallback(() => {\n    const cleanValue = value.trim();\n\n    if (!cleanValue) {\n      fieldRef.current.select();\n      return;\n    }\n\n    if (cleanValue !== defaultValue) {\n      dispatch(\n        entryActions.updateCard(cardId, {\n          name: cleanValue,\n        }),\n      );\n    }\n\n    onClose();\n  }, [cardId, onClose, defaultValue, dispatch, value, fieldRef]);\n\n  const handleSubmit = useCallback(() => {\n    submit();\n  }, [submit]);\n\n  const handleFieldKeyDown = useCallback(\n    (event) => {\n      switch (event.key) {\n        case 'Enter':\n          event.preventDefault();\n          submit();\n\n          break;\n        case 'Escape':\n          onClose();\n\n          break;\n        default:\n      }\n    },\n    [onClose, submit],\n  );\n\n  const handleClickAwayCancel = useCallback(() => {\n    fieldRef.current.focus();\n  }, [fieldRef]);\n\n  const clickAwayProps = useClickAwayListener(\n    [fieldRef, buttonRef],\n    onClose,\n    handleClickAwayCancel,\n  );\n\n  useEffect(() => {\n    focusEnd(fieldRef.current);\n  }, [fieldRef]);\n\n  return (\n    <Form onSubmit={handleSubmit}>\n      <div className={styles.fieldWrapper}>\n        <TextArea\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          ref={handleFieldRef}\n          as={TextareaAutosize}\n          value={value}\n          maxLength={1024}\n          minRows={3}\n          maxRows={8}\n          className={styles.field}\n          onKeyDown={handleFieldKeyDown}\n          onChange={handleFieldChange}\n        />\n      </div>\n      <div>\n        <Button\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          positive\n          ref={handleButtonRef}\n          content={t('action.save')}\n          className={styles.submitButton}\n        />\n      </div>\n    </Form>\n  );\n});\n\nEditName.propTypes = {\n  cardId: PropTypes.string.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default EditName;\n"
  },
  {
    "path": "client/src/components/cards/Card/EditName.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    border: none;\n    margin-bottom: 4px;\n    outline: none;\n    overflow: hidden;\n    padding: 0;\n    resize: none;\n    width: 100%;\n    word-wrap: break-word;\n  }\n\n  .fieldWrapper {\n    background: #fff;\n    border-radius: 3px;\n    box-shadow: 0 1px 0 #ccc;\n    margin-bottom: 8px;\n    min-height: 20px;\n    padding: 6px 8px 2px;\n  }\n\n  .submitButton {\n    margin-bottom: 8px;\n    vertical-align: top;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/Card/InlineContent.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport markdownToText from '../../../utils/markdown-to-text';\nimport { BoardViews } from '../../../constants/Enums';\nimport UserAvatar from '../../users/UserAvatar';\nimport LabelChip from '../../labels/LabelChip';\n\nimport styles from './InlineContent.module.scss';\n\nconst InlineContent = React.memo(({ cardId }) => {\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n  const selectLabelIdsByCardId = useMemo(() => selectors.makeSelectLabelIdsByCardId(), []);\n\n  const selectNotificationsTotalByCardId = useMemo(\n    () => selectors.makeSelectNotificationsTotalByCardId(),\n    [],\n  );\n\n  const card = useSelector((state) => selectCardById(state, cardId));\n  const list = useSelector((state) => selectListById(state, card.listId));\n  const labelIds = useSelector((state) => selectLabelIdsByCardId(state, cardId));\n\n  const notificationsTotal = useSelector((state) =>\n    selectNotificationsTotalByCardId(state, cardId),\n  );\n\n  const listName = useSelector((state) => {\n    if (!list.name) {\n      return null;\n    }\n\n    const { view } = selectors.selectCurrentBoard(state);\n\n    if (view === BoardViews.KANBAN) {\n      return null;\n    }\n\n    return list.name;\n  });\n\n  const descriptionText = useMemo(\n    () => card.description && markdownToText(card.description),\n    [card.description],\n  );\n\n  return (\n    <div className={styles.wrapper}>\n      <span className={styles.attachments}>\n        <UserAvatar withCreatorIndicator id={card.creatorUserId} />\n      </span>\n      {(notificationsTotal > 0 || listName) && (\n        <span className={styles.attachments}>\n          {notificationsTotal > 0 && (\n            <span\n              className={classNames(styles.attachment, styles.attachmentLeft, styles.notification)}\n            >\n              {notificationsTotal}\n            </span>\n          )}\n          {listName && (\n            <span className={classNames(styles.attachment, styles.attachmentLeft)}>\n              <span className={styles.attachmentContent}>\n                <Icon name=\"columns\" />\n                {listName}\n              </span>\n            </span>\n          )}\n        </span>\n      )}\n      {labelIds.length > 0 && (\n        <span className={classNames(styles.attachments, styles.hidable)}>\n          {labelIds.map((labelId) => (\n            <span key={labelId} className={classNames(styles.attachment, styles.attachmentLeft)}>\n              <LabelChip id={labelId} size=\"tiny\" />\n            </span>\n          ))}\n        </span>\n      )}\n      <span\n        className={classNames(styles.attachments, styles.name, card.isClosed && styles.nameClosed)}\n      >\n        <div className={styles.hidable}>{card.name}</div>\n      </span>\n      {descriptionText && (\n        <span className={classNames(styles.attachments, styles.descriptionText, styles.hidable)}>\n          {descriptionText}\n        </span>\n      )}\n    </div>\n  );\n});\n\nInlineContent.propTypes = {\n  cardId: PropTypes.string.isRequired,\n};\n\nexport default InlineContent;\n"
  },
  {
    "path": "client/src/components/cards/Card/InlineContent.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .attachment {\n    display: inline-block;\n    vertical-align: top;\n  }\n\n  .attachmentContent {\n    color: #6a808b;\n    font-size: 12px;\n    line-height: 20px;\n    max-width: 176px;\n    outline: none;\n    overflow: hidden;\n    padding: 0px 3px;\n    text-overflow: ellipsis;\n    transition: background 0.3s ease;\n    white-space: nowrap;\n  }\n\n  .attachmentLeft {\n    margin-right: 4px;\n  }\n\n  .attachments {\n    white-space: nowrap;\n\n    &:not(:last-child) {\n      margin-right: 20px;\n    }\n  }\n\n  .descriptionText {\n    flex: 1;\n    opacity: 0.5;\n  }\n\n  .hidable {\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n\n  .name {\n    color: #17394d;\n    font-size: 14px;\n    max-width: 30%;\n  }\n\n  .nameClosed {\n    text-decoration: line-through;\n  }\n\n  .notification {\n    background: #eb5a46;\n    color: #fff;\n    font-size: 12px;\n    line-height: 20px;\n    padding: 0px 6px;\n    border: none;\n    border-radius: 3px;\n    outline: none;\n    transition: background 0.3s ease;\n  }\n\n  .wrapper {\n    align-items: center;\n    display: flex;\n    padding: 6px 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/Card/ProjectContent.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { shallowEqual, useDispatch, useSelector } from 'react-redux';\nimport { Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { startStopwatch, stopStopwatch } from '../../../utils/stopwatch';\nimport { isListArchiveOrTrash } from '../../../utils/record-helpers';\nimport { BoardMembershipRoles, BoardViews } from '../../../constants/Enums';\nimport TaskList from './TaskList';\nimport DueDateChip from '../DueDateChip';\nimport StopwatchChip from '../StopwatchChip';\nimport TimeAgo from '../../common/TimeAgo';\nimport UserAvatar from '../../users/UserAvatar';\nimport LabelChip from '../../labels/LabelChip';\nimport CustomFieldValueChip from '../../custom-field-values/CustomFieldValueChip';\n\nimport styles from './ProjectContent.module.scss';\n\nconst ProjectContent = React.memo(({ cardId }) => {\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n  const selectUserIdsByCardId = useMemo(() => selectors.makeSelectUserIdsByCardId(), []);\n  const selectLabelIdsByCardId = useMemo(() => selectors.makeSelectLabelIdsByCardId(), []);\n\n  const selectShownOnFrontOfCardTaskListIdsByCardId = useMemo(\n    () => selectors.makeSelectShownOnFrontOfCardTaskListIdsByCardId(),\n    [],\n  );\n\n  const selectAttachmentsTotalByCardId = useMemo(\n    () => selectors.makeSelectAttachmentsTotalByCardId(),\n    [],\n  );\n\n  const selectShownOnFrontOfCardCustomFieldValueIdsByCardId = useMemo(\n    () => selectors.makeSelectShownOnFrontOfCardCustomFieldValueIdsByCardId(),\n    [],\n  );\n\n  const selectNotificationsTotalByCardId = useMemo(\n    () => selectors.makeSelectNotificationsTotalByCardId(),\n    [],\n  );\n\n  const selectAttachmentById = useMemo(() => selectors.makeSelectAttachmentById(), []);\n\n  const card = useSelector((state) => selectCardById(state, cardId));\n  const list = useSelector((state) => selectListById(state, card.listId));\n  const userIds = useSelector((state) => selectUserIdsByCardId(state, cardId));\n  const labelIds = useSelector((state) => selectLabelIdsByCardId(state, cardId));\n\n  const taskListIds = useSelector((state) =>\n    selectShownOnFrontOfCardTaskListIdsByCardId(state, cardId),\n  );\n\n  const attachmentsTotal = useSelector((state) => selectAttachmentsTotalByCardId(state, cardId));\n\n  const customFieldValueIds = useSelector((state) =>\n    selectShownOnFrontOfCardCustomFieldValueIdsByCardId(state, cardId),\n  );\n\n  const notificationsTotal = useSelector((state) =>\n    selectNotificationsTotalByCardId(state, cardId),\n  );\n\n  const coverUrl = useSelector((state) => {\n    const attachment = selectAttachmentById(state, card.coverAttachmentId);\n    return attachment && attachment.data.thumbnailUrls.outside360;\n  });\n\n  const { listName, withCreator, withAge } = useSelector((state) => {\n    const board = selectors.selectCurrentBoard(state);\n\n    return {\n      listName: list.name && (board.view === BoardViews.KANBAN ? null : list.name),\n      withCreator: board.alwaysDisplayCardCreator,\n      withAge: board.displayCardAges,\n    };\n  }, shallowEqual);\n\n  const canEditStopwatch = useSelector((state) => {\n    if (isListArchiveOrTrash(list)) {\n      return false;\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  const dispatch = useDispatch();\n\n  const handleToggleStopwatchClick = useCallback(\n    (event) => {\n      event.stopPropagation();\n\n      dispatch(\n        entryActions.updateCard(cardId, {\n          stopwatch: card.stopwatch.startedAt\n            ? stopStopwatch(card.stopwatch)\n            : startStopwatch(card.stopwatch),\n        }),\n      );\n    },\n    [cardId, card.stopwatch, dispatch],\n  );\n\n  const hasInformation =\n    card.description ||\n    card.dueDate ||\n    card.stopwatch ||\n    card.commentsTotal > 0 ||\n    withAge ||\n    attachmentsTotal > 0 ||\n    notificationsTotal > 0 ||\n    listName;\n\n  const isCompact =\n    (labelIds.length === 0 || customFieldValueIds.length === 0) &&\n    taskListIds.length === 0 &&\n    !hasInformation;\n\n  const usersNode =\n    userIds.length > 0 || withCreator ? (\n      <span className={classNames(styles.attachments, styles.attachmentsRight)}>\n        {withCreator && (\n          <>\n            <span className={classNames(styles.attachment, styles.attachmentRight)}>\n              <UserAvatar withCreatorIndicator id={card.creatorUserId} size=\"small\" />\n            </span>\n            {userIds.length > 0 && <span className={styles.creatorDivider} />}\n          </>\n        )}\n        {userIds.map((userId) => (\n          <span key={userId} className={classNames(styles.attachment, styles.attachmentRight)}>\n            <UserAvatar id={userId} size=\"small\" />\n          </span>\n        ))}\n      </span>\n    ) : null;\n\n  return (\n    <div className={styles.wrapper}>\n      <div className={classNames(styles.name, card.isClosed && styles.nameClosed)}>{card.name}</div>\n      {coverUrl && (\n        <div className={styles.coverWrapper}>\n          <img src={coverUrl} alt=\"\" className={styles.cover} />\n        </div>\n      )}\n      {labelIds.length > 0 && (\n        <span className={classNames(styles.labels, !isCompact && styles.labelsFull)}>\n          {labelIds.map((labelId) => (\n            <span key={labelId} className={classNames(styles.attachment, styles.attachmentLeft)}>\n              <LabelChip id={labelId} size=\"tiny\" />\n            </span>\n          ))}\n        </span>\n      )}\n      {customFieldValueIds.length > 0 && (\n        <span className={classNames(styles.labels, !isCompact && styles.labelsFull)}>\n          {customFieldValueIds.map((customFieldValueId) => (\n            <span\n              key={customFieldValueId}\n              className={classNames(styles.attachment, styles.attachmentLeft)}\n            >\n              <CustomFieldValueChip id={customFieldValueId} size=\"tiny\" />\n            </span>\n          ))}\n        </span>\n      )}\n      {isCompact && usersNode}\n      {taskListIds.map((taskListId) => (\n        <TaskList key={taskListId} id={taskListId} />\n      ))}\n      {hasInformation && (\n        <span className={styles.attachments}>\n          {notificationsTotal > 0 && (\n            <span\n              className={classNames(styles.attachment, styles.attachmentLeft, styles.notification)}\n            >\n              {notificationsTotal}\n            </span>\n          )}\n          {card.dueDate && (\n            <span className={classNames(styles.attachment, styles.attachmentLeft)}>\n              <DueDateChip\n                value={card.dueDate}\n                size=\"tiny\"\n                isCompleted={card.isDueCompleted}\n                withStatus={!card.isClosed}\n              />\n            </span>\n          )}\n          {card.stopwatch && (\n            <span className={classNames(styles.attachment, styles.attachmentLeft)}>\n              <StopwatchChip\n                value={card.stopwatch}\n                as=\"span\"\n                size=\"tiny\"\n                onClick={canEditStopwatch ? handleToggleStopwatchClick : undefined}\n              />\n            </span>\n          )}\n          {listName && (\n            <span className={classNames(styles.attachment, styles.attachmentLeft)}>\n              <span className={styles.attachmentContent}>\n                <Icon name=\"columns\" />\n                {listName}\n              </span>\n            </span>\n          )}\n          {card.description && (\n            <span className={classNames(styles.attachment, styles.attachmentLeft)}>\n              <span className={styles.attachmentContent}>\n                <Icon name=\"align left\" />\n              </span>\n            </span>\n          )}\n          {attachmentsTotal > 0 && (\n            <span className={classNames(styles.attachment, styles.attachmentLeft)}>\n              <span className={styles.attachmentContent}>\n                <Icon name=\"attach\" />\n                {attachmentsTotal}\n              </span>\n            </span>\n          )}\n          {card.commentsTotal > 0 && (\n            <span className={classNames(styles.attachment, styles.attachmentLeft)}>\n              <span className={styles.attachmentContent}>\n                <Icon name=\"comment outline\" />\n                {card.commentsTotal}\n              </span>\n            </span>\n          )}\n          {withAge && card.createdAt && (\n            <span className={classNames(styles.attachment, styles.attachmentLeft)}>\n              <span className={styles.attachmentContent}>\n                <Icon name=\"history\" />\n                <TimeAgo date={card.createdAt} />\n              </span>\n            </span>\n          )}\n        </span>\n      )}\n      {!isCompact && usersNode}\n    </div>\n  );\n});\n\nProjectContent.propTypes = {\n  cardId: PropTypes.string.isRequired,\n};\n\nexport default ProjectContent;\n"
  },
  {
    "path": "client/src/components/cards/Card/ProjectContent.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .attachment {\n    display: inline-block;\n    line-height: 0;\n    margin: 0 0 6px 0;\n    max-width: 100%;\n    vertical-align: top;\n  }\n\n  .attachmentContent {\n    color: #6a808b;\n    font-size: 12px;\n    line-height: 20px;\n    max-width: 176px;\n    outline: none;\n    overflow: hidden;\n    padding: 0px 3px;\n    text-overflow: ellipsis;\n    transition: background 0.3s ease;\n    white-space: nowrap;\n  }\n\n  .attachmentLeft {\n    margin-right: 4px;\n  }\n\n  .attachmentRight {\n    margin-left: 2px;\n  }\n\n  .attachments {\n    display: inline-block;\n    padding-bottom: 2px;\n  }\n\n  .attachmentsRight {\n    float: right;\n    line-height: 0;\n    margin-top: 6px;\n  }\n\n  .cover {\n    max-height: 340px;\n    object-fit: cover;\n    transform-origin: bottom;\n    transition: all .6s cubic-bezier(0.25, 0.1, 0.25, 1);\n    vertical-align: middle;\n    width: 100%;\n  }\n\n  .coverWrapper {\n    border-radius: 3px;\n    margin-bottom: 8px;\n    overflow: hidden;\n  }\n\n  .creatorDivider {\n    background: #dce0e4;\n    display: inline-block;\n    height: 24px;\n    margin: 2px 2px 0 4px;\n    width: 1px;\n  }\n\n  .labels {\n    max-width: 100%;\n    overflow: hidden;\n  }\n\n  .labelsFull {\n    display: block;\n  }\n\n  .name {\n    color: #17394d;\n    font-size: 14px;\n    line-height: 18px;\n    margin-bottom: 8px;\n    word-wrap: break-word;\n  }\n\n  .nameClosed {\n    text-decoration: line-through;\n  }\n\n  .notification {\n    background: #eb5a46;\n    color: #fff;\n    font-size: 12px;\n    line-height: 20px;\n    padding: 0px 6px;\n    border: none;\n    border-radius: 3px;\n    display: inline-block;\n    outline: none;\n    text-align: left;\n    transition: background 0.3s ease;\n    vertical-align: top;\n  }\n\n  .wrapper {\n    padding: 6px 8px 0;\n\n    &:after {\n      clear: both;\n      content: \"\";\n      display: table;\n    }\n  }\n\n  :global(.card):hover {\n    .cover {\n      filter: brightness(1.1);\n      transform: scale(1.05);\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/Card/StoryContent.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { shallowEqual, useSelector } from 'react-redux';\nimport { Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport markdownToText from '../../../utils/markdown-to-text';\nimport { BoardViews } from '../../../constants/Enums';\nimport TimeAgo from '../../common/TimeAgo';\nimport LabelChip from '../../labels/LabelChip';\nimport CustomFieldValueChip from '../../custom-field-values/CustomFieldValueChip';\n\nimport styles from './StoryContent.module.scss';\n\nconst StoryContent = React.memo(({ cardId }) => {\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n  const selectLabelIdsByCardId = useMemo(() => selectors.makeSelectLabelIdsByCardId(), []);\n\n  const selectAttachmentsTotalByCardId = useMemo(\n    () => selectors.makeSelectAttachmentsTotalByCardId(),\n    [],\n  );\n\n  const selectShownOnFrontOfCardCustomFieldValueIdsByCardId = useMemo(\n    () => selectors.makeSelectShownOnFrontOfCardCustomFieldValueIdsByCardId(),\n    [],\n  );\n\n  const selectNotificationsTotalByCardId = useMemo(\n    () => selectors.makeSelectNotificationsTotalByCardId(),\n    [],\n  );\n\n  const selectAttachmentById = useMemo(() => selectors.makeSelectAttachmentById(), []);\n\n  const card = useSelector((state) => selectCardById(state, cardId));\n  const list = useSelector((state) => selectListById(state, card.listId));\n  const labelIds = useSelector((state) => selectLabelIdsByCardId(state, cardId));\n  const attachmentsTotal = useSelector((state) => selectAttachmentsTotalByCardId(state, cardId));\n\n  const customFieldValueIds = useSelector((state) =>\n    selectShownOnFrontOfCardCustomFieldValueIdsByCardId(state, cardId),\n  );\n\n  const notificationsTotal = useSelector((state) =>\n    selectNotificationsTotalByCardId(state, cardId),\n  );\n\n  const { listName, withAge } = useSelector((state) => {\n    const board = selectors.selectCurrentBoard(state);\n\n    return {\n      listName: list.name && (board.view === BoardViews.KANBAN ? null : list.name),\n      withAge: board.displayCardAges,\n    };\n  }, shallowEqual);\n\n  const coverUrl = useSelector((state) => {\n    const attachment = selectAttachmentById(state, card.coverAttachmentId);\n    return attachment && attachment.data.thumbnailUrls.outside360;\n  });\n\n  const descriptionText = useMemo(\n    () => card.description && markdownToText(card.description),\n    [card.description],\n  );\n\n  return (\n    <>\n      {coverUrl && (\n        <div className={styles.coverWrapper}>\n          <img src={coverUrl} alt=\"\" className={styles.cover} />\n        </div>\n      )}\n      <div className={styles.wrapper}>\n        {labelIds.length > 0 && (\n          <span className={styles.labels}>\n            {labelIds.map((labelId) => (\n              <span key={labelId} className={classNames(styles.attachment, styles.attachmentLeft)}>\n                <LabelChip id={labelId} size=\"tiny\" />\n              </span>\n            ))}\n          </span>\n        )}\n        {customFieldValueIds.length > 0 && (\n          <span className={classNames(styles.labels)}>\n            {customFieldValueIds.map((customFieldValueId) => (\n              <span\n                key={customFieldValueId}\n                className={classNames(styles.attachment, styles.attachmentLeft)}\n              >\n                <CustomFieldValueChip id={customFieldValueId} size=\"tiny\" />\n              </span>\n            ))}\n          </span>\n        )}\n        <div className={classNames(styles.name, card.isClosed && styles.nameClosed)}>\n          {card.name}\n        </div>\n        {card.description && <div className={styles.descriptionText}>{descriptionText}</div>}\n        {(withAge || attachmentsTotal > 0 || notificationsTotal > 0 || listName) && (\n          <span className={styles.attachments}>\n            {notificationsTotal > 0 && (\n              <span\n                className={classNames(\n                  styles.attachment,\n                  styles.attachmentLeft,\n                  styles.notification,\n                )}\n              >\n                {notificationsTotal}\n              </span>\n            )}\n            {listName && (\n              <span className={classNames(styles.attachment, styles.attachmentLeft)}>\n                <span className={styles.attachmentContent}>\n                  <Icon name=\"columns\" />\n                  {listName}\n                </span>\n              </span>\n            )}\n            {attachmentsTotal > 0 && (\n              <span className={classNames(styles.attachment, styles.attachmentLeft)}>\n                <span className={styles.attachmentContent}>\n                  <Icon name=\"attach\" />\n                  {attachmentsTotal}\n                </span>\n              </span>\n            )}\n            {withAge && card.createdAt && (\n              <span className={classNames(styles.attachment, styles.attachmentLeft)}>\n                <span className={styles.attachmentContent}>\n                  <Icon name=\"history\" />\n                  <TimeAgo date={card.createdAt} />\n                </span>\n              </span>\n            )}\n          </span>\n        )}\n      </div>\n    </>\n  );\n});\n\nStoryContent.propTypes = {\n  cardId: PropTypes.string.isRequired,\n};\n\nexport default StoryContent;\n"
  },
  {
    "path": "client/src/components/cards/Card/StoryContent.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .attachment {\n    display: inline-block;\n    line-height: 0;\n    margin: 0 0 6px 0;\n    max-width: 100%;\n    vertical-align: top;\n  }\n\n  .attachmentContent {\n    color: #6a808b;\n    font-size: 12px;\n    line-height: 20px;\n    max-width: 176px;\n    outline: none;\n    overflow: hidden;\n    padding: 0px 3px;\n    text-overflow: ellipsis;\n    transition: background 0.3s ease;\n    white-space: nowrap;\n  }\n\n  .attachmentLeft {\n    margin-right: 4px;\n  }\n\n  .attachments {\n    display: inline-block;\n    padding-bottom: 2px;\n  }\n\n  .cover {\n    max-height: 340px;\n    object-fit: cover;\n    transition: all .6s cubic-bezier(0.25, 0.1, 0.25, 1);\n    vertical-align: middle;\n    width: 100%;\n  }\n\n  .coverWrapper {\n    border-radius: 3px 3px 0 0;\n    overflow: hidden;\n  }\n\n  .descriptionText {\n    color: #17394d;\n    font-size: 10px;\n    margin-bottom: 8px;\n    mask-image: linear-gradient(180deg, #000 60%, transparent);\n    -webkit-mask-image: linear-gradient(180deg, #000 60%, transparent);\n    max-height: 100px;\n    overflow: hidden;\n  }\n\n  .labels {\n    display: block;\n    margin-bottom: 4px;\n    max-width: 100%;\n    overflow: hidden;\n  }\n\n  .name {\n    color: #17394d;\n    font-size: 16px;\n    font-weight: bold;\n    line-height: 20px;\n    margin: 0 0 8px;\n    text-transform: uppercase;\n    word-wrap: break-word;\n  }\n\n  .nameClosed {\n    text-decoration: line-through;\n  }\n\n  .notification {\n    background: #eb5a46;\n    color: #fff;\n    font-size: 12px;\n    line-height: 20px;\n    padding: 0px 6px;\n    border: none;\n    border-radius: 3px;\n    display: inline-block;\n    outline: none;\n    text-align: left;\n    transition: background 0.3s ease;\n    vertical-align: top;\n  }\n\n  .wrapper {\n    padding: 8px 8px 0;\n  }\n\n  :global(.card):hover {\n    .cover {\n      filter: brightness(1.1);\n      transform: scale(1.1) rotate(-2deg);\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/Card/TaskList/Task.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { Link } from 'react-router';\nimport { Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../../selectors';\nimport Paths from '../../../../constants/Paths';\nimport Linkify from '../../../common/Linkify';\n\nimport styles from './Task.module.scss';\n\nconst Task = React.memo(({ id }) => {\n  const selectTaskById = useMemo(() => selectors.makeSelectTaskById(), []);\n  const selectLinkedCardById = useMemo(() => selectors.makeSelectCardById(), []);\n\n  const task = useSelector((state) => selectTaskById(state, id));\n\n  const linkedCard = useSelector(\n    (state) => task.linkedCardId && selectLinkedCardById(state, task.linkedCardId),\n  );\n\n  const handleLinkClick = useCallback((event) => {\n    event.stopPropagation();\n  }, []);\n\n  return (\n    <li className={styles.wrapper}>\n      {task.linkedCardId ? (\n        <>\n          <Icon name=\"exchange\" size=\"small\" className={styles.icon} />\n          <span className={classNames(styles.name, task.isCompleted && styles.nameCompleted)}>\n            <Link to={Paths.CARDS.replace(':id', task.linkedCardId)} onClick={handleLinkClick}>\n              {linkedCard ? linkedCard.name : task.name}\n            </Link>\n          </span>\n        </>\n      ) : (\n        <span className={classNames(styles.name, task.isCompleted && styles.nameCompleted)}>\n          <Linkify linkStopPropagation>{task.name}</Linkify>\n        </span>\n      )}\n    </li>\n  );\n});\n\nTask.propTypes = {\n  id: PropTypes.string.isRequired,\n};\n\nexport default Task;\n"
  },
  {
    "path": "client/src/components/cards/Card/TaskList/Task.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .icon {\n    color: rgba(9, 30, 66, 0.24);\n  }\n\n  .name {\n    a:hover {\n      text-decoration: underline;\n    }\n  }\n\n  .nameCompleted {\n    color: #aaa;\n    text-decoration: line-through;\n  }\n\n  .wrapper {\n    display: block;\n    font-size: 12px;\n    line-height: 14px;\n    overflow-wrap: break-word;\n    padding-bottom: 6px;\n    padding-left: 14px;\n\n    &:before {\n      content: \"–\";\n      position: absolute;\n      left: 10px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/Card/TaskList/TaskList.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { Progress } from 'semantic-ui-react';\nimport { useDidUpdate, useToggle } from '../../../../lib/hooks';\n\nimport selectors from '../../../../selectors';\nimport Task from './Task';\n\nimport styles from './TaskList.module.scss';\n\nconst TaskList = React.memo(({ id }) => {\n  const selectTaskListById = useMemo(() => selectors.makeSelectTaskListById(), []);\n  const selectTasksByTaskListId = useMemo(() => selectors.makeSelectTasksByTaskListId(), []);\n\n  const taskLists = useSelector((state) => selectTaskListById(state, id));\n  const tasks = useSelector((state) => selectTasksByTaskListId(state, id));\n\n  const defaultIsOpened = useSelector(\n    (state) => selectors.selectCurrentBoard(state).expandTaskListsByDefault,\n  );\n\n  const [isOpened, toggleOpened] = useToggle(defaultIsOpened);\n\n  const filteredTasks = useMemo(\n    () => (taskLists.hideCompletedTasks ? tasks.filter((task) => !task.isCompleted) : tasks),\n    [taskLists.hideCompletedTasks, tasks],\n  );\n\n  // TODO: move to selector?\n  const completedTasksTotal = useMemo(\n    () => tasks.reduce((result, task) => (task.isCompleted ? result + 1 : result), 0),\n    [tasks],\n  );\n\n  const handleToggleClick = useCallback(\n    (event) => {\n      if (filteredTasks.length === 0) {\n        return;\n      }\n\n      event.stopPropagation();\n      toggleOpened();\n    },\n    [toggleOpened, filteredTasks.length],\n  );\n\n  useDidUpdate(() => {\n    toggleOpened();\n  }, [defaultIsOpened]);\n\n  if (tasks.length === 0) {\n    return null;\n  }\n\n  return (\n    <>\n      {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                   jsx-a11y/no-static-element-interactions */}\n      <div className={styles.progressRow} onClick={handleToggleClick}>\n        <span className={styles.progressWrapper}>\n          <Progress\n            autoSuccess\n            value={completedTasksTotal}\n            total={tasks.length}\n            color=\"blue\"\n            size=\"tiny\"\n            className={styles.progress}\n          />\n        </span>\n        <span\n          className={classNames(\n            styles.count,\n            filteredTasks.length > 0 && styles.countOpenable,\n            filteredTasks.length > 0 && (isOpened ? styles.countOpened : styles.countClosed),\n          )}\n        >\n          {completedTasksTotal}/{tasks.length}\n        </span>\n      </div>\n      {isOpened && filteredTasks.length > 0 && (\n        <ul className={styles.tasks}>\n          {filteredTasks.map((task) => (\n            <Task key={task.id} id={task.id} />\n          ))}\n        </ul>\n      )}\n    </>\n  );\n});\n\nTaskList.propTypes = {\n  id: PropTypes.string.isRequired,\n};\n\nexport default TaskList;\n"
  },
  {
    "path": "client/src/components/cards/Card/TaskList/TaskList.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .count {\n    color: #888;\n    font-size: 12px;\n    line-height: 12px;\n\n    &:after {\n      content: \"\";\n      opacity: 0.4;\n    }\n  }\n\n  .countOpenable {\n    &:after {\n      margin-left: 2px;\n    }\n\n    &:hover {\n      opacity: 0.75;\n    }\n  }\n\n  .countOpened:after {\n    background: url(\"data:image/gif;base64,R0lGODlhCwALAJEAAAAAAP///xUVFf///yH5BAEAAAMALAAAAAALAAsAAAIPnI+py+0/hJzz0IruwjsVADs=\") no-repeat center right;\n    padding: 6px 6px 0px;\n  }\n\n  .countClosed:after {\n    background: url(\"data:image/gif;base64,R0lGODlhCwALAJEAAAAAAP///xUVFf///yH5BAEAAAMALAAAAAALAAsAAAIRnC2nKLnT4or00Puy3rx7VQAAOw==\") no-repeat center right;\n    padding: 0 6px 6px;\n  }\n\n  .progress {\n    margin: 0;\n  }\n\n  .progressRow {\n    display: flex;\n    gap: 8px;\n    justify-content: space-between;\n    margin: 0 -8px;\n    padding: 0px 8px 8px;\n  }\n\n  .progressWrapper {\n    padding: 3px 0;\n    width: 100%;\n  }\n\n  .tasks {\n    color: #333;\n    list-style: none;\n    margin: -2px 0 0;\n    padding-left: 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/Card/TaskList/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport TaskList from './TaskList';\n\nexport default TaskList;\n"
  },
  {
    "path": "client/src/components/cards/Card/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Card from './Card';\n\nexport default Card;\n"
  },
  {
    "path": "client/src/components/cards/CardActionsStep/CardActionsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { shallowEqual, useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useSteps } from '../../../hooks';\nimport { isListArchiveOrTrash } from '../../../utils/record-helpers';\nimport { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums';\nimport SelectCardTypeStep from '../SelectCardTypeStep';\nimport EditDueDateStep from '../EditDueDateStep';\nimport EditStopwatchStep from '../EditStopwatchStep';\nimport MoveCardStep from '../MoveCardStep';\nimport ConfirmationStep from '../../common/ConfirmationStep';\nimport BoardMembershipsStep from '../../board-memberships/BoardMembershipsStep';\nimport LabelsStep from '../../labels/LabelsStep';\n\nimport styles from './CardActionsStep.module.scss';\n\nconst StepTypes = {\n  MEMBERS: 'MEMBERS',\n  LABELS: 'LABELS',\n  EDIT_TYPE: 'EDIT_TYPE',\n  EDIT_DUE_DATE: 'EDIT_DUE_DATE',\n  EDIT_STOPWATCH: 'EDIT_STOPWATCH',\n  MOVE: 'MOVE',\n  ARCHIVE: 'ARCHIVE',\n  DELETE: 'DELETE',\n};\n\nconst CardActionsStep = React.memo(({ cardId, defaultStep, onNameEdit, onClose }) => {\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n  const selectPrevListById = useMemo(() => selectors.makeSelectListById(), []);\n  const selectUserIdsByCardId = useMemo(() => selectors.makeSelectUserIdsByCardId(), []);\n  const selectLabelIdsByCardId = useMemo(() => selectors.makeSelectLabelIdsByCardId(), []);\n\n  const board = useSelector(selectors.selectCurrentBoard);\n  const card = useSelector((state) => selectCardById(state, cardId));\n  const list = useSelector((state) => selectListById(state, card.listId));\n\n  // TODO: check availability?\n  const prevList = useSelector(\n    (state) => card.prevListId && selectPrevListById(state, card.prevListId),\n  );\n\n  const userIds = useSelector((state) => selectUserIdsByCardId(state, cardId));\n  const labelIds = useSelector((state) => selectLabelIdsByCardId(state, cardId));\n\n  const {\n    canEditType,\n    canEditName,\n    canEditDueDate,\n    canEditStopwatch,\n    canCopy,\n    canCut,\n    canDuplicate,\n    canMove,\n    canRestore,\n    canArchive,\n    canDelete,\n    canUseMembers,\n    canUseLabels,\n  } = useSelector((state) => {\n    const isManager = selectors.selectIsCurrentUserManagerForCurrentProject(state);\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    const isEditor = !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n\n    if (isListArchiveOrTrash(list)) {\n      return {\n        canEditType: false,\n        canEditName: false,\n        canEditDueDate: false,\n        canEditStopwatch: false,\n        canCopy: isManager || isEditor,\n        canCut: isEditor,\n        canDuplicate: false,\n        canMove: false,\n        canRestore: isEditor,\n        canArchive: isEditor,\n        canDelete: isEditor,\n        canUseMembers: false,\n        canUseLabels: false,\n      };\n    }\n\n    return {\n      canEditType: isEditor,\n      canEditName: isEditor,\n      canEditDueDate: isEditor,\n      canEditStopwatch: isEditor,\n      canCopy: isManager || isEditor,\n      canCut: isEditor,\n      canDuplicate: isEditor,\n      canMove: isEditor,\n      canRestore: null,\n      canArchive: isEditor,\n      canDelete: isEditor,\n      canUseMembers: isEditor,\n      canUseLabels: isEditor,\n    };\n  }, shallowEqual);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps(defaultStep || null);\n\n  const withActionBar = useMemo(() => {\n    let menuItemsTotal = 0;\n    let actionBarItemsTotal = 0;\n\n    if (card.type === CardTypes.PROJECT && canUseMembers) menuItemsTotal += 1;\n    if (canUseLabels) menuItemsTotal += 1;\n    if (card.type !== CardTypes.PROJECT && canUseMembers) menuItemsTotal += 1;\n    if (card.type === CardTypes.PROJECT && canEditDueDate) menuItemsTotal += 1;\n    if (card.type === CardTypes.PROJECT && canEditStopwatch) menuItemsTotal += 1;\n    if (canEditName) menuItemsTotal += 1;\n    if (!board.limitCardTypesToDefaultOne && canEditType) menuItemsTotal += 1;\n    if (canDuplicate) menuItemsTotal += 1;\n    if (canMove) menuItemsTotal += 1;\n    if (prevList && canRestore) menuItemsTotal += 1;\n\n    if (canCopy) {\n      menuItemsTotal += 1;\n      actionBarItemsTotal += 1;\n    }\n    if (canCut) {\n      menuItemsTotal += 1;\n      actionBarItemsTotal += 1;\n    }\n    if (list.type !== ListTypes.ARCHIVE && canArchive) {\n      menuItemsTotal += 1;\n      actionBarItemsTotal += 1;\n    }\n    if (canDelete) {\n      menuItemsTotal += 1;\n      actionBarItemsTotal += 1;\n    }\n\n    return menuItemsTotal > 4 && actionBarItemsTotal > 1;\n  }, [\n    board.limitCardTypesToDefaultOne,\n    card.type,\n    list.type,\n    prevList,\n    canEditType,\n    canEditName,\n    canEditDueDate,\n    canEditStopwatch,\n    canCopy,\n    canCut,\n    canDuplicate,\n    canMove,\n    canRestore,\n    canArchive,\n    canDelete,\n    canUseMembers,\n    canUseLabels,\n  ]);\n\n  const hasTopSection = useMemo(() => {\n    return (\n      (card.type === CardTypes.PROJECT && canUseMembers) ||\n      canUseLabels ||\n      (card.type !== CardTypes.PROJECT && canUseMembers) ||\n      (card.type === CardTypes.PROJECT && canEditDueDate) ||\n      (card.type === CardTypes.PROJECT && canEditStopwatch) ||\n      canEditName ||\n      (!board.limitCardTypesToDefaultOne && canEditType)\n    );\n  }, [\n    board.limitCardTypesToDefaultOne,\n    card.type,\n    canEditType,\n    canEditName,\n    canEditDueDate,\n    canEditStopwatch,\n    canUseMembers,\n    canUseLabels,\n  ]);\n\n  const hasBottomSection = useMemo(() => {\n    return (\n      (canCopy && !withActionBar) ||\n      (canCut && !withActionBar) ||\n      canDuplicate ||\n      canMove ||\n      (prevList && canRestore) ||\n      (list.type !== ListTypes.ARCHIVE && canArchive && !withActionBar) ||\n      (canDelete && !withActionBar)\n    );\n  }, [\n    list.type,\n    prevList,\n    canCopy,\n    canCut,\n    canDuplicate,\n    canMove,\n    canRestore,\n    canArchive,\n    canDelete,\n    withActionBar,\n  ]);\n\n  const handleTypeSelect = useCallback(\n    (type) => {\n      dispatch(\n        entryActions.updateCard(cardId, {\n          type,\n        }),\n      );\n    },\n    [cardId, dispatch],\n  );\n\n  const handleCopyClick = useCallback(() => {\n    dispatch(entryActions.copyCard(cardId));\n    onClose();\n  }, [cardId, onClose, dispatch]);\n\n  const handleCutClick = useCallback(() => {\n    dispatch(entryActions.cutCard(cardId));\n    onClose();\n  }, [cardId, onClose, dispatch]);\n\n  const handleDuplicateClick = useCallback(() => {\n    dispatch(entryActions.duplicateCard(cardId));\n    onClose();\n  }, [cardId, onClose, dispatch]);\n\n  const handleRestoreClick = useCallback(() => {\n    dispatch(entryActions.moveCard(cardId, card.prevListId, undefined, true));\n  }, [cardId, card.prevListId, dispatch]);\n\n  const handleArchiveConfirm = useCallback(() => {\n    dispatch(entryActions.moveCardToArchive(cardId));\n  }, [cardId, dispatch]);\n\n  const isInTrashList = list.type === ListTypes.TRASH;\n\n  const handleDeleteConfirm = useCallback(() => {\n    if (isInTrashList) {\n      dispatch(entryActions.deleteCard(cardId));\n    } else {\n      dispatch(entryActions.moveCardToTrash(cardId));\n    }\n  }, [cardId, isInTrashList, dispatch]);\n\n  const handleUserSelect = useCallback(\n    (userId) => {\n      dispatch(entryActions.addUserToCard(userId, cardId));\n    },\n    [cardId, dispatch],\n  );\n\n  const handleUserDeselect = useCallback(\n    (userId) => {\n      dispatch(entryActions.removeUserFromCard(userId, cardId));\n    },\n    [cardId, dispatch],\n  );\n\n  const handleLabelSelect = useCallback(\n    (labelId) => {\n      dispatch(entryActions.addLabelToCard(labelId, cardId));\n    },\n    [cardId, dispatch],\n  );\n\n  const handleLabelDeselect = useCallback(\n    (labelId) => {\n      dispatch(entryActions.removeLabelFromCard(labelId, cardId));\n    },\n    [cardId, dispatch],\n  );\n\n  const handleEditNameClick = useCallback(() => {\n    onNameEdit();\n    onClose();\n  }, [onNameEdit, onClose]);\n\n  const handleMembersClick = useCallback(() => {\n    openStep(StepTypes.MEMBERS);\n  }, [openStep]);\n\n  const handleLabelsClick = useCallback(() => {\n    openStep(StepTypes.LABELS);\n  }, [openStep]);\n\n  const handleEditTypeClick = useCallback(() => {\n    openStep(StepTypes.EDIT_TYPE);\n  }, [openStep]);\n\n  const handleEditDueDateClick = useCallback(() => {\n    openStep(StepTypes.EDIT_DUE_DATE);\n  }, [openStep]);\n\n  const handleEditStopwatchClick = useCallback(() => {\n    openStep(StepTypes.EDIT_STOPWATCH);\n  }, [openStep]);\n\n  const handleMoveClick = useCallback(() => {\n    openStep(StepTypes.MOVE);\n  }, [openStep]);\n\n  const handleArchiveClick = useCallback(() => {\n    openStep(StepTypes.ARCHIVE);\n  }, [openStep]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.MEMBERS:\n        return (\n          <BoardMembershipsStep\n            currentUserIds={userIds}\n            onUserSelect={handleUserSelect}\n            onUserDeselect={handleUserDeselect}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.LABELS:\n        return (\n          <LabelsStep\n            currentIds={labelIds}\n            cardId={cardId}\n            onSelect={handleLabelSelect}\n            onDeselect={handleLabelDeselect}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.EDIT_TYPE:\n        return (\n          <SelectCardTypeStep\n            withButton\n            defaultValue={card.type}\n            title=\"common.editType\"\n            buttonContent=\"action.save\"\n            onSelect={handleTypeSelect}\n            onBack={handleBack}\n            onClose={onClose}\n          />\n        );\n      case StepTypes.EDIT_DUE_DATE:\n        return <EditDueDateStep cardId={cardId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.EDIT_STOPWATCH:\n        return <EditStopwatchStep cardId={cardId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.MOVE:\n        return <MoveCardStep id={cardId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.ARCHIVE:\n        return (\n          <ConfirmationStep\n            title=\"common.archiveCard\"\n            content=\"common.areYouSureYouWantToArchiveThisCard\"\n            buttonContent=\"action.archiveCard\"\n            onConfirm={handleArchiveConfirm}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.DELETE:\n        return (\n          <ConfirmationStep\n            title={isInTrashList ? 'common.deleteCardForever' : 'common.deleteCard'}\n            content={\n              isInTrashList\n                ? 'common.areYouSureYouWantToDeleteThisCardForever'\n                : 'common.areYouSureYouWantToDeleteThisCard'\n            }\n            buttonContent={isInTrashList ? 'action.deleteCardForever' : 'action.deleteCard'}\n            onConfirm={handleDeleteConfirm}\n            onBack={handleBack}\n          />\n        );\n      default:\n    }\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.cardActions', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Menu secondary vertical className={styles.menu}>\n          {card.type === CardTypes.PROJECT && canUseMembers && (\n            <Menu.Item className={styles.menuItem} onClick={handleMembersClick}>\n              <Icon name=\"user outline\" className={styles.menuItemIcon} />\n              {t('common.members', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {canUseLabels && (\n            <Menu.Item className={styles.menuItem} onClick={handleLabelsClick}>\n              <Icon name=\"bookmark outline\" className={styles.menuItemIcon} />\n              {t('common.labels', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {card.type !== CardTypes.PROJECT && canUseMembers && (\n            <Menu.Item className={styles.menuItem} onClick={handleMembersClick}>\n              <Icon name=\"user outline\" className={styles.menuItemIcon} />\n              {t('common.members', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {card.type === CardTypes.PROJECT && canEditDueDate && (\n            <Menu.Item className={styles.menuItem} onClick={handleEditDueDateClick}>\n              <Icon name=\"calendar check outline\" className={styles.menuItemIcon} />\n              {t('action.editDueDate', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {card.type === CardTypes.PROJECT && canEditStopwatch && (\n            <Menu.Item className={styles.menuItem} onClick={handleEditStopwatchClick}>\n              <Icon name=\"clock outline\" className={styles.menuItemIcon} />\n              {t('action.editStopwatch', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {canEditName && (\n            <Menu.Item className={styles.menuItem} onClick={handleEditNameClick}>\n              <Icon name=\"edit outline\" className={styles.menuItemIcon} />\n              {t('action.editTitle', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {!board.limitCardTypesToDefaultOne && canEditType && (\n            <Menu.Item className={styles.menuItem} onClick={handleEditTypeClick}>\n              <Icon name=\"map outline\" className={styles.menuItemIcon} />\n              {t('action.editType', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {hasTopSection && hasBottomSection && <hr className={styles.divider} />}\n          {canCopy && !withActionBar && (\n            <Menu.Item className={styles.menuItem} onClick={handleCopyClick}>\n              <Icon name=\"copy outline\" className={styles.menuItemIcon} />\n              {t('action.copyCard', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {canCut && !withActionBar && (\n            <Menu.Item className={styles.menuItem} onClick={handleCutClick}>\n              <Icon name=\"cut\" className={styles.menuItemIcon} />\n              {t('action.cutCard', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {canDuplicate && (\n            <Menu.Item className={styles.menuItem} onClick={handleDuplicateClick}>\n              <Icon name=\"copy outline\" className={styles.menuItemIcon} />\n              {t('action.duplicateCard', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {canMove && (\n            <Menu.Item className={styles.menuItem} onClick={handleMoveClick}>\n              <Icon name=\"share square outline\" className={styles.menuItemIcon} />\n              {t('action.moveCard', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {prevList && canRestore && (\n            <Menu.Item className={styles.menuItem} onClick={handleRestoreClick}>\n              <Icon name=\"undo alternate\" className={styles.menuItemIcon} />\n              {t('action.restoreToList', {\n                context: 'title',\n                list: prevList.name || t(`common.${prevList.type}`),\n              })}\n            </Menu.Item>\n          )}\n          {list.type !== ListTypes.ARCHIVE && canArchive && !withActionBar && (\n            <Menu.Item className={styles.menuItem} onClick={handleArchiveClick}>\n              <Icon name=\"folder open outline\" className={styles.menuItemIcon} />\n              {t('action.archiveCard', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {canDelete && !withActionBar && (\n            <Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>\n              <Icon name=\"trash alternate outline\" className={styles.menuItemIcon} />\n              {isInTrashList\n                ? t('action.deleteForever', {\n                    context: 'title',\n                  })\n                : t('action.deleteCard', {\n                    context: 'title',\n                  })}\n            </Menu.Item>\n          )}\n          {withActionBar && (\n            <>\n              <hr className={styles.divider} />\n              <div className={styles.actionBar}>\n                {canCopy && (\n                  /* eslint-disable-next-line jsx-a11y/anchor-is-valid,\n                                              jsx-a11y/click-events-have-key-events,\n                                              jsx-a11y/no-static-element-interactions */\n                  <a className={styles.actionBarItem} onClick={handleCopyClick}>\n                    <Icon fitted name=\"copy outline\" />\n                    <span className={styles.actionBarItemText}>\n                      {t('action.copy', {\n                        context: 'title',\n                      })}\n                    </span>\n                  </a>\n                )}\n                {canCut && (\n                  /* eslint-disable-next-line jsx-a11y/anchor-is-valid,\n                                              jsx-a11y/click-events-have-key-events,\n                                              jsx-a11y/no-static-element-interactions */\n                  <a className={styles.actionBarItem} onClick={handleCutClick}>\n                    <Icon fitted name=\"cut\" />\n                    <span className={styles.actionBarItemText}>\n                      {t('action.cut', {\n                        context: 'title',\n                      })}\n                    </span>\n                  </a>\n                )}\n                {list.type !== ListTypes.ARCHIVE && canArchive && (\n                  /* eslint-disable-next-line jsx-a11y/anchor-is-valid,\n                                              jsx-a11y/click-events-have-key-events,\n                                              jsx-a11y/no-static-element-interactions */\n                  <a className={styles.actionBarItem} onClick={handleArchiveClick}>\n                    <Icon fitted name=\"archive\" />\n                    <span className={styles.actionBarItemText}>\n                      {t('action.archive', {\n                        context: 'title',\n                      })}\n                    </span>\n                  </a>\n                )}\n                {canDelete && (\n                  /* eslint-disable-next-line jsx-a11y/anchor-is-valid,\n                                              jsx-a11y/click-events-have-key-events,\n                                              jsx-a11y/no-static-element-interactions */\n                  <a className={styles.actionBarItem} onClick={handleDeleteClick}>\n                    <Icon fitted name=\"trash alternate outline\" />\n                    <span className={styles.actionBarItemText}>\n                      {isInTrashList\n                        ? t('action.deleteForever', {\n                            context: 'title',\n                          })\n                        : t('action.delete', {\n                            context: 'title',\n                          })}\n                    </span>\n                  </a>\n                )}\n              </div>\n            </>\n          )}\n        </Menu>\n      </Popup.Content>\n    </>\n  );\n});\n\nCardActionsStep.propTypes = {\n  cardId: PropTypes.string.isRequired,\n  defaultStep: PropTypes.string,\n  onNameEdit: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nCardActionsStep.defaultProps = {\n  defaultStep: undefined,\n};\n\nCardActionsStep.StepTypes = StepTypes;\n\nexport default CardActionsStep;\n"
  },
  {
    "path": "client/src/components/cards/CardActionsStep/CardActionsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actionBar {\n    display: flex;\n    margin: -7px 0;\n  }\n\n  .actionBarItem {\n    color: rgba(0, 0, 0, 0.87);\n    cursor: pointer;\n    display: flex;\n    flex: 1;\n    flex-direction: column;\n    font-size: 13px;\n    padding: 6px;\n    row-gap: 6px;\n    user-select: none;\n\n    &:not(:last-child) {\n      border-right: 1px solid #eee;\n    }\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.05);\n      color: rgba(0, 0, 0, 0.95);\n    }\n  }\n\n  .actionBarItemText {\n    display: -webkit-box;\n    line-clamp: 2;\n    -webkit-line-clamp: 2;\n    overflow: hidden;\n    text-align: center;\n    -webkit-box-orient: vertical;\n  }\n\n  .divider {\n    background: #eee;\n    border: 0;\n    height: 1px;\n  }\n\n  .menu {\n    margin: 0 -12px -5px;\n    width: calc(100% + 24px);\n  }\n\n  .menuItem {\n    margin: 0;\n    padding-left: 14px;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.5em 0 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardActionsStep/index.js",
    "content": "import CardActionsStep from './CardActionsStep';\n\nexport default CardActionsStep;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/AddAttachmentZone/AddAttachmentZone.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { useDropzone } from 'react-dropzone';\nimport { closePopup } from '../../../../lib/popup';\n\nimport entryActions from '../../../../entry-actions';\nimport { useModal } from '../../../../hooks';\nimport { isUrl } from '../../../../utils/validator';\nimport { isActiveTextElement } from '../../../../utils/element-helpers';\nimport { AttachmentTypes } from '../../../../constants/Enums';\nimport AddTextFileModal from './AddTextFileModal';\n\nimport styles from './AddAttachmentZone.module.scss';\n\nconst AddAttachmentZone = React.memo(({ children }) => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [modal, openModal, handleModalClose] = useModal();\n\n  const submitFile = useCallback(\n    (file) => {\n      dispatch(\n        entryActions.createAttachmentInCurrentCard({\n          file,\n          type: AttachmentTypes.FILE,\n          name: file.name,\n        }),\n      );\n    },\n    [dispatch],\n  );\n\n  const submitLink = useCallback(\n    (url) => {\n      dispatch(\n        entryActions.createAttachmentInCurrentCard({\n          url,\n          type: AttachmentTypes.LINK,\n          name: url,\n        }),\n      );\n    },\n    [dispatch],\n  );\n\n  const handleDropAccepted = useCallback(\n    (files) => {\n      files.forEach((file) => {\n        submitFile(file);\n      });\n    },\n    [submitFile],\n  );\n\n  const { getRootProps, getInputProps, isDragActive } = useDropzone({\n    noClick: true,\n    noKeyboard: true,\n    onDropAccepted: handleDropAccepted,\n  });\n\n  const handleFileCreate = useCallback(\n    (file) => {\n      submitFile(file);\n    },\n    [submitFile],\n  );\n\n  useEffect(() => {\n    const handlePaste = (event) => {\n      if (!event.clipboardData) {\n        return;\n      }\n\n      const { files, items } = event.clipboardData;\n\n      if (files.length > 0) {\n        [...files].forEach((file) => {\n          submitFile(file);\n        });\n\n        return;\n      }\n\n      if (items.length === 0) {\n        return;\n      }\n\n      if (items[0].kind === 'string') {\n        if (isActiveTextElement(event.target)) {\n          return;\n        }\n\n        items[0].getAsString((content) => {\n          if (isUrl(content)) {\n            submitLink(content);\n          } else {\n            closePopup();\n\n            openModal({\n              content,\n            });\n          }\n        });\n\n        return;\n      }\n\n      [...items].forEach((item) => {\n        if (item.kind !== 'file') {\n          return;\n        }\n\n        submitFile(item.getAsFile());\n      });\n    };\n\n    window.addEventListener('paste', handlePaste);\n\n    return () => {\n      window.removeEventListener('paste', handlePaste);\n    };\n  }, [openModal, submitFile, submitLink]);\n\n  return (\n    <>\n      {/* eslint-disable-next-line react/jsx-props-no-spreading */}\n      <div {...getRootProps()}>\n        {isDragActive && (\n          <div className={styles.dropzone}>\n            <div className={styles.dropzoneText}>{t('common.dropFileToUpload')}</div>\n          </div>\n        )}\n        {children}\n        {/* eslint-disable-next-line react/jsx-props-no-spreading */}\n        <input {...getInputProps()} />\n      </div>\n      {modal && (\n        <AddTextFileModal\n          content={modal.content}\n          onCreate={handleFileCreate}\n          onClose={handleModalClose}\n        />\n      )}\n    </>\n  );\n});\n\nAddAttachmentZone.propTypes = {\n  children: PropTypes.element.isRequired,\n};\n\nexport default AddAttachmentZone;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/AddAttachmentZone/AddAttachmentZone.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .dropzone {\n    background: white;\n    font-size: 20px;\n    font-weight: bold;\n    inset: 0;\n    line-height: 30px;\n    opacity: 0.7;\n    position: absolute;\n    text-align: center;\n    z-index: 2001;\n  }\n\n  .dropzoneText {\n    left: 50%;\n    position: absolute;\n    top: min(200px, 50%);\n    transform: translateX(-50%) translateY(-50%);\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/AddAttachmentZone/AddTextFileModal.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Header, Modal } from 'semantic-ui-react';\nimport { Input } from '../../../../lib/custom-ui';\n\nimport { useForm, useNestedRef } from '../../../../hooks';\n\nimport styles from './AddTextFileModal.module.scss';\n\nconst AddTextFileModal = React.memo(({ content, onCreate, onClose }) => {\n  const [t] = useTranslation();\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: '',\n  }));\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n    };\n\n    if (!cleanData.name) {\n      nameFieldRef.current.select();\n      return;\n    }\n\n    const file = new File([content], `${cleanData.name}.txt`, {\n      type: 'plain/text',\n    });\n\n    onCreate(file);\n    onClose();\n  }, [content, onCreate, onClose, data, nameFieldRef]);\n\n  useEffect(() => {\n    nameFieldRef.current.focus();\n  }, [nameFieldRef]);\n\n  return (\n    <Modal open basic closeIcon size=\"tiny\" onClose={onClose}>\n      <Modal.Content>\n        <Header inverted size=\"huge\">\n          {t('common.createTextFile', {\n            context: 'title',\n          })}\n        </Header>\n        <p>{t('common.enterFilename')}</p>\n        <Form onSubmit={handleSubmit}>\n          <Input\n            fluid\n            inverted\n            ref={handleNameFieldRef}\n            name=\"name\"\n            value={data.name}\n            maxLength={124}\n            label=\".txt\"\n            labelPosition=\"right\"\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          <Button\n            inverted\n            color=\"green\"\n            icon=\"checkmark\"\n            content={t('action.createFile')}\n            floated=\"right\"\n          />\n        </Form>\n      </Modal.Content>\n    </Modal>\n  );\n});\n\nAddTextFileModal.propTypes = {\n  content: PropTypes.string.isRequired,\n  onCreate: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default AddTextFileModal;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/AddAttachmentZone/AddTextFileModal.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 20px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/AddAttachmentZone/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AddAttachmentZone from './AddAttachmentZone';\n\nexport default AddAttachmentZone;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/CardModal.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Icon } from 'semantic-ui-react';\nimport { push } from '../../../lib/redux-router';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useClosableModal } from '../../../hooks';\nimport { isListArchiveOrTrash } from '../../../utils/record-helpers';\nimport { isActiveTextElement } from '../../../utils/element-helpers';\nimport Paths from '../../../constants/Paths';\nimport { BoardMembershipRoles, CardTypes } from '../../../constants/Enums';\nimport ProjectContent from './ProjectContent';\nimport StoryContent from './StoryContent';\nimport AddAttachmentZone from './AddAttachmentZone';\n\nimport styles from './CardModal.module.scss';\n\nconst DIRECTION_BY_KEY = {\n  ArrowLeft: -1,\n  ArrowRight: 1,\n};\n\nconst CardModal = React.memo(() => {\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const card = useSelector(selectors.selectCurrentCard);\n  const prevCardId = useSelector(selectors.selectPrevCardId);\n\n  const canEdit = useSelector((state) => {\n    const list = selectListById(state, card.listId);\n\n    if (isListArchiveOrTrash(list)) {\n      return false;\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  const dispatch = useDispatch();\n\n  const handleClose = useCallback(() => {\n    dispatch(push(Paths.BOARDS.replace(':id', card.boardId)));\n  }, [card.boardId, dispatch]);\n\n  const handlePrevClick = useCallback(() => {\n    dispatch(push(Paths.CARDS.replace(':id', prevCardId)));\n  }, [prevCardId, dispatch]);\n\n  const [ClosableModal, isClosableActiveRef] = useClosableModal();\n\n  useEffect(() => {\n    const handleKeydown = (event) => {\n      if (isClosableActiveRef.current) {\n        return;\n      }\n\n      if (isActiveTextElement(event.target)) {\n        return;\n      }\n\n      if (!Object.keys(DIRECTION_BY_KEY).includes(event.key)) {\n        return;\n      }\n\n      event.preventDefault();\n      dispatch(entryActions.goToAdjacentCard(DIRECTION_BY_KEY[event.key]));\n    };\n\n    document.addEventListener('keydown', handleKeydown);\n\n    return () => {\n      document.removeEventListener('keydown', handleKeydown);\n    };\n  }, [card, dispatch, isClosableActiveRef]);\n\n  let Content;\n  switch (card.type) {\n    case CardTypes.PROJECT:\n      Content = ProjectContent;\n\n      break;\n    case CardTypes.STORY:\n      Content = StoryContent;\n\n      break;\n    default:\n  }\n\n  return (\n    <ClosableModal\n      closeIcon\n      centered={false}\n      className={classNames(styles.wrapper, card.type === CardTypes.STORY && styles.wrapperStory)}\n      onClose={handleClose}\n    >\n      {prevCardId && (\n        <button type=\"button\" className={styles.prevButton} onClick={handlePrevClick}>\n          <Icon fitted name=\"arrow left\" size=\"large\" className={styles.prevButtonIcon} />\n        </button>\n      )}\n      {canEdit ? (\n        <AddAttachmentZone>\n          <Content onClose={handleClose} />\n        </AddAttachmentZone>\n      ) : (\n        <Content onClose={handleClose} />\n      )}\n    </ClosableModal>\n  );\n});\n\nexport default CardModal;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/CardModal.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  @mixin smallScreen {\n    margin: 1rem auto;\n    width: 95%;\n  }\n\n  .prevButton {\n    background: rgba(255, 255, 255, 0.036);\n    border: none;\n    border-radius: 4px;\n    cursor: pointer;\n    display: flex;\n    height: 100%;\n    justify-content: center;\n    left: -120px;\n    padding: 18px 0;\n    position: absolute;\n    width: 100px;\n\n    &:hover {\n      background: rgba(255, 255, 255, 0.072);\n\n      .prevkButtonIcon {\n        color: rgba(255, 255, 255, 0.72);\n      }\n    }\n  }\n\n  .prevButtonIcon {\n    color: rgba(255, 255, 255, 0.36);\n    position: sticky;\n    top: 5px;\n  }\n\n  .wrapper {\n    margin: 2rem auto;\n    width: 880px;\n\n    @media (width < 926px) {\n      @include smallScreen;\n    }\n  }\n\n  .wrapperStory {\n    width: 980px;\n\n    @media (width < 1026px) {\n      @include smallScreen;\n    }\n\n    .prevButton {\n      padding: 20px 0;\n    }\n\n    .prevButtonIcon {\n      top: 7px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/Communication.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { Menu, Tab } from 'semantic-ui-react';\n\nimport Comments from '../../comments/Comments';\nimport CardActivities from '../../activities/CardActivities';\n\nimport styles from './Communication.module.scss';\n\nconst Communication = React.memo(() => {\n  const [t] = useTranslation();\n\n  const panes = [\n    {\n      menuItem: (\n        <Menu.Item key=\"comments\" className={styles.menuItem}>\n          {t('common.comments', {\n            context: 'title',\n          })}\n        </Menu.Item>\n      ),\n      render: () => <Comments />,\n    },\n    {\n      menuItem: (\n        <Menu.Item key=\"actions\" className={styles.menuItem}>\n          {t('common.actions', {\n            context: 'title',\n          })}\n        </Menu.Item>\n      ),\n      render: () => <CardActivities />,\n    },\n  ];\n\n  return (\n    <Tab\n      menu={{\n        secondary: true,\n        pointing: true,\n      }}\n      panes={panes}\n    />\n  );\n});\n\nexport default Communication;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/Communication.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menuItem {\n    color: #17394d;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/CreationDetailsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport { isUserStatic } from '../../../utils/record-helpers';\nimport TimeAgo from '../../common/TimeAgo';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './CreationDetailsStep.module.scss';\n\nconst CreationDetailsStep = React.memo(({ userId }) => {\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const card = useSelector(selectors.selectCurrentCard);\n  const user = useSelector((state) => selectUserById(state, userId));\n\n  const [t] = useTranslation();\n\n  return (\n    <>\n      <div className={styles.userWrapper}>\n        <span className={styles.user}>\n          <UserAvatar id={userId} size=\"large\" />\n        </span>\n        <span className={styles.content}>\n          <div className={styles.name}>\n            {isUserStatic(user)\n              ? t(`common.${user.name}`, {\n                  context: 'title',\n                })\n              : user.name}\n          </div>\n          {user && user.username && <div className={styles.username}>@{user.username}</div>}\n        </span>\n      </div>\n      <div className={styles.information}>\n        <Icon name=\"clock\" className={styles.informationIcon} />\n        <TimeAgo date={card.createdAt} />\n      </div>\n    </>\n  );\n});\n\nCreationDetailsStep.propTypes = {\n  userId: PropTypes.string,\n};\n\nCreationDetailsStep.defaultProps = {\n  userId: undefined,\n};\n\nexport default CreationDetailsStep;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/CreationDetailsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .content {\n    display: inline-block;\n    width: calc(100% - 44px);\n  }\n\n  .information {\n    color: #888888;\n    margin-bottom: 8px;\n  }\n\n  .informationIcon {\n    margin-right: 6px;\n    opacity: 0.45;\n  }\n\n  .name {\n    color: #212121;\n    font-size: 16px;\n    font-weight: bold;\n    line-height: 1.2;\n    padding: 9px 28px 0 2px;\n  }\n\n  .userWrapper {\n    margin-bottom: 8px;\n  }\n\n  .user {\n    display: inline-block;\n    padding-right: 8px;\n    padding-top: 10px;\n    vertical-align: top;\n  }\n\n  .username {\n    color: #888888;\n    font-size: 14px;\n    line-height: 1.2;\n    padding: 2px 0 2px 2px;\n    word-wrap: break-word;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/CustomFieldGroups/CustomFieldGroups.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { DragDropContext, Droppable } from 'react-beautiful-dnd';\nimport { closePopup } from '../../../../lib/popup';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport DroppableTypes from '../../../../constants/DroppableTypes';\nimport Item from './Item';\nimport DraggableItem from './DraggableItem';\n\nimport styles from './CustomFieldGroups.module.scss';\nimport globalStyles from '../../../../styles.module.scss';\n\nconst CustomFieldGroups = React.memo(() => {\n  const boardCustomFieldGroupIds = useSelector(selectors.selectCustomFieldGroupIdsForCurrentBoard);\n  const cardCustomFieldGroupIds = useSelector(selectors.selectCustomFieldGroupIdsForCurrentCard);\n\n  const dispatch = useDispatch();\n\n  const handleDragStart = useCallback(() => {\n    document.body.classList.add(globalStyles.dragging);\n    closePopup();\n  }, []);\n\n  const handleDragEnd = useCallback(\n    ({ draggableId, source, destination }) => {\n      document.body.classList.remove(globalStyles.dragging);\n\n      if (!destination || source.index === destination.index) {\n        return;\n      }\n\n      dispatch(entryActions.moveCustomFieldGroup(draggableId, destination.index));\n    },\n    [dispatch],\n  );\n\n  return (\n    <>\n      {boardCustomFieldGroupIds.map((customFieldGroupId) => (\n        <div key={customFieldGroupId} className={styles.item}>\n          <Item id={customFieldGroupId} />\n        </div>\n      ))}\n      <DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>\n        <Droppable droppableId=\"card\" type={DroppableTypes.CUSTOM_FIELD_GROUP} direction=\"vertical\">\n          {({ innerRef, droppableProps, placeholder }) => (\n            // eslint-disable-next-line react/jsx-props-no-spreading\n            <div {...droppableProps} ref={innerRef}>\n              {cardCustomFieldGroupIds.map((customFieldGroupId, index) => (\n                <DraggableItem\n                  key={customFieldGroupId}\n                  id={customFieldGroupId}\n                  index={index}\n                  className={styles.item}\n                />\n              ))}\n              {placeholder}\n            </div>\n          )}\n        </Droppable>\n      </DragDropContext>\n    </>\n  );\n});\n\nexport default CustomFieldGroups;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/CustomFieldGroups/CustomFieldGroups.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .item {\n    border-radius: 3px;\n    margin-bottom: 24px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/CustomFieldGroups/DraggableItem.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport ReactDOM from 'react-dom';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { Draggable } from 'react-beautiful-dnd';\n\nimport classNames from 'classnames';\nimport selectors from '../../../../selectors';\nimport { isListArchiveOrTrash } from '../../../../utils/record-helpers';\nimport { BoardMembershipRoles } from '../../../../constants/Enums';\nimport Item from './Item';\n\nimport styles from './DraggableItem.module.scss';\n\nconst DraggableItem = React.memo(({ id, index, className, ...props }) => {\n  const selectCustomFieldGroupById = useMemo(() => selectors.makeSelectCustomFieldGroupById(), []);\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const customFieldGroup = useSelector((state) => selectCustomFieldGroupById(state, id));\n\n  const canEdit = useSelector((state) => {\n    if (customFieldGroup.boardId) {\n      return false;\n    }\n\n    const { listId } = selectors.selectCurrentCard(state);\n    const list = selectListById(state, listId);\n\n    if (isListArchiveOrTrash(list)) {\n      return false;\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  return (\n    <Draggable\n      draggableId={id}\n      index={index}\n      isDragDisabled={!customFieldGroup.isPersisted || !canEdit}\n    >\n      {({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {\n        const contentNode = (\n          <div\n            {...draggableProps} // eslint-disable-line react/jsx-props-no-spreading\n            ref={innerRef}\n            className={classNames(className, isDragging && styles.dragging)}\n          >\n            {/* eslint-disable-next-line react/jsx-props-no-spreading */}\n            <Item {...props} id={id} dragHandleProps={dragHandleProps} />\n          </div>\n        );\n\n        return isDragging ? ReactDOM.createPortal(contentNode, document.body) : contentNode;\n      }}\n    </Draggable>\n  );\n});\n\nDraggableItem.propTypes = {\n  id: PropTypes.string.isRequired,\n  index: PropTypes.number.isRequired,\n  className: PropTypes.string,\n};\n\nDraggableItem.defaultProps = {\n  className: undefined,\n};\n\nexport default DraggableItem;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/CustomFieldGroups/DraggableItem.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .dragging {\n    background: rgba(247, 246, 247, 0.8);\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/CustomFieldGroups/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { Button, Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../../selectors';\nimport { usePopupInClosableContext } from '../../../../hooks';\nimport { isListArchiveOrTrash } from '../../../../utils/record-helpers';\nimport { BoardMembershipRoles } from '../../../../constants/Enums';\nimport CustomFieldGroup from '../../../custom-field-groups/CustomFieldGroup';\nimport CustomFieldGroupStep from '../../../custom-field-groups/CustomFieldGroupStep';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id, dragHandleProps }) => {\n  const selectCustomFieldGroupById = useMemo(() => selectors.makeSelectCustomFieldGroupById(), []);\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const customFieldGroup = useSelector((state) => selectCustomFieldGroupById(state, id));\n\n  const canEdit = useSelector((state) => {\n    if (customFieldGroup.boardId) {\n      return false;\n    }\n\n    const { listId } = selectors.selectCurrentCard(state);\n    const list = selectListById(state, listId);\n\n    if (isListArchiveOrTrash(list)) {\n      return false;\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  const CustomFieldGroupPopup = usePopupInClosableContext(CustomFieldGroupStep);\n\n  return (\n    <div className={styles.wrapper}>\n      <div className={styles.moduleWrapper}>\n        <Icon name=\"sticky note outline\" className={styles.moduleIcon} />\n        {/* eslint-disable-next-line react/jsx-props-no-spreading */}\n        <div {...dragHandleProps}>\n          <div className={classNames(styles.moduleHeader, canEdit && styles.moduleHeaderEditable)}>\n            {customFieldGroup.isPersisted && canEdit && (\n              <CustomFieldGroupPopup id={customFieldGroup.id}>\n                <Button className={styles.editButton}>\n                  <Icon fitted name=\"pencil\" size=\"small\" />\n                </Button>\n              </CustomFieldGroupPopup>\n            )}\n            <span className={styles.moduleHeaderTitle}>{customFieldGroup.name}</span>\n          </div>\n        </div>\n        <CustomFieldGroup id={id} />\n      </div>\n    </div>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n  dragHandleProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n};\n\nItem.defaultProps = {\n  dragHandleProps: undefined,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/CustomFieldGroups/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .editButton {\n    background: transparent;\n    box-shadow: none;\n    line-height: 28px;\n    margin: 0;\n    min-height: auto;\n    opacity: 0;\n    padding: 0;\n    position: absolute;\n    right: 0;\n    top: 4px;\n    width: 28px;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n    }\n  }\n\n  .moduleHeader {\n    color: #17394d;\n    display: flex;\n    font-size: 16px;\n    font-weight: bold;\n    line-height: 1.5;\n    margin: 0 0 4px;\n    padding: 6px 0;\n  }\n\n  .moduleHeaderEditable {\n    padding-right: 32px;\n\n    &:hover {\n      .editButton {\n        opacity: 1;\n      }\n    }\n  }\n\n  .moduleHeaderTitle {\n    min-width: 0;\n    word-wrap: break-word;\n  }\n\n  .moduleIcon {\n    color: #17394d;\n    font-size: 17px;\n    height: 32px;\n    left: -40px;\n    line-height: 32px;\n    margin-right: 0;\n    position: absolute;\n    top: 2px;\n    width: 32px;\n  }\n\n  .moduleWrapper {\n    margin: 0 0 0 40px;\n    position: relative;\n  }\n\n  .wrapper {\n    border-radius: 3px;\n    margin-bottom: 24px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/CustomFieldGroups/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport CustomFieldGroups from './CustomFieldGroups';\n\nexport default CustomFieldGroups;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/MoreActionsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { shallowEqual, useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useSteps } from '../../../hooks';\nimport { isListArchiveOrTrash } from '../../../utils/record-helpers';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\nimport SelectCardTypeStep from '../SelectCardTypeStep';\nimport MoveCardStep from '../MoveCardStep';\n\nimport styles from './MoreActionsStep.module.scss';\n\nconst StepTypes = {\n  EDIT_TYPE: 'EDIT_TYPE',\n  MOVE: 'MOVE',\n};\n\nconst MoreActionsStep = React.memo(({ onClose }) => {\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const card = useSelector(selectors.selectCurrentCard);\n  const board = useSelector(selectors.selectCurrentBoard);\n\n  const { canEditType, canDuplicate, canMove } = useSelector((state) => {\n    const list = selectListById(state, card.listId);\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    const isEditor = !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n\n    if (isListArchiveOrTrash(list)) {\n      return {\n        canEditType: false,\n        canDuplicate: false,\n        canMove: isEditor,\n      };\n    }\n\n    return {\n      canEditType: isEditor,\n      canDuplicate: isEditor,\n      canMove: isEditor,\n    };\n  }, shallowEqual);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n\n  const handleTypeSelect = useCallback(\n    (type) => {\n      dispatch(\n        entryActions.updateCurrentCard({\n          type,\n        }),\n      );\n    },\n    [dispatch],\n  );\n\n  const handleDuplicateClick = useCallback(() => {\n    dispatch(entryActions.duplicateCurrentCard());\n  }, [dispatch]);\n\n  const handleEditTypeClick = useCallback(() => {\n    openStep(StepTypes.EDIT_TYPE);\n  }, [openStep]);\n\n  const handleMoveClick = useCallback(() => {\n    openStep(StepTypes.MOVE);\n  }, [openStep]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.EDIT_TYPE:\n        return (\n          <SelectCardTypeStep\n            withButton\n            defaultValue={card.type}\n            title=\"common.editType\"\n            buttonContent=\"action.save\"\n            onSelect={handleTypeSelect}\n            onBack={handleBack}\n            onClose={onClose}\n          />\n        );\n      case StepTypes.MOVE:\n        return <MoveCardStep id={card.id} onBack={handleBack} onClose={onClose} />;\n      default:\n    }\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.moreActions', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Menu secondary vertical className={styles.menu}>\n          {!board.limitCardTypesToDefaultOne && canEditType && (\n            <Menu.Item className={styles.menuItem} onClick={handleEditTypeClick}>\n              <Icon name=\"map outline\" className={styles.menuItemIcon} />\n              {t('action.editType', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {canDuplicate && (\n            <Menu.Item className={styles.menuItem} onClick={handleDuplicateClick}>\n              <Icon name=\"copy outline\" className={styles.menuItemIcon} />\n              {t('action.duplicateCard', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {canMove && (\n            <Menu.Item className={styles.menuItem} onClick={handleMoveClick}>\n              <Icon name=\"share square outline\" className={styles.menuItemIcon} />\n              {t('action.moveCard', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n        </Menu>\n      </Popup.Content>\n    </>\n  );\n});\n\nMoreActionsStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default MoreActionsStep;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/MoreActionsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menu {\n    margin: 0 -12px -5px;\n    width: calc(100% + 24px);\n  }\n\n  .menuItem {\n    margin: 0;\n    padding-left: 14px;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.5em 0 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/NameField.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport React, { useCallback, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport TextareaAutosize from 'react-textarea-autosize';\nimport { TextArea } from 'semantic-ui-react';\nimport { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';\n\nimport { useEscapeInterceptor, useField, useNestedRef } from '../../../hooks';\n\nimport styles from './NameField.module.scss';\n\nconst Sizes = {\n  MEDIUM: 'medium',\n  LARGE: 'large',\n};\n\nconst NameField = React.memo(({ defaultValue, size, onUpdate }) => {\n  const prevDefaultValue = usePrevious(defaultValue);\n  const [value, handleChange, setValue] = useField(defaultValue);\n  const [blurFieldState, blurField] = useToggle();\n\n  const [fiedRef, handleFieldRef] = useNestedRef();\n  const isFocusedRef = useRef(false);\n\n  const handleEscape = useCallback(() => {\n    setValue(defaultValue);\n    blurField();\n  }, [defaultValue, setValue, blurField]);\n\n  const [activateEscapeInterceptor, deactivateEscapeInterceptor] =\n    useEscapeInterceptor(handleEscape);\n\n  const handleFocus = useCallback(() => {\n    activateEscapeInterceptor();\n    isFocusedRef.current = true;\n  }, [activateEscapeInterceptor]);\n\n  const handleKeyDown = useCallback(\n    (event) => {\n      if (event.key === 'Enter') {\n        event.preventDefault();\n        fiedRef.current.blur();\n      }\n    },\n    [fiedRef],\n  );\n\n  const handleBlur = useCallback(() => {\n    deactivateEscapeInterceptor();\n    isFocusedRef.current = false;\n\n    const cleanValue = value.trim();\n\n    if (cleanValue) {\n      if (cleanValue !== defaultValue) {\n        onUpdate(cleanValue);\n      }\n    } else {\n      setValue(defaultValue);\n    }\n  }, [defaultValue, onUpdate, value, setValue, deactivateEscapeInterceptor]);\n\n  useDidUpdate(() => {\n    if (!isFocusedRef.current && defaultValue !== prevDefaultValue) {\n      setValue(defaultValue);\n    }\n  }, [defaultValue, prevDefaultValue]);\n\n  useDidUpdate(() => {\n    fiedRef.current.blur();\n  }, [blurFieldState]);\n\n  return (\n    <TextArea\n      ref={handleFieldRef}\n      as={TextareaAutosize}\n      value={value}\n      maxLength={1024}\n      className={classNames(styles.field, styles[`field${upperFirst(size)}`])}\n      onFocus={handleFocus}\n      onKeyDown={handleKeyDown}\n      onChange={handleChange}\n      onBlur={handleBlur}\n    />\n  );\n});\n\nNameField.propTypes = {\n  defaultValue: PropTypes.string.isRequired,\n  size: PropTypes.oneOf(Object.values(Sizes)),\n  onUpdate: PropTypes.func.isRequired,\n};\n\nNameField.defaultProps = {\n  size: Sizes.MEDIUM,\n};\n\nexport default NameField;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/NameField.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    background: transparent;\n    border: 1px solid transparent;\n    border-radius: 3px;\n    box-shadow: none;\n    color: #17394d;\n    font-weight: bold;\n    margin: -5px;\n    overflow: hidden;\n    padding: 4px;\n    resize: none;\n    width: 100%;\n\n    &:focus {\n      background: #fff;\n      border-color: #5ba4cf;\n      box-shadow: 0 0 2px 0 #5ba4cf;\n      outline: 0;\n    }\n  }\n\n  /* Sizes */\n\n  .fieldMedium {\n    font-size: 20px;\n    line-height: 24px;\n  }\n\n  .fieldLarge {\n    font-size: 28px;\n    line-height: 34px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/ProjectContent.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useContext, useMemo, useState } from 'react';\nimport classNames from 'classnames';\nimport { shallowEqual, useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Checkbox, Grid, Icon } from 'semantic-ui-react';\nimport { useDidUpdate } from '../../../lib/hooks';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { usePopupInClosableContext } from '../../../hooks';\nimport { startStopwatch, stopStopwatch } from '../../../utils/stopwatch';\nimport { isUsableMarkdownElement } from '../../../utils/element-helpers';\nimport { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums';\nimport { CardTypeIcons } from '../../../constants/Icons';\nimport { ClosableContext } from '../../../contexts';\nimport NameField from './NameField';\nimport TaskLists from './TaskLists';\nimport CustomFieldGroups from './CustomFieldGroups';\nimport Communication from './Communication';\nimport CreationDetailsStep from './CreationDetailsStep';\nimport MoreActionsStep from './MoreActionsStep';\nimport DueDateChip from '../DueDateChip';\nimport StopwatchChip from '../StopwatchChip';\nimport EditDueDateStep from '../EditDueDateStep';\nimport EditStopwatchStep from '../EditStopwatchStep';\nimport ExpandableMarkdown from '../../common/ExpandableMarkdown';\nimport EditMarkdown from '../../common/EditMarkdown';\nimport ConfirmationStep from '../../common/ConfirmationStep';\nimport UserAvatar from '../../users/UserAvatar';\nimport BoardMembershipsStep from '../../board-memberships/BoardMembershipsStep';\nimport LabelChip from '../../labels/LabelChip';\nimport LabelsStep from '../../labels/LabelsStep';\nimport ListsStep from '../../lists/ListsStep';\nimport AddTaskListStep from '../../task-lists/AddTaskListStep';\nimport Attachments from '../../attachments/Attachments';\nimport AddAttachmentStep from '../../attachments/AddAttachmentStep';\nimport AddCustomFieldGroupStep from '../../custom-field-groups/AddCustomFieldGroupStep';\n\nimport styles from './ProjectContent.module.scss';\n\nconst ProjectContent = React.memo(() => {\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n  const selectPrevListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const card = useSelector(selectors.selectCurrentCard);\n  const board = useSelector(selectors.selectCurrentBoard);\n  const userIds = useSelector(selectors.selectUserIdsForCurrentCard);\n  const labelIds = useSelector(selectors.selectLabelIdsForCurrentCard);\n  const attachmentIds = useSelector(selectors.selectAttachmentIdsForCurrentCard);\n\n  const isJoined = useSelector(selectors.selectIsCurrentUserInCurrentCard);\n\n  const list = useSelector((state) => selectListById(state, card.listId));\n\n  // TODO: check availability?\n  const prevList = useSelector(\n    (state) => card.prevListId && selectPrevListById(state, card.prevListId),\n  );\n\n  const isInArchiveList = list.type === ListTypes.ARCHIVE;\n  const isInTrashList = list.type === ListTypes.TRASH;\n\n  const {\n    canEditType,\n    canEditName,\n    canEditDescription,\n    canEditDueDate,\n    canEditStopwatch,\n    canSubscribe,\n    canJoin,\n    canDuplicate,\n    canMove,\n    canRestore,\n    canArchive,\n    canDelete,\n    canUseLists,\n    canUseMembers,\n    canUseLabels,\n    canAddTaskList,\n    canAddAttachment,\n    canAddCustomFieldGroup,\n  } = useSelector((state) => {\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n\n    let isMember = false;\n    let isEditor = false;\n\n    if (boardMembership) {\n      isMember = true;\n      isEditor = boardMembership.role === BoardMembershipRoles.EDITOR;\n    }\n\n    if (isInArchiveList || isInTrashList) {\n      return {\n        canEditType: false,\n        canEditName: false,\n        canEditDescription: false,\n        canEditDueDate: false,\n        canEditStopwatch: false,\n        canSubscribe: isMember,\n        canJoin: false,\n        canDuplicate: false,\n        canMove: isEditor,\n        canRestore: isEditor,\n        canArchive: isEditor,\n        canDelete: isEditor,\n        canUseLists: isEditor,\n        canUseMembers: false,\n        canUseLabels: false,\n        canAddTaskList: false,\n        canAddAttachment: false,\n        canAddCustomFieldGroup: false,\n      };\n    }\n\n    return {\n      canEditType: isEditor,\n      canEditName: isEditor,\n      canEditDescription: isEditor,\n      canEditDueDate: isEditor,\n      canEditStopwatch: isEditor,\n      canSubscribe: isMember,\n      canJoin: isEditor,\n      canDuplicate: isEditor,\n      canMove: isEditor,\n      canRestore: null,\n      canArchive: isEditor,\n      canDelete: isEditor,\n      canUseLists: isEditor,\n      canUseMembers: isEditor,\n      canUseLabels: isEditor,\n      canAddTaskList: isEditor,\n      canAddAttachment: isEditor,\n      canAddCustomFieldGroup: isEditor,\n    };\n  }, shallowEqual);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [descriptionDraft, setDescriptionDraft] = useState(null);\n  const [isEditDescriptionOpened, setIsEditDescriptionOpened] = useState(false);\n  const [, , setIsClosableActive] = useContext(ClosableContext);\n\n  const handleListSelect = useCallback(\n    (listId) => {\n      dispatch(entryActions.moveCurrentCard(listId));\n    },\n    [dispatch],\n  );\n\n  const handleNameUpdate = useCallback(\n    (name) => {\n      dispatch(\n        entryActions.updateCurrentCard({\n          name,\n        }),\n      );\n    },\n    [dispatch],\n  );\n\n  const handleDescriptionUpdate = useCallback(\n    (description) => {\n      dispatch(\n        entryActions.updateCurrentCard({\n          description,\n        }),\n      );\n    },\n    [dispatch],\n  );\n\n  const handleDueCompletionChange = useCallback(() => {\n    dispatch(\n      entryActions.updateCurrentCard({\n        isDueCompleted: !card.isDueCompleted,\n      }),\n    );\n  }, [card.isDueCompleted, dispatch]);\n\n  const handleToggleStopwatchClick = useCallback(() => {\n    dispatch(\n      entryActions.updateCurrentCard({\n        stopwatch: card.stopwatch.startedAt\n          ? stopStopwatch(card.stopwatch)\n          : startStopwatch(card.stopwatch),\n      }),\n    );\n  }, [card.stopwatch, dispatch]);\n\n  const handleRestoreClick = useCallback(() => {\n    dispatch(entryActions.moveCurrentCard(card.prevListId, undefined, true));\n  }, [card.prevListId, dispatch]);\n\n  const handleArchiveConfirm = useCallback(() => {\n    dispatch(entryActions.moveCurrentCardToArchive());\n  }, [dispatch]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    if (isInTrashList) {\n      dispatch(entryActions.deleteCurrentCard());\n    } else {\n      dispatch(entryActions.moveCurrentCardToTrash());\n    }\n  }, [isInTrashList, dispatch]);\n\n  const handleUserSelect = useCallback(\n    (userId) => {\n      dispatch(entryActions.addUserToCurrentCard(userId));\n    },\n    [dispatch],\n  );\n\n  const handleUserDeselect = useCallback(\n    (userId) => {\n      dispatch(entryActions.removeUserFromCurrentCard(userId));\n    },\n    [dispatch],\n  );\n\n  const handleLabelSelect = useCallback(\n    (labelId) => {\n      dispatch(entryActions.addLabelToCurrentCard(labelId));\n    },\n    [dispatch],\n  );\n\n  const handleLabelDeselect = useCallback(\n    (labelId) => {\n      dispatch(entryActions.removeLabelFromCurrentCard(labelId));\n    },\n    [dispatch],\n  );\n\n  const handleCustomFieldGroupCreate = useCallback(\n    (data) => {\n      dispatch(entryActions.createCustomFieldGroupInCurrentCard(data));\n    },\n    [dispatch],\n  );\n\n  const handleToggleJointClick = useCallback(() => {\n    if (isJoined) {\n      dispatch(entryActions.removeCurrentUserFromCurrentCard());\n    } else {\n      dispatch(entryActions.addCurrentUserToCurrentCard());\n    }\n  }, [isJoined, dispatch]);\n\n  const handleToggleSubscriptionClick = useCallback(() => {\n    dispatch(\n      entryActions.updateCurrentCard({\n        isSubscribed: !card.isSubscribed,\n      }),\n    );\n  }, [card.isSubscribed, dispatch]);\n\n  const handleEditDescriptionClick = useCallback((event) => {\n    if (window.getSelection().toString() || isUsableMarkdownElement(event.target)) {\n      return;\n    }\n\n    setIsEditDescriptionOpened(true);\n  }, []);\n\n  const handleEditDescriptionClose = useCallback((nextDescriptionDraft) => {\n    setDescriptionDraft(nextDescriptionDraft);\n    setIsEditDescriptionOpened(false);\n  }, []);\n\n  useDidUpdate(() => {\n    if (!canEditDescription) {\n      setIsEditDescriptionOpened(false);\n    }\n  }, [canEditDescription]);\n\n  useDidUpdate(() => {\n    setIsClosableActive(isEditDescriptionOpened);\n  }, [isEditDescriptionOpened]);\n\n  const CreationDetailsPopup = usePopupInClosableContext(CreationDetailsStep);\n  const BoardMembershipsPopup = usePopupInClosableContext(BoardMembershipsStep);\n  const LabelsPopup = usePopupInClosableContext(LabelsStep);\n  const ListsPopup = usePopupInClosableContext(ListsStep);\n  const EditDueDatePopup = usePopupInClosableContext(EditDueDateStep);\n  const EditStopwatchPopup = usePopupInClosableContext(EditStopwatchStep);\n  const AddTaskListPopup = usePopupInClosableContext(AddTaskListStep);\n  const AddAttachmentPopup = usePopupInClosableContext(AddAttachmentStep);\n  const AddCustomFieldGroupPopup = usePopupInClosableContext(AddCustomFieldGroupStep);\n  const MoreActionsPopup = usePopupInClosableContext(MoreActionsStep);\n  const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);\n\n  return (\n    <Grid className={styles.wrapper}>\n      <Grid.Row className={styles.headerPadding}>\n        <Grid.Column width={16} className={styles.headerPadding}>\n          <div className={styles.headerWrapper}>\n            <Icon name={CardTypeIcons[CardTypes.PROJECT]} className={styles.moduleIcon} />\n            <div className={styles.headerTitleWrapper}>\n              {canEditName ? (\n                <NameField defaultValue={card.name} onUpdate={handleNameUpdate} />\n              ) : (\n                <div className={styles.headerTitle}>{card.name}</div>\n              )}\n            </div>\n          </div>\n        </Grid.Column>\n      </Grid.Row>\n      <Grid.Row className={styles.modalPadding}>\n        <Grid.Column width={12} className={styles.contentPadding}>\n          {(card.dueDate ||\n            card.stopwatch ||\n            board.alwaysDisplayCardCreator ||\n            userIds.length > 0 ||\n            labelIds.length > 0) && (\n            <div className={styles.moduleWrapper}>\n              {board.alwaysDisplayCardCreator && (\n                <div className={styles.attachments}>\n                  <div className={styles.text}>\n                    {t('common.creator', {\n                      context: 'title',\n                    })}\n                  </div>\n                  <span className={styles.attachment}>\n                    <CreationDetailsPopup userId={card.creatorUserId}>\n                      <UserAvatar withCreatorIndicator id={card.creatorUserId} />\n                    </CreationDetailsPopup>\n                  </span>\n                </div>\n              )}\n              {userIds.length > 0 && (\n                <div className={styles.attachments}>\n                  <div className={styles.text}>\n                    {t('common.members', {\n                      context: 'title',\n                    })}\n                  </div>\n                  {userIds.map((userId) => (\n                    <span key={userId} className={styles.attachment}>\n                      {canUseMembers ? (\n                        <BoardMembershipsPopup\n                          currentUserIds={userIds}\n                          onUserSelect={handleUserSelect}\n                          onUserDeselect={handleUserDeselect}\n                        >\n                          <UserAvatar id={userId} />\n                        </BoardMembershipsPopup>\n                      ) : (\n                        <UserAvatar id={userId} />\n                      )}\n                    </span>\n                  ))}\n                  {canUseMembers && (\n                    <BoardMembershipsPopup\n                      currentUserIds={userIds}\n                      onUserSelect={handleUserSelect}\n                      onUserDeselect={handleUserDeselect}\n                    >\n                      <button\n                        type=\"button\"\n                        className={classNames(styles.attachment, styles.dueDate)}\n                      >\n                        <Icon name=\"add\" size=\"small\" className={styles.addAttachment} />\n                      </button>\n                    </BoardMembershipsPopup>\n                  )}\n                </div>\n              )}\n              {labelIds.length > 0 && (\n                <div className={styles.attachments}>\n                  <div className={styles.text}>\n                    {t('common.labels', {\n                      context: 'title',\n                    })}\n                  </div>\n                  {labelIds.map((labelId) => (\n                    <span key={labelId} className={styles.attachment}>\n                      {canUseLabels ? (\n                        <LabelsPopup\n                          currentIds={labelIds}\n                          cardId={card.id}\n                          onSelect={handleLabelSelect}\n                          onDeselect={handleLabelDeselect}\n                        >\n                          <LabelChip id={labelId} />\n                        </LabelsPopup>\n                      ) : (\n                        <LabelChip id={labelId} />\n                      )}\n                    </span>\n                  ))}\n                  {canUseLabels && (\n                    <LabelsPopup\n                      currentIds={labelIds}\n                      cardId={card.id}\n                      onSelect={handleLabelSelect}\n                      onDeselect={handleLabelDeselect}\n                    >\n                      <button\n                        type=\"button\"\n                        className={classNames(styles.attachment, styles.dueDate)}\n                      >\n                        <Icon name=\"add\" size=\"small\" className={styles.addAttachment} />\n                      </button>\n                    </LabelsPopup>\n                  )}\n                </div>\n              )}\n              {card.dueDate && (\n                <div className={styles.attachments}>\n                  <div className={styles.text}>\n                    {t('common.dueDate', {\n                      context: 'title',\n                    })}\n                  </div>\n                  <span className={classNames(styles.attachment, styles.attachmentDueDate)}>\n                    {canEditDueDate ? (\n                      <>\n                        {!card.isClosed && (\n                          <Checkbox\n                            checked={card.isDueCompleted}\n                            disabled={!canEditDueDate}\n                            onChange={handleDueCompletionChange}\n                          />\n                        )}\n                        <EditDueDatePopup cardId={card.id}>\n                          <DueDateChip\n                            withStatusIcon\n                            value={card.dueDate}\n                            isCompleted={card.isDueCompleted}\n                            withStatus={!card.isClosed}\n                          />\n                        </EditDueDatePopup>\n                      </>\n                    ) : (\n                      <DueDateChip\n                        withStatusIcon\n                        value={card.dueDate}\n                        isCompleted={card.isDueCompleted}\n                        withStatus={!card.isClosed}\n                      />\n                    )}\n                  </span>\n                </div>\n              )}\n              {card.stopwatch && (\n                <div className={styles.attachments}>\n                  <div className={styles.text}>\n                    {t('common.stopwatch', {\n                      context: 'title',\n                    })}\n                  </div>\n                  <span className={styles.attachment}>\n                    {canEditStopwatch ? (\n                      <EditStopwatchPopup cardId={card.id}>\n                        <StopwatchChip value={card.stopwatch} />\n                      </EditStopwatchPopup>\n                    ) : (\n                      <StopwatchChip value={card.stopwatch} />\n                    )}\n                  </span>\n                  {canEditStopwatch && (\n                    <button\n                      type=\"button\"\n                      className={classNames(styles.attachment, styles.dueDate)}\n                      onClick={handleToggleStopwatchClick}\n                    >\n                      <Icon\n                        name={card.stopwatch.startedAt ? 'pause' : 'play'}\n                        size=\"small\"\n                        className={styles.addAttachment}\n                      />\n                    </button>\n                  )}\n                </div>\n              )}\n            </div>\n          )}\n          {(card.description || canEditDescription) && (\n            <div className={classNames(styles.contentModule, styles.contentModuleDescription)}>\n              <div className={styles.moduleWrapper}>\n                <Icon name=\"align left\" className={styles.moduleIcon} />\n                <div className={styles.moduleHeader}>\n                  {t('common.description')}\n                  {canEditDescription && !isEditDescriptionOpened && descriptionDraft && (\n                    <span className={styles.draftChip}>{t('common.unsavedChanges')}</span>\n                  )}\n                </div>\n                {canEditDescription && (\n                  <>\n                    {isEditDescriptionOpened && (\n                      <EditMarkdown\n                        defaultValue={card.description}\n                        draftValue={descriptionDraft}\n                        placeholder=\"common.enterDescription\"\n                        onUpdate={handleDescriptionUpdate}\n                        onClose={handleEditDescriptionClose}\n                      />\n                    )}\n                    {!isEditDescriptionOpened &&\n                      (card.description ? (\n                        /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                                    jsx-a11y/no-static-element-interactions */\n                        <div className={styles.cursorPointer} onClick={handleEditDescriptionClick}>\n                          <Button className={styles.editButton}>\n                            <Icon fitted name=\"pencil\" size=\"small\" />\n                          </Button>\n                          <ExpandableMarkdown>{card.description}</ExpandableMarkdown>\n                        </div>\n                      ) : (\n                        <button\n                          type=\"button\"\n                          className={styles.descriptionButton}\n                          onClick={handleEditDescriptionClick}\n                        >\n                          <span className={styles.descriptionButtonText}>\n                            {t('action.addMoreDetailedDescription')}\n                          </span>\n                        </button>\n                      ))}\n                  </>\n                )}\n                {!canEditDescription && <ExpandableMarkdown>{card.description}</ExpandableMarkdown>}\n              </div>\n            </div>\n          )}\n          <CustomFieldGroups />\n          <TaskLists />\n          {attachmentIds.length > 0 && (\n            <div className={styles.contentModule}>\n              <div className={styles.moduleWrapper}>\n                <Icon name=\"attach\" className={styles.moduleIcon} />\n                <div className={styles.moduleHeader}>{t('common.attachments')}</div>\n                <Attachments />\n              </div>\n            </div>\n          )}\n          <div className={styles.contentModule}>\n            <div className={styles.moduleWrapper}>\n              <Icon name=\"list ul\" className={styles.moduleIcon} />\n              <Communication />\n            </div>\n          </div>\n        </Grid.Column>\n        <Grid.Column width={4} className={styles.sidebarPadding}>\n          <div className={styles.sticky}>\n            <div className={styles.actions}>\n              <div className={classNames(styles.attachments, styles.attachmentsList)}>\n                <div className={classNames(styles.text, styles.textList)}>{t('common.list')}</div>\n                {canUseLists ? (\n                  <ListsPopup currentId={list.id} onSelect={handleListSelect}>\n                    <button type=\"button\" className={styles.listButton}>\n                      <span className={classNames(styles.list, styles.listHoverable)}>\n                        <Icon name=\"columns\" size=\"small\" className={styles.listIcon} />\n                        <span className={styles.hidable}>\n                          {list.name || t(`common.${list.type}`)}\n                        </span>\n                      </span>\n                    </button>\n                  </ListsPopup>\n                ) : (\n                  <span className={styles.list}>\n                    <Icon name=\"columns\" size=\"small\" className={styles.listIcon} />\n                    <span className={styles.hidable}>{list.name || t(`common.${list.type}`)}</span>\n                  </span>\n                )}\n              </div>\n            </div>\n            {(canEditDueDate ||\n              canEditStopwatch ||\n              canUseMembers ||\n              canUseLabels ||\n              canAddTaskList ||\n              canAddAttachment ||\n              canAddCustomFieldGroup) && (\n              <div className={styles.actions}>\n                <span className={styles.actionsTitle}>{t('action.addToCard')}</span>\n                {canUseMembers && (\n                  <BoardMembershipsPopup\n                    currentUserIds={userIds}\n                    onUserSelect={handleUserSelect}\n                    onUserDeselect={handleUserDeselect}\n                  >\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"user outline\" className={styles.actionIcon} />\n                      {t('common.members')}\n                    </Button>\n                  </BoardMembershipsPopup>\n                )}\n                {canUseLabels && (\n                  <LabelsPopup\n                    currentIds={labelIds}\n                    cardId={card.id}\n                    onSelect={handleLabelSelect}\n                    onDeselect={handleLabelDeselect}\n                  >\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"bookmark outline\" className={styles.actionIcon} />\n                      {t('common.labels')}\n                    </Button>\n                  </LabelsPopup>\n                )}\n                {canEditDueDate && (\n                  <EditDueDatePopup cardId={card.id}>\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"calendar check outline\" className={styles.actionIcon} />\n                      {t('common.dueDate', {\n                        context: 'title',\n                      })}\n                    </Button>\n                  </EditDueDatePopup>\n                )}\n                {canEditStopwatch && (\n                  <EditStopwatchPopup cardId={card.id}>\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"clock outline\" className={styles.actionIcon} />\n                      {t('common.stopwatch')}\n                    </Button>\n                  </EditStopwatchPopup>\n                )}\n                {canAddTaskList && (\n                  <AddTaskListPopup>\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"check square outline\" className={styles.actionIcon} />\n                      {t('common.taskList', {\n                        context: 'title',\n                      })}\n                    </Button>\n                  </AddTaskListPopup>\n                )}\n                {canAddAttachment && (\n                  <AddAttachmentPopup>\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"attach\" className={styles.actionIcon} />\n                      {t('common.attachment')}\n                    </Button>\n                  </AddAttachmentPopup>\n                )}\n                {canAddCustomFieldGroup && (\n                  <AddCustomFieldGroupPopup onCreate={handleCustomFieldGroupCreate}>\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"sticky note outline\" className={styles.actionIcon} />\n                      {t('common.customField', {\n                        context: 'title',\n                      })}\n                    </Button>\n                  </AddCustomFieldGroupPopup>\n                )}\n              </div>\n            )}\n            {((!board.limitCardTypesToDefaultOne && canEditType) ||\n              canSubscribe ||\n              canJoin ||\n              canDuplicate ||\n              canMove ||\n              (canRestore && (isInArchiveList || isInTrashList)) ||\n              (canArchive && !isInArchiveList) ||\n              canDelete) && (\n              <div className={styles.actions}>\n                <span className={styles.actionsTitle}>{t('common.actions')}</span>\n                {canJoin && (\n                  <Button\n                    fluid\n                    className={classNames(styles.actionButton, styles.hidable)}\n                    onClick={handleToggleJointClick}\n                  >\n                    <Icon\n                      name={isJoined ? 'flag outline' : 'flag checkered'}\n                      className={styles.actionIcon}\n                    />\n                    {isJoined ? t('action.leave') : t('action.join')}\n                  </Button>\n                )}\n                {canSubscribe && (\n                  <Button\n                    fluid\n                    disabled={board.isSubscribed}\n                    className={classNames(styles.actionButton, styles.hidable)}\n                    onClick={handleToggleSubscriptionClick}\n                  >\n                    {board.isSubscribed ? (\n                      <>\n                        <Icon name=\"bell slash outline\" className={styles.actionIcon} />\n                        {t('common.boardSubscribed')}\n                      </>\n                    ) : (\n                      <>\n                        <Icon\n                          name={card.isSubscribed ? 'bell slash outline' : 'bell outline'}\n                          className={styles.actionIcon}\n                        />\n                        {card.isSubscribed ? t('action.unsubscribe') : t('action.subscribe')}\n                      </>\n                    )}\n                  </Button>\n                )}\n                {canRestore && (isInArchiveList || isInTrashList) && (\n                  <Button\n                    fluid\n                    disabled={!prevList}\n                    className={classNames(styles.actionButton, styles.hidable)}\n                    onClick={handleRestoreClick}\n                  >\n                    <Icon name=\"undo alternate\" className={styles.actionIcon} />\n                    {prevList\n                      ? t('action.restoreToList', {\n                          list: prevList.name || t(`common.${prevList.type}`),\n                        })\n                      : t('common.selectListToRestoreThisCard')}\n                  </Button>\n                )}\n                {canArchive && !isInArchiveList && (\n                  <ConfirmationPopup\n                    title=\"common.archiveCard\"\n                    content=\"common.areYouSureYouWantToArchiveThisCard\"\n                    buttonContent=\"action.archiveCard\"\n                    onConfirm={handleArchiveConfirm}\n                  >\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"folder open outline\" className={styles.actionIcon} />\n                      {t('action.archive')}\n                    </Button>\n                  </ConfirmationPopup>\n                )}\n                {canDelete && (\n                  <ConfirmationPopup\n                    title={isInTrashList ? 'common.deleteCardForever' : 'common.deleteCard'}\n                    content={\n                      isInTrashList\n                        ? 'common.areYouSureYouWantToDeleteThisCardForever'\n                        : 'common.areYouSureYouWantToDeleteThisCard'\n                    }\n                    buttonContent={isInTrashList ? 'action.deleteCardForever' : 'action.deleteCard'}\n                    onConfirm={handleDeleteConfirm}\n                  >\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"trash alternate outline\" className={styles.actionIcon} />\n                      {isInTrashList\n                        ? t('action.deleteForever', {\n                            context: 'title',\n                          })\n                        : t('action.delete')}\n                    </Button>\n                  </ConfirmationPopup>\n                )}\n                {((!board.limitCardTypesToDefaultOne && canEditType) ||\n                  canDuplicate ||\n                  canMove) && (\n                  <MoreActionsPopup>\n                    <Button fluid className={classNames(styles.moreActionsButton, styles.hidable)}>\n                      <Icon name=\"ellipsis horizontal\" className={styles.moreActionsButtonIcon} />\n                      {t('common.moreActions')}\n                    </Button>\n                  </MoreActionsPopup>\n                )}\n              </div>\n            )}\n          </div>\n        </Grid.Column>\n      </Grid.Row>\n    </Grid>\n  );\n});\n\nexport default ProjectContent;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/ProjectContent.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actionButton {\n    background: #ebeef0;\n    box-shadow: 0 1px 0 0 rgba(9, 30, 66, 0.13);\n    color: #444;\n    margin-top: 8px;\n    padding: 6px 8px 6px 18px;\n    text-align: left;\n    transition: background 85ms ease;\n\n    &:hover {\n      background: #dfe3e6;\n      box-shadow: 0 1px 0 0 rgba(9, 30, 66, 0.25);\n      color: #4c4c4c;\n    }\n  }\n\n  .actionIcon {\n    color: #17394d;\n    margin-right: 8px;\n  }\n\n  .actions {\n    margin-bottom: 24px;\n\n    @media only screen and (width < 768px) {\n      flex: 1;\n\n      &:first-child {\n        flex: 0 0 100%;\n      }\n    }\n  }\n\n  .actionsTitle {\n    color: #8c8c8c;\n    font-size: 12px;\n    font-weight: normal;\n    letter-spacing: 0.04em;\n    margin-top: 16px;\n    text-transform: uppercase;\n    line-height: 20px;\n    margin-bottom: -4px;\n  }\n\n  .addAttachment {\n    margin: 0 -4.3px;\n    text-decoration: none;\n  }\n\n  .attachment {\n    display: inline-block;\n    margin: 0 4px 4px 0;\n    max-width: 100%;\n    vertical-align: top;\n  }\n\n  .attachmentDueDate {\n    align-items: center;\n    display: flex;\n    gap: 4px;\n  }\n\n  .attachments {\n    display: inline-block;\n    margin: 0 8px 8px 0;\n    max-width: 100%;\n    vertical-align: top;\n  }\n\n  .attachmentsList {\n    width: 100%;\n  }\n\n  .contentModule {\n    margin-bottom: 24px;\n  }\n\n  .contentModuleDescription:hover {\n    .editButton {\n      opacity: 1;\n    }\n  }\n\n  .contentPadding {\n    padding: 8px 24px 0 16px;\n\n    @media only screen and (width < 768px) {\n      padding-right: 16px;\n      width: 100% !important;\n    }\n  }\n\n  .cursorPointer {\n    cursor: pointer;\n  }\n\n  .dueDate {\n    background: rgba(9, 30, 66, 0.04);\n    border: none;\n    border-radius: 3px;\n    color: #6b808c;\n    cursor: pointer;\n    line-height: 20px;\n    outline: none;\n    padding: 6px 14px;\n    text-align: left;\n    text-decoration: underline;\n    transition: background 0.3s ease;\n    vertical-align: top;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #17394d;\n    }\n  }\n\n  .descriptionButton {\n    background: rgba(9, 30, 66, 0.04);\n    border: none;\n    border-radius: 3px;\n    display: block;\n    color: #6b808c;\n    cursor: pointer;\n    min-height: 54px;\n    outline: none;\n    padding: 8px 12px;\n    position: relative;\n    text-align: left;\n    text-decoration: none;\n    width: 100%;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .descriptionButtonText {\n    position: absolute;\n    top: 12px;\n  }\n\n  .draftChip {\n    background: #f1eee2;\n    border-radius: 3px;\n    color: rgba(0, 0, 0, 0.87);\n    font-size: 14px;\n    font-weight: normal;\n    margin-left: 8px;\n    padding: 2px 8px;\n  }\n\n  .editButton {\n    background: transparent;\n    box-shadow: none;\n    line-height: 28px;\n    margin: 0;\n    min-height: auto;\n    opacity: 0;\n    padding: 0;\n    position: absolute;\n    right: 0;\n    top: 4px;\n    width: 28px;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n    }\n  }\n\n  .headerPadding {\n    padding: 0;\n  }\n\n  .headerTitle {\n    color: #17394d;\n    font-size: 20px;\n    font-weight: bold;\n    line-height: 24px;\n    word-wrap: break-word;\n  }\n\n  .headerTitleWrapper {\n    margin: 4px 0;\n    padding: 6px 0 0;\n  }\n\n  .headerWrapper {\n    margin: 12px 48px 12px 56px;\n    position: relative;\n  }\n\n  .hidable {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .list {\n    align-items: center;\n    background: rgba(9, 30, 66, 0.04);\n    border: none;\n    border-radius: 3px;\n    color: #6b808c;\n    display: flex;\n    line-height: 20px;\n    outline: none;\n    padding: 6px 12px;\n    transition: background 0.3s ease;\n  }\n\n  .listButton {\n    background: transparent;\n    border: none;\n    cursor: pointer;\n    display: inline-block;\n    max-width: 100%;\n    outline: none;\n    padding: 0;\n    width: 100%;\n  }\n\n  .listHoverable:hover {\n    background: rgba(9, 30, 66, 0.08);\n    color: #17394d;\n  }\n\n  .listIcon {\n    margin: 0 8px 0 0;\n  }\n\n  .modalPadding {\n    padding: 0px;\n  }\n\n  .moduleHeader {\n    color: #17394d;\n    font-size: 16px;\n    font-weight: bold;\n    line-height: 1.5;\n    margin: 0 0 4px;\n    padding: 6px 0;\n    word-wrap: break-word;\n  }\n\n  .moduleIcon {\n    color: #17394d;\n    font-size: 17px;\n    height: 32px;\n    left: -40px;\n    line-height: 32px;\n    margin-right: 0;\n    position: absolute;\n    top: 2px;\n    width: 32px;\n  }\n\n  .moduleWrapper {\n    margin: 0 0 0 40px;\n    position: relative;\n  }\n\n  .moreActionsButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 8px 6px 18px;\n    text-align: left;\n    text-decoration: underline;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .moreActionsButtonIcon {\n    margin-right: 8px;\n    text-decoration: none;\n  }\n\n  .sidebarPadding {\n    padding: 8px 16px 8px 8px;\n\n    @media only screen and (width < 768px) {\n      padding-left: 16px;\n      width: 100% !important;\n    }\n  }\n\n  .sticky {\n    position: sticky;\n    top: 0;\n\n    @media only screen and (width < 768px) {\n      display: flex;\n      flex-flow: row wrap;\n      gap: 10px;\n    }\n  }\n\n  .text {\n    color: #6b808c;\n    font-size: 12px;\n    font-weight: bold;\n    letter-spacing: 0.3px;\n    line-height: 20px;\n    margin: 0 8px 4px 0;\n    text-transform: uppercase;\n  }\n\n  .textList {\n    font-weight: normal;\n  }\n\n  .wrapper {\n    background: #f5f6f7;\n    border-radius: 4px;\n    margin: 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/StoryContent.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useContext, useMemo, useState } from 'react';\nimport classNames from 'classnames';\nimport { shallowEqual, useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Gallery, Item as GalleryItem } from 'react-photoswipe-gallery';\nimport { Button, Grid, Icon } from 'semantic-ui-react';\nimport { useDidUpdate } from '../../../lib/hooks';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { usePopupInClosableContext } from '../../../hooks';\nimport { isUsableMarkdownElement } from '../../../utils/element-helpers';\nimport { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums';\nimport { CardTypeIcons } from '../../../constants/Icons';\nimport { ClosableContext } from '../../../contexts';\nimport NameField from './NameField';\nimport Thumbnail from './Thumbnail';\nimport CustomFieldGroups from './CustomFieldGroups';\nimport Communication from './Communication';\nimport CreationDetailsStep from './CreationDetailsStep';\nimport MoreActionsStep from './MoreActionsStep';\nimport Markdown from '../../common/Markdown';\nimport EditMarkdown from '../../common/EditMarkdown';\nimport ConfirmationStep from '../../common/ConfirmationStep';\nimport UserAvatar from '../../users/UserAvatar';\nimport BoardMembershipsStep from '../../board-memberships/BoardMembershipsStep';\nimport LabelChip from '../../labels/LabelChip';\nimport LabelsStep from '../../labels/LabelsStep';\nimport ListsStep from '../../lists/ListsStep';\nimport Attachments from '../../attachments/Attachments';\nimport AddAttachmentStep from '../../attachments/AddAttachmentStep';\nimport AddCustomFieldGroupStep from '../../custom-field-groups/AddCustomFieldGroupStep';\n\nimport styles from './StoryContent.module.scss';\n\nconst StoryContent = React.memo(() => {\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n  const selectPrevListById = useMemo(() => selectors.makeSelectListById(), []);\n  const selectAttachmentById = useMemo(() => selectors.makeSelectAttachmentById(), []);\n\n  const card = useSelector(selectors.selectCurrentCard);\n  const board = useSelector(selectors.selectCurrentBoard);\n  const userIds = useSelector(selectors.selectUserIdsForCurrentCard);\n  const labelIds = useSelector(selectors.selectLabelIdsForCurrentCard);\n  const attachmentIds = useSelector(selectors.selectAttachmentIdsForCurrentCard);\n\n  const imageAttachmentIdsExceptCover = useSelector(\n    selectors.selectImageAttachmentIdsExceptCoverForCurrentCard,\n  );\n\n  const isJoined = useSelector(selectors.selectIsCurrentUserInCurrentCard);\n\n  const list = useSelector((state) => selectListById(state, card.listId));\n\n  // TODO: check availability?\n  const prevList = useSelector(\n    (state) => card.prevListId && selectPrevListById(state, card.prevListId),\n  );\n\n  const coverAttachment = useSelector((state) =>\n    selectAttachmentById(state, card.coverAttachmentId),\n  );\n\n  const isInArchiveList = list.type === ListTypes.ARCHIVE;\n  const isInTrashList = list.type === ListTypes.TRASH;\n\n  const {\n    canEditType,\n    canEditName,\n    canEditDescription,\n    canSubscribe,\n    canJoin,\n    canDuplicate,\n    canMove,\n    canRestore,\n    canArchive,\n    canDelete,\n    canUseLists,\n    canUseMembers,\n    canUseLabels,\n    canAddAttachment,\n    canAddCustomFieldGroup,\n  } = useSelector((state) => {\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n\n    let isMember = false;\n    let isEditor = false;\n\n    if (boardMembership) {\n      isMember = true;\n      isEditor = boardMembership.role === BoardMembershipRoles.EDITOR;\n    }\n\n    if (isInArchiveList || isInTrashList) {\n      return {\n        canEditType: false,\n        canEditName: false,\n        canEditDescription: false,\n        canSubscribe: isMember,\n        canJoin: false,\n        canDuplicate: false,\n        canMove: isEditor,\n        canRestore: isEditor,\n        canArchive: isEditor,\n        canDelete: isEditor,\n        canUseLists: isEditor,\n        canUseMembers: false,\n        canUseLabels: false,\n        canAddAttachment: false,\n        canAddCustomFieldGroup: false,\n      };\n    }\n\n    return {\n      canEditType: isEditor,\n      canEditName: isEditor,\n      canEditDescription: isEditor,\n      canSubscribe: isMember,\n      canJoin: isEditor,\n      canDuplicate: isEditor,\n      canMove: isEditor,\n      canRestore: null,\n      canArchive: isEditor,\n      canDelete: isEditor,\n      canUseLists: isEditor,\n      canUseMembers: isEditor,\n      canUseLabels: isEditor,\n      canAddAttachment: isEditor,\n      canAddCustomFieldGroup: isEditor,\n    };\n  }, shallowEqual);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [descriptionDraft, setDescriptionDraft] = useState(null);\n  const [isEditDescriptionOpened, setIsEditDescriptionOpened] = useState(false);\n  const [activateClosable, deactivateClosable, setIsClosableActive] = useContext(ClosableContext);\n\n  const handleListSelect = useCallback(\n    (listId) => {\n      dispatch(entryActions.moveCurrentCard(listId));\n    },\n    [dispatch],\n  );\n\n  const handleNameUpdate = useCallback(\n    (name) => {\n      dispatch(\n        entryActions.updateCurrentCard({\n          name,\n        }),\n      );\n    },\n    [dispatch],\n  );\n\n  const handleDescriptionUpdate = useCallback(\n    (description) => {\n      dispatch(\n        entryActions.updateCurrentCard({\n          description,\n        }),\n      );\n    },\n    [dispatch],\n  );\n\n  const handleRestoreClick = useCallback(() => {\n    dispatch(entryActions.moveCurrentCard(card.prevListId, undefined, true));\n  }, [card.prevListId, dispatch]);\n\n  const handleArchiveConfirm = useCallback(() => {\n    dispatch(entryActions.moveCurrentCardToArchive());\n  }, [dispatch]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    if (isInTrashList) {\n      dispatch(entryActions.deleteCurrentCard());\n    } else {\n      dispatch(entryActions.moveCurrentCardToTrash());\n    }\n  }, [isInTrashList, dispatch]);\n\n  const handleUserSelect = useCallback(\n    (userId) => {\n      dispatch(entryActions.addUserToCurrentCard(userId));\n    },\n    [dispatch],\n  );\n\n  const handleUserDeselect = useCallback(\n    (userId) => {\n      dispatch(entryActions.removeUserFromCurrentCard(userId));\n    },\n    [dispatch],\n  );\n\n  const handleLabelSelect = useCallback(\n    (labelId) => {\n      dispatch(entryActions.addLabelToCurrentCard(labelId));\n    },\n    [dispatch],\n  );\n\n  const handleLabelDeselect = useCallback(\n    (labelId) => {\n      dispatch(entryActions.removeLabelFromCurrentCard(labelId));\n    },\n    [dispatch],\n  );\n\n  const handleCustomFieldGroupCreate = useCallback(\n    (data) => {\n      dispatch(entryActions.createCustomFieldGroupInCurrentCard(data));\n    },\n    [dispatch],\n  );\n\n  const handleToggleJointClick = useCallback(() => {\n    if (isJoined) {\n      dispatch(entryActions.removeCurrentUserFromCurrentCard());\n    } else {\n      dispatch(entryActions.addCurrentUserToCurrentCard());\n    }\n  }, [isJoined, dispatch]);\n\n  const handleToggleSubscriptionClick = useCallback(() => {\n    dispatch(\n      entryActions.updateCurrentCard({\n        isSubscribed: !card.isSubscribed,\n      }),\n    );\n  }, [card.isSubscribed, dispatch]);\n\n  const handleEditDescriptionClick = useCallback((event) => {\n    if (window.getSelection().toString() || isUsableMarkdownElement(event.target)) {\n      return;\n    }\n\n    setIsEditDescriptionOpened(true);\n  }, []);\n\n  const handleEditDescriptionClose = useCallback((nextDescriptionDraft) => {\n    setDescriptionDraft(nextDescriptionDraft);\n    setIsEditDescriptionOpened(false);\n  }, []);\n\n  const handleBeforeGalleryOpen = useCallback(\n    (gallery) => {\n      activateClosable();\n\n      gallery.on('destroy', () => {\n        deactivateClosable();\n      });\n    },\n    [activateClosable, deactivateClosable],\n  );\n\n  useDidUpdate(() => {\n    if (!canEditDescription) {\n      setIsEditDescriptionOpened(false);\n    }\n  }, [canEditDescription]);\n\n  useDidUpdate(() => {\n    setIsClosableActive(isEditDescriptionOpened);\n  }, [isEditDescriptionOpened]);\n\n  const CreationDetailsPopup = usePopupInClosableContext(CreationDetailsStep);\n  const BoardMembershipsPopup = usePopupInClosableContext(BoardMembershipsStep);\n  const LabelsPopup = usePopupInClosableContext(LabelsStep);\n  const ListsPopup = usePopupInClosableContext(ListsStep);\n  const AddAttachmentPopup = usePopupInClosableContext(AddAttachmentStep);\n  const AddCustomFieldGroupPopup = usePopupInClosableContext(AddCustomFieldGroupStep);\n  const MoreActionsPopup = usePopupInClosableContext(MoreActionsStep);\n  const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);\n\n  return (\n    <Grid className={styles.wrapper}>\n      <Grid.Row className={styles.headerPadding}>\n        <Grid.Column width={16} className={styles.headerPadding}>\n          <div className={styles.headerWrapper}>\n            <Icon\n              name={CardTypeIcons[CardTypes.STORY]}\n              className={classNames(styles.moduleIcon, styles.moduleIconTitle)}\n            />\n            <div className={styles.headerTitleWrapper}>\n              {canEditName ? (\n                <NameField defaultValue={card.name} size=\"large\" onUpdate={handleNameUpdate} />\n              ) : (\n                <div className={styles.headerTitle}>{card.name}</div>\n              )}\n            </div>\n          </div>\n        </Grid.Column>\n      </Grid.Row>\n      <Grid.Row className={styles.modalPadding}>\n        <Grid.Column width={12} className={styles.contentPadding}>\n          <Gallery\n            withCaption\n            withDownloadButton\n            options={{\n              wheelToZoom: true,\n              showHideAnimationType: 'none',\n              closeTitle: '',\n              zoomTitle: '',\n              arrowPrevTitle: '',\n              arrowNextTitle: '',\n              errorMsg: '',\n              paddingFn: (viewportSize) => {\n                const paddingX = viewportSize.x / 20;\n                const paddingY = viewportSize.y / 20;\n\n                return {\n                  top: paddingX,\n                  bottom: paddingX,\n                  left: paddingY,\n                  right: paddingY,\n                };\n              },\n            }}\n            onBeforeOpen={handleBeforeGalleryOpen}\n          >\n            {(board.alwaysDisplayCardCreator || labelIds.length > 0 || coverAttachment) && (\n              <div className={classNames(styles.moduleWrapper, styles.moduleWrapperAttachments)}>\n                {coverAttachment && (\n                  <div className={styles.coverWrapper}>\n                    <GalleryItem\n                      {...coverAttachment.data.image} // eslint-disable-line react/jsx-props-no-spreading\n                      original={coverAttachment.data.url}\n                      caption={coverAttachment.name}\n                    >\n                      {({ ref, open }) => (\n                        /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                                    jsx-a11y/no-noninteractive-element-interactions */\n                        <img\n                          ref={ref}\n                          src={coverAttachment.data.thumbnailUrls.outside720}\n                          alt={coverAttachment.name}\n                          className={styles.cover}\n                          onClick={open}\n                        />\n                      )}\n                    </GalleryItem>\n                  </div>\n                )}\n                {board.alwaysDisplayCardCreator && (\n                  <div className={styles.attachments}>\n                    <span className={styles.attachment}>\n                      <CreationDetailsPopup userId={card.creatorUserId}>\n                        <UserAvatar withCreatorIndicator id={card.creatorUserId} size=\"tiny\" />\n                      </CreationDetailsPopup>\n                    </span>\n                  </div>\n                )}\n                {labelIds.length > 0 && (\n                  <div className={styles.attachments}>\n                    {labelIds.map((labelId) => (\n                      <span key={labelId} className={styles.attachment}>\n                        {canUseLabels ? (\n                          <LabelsPopup\n                            currentIds={labelIds}\n                            cardId={card.id}\n                            onSelect={handleLabelSelect}\n                            onDeselect={handleLabelDeselect}\n                          >\n                            <LabelChip id={labelId} size=\"small\" />\n                          </LabelsPopup>\n                        ) : (\n                          <LabelChip id={labelId} size=\"small\" />\n                        )}\n                      </span>\n                    ))}\n                    {canUseLabels && (\n                      <LabelsPopup\n                        currentIds={labelIds}\n                        cardId={card.id}\n                        onSelect={handleLabelSelect}\n                        onDeselect={handleLabelDeselect}\n                      >\n                        <button\n                          type=\"button\"\n                          className={classNames(styles.attachment, styles.dueDate)}\n                        >\n                          <Icon name=\"add\" size=\"small\" className={styles.addAttachment} />\n                        </button>\n                      </LabelsPopup>\n                    )}\n                  </div>\n                )}\n              </div>\n            )}\n            {(card.description || canEditDescription) && (\n              <div className={classNames(styles.contentModule, styles.contentModuleDescription)}>\n                <div className={styles.moduleWrapper}>\n                  {canEditDescription &&\n                    (isEditDescriptionOpened ? (\n                      <EditMarkdown\n                        defaultValue={card.description}\n                        draftValue={descriptionDraft}\n                        placeholder=\"common.enterDescription\"\n                        onUpdate={handleDescriptionUpdate}\n                        onClose={handleEditDescriptionClose}\n                      />\n                    ) : (\n                      <>\n                        {descriptionDraft && (\n                          <span className={styles.draftChip}>{t('common.unsavedChanges')}</span>\n                        )}\n                        {card.description ? (\n                          /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                                      jsx-a11y/no-static-element-interactions */\n                          <div\n                            className={classNames(styles.descriptionText, styles.cursorPointer)}\n                            onClick={handleEditDescriptionClick}\n                          >\n                            <Button className={styles.editButton}>\n                              <Icon fitted name=\"pencil\" size=\"small\" />\n                            </Button>\n                            <Markdown>{card.description}</Markdown>\n                          </div>\n                        ) : (\n                          <button\n                            type=\"button\"\n                            className={styles.descriptionButton}\n                            onClick={handleEditDescriptionClick}\n                          >\n                            <span className={styles.descriptionButtonText}>\n                              {t('action.addMoreDetailedDescription')}\n                            </span>\n                          </button>\n                        )}\n                      </>\n                    ))}\n                  {!canEditDescription && (\n                    <div className={styles.descriptionText}>\n                      <Markdown>{card.description}</Markdown>\n                    </div>\n                  )}\n                  {imageAttachmentIdsExceptCover.length > 0 && (\n                    <div className={styles.thumbnails}>\n                      {imageAttachmentIdsExceptCover.map((attachmentId) => (\n                        <Thumbnail key={attachmentId} attachmentId={attachmentId} />\n                      ))}\n                    </div>\n                  )}\n                </div>\n              </div>\n            )}\n          </Gallery>\n          <CustomFieldGroups />\n          {attachmentIds.length > 0 && (\n            <div className={styles.contentModule}>\n              <div className={styles.moduleWrapper}>\n                <Icon name=\"attach\" className={styles.moduleIcon} />\n                <div className={styles.moduleHeader}>{t('common.attachments')}</div>\n                <Attachments hideImagesWhenNotAllVisible />\n              </div>\n            </div>\n          )}\n          <div className={styles.contentModule}>\n            <div className={styles.moduleWrapper}>\n              <Icon name=\"list ul\" className={styles.moduleIcon} />\n              <Communication />\n            </div>\n          </div>\n        </Grid.Column>\n        <Grid.Column width={4} className={styles.sidebarPadding}>\n          <div className={styles.sticky}>\n            <div className={styles.actions}>\n              <div className={classNames(styles.attachments, styles.attachmentsList)}>\n                <div className={classNames(styles.text, styles.textList)}>{t('common.list')}</div>\n                {canUseLists ? (\n                  <ListsPopup currentId={list.id} onSelect={handleListSelect}>\n                    <button type=\"button\" className={styles.listButton}>\n                      <span className={classNames(styles.list, styles.listHoverable)}>\n                        <Icon name=\"columns\" size=\"small\" className={styles.listIcon} />\n                        <span className={styles.hidable}>\n                          {list.name || t(`common.${list.type}`)}\n                        </span>\n                      </span>\n                    </button>\n                  </ListsPopup>\n                ) : (\n                  <span className={styles.list}>\n                    <Icon name=\"columns\" size=\"small\" className={styles.listIcon} />\n                    <span className={styles.hidable}>{list.name || t(`common.${list.type}`)}</span>\n                  </span>\n                )}\n              </div>\n            </div>\n            {(canUseMembers || canUseLabels || canAddAttachment || canAddCustomFieldGroup) && (\n              <div className={styles.actions}>\n                <span className={styles.actionsTitle}>{t('action.addToCard')}</span>\n                {canUseLabels && (\n                  <LabelsPopup\n                    currentIds={labelIds}\n                    cardId={card.id}\n                    onSelect={handleLabelSelect}\n                    onDeselect={handleLabelDeselect}\n                  >\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"bookmark outline\" className={styles.actionIcon} />\n                      {t('common.labels')}\n                    </Button>\n                  </LabelsPopup>\n                )}\n                {canAddAttachment && (\n                  <AddAttachmentPopup>\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"attach\" className={styles.actionIcon} />\n                      {t('common.attachment')}\n                    </Button>\n                  </AddAttachmentPopup>\n                )}\n                {canAddCustomFieldGroup && (\n                  <AddCustomFieldGroupPopup onCreate={handleCustomFieldGroupCreate}>\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"sticky note outline\" className={styles.actionIcon} />\n                      {t('common.customField', {\n                        context: 'title',\n                      })}\n                    </Button>\n                  </AddCustomFieldGroupPopup>\n                )}\n                {canUseMembers && (\n                  <BoardMembershipsPopup\n                    currentUserIds={userIds}\n                    onUserSelect={handleUserSelect}\n                    onUserDeselect={handleUserDeselect}\n                  >\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"user outline\" className={styles.actionIcon} />\n                      {t('common.members')}\n                    </Button>\n                  </BoardMembershipsPopup>\n                )}\n              </div>\n            )}\n            {((!board.limitCardTypesToDefaultOne && canEditType) ||\n              canSubscribe ||\n              canJoin ||\n              canDuplicate ||\n              canMove ||\n              (canRestore && (isInArchiveList || isInTrashList)) ||\n              (canArchive && !isInArchiveList) ||\n              canDelete) && (\n              <div className={styles.actions}>\n                <span className={styles.actionsTitle}>{t('common.actions')}</span>\n                {canJoin && (\n                  <Button\n                    fluid\n                    className={classNames(styles.actionButton, styles.hidable)}\n                    onClick={handleToggleJointClick}\n                  >\n                    <Icon\n                      name={isJoined ? 'flag outline' : 'flag checkered'}\n                      className={styles.actionIcon}\n                    />\n                    {isJoined ? t('action.leave') : t('action.join')}\n                  </Button>\n                )}\n                {canSubscribe && (\n                  <Button\n                    fluid\n                    disabled={board.isSubscribed}\n                    className={classNames(styles.actionButton, styles.hidable)}\n                    onClick={handleToggleSubscriptionClick}\n                  >\n                    {board.isSubscribed ? (\n                      <>\n                        <Icon name=\"bell slash outline\" className={styles.actionIcon} />\n                        {t('common.boardSubscribed')}\n                      </>\n                    ) : (\n                      <>\n                        <Icon\n                          name={card.isSubscribed ? 'bell slash outline' : 'bell outline'}\n                          className={styles.actionIcon}\n                        />\n                        {card.isSubscribed ? t('action.unsubscribe') : t('action.subscribe')}\n                      </>\n                    )}\n                  </Button>\n                )}\n                {canRestore && (isInArchiveList || isInTrashList) && (\n                  <Button\n                    fluid\n                    disabled={!prevList}\n                    className={classNames(styles.actionButton, styles.hidable)}\n                    onClick={handleRestoreClick}\n                  >\n                    <Icon name=\"undo alternate\" className={styles.actionIcon} />\n                    {prevList\n                      ? t('action.restoreToList', {\n                          list: prevList.name || t(`common.${prevList.type}`),\n                        })\n                      : t('common.selectListToRestoreThisCard')}\n                  </Button>\n                )}\n                {canArchive && !isInArchiveList && (\n                  <ConfirmationPopup\n                    title=\"common.archiveCard\"\n                    content=\"common.areYouSureYouWantToArchiveThisCard\"\n                    buttonContent=\"action.archiveCard\"\n                    onConfirm={handleArchiveConfirm}\n                  >\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"folder open outline\" className={styles.actionIcon} />\n                      {t('action.archive')}\n                    </Button>\n                  </ConfirmationPopup>\n                )}\n                {canDelete && (\n                  <ConfirmationPopup\n                    title={isInTrashList ? 'common.deleteCardForever' : 'common.deleteCard'}\n                    content={\n                      isInTrashList\n                        ? 'common.areYouSureYouWantToDeleteThisCardForever'\n                        : 'common.areYouSureYouWantToDeleteThisCard'\n                    }\n                    buttonContent={isInTrashList ? 'action.deleteCardForever' : 'action.deleteCard'}\n                    onConfirm={handleDeleteConfirm}\n                  >\n                    <Button fluid className={classNames(styles.actionButton, styles.hidable)}>\n                      <Icon name=\"trash alternate outline\" className={styles.actionIcon} />\n                      {isInTrashList\n                        ? t('action.deleteForever', {\n                            context: 'title',\n                          })\n                        : t('action.delete')}\n                    </Button>\n                  </ConfirmationPopup>\n                )}\n                {((!board.limitCardTypesToDefaultOne && canEditType) ||\n                  canDuplicate ||\n                  canMove) && (\n                  <MoreActionsPopup>\n                    <Button fluid className={classNames(styles.moreActionsButton, styles.hidable)}>\n                      <Icon name=\"ellipsis horizontal\" className={styles.moreActionsButtonIcon} />\n                      {t('common.moreActions')}\n                    </Button>\n                  </MoreActionsPopup>\n                )}\n              </div>\n            )}\n          </div>\n        </Grid.Column>\n      </Grid.Row>\n    </Grid>\n  );\n});\n\nexport default StoryContent;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/StoryContent.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actionButton {\n    background: #ebeef0;\n    box-shadow: 0 1px 0 0 rgba(9, 30, 66, 0.13);\n    color: #444;\n    margin-top: 8px;\n    padding: 6px 8px 6px 18px;\n    text-align: left;\n    transition: background 85ms ease;\n\n    &:hover {\n      background: #dfe3e6;\n      box-shadow: 0 1px 0 0 rgba(9, 30, 66, 0.25);\n      color: #4c4c4c;\n    }\n  }\n\n  .actionIcon {\n    color: #17394d;\n    margin-right: 8px;\n  }\n\n  .actions {\n    margin-bottom: 24px;\n\n    @media only screen and (width < 768px) {\n      flex: 1;\n\n      &:first-child {\n        flex: 0 0 100%;\n      }\n    }\n  }\n\n  .actionsTitle {\n    color: #8c8c8c;\n    font-size: 12px;\n    font-weight: normal;\n    letter-spacing: 0.04em;\n    margin-top: 16px;\n    text-transform: uppercase;\n    line-height: 20px;\n    margin-bottom: -4px;\n  }\n\n  .addAttachment {\n    margin: 0 -4.3px;\n    text-decoration: none;\n  }\n\n  .attachment {\n    display: inline-block;\n    margin: 0 4px 4px 0;\n    max-width: 100%;\n    vertical-align: top;\n  }\n\n  .attachments {\n    display: inline-block;\n    margin: 0 8px 4px 0;\n    max-width: 100%;\n    vertical-align: top;\n  }\n\n  .attachmentsList {\n    width: 100%;\n  }\n\n  .contentModule {\n    margin-bottom: 24px;\n  }\n\n  .contentModuleDescription:hover {\n    .editButton {\n      opacity: 1;\n    }\n  }\n\n  .contentPadding {\n    padding: 8px 24px 0 16px;\n\n    @media only screen and (width < 768px) {\n      padding-right: 16px;\n      width: 100% !important;\n    }\n  }\n\n  .cover {\n    border-radius: 3px;\n    cursor: pointer;\n    display: block;\n    max-height: 450px;\n    max-width: 100%;\n  }\n\n  .coverWrapper {\n    padding-bottom: 10px;\n  }\n\n  .cursorPointer {\n    cursor: pointer;\n  }\n\n  .dueDate {\n    background: rgba(9, 30, 66, 0.04);\n    border: none;\n    border-radius: 3px;\n    color: #6b808c;\n    cursor: pointer;\n    font-size: 12px;\n    line-height: 20px;\n    outline: none;\n    padding: 2px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: background 0.3s ease;\n    vertical-align: top;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #17394d;\n    }\n  }\n\n  .descriptionButton {\n    background: rgba(9, 30, 66, 0.04);\n    border: none;\n    border-radius: 3px;\n    display: block;\n    color: #6b808c;\n    cursor: pointer;\n    min-height: 54px;\n    outline: none;\n    padding: 8px 12px;\n    position: relative;\n    text-align: left;\n    text-decoration: none;\n    width: 100%;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .descriptionButtonText {\n    position: absolute;\n    top: 12px;\n  }\n\n  .descriptionText {\n    min-height: 36px;\n  }\n\n  .draftChip {\n    background: #f1eee2;\n    border-radius: 3px;\n    color: rgba(0, 0, 0, 0.87);\n    display: inline-block;\n    line-height: 1.5;\n    margin-bottom: 8px;\n    padding: 2px 12px;\n  }\n\n  .editButton {\n    background: transparent;\n    box-shadow: none;\n    line-height: 28px;\n    margin: 0;\n    min-height: auto;\n    opacity: 0;\n    padding: 0;\n    position: absolute;\n    right: 0;\n    top: 4px;\n    width: 28px;\n    z-index: 1;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n    }\n  }\n\n  .headerPadding {\n    padding: 0;\n  }\n\n  .headerTitle {\n    color: #17394d;\n    font-size: 28px;\n    font-weight: bold;\n    line-height: 34px;\n    word-wrap: break-word;\n  }\n\n  .headerTitleWrapper {\n    margin: 4px 0;\n    padding: 6px 0 0;\n  }\n\n  .headerWrapper {\n    margin: 12px 48px 12px 56px;\n    position: relative;\n  }\n\n  .hidable {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .list {\n    align-items: center;\n    background: rgba(9, 30, 66, 0.04);\n    border: none;\n    border-radius: 3px;\n    color: #6b808c;\n    display: flex;\n    line-height: 20px;\n    outline: none;\n    padding: 6px 12px;\n    transition: background 0.3s ease;\n  }\n\n  .listButton {\n    background: transparent;\n    border: none;\n    cursor: pointer;\n    display: inline-block;\n    max-width: 100%;\n    outline: none;\n    padding: 0;\n    width: 100%;\n  }\n\n  .listHoverable:hover {\n    background: rgba(9, 30, 66, 0.08);\n    color: #17394d;\n  }\n\n  .listIcon {\n    margin: 0 8px 0 0;\n  }\n\n  .modalPadding {\n    padding: 0px;\n  }\n\n  .moduleHeader {\n    color: #17394d;\n    font-size: 16px;\n    font-weight: bold;\n    line-height: 1.5;\n    margin: 0 0 4px;\n    padding: 6px 0;\n    word-wrap: break-word;\n  }\n\n  .moduleIcon {\n    color: #17394d;\n    font-size: 17px;\n    height: 32px;\n    left: -40px;\n    line-height: 32px;\n    margin-right: 0;\n    position: absolute;\n    top: 2px;\n    width: 32px;\n  }\n\n  .moduleIconTitle {\n    top: 6px;\n  }\n\n  .moduleWrapper {\n    margin: 0 0 0 40px;\n    position: relative;\n  }\n\n  .moduleWrapperAttachments {\n    margin-bottom: 10px;\n  }\n\n  .moreActionsButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 8px 6px 18px;\n    text-align: left;\n    text-decoration: underline;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .moreActionsButtonIcon {\n    margin-right: 8px;\n    text-decoration: none;\n  }\n\n  .sidebarPadding {\n    padding: 8px 16px 8px 8px;\n\n    @media only screen and (width < 768px) {\n      padding-left: 16px;\n      width: 100% !important;\n    }\n  }\n\n  .sticky {\n    position: sticky;\n    top: 0;\n\n    @media only screen and (width < 768px) {\n      display: flex;\n      flex-flow: row wrap;\n      gap: 10px;\n    }\n  }\n\n  .text {\n    color: #6b808c;\n    font-size: 12px;\n    font-weight: bold;\n    letter-spacing: 0.3px;\n    line-height: 20px;\n    margin: 0 8px 4px 0;\n    text-transform: uppercase;\n  }\n\n  .textList {\n    font-weight: normal;\n  }\n\n  .thumbnails {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 8px;\n    margin-top: 20px;\n\n    &::after {\n      content: '';\n      flex-grow: 10;\n    }\n  }\n\n  .wrapper {\n    background: #f5f6f7;\n    border-radius: 4px;\n    margin: 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/TaskLists/EditStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport React, { useCallback, useMemo, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Popup } from '../../../../lib/custom-ui';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { useForm, useSteps } from '../../../../hooks';\nimport ConfirmationStep from '../../../common/ConfirmationStep';\nimport TaskListEditor from '../../../task-lists/TaskListEditor';\n\nimport styles from './EditStep.module.scss';\n\nconst StepTypes = {\n  DELETE: 'DELETE',\n};\n\nconst EditStep = React.memo(({ taskListId, onClose }) => {\n  const selectTaskListById = useMemo(() => selectors.makeSelectTaskListById(), []);\n\n  const taskList = useSelector((state) => selectTaskListById(state, taskListId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultData = useMemo(\n    () => ({\n      name: taskList.name,\n      showOnFrontOfCard: taskList.showOnFrontOfCard,\n      hideCompletedTasks: taskList.hideCompletedTasks,\n    }),\n    [taskList.name, taskList.showOnFrontOfCard, taskList.hideCompletedTasks],\n  );\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: t('common.taskList', {\n      context: 'title',\n    }),\n    showOnFrontOfCard: true,\n    hideCompletedTasks: false,\n    ...defaultData,\n  }));\n\n  const [step, openStep, handleBack] = useSteps();\n\n  const taskListEditorRef = useRef(null);\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n    };\n\n    if (!cleanData.name) {\n      taskListEditorRef.current.selectNameField();\n      return;\n    }\n\n    if (!dequal(cleanData, defaultData)) {\n      dispatch(entryActions.updateTaskList(taskListId, data));\n    }\n\n    onClose();\n  }, [taskListId, onClose, dispatch, defaultData, data, taskListEditorRef]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteTaskList(taskListId));\n  }, [taskListId, dispatch]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  if (step && step.type === StepTypes.DELETE) {\n    return (\n      <ConfirmationStep\n        title=\"common.deleteTaskList\"\n        content=\"common.areYouSureYouWantToDeleteThisTaskList\"\n        buttonContent=\"action.deleteTaskList\"\n        onConfirm={handleDeleteConfirm}\n        onBack={handleBack}\n      />\n    );\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.taskListActions', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <TaskListEditor ref={taskListEditorRef} data={data} onFieldChange={handleFieldChange} />\n          <Button positive content={t('action.save')} />\n        </Form>\n        <Button\n          content={t('action.delete')}\n          className={styles.deleteButton}\n          onClick={handleDeleteClick}\n        />\n      </Popup.Content>\n    </>\n  );\n});\n\nEditStep.propTypes = {\n  taskListId: PropTypes.string.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default EditStep;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/TaskLists/EditStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .deleteButton {\n    bottom: 12px;\n    box-shadow: 0 1px 0 #cbcccc;\n    position: absolute;\n    right: 9px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/TaskLists/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport ReactDOM from 'react-dom';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { Draggable } from 'react-beautiful-dnd';\nimport { Button, Icon } from 'semantic-ui-react';\nimport { useToggle } from '../../../../lib/hooks';\n\nimport selectors from '../../../../selectors';\nimport { usePopupInClosableContext } from '../../../../hooks';\nimport { BoardMembershipRoles } from '../../../../constants/Enums';\nimport EditStep from './EditStep';\nimport TaskList from '../../../task-lists/TaskList';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id, index }) => {\n  const selectTaskListById = useMemo(() => selectors.makeSelectTaskListById(), []);\n\n  const taskList = useSelector((state) => selectTaskListById(state, id));\n\n  const canEdit = useSelector((state) => {\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  const [isCompletedVisible, toggleCompletedVisible] = useToggle();\n\n  const handleToggleCompletedVisibleClick = useCallback(() => {\n    toggleCompletedVisible();\n  }, [toggleCompletedVisible]);\n\n  const EditPopup = usePopupInClosableContext(EditStep);\n\n  const withActions = taskList.hideCompletedTasks || canEdit;\n\n  return (\n    <Draggable\n      draggableId={`task-list:${id}`}\n      index={index}\n      isDragDisabled={!taskList.isPersisted || !canEdit}\n    >\n      {({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {\n        const contentNode = (\n          <div\n            {...draggableProps} // eslint-disable-line react/jsx-props-no-spreading\n            ref={innerRef}\n            className={classNames(styles.wrapper, styles.wrapperDragging)}\n          >\n            <div className={styles.moduleWrapper}>\n              <Icon name=\"check square outline\" className={styles.moduleIcon} />\n              {/* eslint-disable-next-line react/jsx-props-no-spreading */}\n              <div {...dragHandleProps}>\n                <div\n                  className={classNames(\n                    styles.moduleHeader,\n                    withActions && styles.moduleHeaderWithActions,\n                    taskList.hideCompletedTasks && canEdit && styles.both,\n                  )}\n                >\n                  {taskList.isPersisted && withActions && (\n                    <div className={classNames(styles.actions)}>\n                      {taskList.hideCompletedTasks && (\n                        <Button\n                          className={styles.button}\n                          onClick={handleToggleCompletedVisibleClick}\n                        >\n                          <Icon\n                            fitted\n                            name={isCompletedVisible ? 'eye slash' : 'eye'}\n                            size=\"small\"\n                          />\n                        </Button>\n                      )}\n                      {canEdit && (\n                        <EditPopup taskListId={taskList.id}>\n                          <Button className={styles.button}>\n                            <Icon fitted name=\"pencil\" size=\"small\" />\n                          </Button>\n                        </EditPopup>\n                      )}\n                    </div>\n                  )}\n                  <span className={styles.moduleHeaderTitle}>{taskList.name}</span>\n                </div>\n              </div>\n              <TaskList id={id} isCompletedVisible={isCompletedVisible} />\n            </div>\n          </div>\n        );\n\n        return isDragging ? ReactDOM.createPortal(contentNode, document.body) : contentNode;\n      }}\n    </Draggable>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n  index: PropTypes.number.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/TaskLists/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actions {\n    display: flex;\n    gap: 2px;\n    position: absolute;\n    right: 0;\n    top: 4px;\n  }\n\n  .button {\n    background: transparent;\n    box-shadow: none;\n    line-height: 28px;\n    margin: 0;\n    min-height: auto;\n    opacity: 0;\n    padding: 0;\n    width: 28px;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n    }\n  }\n\n  .moduleHeader {\n    color: #17394d;\n    display: flex;\n    font-size: 16px;\n    font-weight: bold;\n    line-height: 1.5;\n    margin: 0 0 4px;\n    padding: 6px 0;\n  }\n\n  .moduleHeaderWithActions {\n    padding-right: 32px;\n\n    &:hover {\n      .button {\n        opacity: 1;\n      }\n    }\n\n    &.both {\n      padding-right: 62px;\n    }\n  }\n\n  .moduleHeaderTitle {\n    min-width: 0;\n    word-wrap: break-word;\n  }\n\n  .moduleIcon {\n    color: #17394d;\n    font-size: 17px;\n    height: 32px;\n    left: -40px;\n    line-height: 32px;\n    margin-right: 0;\n    position: absolute;\n    top: 2px;\n    width: 32px;\n  }\n\n  .moduleWrapper {\n    margin: 0 0 0 40px;\n    position: relative;\n  }\n\n  .wrapper {\n    border-radius: 3px;\n    margin-bottom: 24px;\n  }\n\n  .wrapperDragging {\n    background: rgba(247, 246, 247, 0.8);\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/TaskLists/TaskLists.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { DragDropContext, Droppable } from 'react-beautiful-dnd';\nimport { closePopup } from '../../../../lib/popup';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport parseDndId from '../../../../utils/parse-dnd-id';\nimport DroppableTypes from '../../../../constants/DroppableTypes';\nimport Item from './Item';\n\nimport globalStyles from '../../../../styles.module.scss';\n\nconst TaskLists = React.memo(() => {\n  const taskListIds = useSelector(selectors.selectTaskListIdsForCurrentCard);\n\n  const dispatch = useDispatch();\n\n  const handleDragStart = useCallback(() => {\n    document.body.classList.add(globalStyles.dragging);\n    closePopup();\n  }, []);\n\n  const handleDragEnd = useCallback(\n    ({ draggableId, type, source, destination }) => {\n      document.body.classList.remove(globalStyles.dragging);\n\n      if (!destination) {\n        return;\n      }\n\n      if (source.droppableId === destination.droppableId && source.index === destination.index) {\n        return;\n      }\n\n      const id = parseDndId(draggableId);\n\n      switch (type) {\n        case DroppableTypes.TASK_LIST:\n          dispatch(entryActions.moveTaskList(id, destination.index));\n\n          break;\n        case DroppableTypes.TASK:\n          dispatch(\n            entryActions.moveTask(id, parseDndId(destination.droppableId), destination.index),\n          );\n\n          break;\n        default:\n      }\n    },\n    [dispatch],\n  );\n\n  return (\n    <DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>\n      <Droppable droppableId=\"card\" type={DroppableTypes.TASK_LIST} direction=\"vertical\">\n        {({ innerRef, droppableProps, placeholder }) => (\n          // eslint-disable-next-line react/jsx-props-no-spreading\n          <div {...droppableProps} ref={innerRef}>\n            {taskListIds.map((taskListId, index) => (\n              <Item key={taskListId} id={taskListId} index={index} />\n            ))}\n            {placeholder}\n          </div>\n        )}\n      </Droppable>\n    </DragDropContext>\n  );\n});\n\nexport default TaskLists;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/TaskLists/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport TaskLists from './TaskLists';\n\nexport default TaskLists;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/Thumbnail.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { Item as GalleryItem } from 'react-photoswipe-gallery';\n\nimport selectors from '../../../selectors';\n\nimport styles from './Thumbnail.module.scss';\n\nconst Thumbnail = React.memo(({ attachmentId }) => {\n  const selectAttachmentById = useMemo(() => selectors.makeSelectAttachmentById(), []);\n\n  const attachment = useSelector((state) => selectAttachmentById(state, attachmentId));\n\n  return (\n    <GalleryItem\n      {...attachment.data.image} // eslint-disable-line react/jsx-props-no-spreading\n      original={attachment.data.url}\n      caption={attachment.name}\n    >\n      {({ ref, open }) => (\n        /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                    jsx-a11y/no-noninteractive-element-interactions */\n        <img\n          ref={ref}\n          src={attachment.data.thumbnailUrls.outside360}\n          alt={attachment.name}\n          className={styles.image}\n          onClick={open}\n        />\n      )}\n    </GalleryItem>\n  );\n});\n\nThumbnail.propTypes = {\n  attachmentId: PropTypes.string.isRequired,\n};\n\nexport default Thumbnail;\n"
  },
  {
    "path": "client/src/components/cards/CardModal/Thumbnail.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .image {\n    border-radius: 3px;\n    cursor: pointer;\n    flex-grow: 1;\n    height: 80px;\n    object-fit: cover;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/CardModal/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport CardModal from './CardModal';\n\nexport default CardModal;\n"
  },
  {
    "path": "client/src/components/cards/DraggableCard/DraggableCard.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { Draggable } from 'react-beautiful-dnd';\n\nimport selectors from '../../../selectors';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\nimport Card from '../Card';\n\nimport styles from './DraggableCard.module.scss';\n\nconst DraggableCard = React.memo(({ id, index, className, ...props }) => {\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n\n  const card = useSelector((state) => selectCardById(state, id));\n\n  const canDrag = useSelector((state) => {\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  return (\n    <Draggable\n      draggableId={`card:${id}`}\n      index={index}\n      isDragDisabled={!card.isPersisted || !canDrag}\n    >\n      {({ innerRef, draggableProps, dragHandleProps }) => (\n        <div\n          {...draggableProps} // eslint-disable-line react/jsx-props-no-spreading\n          {...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading\n          ref={innerRef}\n          className={classNames(styles.wrapper, className)}\n        >\n          {/* eslint-disable-next-line react/jsx-props-no-spreading */}\n          <Card {...props} id={id} />\n        </div>\n      )}\n    </Draggable>\n  );\n});\n\nDraggableCard.propTypes = {\n  id: PropTypes.string.isRequired,\n  index: PropTypes.number.isRequired,\n  className: PropTypes.string,\n};\n\nDraggableCard.defaultProps = {\n  className: undefined,\n};\n\nexport default DraggableCard;\n"
  },
  {
    "path": "client/src/components/cards/DraggableCard/DraggableCard.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    cursor: auto;\n    outline: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/DraggableCard/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport DraggableCard from './DraggableCard';\n\nexport default DraggableCard;\n"
  },
  {
    "path": "client/src/components/cards/DueDateChip/DueDateChip.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport React, { useEffect, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { Icon } from 'semantic-ui-react';\nimport { useForceUpdate } from '../../../lib/hooks';\n\nimport getDateFormat from '../../../utils/get-date-format';\n\nimport styles from './DueDateChip.module.scss';\n\nconst Sizes = {\n  TINY: 'tiny',\n  SMALL: 'small',\n  MEDIUM: 'medium',\n};\n\nconst Statuses = {\n  DUE_SOON: 'dueSoon',\n  OVERDUE: 'overdue',\n  COMPLETED: 'completed',\n};\n\nconst LONG_DATE_FORMAT_BY_SIZE = {\n  [Sizes.TINY]: 'longDate',\n  [Sizes.SMALL]: 'longDate',\n  [Sizes.MEDIUM]: 'longDateTime',\n};\n\nconst FULL_DATE_FORMAT_BY_SIZE = {\n  [Sizes.TINY]: 'fullDate',\n  [Sizes.SMALL]: 'fullDate',\n  [Sizes.MEDIUM]: 'fullDateTime',\n};\n\nconst STATUS_ICON_PROPS_BY_STATUS = {\n  [Statuses.DUE_SOON]: {\n    name: 'hourglass half',\n    color: 'orange',\n  },\n  [Statuses.OVERDUE]: {\n    name: 'hourglass end',\n    color: 'red',\n  },\n  [Statuses.COMPLETED]: {\n    name: 'checkmark',\n    color: 'green',\n  },\n};\n\nconst getStatus = (date, isCompleted) => {\n  if (isCompleted) {\n    return Statuses.COMPLETED;\n  }\n\n  const secondsLeft = Math.floor((date.getTime() - new Date().getTime()) / 1000);\n\n  if (secondsLeft <= 0) {\n    return Statuses.OVERDUE;\n  }\n\n  if (secondsLeft <= 24 * 60 * 60) {\n    return Statuses.DUE_SOON;\n  }\n\n  return null;\n};\n\nconst DueDateChip = React.memo(\n  ({ value, size, isCompleted, isDisabled, withStatus, withStatusIcon, onClick }) => {\n    const [t] = useTranslation();\n    const forceUpdate = useForceUpdate();\n\n    const statusRef = useRef(null);\n    statusRef.current = withStatus ? getStatus(value, isCompleted) : null;\n\n    const intervalRef = useRef(null);\n\n    const dateFormat = getDateFormat(\n      value,\n      LONG_DATE_FORMAT_BY_SIZE[size],\n      FULL_DATE_FORMAT_BY_SIZE[size],\n    );\n\n    useEffect(() => {\n      if (\n        withStatus &&\n        statusRef.current !== Statuses.OVERDUE &&\n        statusRef.current !== Statuses.COMPLETED\n      ) {\n        intervalRef.current = setInterval(() => {\n          const status = getStatus(value, isCompleted);\n\n          if (status !== statusRef.current) {\n            forceUpdate();\n          }\n\n          if (status === Statuses.OVERDUE) {\n            clearInterval(intervalRef.current);\n          }\n        }, 1000);\n      }\n\n      return () => {\n        if (intervalRef.current) {\n          clearInterval(intervalRef.current);\n        }\n      };\n    }, [value, isCompleted, withStatus, forceUpdate]);\n\n    const contentNode = (\n      <span\n        className={classNames(\n          styles.wrapper,\n          styles[`wrapper${upperFirst(size)}`],\n          !withStatusIcon && statusRef.current && styles[`wrapper${upperFirst(statusRef.current)}`],\n          onClick && styles.wrapperHoverable,\n        )}\n      >\n        {t(`format:${dateFormat}`, {\n          value,\n          postProcess: 'formatDate',\n        })}\n        {withStatusIcon && statusRef.current && (\n          // eslint-disable-next-line react/jsx-props-no-spreading\n          <Icon {...STATUS_ICON_PROPS_BY_STATUS[statusRef.current]} className={styles.statusIcon} />\n        )}\n      </span>\n    );\n\n    return onClick ? (\n      <button type=\"button\" disabled={isDisabled} className={styles.button} onClick={onClick}>\n        {contentNode}\n      </button>\n    ) : (\n      contentNode\n    );\n  },\n);\n\nDueDateChip.propTypes = {\n  value: PropTypes.instanceOf(Date).isRequired,\n  size: PropTypes.oneOf(Object.values(Sizes)),\n  isCompleted: PropTypes.bool.isRequired,\n  isDisabled: PropTypes.bool,\n  withStatus: PropTypes.bool.isRequired,\n  withStatusIcon: PropTypes.bool,\n  onClick: PropTypes.func,\n};\n\nDueDateChip.defaultProps = {\n  size: Sizes.MEDIUM,\n  isDisabled: false,\n  withStatusIcon: false,\n  onClick: undefined,\n};\n\nexport default DueDateChip;\n"
  },
  {
    "path": "client/src/components/cards/DueDateChip/DueDateChip.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    background: transparent;\n    border: none;\n    cursor: pointer;\n    display: inline-block;\n    outline: none;\n    padding: 0;\n  }\n\n  .statusIcon {\n    line-height: 1;\n    margin: 0 0 0 8px;\n  }\n\n  .wrapper {\n    background: #dce0e4;\n    border-radius: 3px;\n    color: #6a808b;\n    display: inline-block;\n    transition: background 0.3s ease;\n    white-space: nowrap;\n  }\n\n  .wrapperHoverable:hover {\n    background: #d2d8dc;\n    color: #17394d;\n  }\n\n  /* Sizes */\n\n  .wrapperTiny {\n    font-size: 12px;\n    line-height: 20px;\n    padding: 0px 6px;\n  }\n\n  .wrapperSmall {\n    font-size: 12px;\n    line-height: 20px;\n    padding: 2px 8px;\n  }\n\n  .wrapperMedium {\n    line-height: 20px;\n    padding: 6px 12px;\n  }\n\n  /* Statuses */\n\n  .wrapperDueSoon {\n    background: #f2711c;\n    color: #fff;\n  }\n\n  .wrapperOverdue {\n    background: #db2828;\n    color: #fff;\n  }\n\n  .wrapperCompleted {\n    background: #21ba45;\n    color: #fff;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/DueDateChip/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport DueDateChip from './DueDateChip';\n\nexport default DueDateChip;\n"
  },
  {
    "path": "client/src/components/cards/EditDueDateStep/EditDueDateStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport DatePicker from 'react-datepicker';\nimport { Button, Form } from 'semantic-ui-react';\nimport { useDidUpdate, useToggle } from '../../../lib/hooks';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\nimport parseTime from '../../../utils/parse-time';\n\nimport styles from './EditDueDateStep.module.scss';\n\nconst EditDueDateStep = React.memo(({ cardId, onBack, onClose }) => {\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n\n  const defaultValue = useSelector((state) => selectCardById(state, cardId).dueDate);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const [data, handleFieldChange, setData] = useForm(() => {\n    const date = defaultValue || new Date().setHours(12, 0, 0, 0);\n\n    return {\n      date: t('format:date', {\n        postProcess: 'formatDate',\n        value: date,\n      }),\n      time: t('format:time', {\n        postProcess: 'formatDate',\n        value: date,\n      }),\n    };\n  });\n\n  const [selectTimeFieldState, selectTimeField] = useToggle();\n\n  const [dateFieldRef, handleDateFieldRef] = useNestedRef('inputRef');\n  const [timeFieldRef, handleTimeFieldRef] = useNestedRef('inputRef');\n\n  const nullableDate = useMemo(() => {\n    const date = t('format:date', {\n      postProcess: 'parseDate',\n      value: data.date,\n    });\n\n    if (Number.isNaN(date.getTime())) {\n      return null;\n    }\n\n    return date;\n  }, [data.date, t]);\n\n  const handleSubmit = useCallback(() => {\n    if (!nullableDate) {\n      dateFieldRef.current.select();\n      return;\n    }\n\n    let value = t('format:dateTime', {\n      postProcess: 'parseDate',\n      value: `${data.date} ${data.time}`,\n    });\n\n    if (Number.isNaN(value.getTime())) {\n      value = parseTime(data.time, nullableDate);\n\n      if (Number.isNaN(value.getTime())) {\n        timeFieldRef.current.select();\n        return;\n      }\n    }\n\n    if (!defaultValue || value.getTime() !== defaultValue.getTime()) {\n      dispatch(\n        entryActions.updateCard(cardId, {\n          dueDate: value,\n        }),\n      );\n    }\n\n    onClose();\n  }, [cardId, onClose, defaultValue, dispatch, t, data, dateFieldRef, timeFieldRef, nullableDate]);\n\n  const handleClearClick = useCallback(() => {\n    if (defaultValue) {\n      dispatch(\n        entryActions.updateCard(cardId, {\n          dueDate: null,\n        }),\n      );\n    }\n\n    onClose();\n  }, [cardId, onClose, defaultValue, dispatch]);\n\n  const handleDatePickerChange = useCallback(\n    (date) => {\n      setData((prevData) => ({\n        ...prevData,\n        date: t('format:date', {\n          postProcess: 'formatDate',\n          value: date,\n        }),\n      }));\n      selectTimeField();\n    },\n    [t, setData, selectTimeField],\n  );\n\n  useEffect(() => {\n    dateFieldRef.current.select();\n  }, [dateFieldRef]);\n\n  useDidUpdate(() => {\n    timeFieldRef.current.select();\n  }, [selectTimeFieldState]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editDueDate', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.fieldWrapper}>\n            <div className={styles.fieldBox}>\n              <div className={styles.text}>{t('common.date')}</div>\n              <Input\n                ref={handleDateFieldRef}\n                name=\"date\"\n                value={data.date}\n                maxLength={16}\n                onChange={handleFieldChange}\n              />\n            </div>\n            <div className={styles.fieldBox}>\n              <div className={styles.text}>{t('common.time')}</div>\n              <Input\n                ref={handleTimeFieldRef}\n                name=\"time\"\n                value={data.time}\n                maxLength={16}\n                onChange={handleFieldChange}\n              />\n            </div>\n          </div>\n          <DatePicker\n            inline\n            disabledKeyboardNavigation\n            selected={nullableDate}\n            onChange={handleDatePickerChange}\n          />\n          <Button positive content={t('action.save')} />\n        </Form>\n        <Button\n          negative\n          content={t('action.remove')}\n          className={styles.deleteButton}\n          onClick={handleClearClick}\n        />\n      </Popup.Content>\n    </>\n  );\n});\n\nEditDueDateStep.propTypes = {\n  cardId: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nEditDueDateStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default EditDueDateStep;\n"
  },
  {
    "path": "client/src/components/cards/EditDueDateStep/EditDueDateStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .deleteButton {\n    bottom: 12px;\n    box-shadow: 0 1px 0 #cbcccc;\n    position: absolute;\n    right: 9px;\n  }\n\n  .fieldBox {\n    display: inline-block;\n    margin: 0 4px 12px;\n    width: calc(50% - 8px);\n  }\n\n  .fieldWrapper {\n    margin: 0 -4px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 4px;\n    padding-left: 2px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/EditDueDateStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EditDueDateStep from './EditDueDateStep';\n\nexport default EditDueDateStep;\n"
  },
  {
    "path": "client/src/components/cards/EditStopwatchStep/EditStopwatchStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { useDidUpdate, useToggle } from '../../../lib/hooks';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\nimport {\n  createStopwatch,\n  getStopwatchParts,\n  startStopwatch,\n  stopStopwatch,\n  updateStopwatch,\n} from '../../../utils/stopwatch';\n\nimport styles from './EditStopwatchStep.module.scss';\n\nconst createData = (stopwatch) => {\n  if (!stopwatch) {\n    return {\n      hours: '0',\n      minutes: '0',\n      seconds: '0',\n    };\n  }\n\n  const { hours, minutes, seconds } = getStopwatchParts(stopwatch);\n\n  return {\n    hours: `${hours}`,\n    minutes: `${minutes}`,\n    seconds: `${seconds}`,\n  };\n};\n\nconst EditStopwatchStep = React.memo(({ cardId, onBack, onClose }) => {\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n\n  const defaultValue = useSelector((state) => selectCardById(state, cardId).stopwatch);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [data, handleFieldChange, setData] = useForm(() => createData(defaultValue));\n  const [isEditing, toggleEditing] = useToggle();\n\n  const [hoursFieldRef, handleHoursFieldRef] = useNestedRef('inputRef');\n  const [minutesFieldRef, handleMinutesFieldRef] = useNestedRef('inputRef');\n  const [secondsFieldRef, handleSecondsFieldRef] = useNestedRef('inputRef');\n\n  const update = useCallback(\n    (stopwatch) => {\n      dispatch(\n        entryActions.updateCard(cardId, {\n          stopwatch,\n        }),\n      );\n    },\n    [cardId, dispatch],\n  );\n\n  const handleSubmit = useCallback(() => {\n    const parts = {\n      hours: parseInt(data.hours, 10),\n      minutes: parseInt(data.minutes, 10),\n      seconds: parseInt(data.seconds, 10),\n    };\n\n    if (Number.isNaN(parts.hours)) {\n      hoursFieldRef.current.select();\n      return;\n    }\n\n    if (Number.isNaN(parts.minutes) || parts.minutes > 60) {\n      minutesFieldRef.current.select();\n      return;\n    }\n\n    if (Number.isNaN(parts.seconds) || parts.seconds > 60) {\n      secondsFieldRef.current.select();\n      return;\n    }\n\n    if (defaultValue) {\n      if (!dequal(parts, getStopwatchParts(defaultValue))) {\n        update(updateStopwatch(defaultValue, parts));\n      }\n    } else {\n      update(createStopwatch(parts));\n    }\n\n    onClose();\n  }, [onClose, defaultValue, data, hoursFieldRef, minutesFieldRef, secondsFieldRef, update]);\n\n  const handleStartClick = useCallback(() => {\n    update(startStopwatch(defaultValue));\n    onClose();\n  }, [onClose, defaultValue, update]);\n\n  const handleStopClick = useCallback(() => {\n    update(stopStopwatch(defaultValue));\n  }, [defaultValue, update]);\n\n  const handleClearClick = useCallback(() => {\n    if (defaultValue) {\n      update(null);\n    }\n\n    onClose();\n  }, [onClose, defaultValue, update]);\n\n  const handleToggleEditingClick = useCallback(() => {\n    setData(createData(defaultValue));\n    toggleEditing();\n  }, [defaultValue, setData, toggleEditing]);\n\n  useDidUpdate(() => {\n    if (isEditing) {\n      hoursFieldRef.current.select();\n    }\n  }, [isEditing]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editStopwatch', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.fieldWrapper}>\n            <div className={styles.fieldBox}>\n              <div className={styles.text}>{t('common.hours')}</div>\n              <Input.Mask\n                ref={handleHoursFieldRef}\n                name=\"hours\"\n                value={data.hours}\n                mask=\"9999\"\n                maskChar={null}\n                disabled={!isEditing}\n                onChange={handleFieldChange}\n              />\n            </div>\n            <div className={styles.fieldBox}>\n              <div className={styles.text}>{t('common.minutes')}</div>\n              <Input.Mask\n                ref={handleMinutesFieldRef}\n                name=\"minutes\"\n                value={data.minutes}\n                mask=\"99\"\n                maskChar={null}\n                disabled={!isEditing}\n                onChange={handleFieldChange}\n              />\n            </div>\n            <div className={styles.fieldBox}>\n              <div className={styles.text}>{t('common.seconds')}</div>\n              <Input.Mask\n                ref={handleSecondsFieldRef}\n                name=\"seconds\"\n                value={data.seconds}\n                mask=\"99\"\n                maskChar={null}\n                disabled={!isEditing}\n                onChange={handleFieldChange}\n              />\n            </div>\n            <Button\n              type=\"button\"\n              icon={isEditing ? 'close' : 'edit'}\n              className={styles.iconButton}\n              onClick={handleToggleEditingClick}\n            />\n          </div>\n          {isEditing && <Button positive content={t('action.save')} />}\n        </Form>\n        {!isEditing &&\n          (defaultValue && defaultValue.startedAt ? (\n            <Button positive content={t('action.stop')} icon=\"pause\" onClick={handleStopClick} />\n          ) : (\n            <Button positive content={t('action.start')} icon=\"play\" onClick={handleStartClick} />\n          ))}\n        <Button\n          negative\n          content={t('action.remove')}\n          className={styles.deleteButton}\n          onClick={handleClearClick}\n        />\n      </Popup.Content>\n    </>\n  );\n});\n\nEditStopwatchStep.propTypes = {\n  cardId: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nEditStopwatchStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default EditStopwatchStep;\n"
  },
  {
    "path": "client/src/components/cards/EditStopwatchStep/EditStopwatchStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .deleteButton {\n    bottom: 12px;\n    box-shadow: 0 1px 0 #cbcccc;\n    position: absolute;\n    right: 9px;\n  }\n\n  .fieldBox {\n    display: inline-block;\n    margin: 0 4px 12px;\n    width: calc(33.3333% - 22px);\n  }\n\n  .fieldWrapper {\n    margin: 0 -4px;\n  }\n\n  .iconButton {\n    background: transparent;\n    box-shadow: none;\n    margin: 0 4px 0 1px;\n    width: 36px;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 4px;\n    padding-left: 2px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/EditStopwatchStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EditStopwatchStep from './EditStopwatchStep';\n\nexport default EditStopwatchStep;\n"
  },
  {
    "path": "client/src/components/cards/MoveCardStep/MoveCardStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo, useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Dropdown, Form } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm } from '../../../hooks';\n\nimport styles from './MoveCardStep.module.scss';\n\nconst MoveCardStep = React.memo(({ id, onBack, onClose }) => {\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n  const selectBoardById = useMemo(() => selectors.makeSelectBoardById(), []);\n\n  const projectsToLists = useSelector(\n    selectors.selectProjectsToListsWithEditorRightsForCurrentUser,\n  );\n\n  const card = useSelector((state) => selectCardById(state, id));\n  const projectId = useSelector((state) => selectBoardById(state, card.boardId).projectId);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultPath = useMemo(\n    () => ({\n      projectId,\n      boardId: card.boardId,\n      listId: card.listId,\n    }),\n    [card.boardId, card.listId, projectId],\n  );\n\n  const [path, handleFieldChange] = useForm(() => ({\n    projectId: null,\n    boardId: null,\n    listId: null,\n    ...defaultPath,\n  }));\n\n  const selectedProject = useMemo(\n    () => projectsToLists.find((project) => project.id === path.projectId) || null,\n    [projectsToLists, path.projectId],\n  );\n\n  const selectedBoard = useMemo(\n    () =>\n      (selectedProject && selectedProject.boards.find((board) => board.id === path.boardId)) ||\n      null,\n    [selectedProject, path.boardId],\n  );\n\n  const selectedList = useMemo(\n    () => (selectedBoard && selectedBoard.lists.find((list) => list.id === path.listId)) || null,\n    [selectedBoard, path.listId],\n  );\n\n  const handleSubmit = useCallback(() => {\n    if (selectedBoard.id !== defaultPath.boardId) {\n      dispatch(entryActions.transferCard(id, selectedBoard.id, selectedList.id));\n    } else if (selectedList.id !== defaultPath.listId) {\n      dispatch(entryActions.moveCard(id, selectedList.id));\n    }\n\n    onClose();\n  }, [id, onClose, dispatch, defaultPath, selectedBoard, selectedList]);\n\n  const handleBoardIdChange = useCallback(\n    (event, data) => {\n      if (selectedProject.boards.find((board) => board.id === data.value).isFetching === null) {\n        dispatch(entryActions.fetchBoard(data.value));\n      }\n\n      handleFieldChange(event, data);\n    },\n    [dispatch, handleFieldChange, selectedProject.boards],\n  );\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.moveCard', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.project')}</div>\n          <Dropdown\n            fluid\n            selection\n            name=\"projectId\"\n            options={projectsToLists.map((project) => ({\n              text: project.name,\n              value: project.id,\n            }))}\n            value={selectedProject && selectedProject.id}\n            placeholder={\n              projectsToLists.length === 0 ? t('common.noProjects') : t('common.selectProject')\n            }\n            disabled={projectsToLists.length === 0}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          {selectedProject && (\n            <>\n              <div className={styles.text}>{t('common.board')}</div>\n              <Dropdown\n                fluid\n                selection\n                name=\"boardId\"\n                options={selectedProject.boards.map((board) => ({\n                  text: board.name,\n                  value: board.id,\n                }))}\n                value={selectedBoard && selectedBoard.id}\n                placeholder={\n                  selectedProject.boards.length === 0\n                    ? t('common.noBoards')\n                    : t('common.selectBoard')\n                }\n                disabled={selectedProject.boards.length === 0}\n                className={styles.field}\n                onChange={handleBoardIdChange}\n              />\n            </>\n          )}\n          {selectedBoard && (\n            <>\n              <div className={styles.text}>{t('common.list')}</div>\n              <Dropdown\n                fluid\n                selection\n                name=\"listId\"\n                options={selectedBoard.lists.map((list) => ({\n                  text: list.name || t(`common.${list.type}`),\n                  value: list.id,\n                  disabled: !list.isPersisted,\n                }))}\n                value={selectedList && selectedList.id}\n                placeholder={\n                  selectedBoard.isFetching === false && selectedBoard.lists.length === 0\n                    ? t('common.noLists')\n                    : t('common.selectList')\n                }\n                loading={selectedBoard.isFetching !== false}\n                disabled={selectedBoard.isFetching !== false || selectedBoard.lists.length === 0}\n                className={styles.field}\n                onChange={handleFieldChange}\n              />\n            </>\n          )}\n          <Button\n            positive\n            content={t('action.move')}\n            disabled={(selectedBoard && selectedBoard.isFetching !== false) || !selectedList}\n          />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nMoveCardStep.propTypes = {\n  id: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nMoveCardStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default MoveCardStep;\n"
  },
  {
    "path": "client/src/components/cards/MoveCardStep/MoveCardStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/MoveCardStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport MoveCardStep from './MoveCardStep';\n\nexport default MoveCardStep;\n"
  },
  {
    "path": "client/src/components/cards/SelectCardType/SelectCardType.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Menu } from 'semantic-ui-react';\n\nimport { CardTypes } from '../../../constants/Enums';\nimport { CardTypeIcons } from '../../../constants/Icons';\n\nimport styles from './SelectCardType.module.scss';\n\nconst DESCRIPTION_BY_TYPE = {\n  [CardTypes.PROJECT]: 'common.taskAssignmentAndProjectCompletion',\n  [CardTypes.STORY]: 'common.referenceDataAndKnowledgeStorage',\n};\n\nconst SelectCardType = React.memo(({ value, onSelect }) => {\n  const [t] = useTranslation();\n\n  const handleSelectClick = useCallback(\n    (_, { value: nextValue }) => {\n      if (nextValue !== value) {\n        onSelect(nextValue);\n      }\n    },\n    [value, onSelect],\n  );\n\n  return (\n    <Menu secondary vertical className={styles.menu}>\n      {[CardTypes.PROJECT, CardTypes.STORY].map((type) => (\n        <Menu.Item\n          key={type}\n          value={type}\n          active={type === value}\n          className={styles.menuItem}\n          onClick={handleSelectClick}\n        >\n          <Icon name={CardTypeIcons[type]} className={styles.menuItemIcon} />\n          <div className={styles.menuItemTitle}>{t(`common.${type}`)}</div>\n          <p className={styles.menuItemDescription}>{t(DESCRIPTION_BY_TYPE[type])}</p>\n        </Menu.Item>\n      ))}\n    </Menu>\n  );\n});\n\nSelectCardType.propTypes = {\n  value: PropTypes.string.isRequired,\n  onSelect: PropTypes.func.isRequired,\n};\n\nexport default SelectCardType;\n"
  },
  {
    "path": "client/src/components/cards/SelectCardType/SelectCardType.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menu {\n    margin: 0 auto 8px;\n    width: 100%;\n  }\n\n  .menuItem:last-child {\n    margin-bottom: 0;\n  }\n\n  .menuItemDescription {\n    opacity: 0.5;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.35714286em 0 0;\n  }\n\n  .menuItemTitle {\n    margin-bottom: 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/SelectCardType/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport SelectCardType from './SelectCardType';\n\nexport default SelectCardType;\n"
  },
  {
    "path": "client/src/components/cards/SelectCardTypeStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Popup } from '../../lib/custom-ui';\n\nimport SelectCardType from './SelectCardType';\n\nconst SelectCardTypeStep = React.memo(\n  ({ defaultValue, title, withButton, buttonContent, onSelect, onBack, onClose }) => {\n    const [t] = useTranslation();\n    const [value, setValue] = useState(defaultValue);\n\n    const handleSelect = useCallback(\n      (nextValue) => {\n        if (withButton) {\n          setValue(nextValue);\n        } else {\n          if (nextValue !== defaultValue) {\n            onSelect(nextValue);\n          }\n\n          onClose();\n        }\n      },\n      [defaultValue, withButton, onSelect, onClose],\n    );\n\n    const handleSubmit = useCallback(() => {\n      if (value !== defaultValue) {\n        onSelect(value);\n      }\n\n      onClose();\n    }, [defaultValue, onSelect, onClose, value]);\n\n    return (\n      <>\n        <Popup.Header onBack={onBack}>\n          {t(title, {\n            context: 'title',\n          })}\n        </Popup.Header>\n        <Popup.Content>\n          <Form onSubmit={handleSubmit}>\n            <SelectCardType value={value} onSelect={handleSelect} />\n            {withButton && <Button positive content={t(buttonContent)} />}\n          </Form>\n        </Popup.Content>\n      </>\n    );\n  },\n);\n\nSelectCardTypeStep.propTypes = {\n  defaultValue: PropTypes.string.isRequired,\n  title: PropTypes.string,\n  withButton: PropTypes.bool,\n  buttonContent: PropTypes.string,\n  onSelect: PropTypes.func.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nSelectCardTypeStep.defaultProps = {\n  title: 'common.selectType',\n  withButton: false,\n  buttonContent: 'action.selectType',\n  onBack: undefined,\n};\n\nexport default SelectCardTypeStep;\n"
  },
  {
    "path": "client/src/components/cards/StopwatchChip/StopwatchChip.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport React, { useCallback, useEffect, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useForceUpdate, usePrevious } from '../../../lib/hooks';\n\nimport { formatStopwatch } from '../../../utils/stopwatch';\n\nimport styles from './StopwatchChip.module.scss';\n\nconst Sizes = {\n  TINY: 'tiny',\n  SMALL: 'small',\n  MEDIUM: 'medium',\n};\n\nconst StopwatchChip = React.memo(({ value, as, size, isDisabled, onClick }) => {\n  const prevStartedAt = usePrevious(value.startedAt);\n  const forceUpdate = useForceUpdate();\n\n  const intervalRef = useRef(null);\n\n  const onStart = useCallback(() => {\n    intervalRef.current = setInterval(() => {\n      forceUpdate();\n    }, 1000);\n  }, [forceUpdate]);\n\n  const onStop = useCallback(() => {\n    clearInterval(intervalRef.current);\n  }, []);\n\n  useEffect(() => {\n    if (prevStartedAt) {\n      if (!value.startedAt) {\n        onStop();\n      }\n    } else if (value.startedAt) {\n      onStart();\n    }\n  }, [value.startedAt, prevStartedAt, onStart, onStop]);\n\n  useEffect(\n    () => () => {\n      onStop();\n    },\n    [onStop],\n  );\n\n  const contentNode = (\n    <span\n      className={classNames(\n        styles.wrapper,\n        styles[`wrapper${upperFirst(size)}`],\n        value.startedAt && styles.wrapperActive,\n        onClick && styles.wrapperHoverable,\n      )}\n    >\n      {formatStopwatch(value)}\n    </span>\n  );\n\n  const ElementType = as;\n\n  return onClick ? (\n    <ElementType type=\"button\" disabled={isDisabled} className={styles.button} onClick={onClick}>\n      {contentNode}\n    </ElementType>\n  ) : (\n    contentNode\n  );\n});\n\nStopwatchChip.propTypes = {\n  value: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  as: PropTypes.elementType,\n  size: PropTypes.oneOf(Object.values(Sizes)),\n  isDisabled: PropTypes.bool,\n  onClick: PropTypes.func,\n};\n\nStopwatchChip.defaultProps = {\n  as: 'button',\n  size: Sizes.MEDIUM,\n  isDisabled: false,\n  onClick: undefined,\n};\n\nexport default StopwatchChip;\n"
  },
  {
    "path": "client/src/components/cards/StopwatchChip/StopwatchChip.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    background: transparent;\n    border: none;\n    cursor: pointer;\n    display: inline-block;\n    outline: none;\n    padding: 0;\n  }\n\n  .wrapper {\n    background: #dce0e4;\n    border-radius: 3px;\n    color: #6a808b;\n    display: inline-block;\n    font-variant-numeric: tabular-nums;\n    transition: background 0.3s ease;\n  }\n\n  .wrapperActive {\n    background: #21ba45;\n    color: #fff;\n\n    &.wrapperHoverable:hover {\n      background: #16ab39;\n      color: #fff;\n    }\n  }\n\n  .wrapperHoverable:hover {\n    background: #d2d8dc;\n    color: #17394d;\n  }\n\n  /* Sizes */\n\n  .wrapperTiny {\n    font-size: 12px;\n    line-height: 20px;\n    padding: 0 6px;\n  }\n\n  .wrapperSmall {\n    font-size: 12px;\n    line-height: 20px;\n    padding: 2px 8px;\n  }\n\n  .wrapperMedium {\n    line-height: 20px;\n    padding: 6px 12px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/cards/StopwatchChip/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport StopwatchChip from './StopwatchChip';\n\nexport default StopwatchChip;\n"
  },
  {
    "path": "client/src/components/comments/Comments/Add.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport keyBy from 'lodash/keyBy';\nimport React, { useCallback, useState, useRef, useMemo } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Mention, MentionsInput } from 'react-mentions';\nimport { Button, Form } from 'semantic-ui-react';\nimport { useClickAwayListener, useDidUpdate, useToggle } from '../../../lib/hooks';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useEscapeInterceptor, useForm, useNestedRef } from '../../../hooks';\nimport { isUsernameChar, mentionTextToMarkup } from '../../../utils/mentions';\nimport { isModifierKeyPressed } from '../../../utils/event-helpers';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './Add.module.scss';\n\nconst DEFAULT_DATA = {\n  text: '',\n};\n\nconst Add = React.memo(() => {\n  const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [data, , setData] = useForm(DEFAULT_DATA);\n  const [isOpened, setIsOpened] = useState(false);\n  const [selectTextFieldState, selectTextField] = useToggle();\n\n  const textFieldRef = useRef(null);\n  const textMentionsRef = useRef(null);\n  const textInputRef = useRef(null);\n  const [buttonRef, handleButtonRef] = useNestedRef();\n\n  const userByUsername = useMemo(\n    () =>\n      keyBy(\n        boardMemberships.flatMap(({ user }) => (user.username ? user : [])),\n        'username',\n      ),\n    [boardMemberships],\n  );\n\n  const submit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      text: mentionTextToMarkup(data.text.trim(), userByUsername),\n    };\n\n    if (!cleanData.text) {\n      textInputRef.current.select();\n      return;\n    }\n\n    dispatch(entryActions.createCommentInCurrentCard(cleanData));\n    setData(DEFAULT_DATA);\n    selectTextField();\n  }, [dispatch, data, setData, selectTextField, userByUsername]);\n\n  const handleEscape = useCallback(() => {\n    if (textMentionsRef.current.isOpened()) {\n      textMentionsRef.current.clearSuggestions();\n      return;\n    }\n\n    setIsOpened(false);\n    textInputRef.current.blur();\n  }, []);\n\n  const [activateEscapeInterceptor, deactivateEscapeInterceptor] =\n    useEscapeInterceptor(handleEscape);\n\n  const handleSubmit = useCallback(() => {\n    submit();\n  }, [submit]);\n\n  const handleFieldFocus = useCallback(() => {\n    setIsOpened(true);\n  }, []);\n\n  const handleFieldChange = useCallback(\n    (_, text) => {\n      setData({\n        text: !isUsernameChar(text.slice(-1)) ? mentionTextToMarkup(text, userByUsername) : text,\n      });\n    },\n    [setData, userByUsername],\n  );\n\n  const handleFieldKeyDown = useCallback(\n    (event) => {\n      if (isModifierKeyPressed(event) && event.key === 'Enter') {\n        submit();\n      }\n    },\n    [submit],\n  );\n\n  const handleAwayClick = useCallback(() => {\n    setIsOpened(false);\n  }, []);\n\n  const handleClickAwayCancel = useCallback(() => {\n    textInputRef.current.focus();\n  }, []);\n\n  const clickAwayProps = useClickAwayListener(\n    [textFieldRef, buttonRef],\n    handleAwayClick,\n    handleClickAwayCancel,\n  );\n\n  const suggestionRenderer = useCallback(\n    (entry, _, highlightedDisplay) => (\n      <div className={styles.suggestion}>\n        <UserAvatar id={entry.id} size=\"tiny\" />\n        {highlightedDisplay}\n      </div>\n    ),\n    [],\n  );\n\n  useDidUpdate(() => {\n    if (isOpened) {\n      activateEscapeInterceptor();\n    } else {\n      deactivateEscapeInterceptor();\n    }\n  }, [isOpened]);\n\n  useDidUpdate(() => {\n    textInputRef.current.focus();\n  }, [selectTextFieldState]);\n\n  return (\n    <Form onSubmit={handleSubmit}>\n      <div ref={textFieldRef} className={styles.field}>\n        <MentionsInput\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          allowSpaceInQuery\n          allowSuggestionsAboveCursor\n          ref={textMentionsRef}\n          inputRef={textInputRef}\n          value={data.text}\n          placeholder={t('common.writeComment')}\n          maxLength={1048576}\n          rows={isOpened ? 3 : 1}\n          className=\"mentions-input\"\n          style={{\n            control: {\n              minHeight: isOpened ? '79px' : '37px',\n            },\n          }}\n          onFocus={handleFieldFocus}\n          onChange={handleFieldChange}\n          onKeyDown={handleFieldKeyDown}\n        >\n          <Mention\n            appendSpaceOnAdd\n            data={boardMemberships.map(({ user }) => ({\n              id: user.id,\n              display: user.username || user.name,\n            }))}\n            displayTransform={(_, display) => `@${display}`}\n            renderSuggestion={suggestionRenderer}\n            className={styles.mention}\n          />\n        </MentionsInput>\n      </div>\n      {(isOpened || data.text.length > 0) && (\n        <div className={styles.controls}>\n          <Button\n            {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n            positive\n            ref={handleButtonRef}\n            content={t('action.addComment')}\n            className={styles.button}\n          />\n        </div>\n      )}\n    </Form>\n  );\n});\n\nexport default Add;\n"
  },
  {
    "path": "client/src/components/comments/Comments/Add.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    white-space: nowrap;\n  }\n\n  .controls {\n    align-items: center;\n    display: flex;\n    font-size: 12px;\n    margin-top: 6px;\n  }\n\n  .field {\n    background: #fff;\n    border-radius: 3px;\n\n    textarea {\n      border: 1px solid rgba(9, 30, 66, 0.13);\n      border-radius: 3px;\n      color: #333;\n      line-height: 1.4;\n      margin: 0 !important;\n      padding: 8px 12px;\n    }\n  }\n\n  .mention {\n    background-color: #f1f8ff;\n    border-radius: 3px;\n  }\n\n  .suggestion {\n    align-items: center;\n    display: flex;\n    gap: 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/comments/Comments/Comments.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useInView } from 'react-intersection-observer';\nimport { Comment, Loader } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { isListArchiveOrTrash } from '../../../utils/record-helpers';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\nimport Item from './Item';\nimport Add from './Add';\n\nimport styles from './Comments.module.scss';\n\nconst Comments = React.memo(() => {\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const commentIds = useSelector(selectors.selectCommentIdsForCurrentCard);\n  const { isCommentsFetching, isAllCommentsFetched } = useSelector(selectors.selectCurrentCard);\n\n  const cadAdd = useSelector((state) => {\n    const { listId } = selectors.selectCurrentCard(state);\n    const list = selectListById(state, listId);\n\n    if (isListArchiveOrTrash(list)) {\n      return false;\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n\n    let isMember = false;\n    let isEditor = false;\n\n    if (boardMembership) {\n      isMember = true;\n      isEditor = boardMembership.role === BoardMembershipRoles.EDITOR;\n    }\n\n    return isEditor || (isMember && boardMembership.canComment);\n  });\n\n  const dispatch = useDispatch();\n\n  const [inViewRef] = useInView({\n    threshold: 1,\n    onChange: (inView) => {\n      if (inView) {\n        dispatch(entryActions.fetchCommentsInCurrentCard());\n      }\n    },\n  });\n\n  return (\n    <>\n      {cadAdd && <Add />}\n      <div className={styles.itemsWrapper}>\n        <Comment.Group className={styles.items}>\n          {commentIds.map((commentId) => (\n            <Item key={commentId} id={commentId} />\n          ))}\n        </Comment.Group>\n      </div>\n      {isCommentsFetching !== undefined && isAllCommentsFetched !== undefined && (\n        <div className={styles.loaderWrapper}>\n          {isCommentsFetching ? (\n            <Loader active inverted inline=\"centered\" size=\"small\" />\n          ) : (\n            !isAllCommentsFetched && <div ref={inViewRef} />\n          )}\n        </div>\n      )}\n    </>\n  );\n});\n\nexport default Comments;\n"
  },
  {
    "path": "client/src/components/comments/Comments/Comments.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .items {\n    max-width: none;\n  }\n\n  .itemsWrapper {\n    margin: 14px 0 14px -40px;\n  }\n\n  .loaderWrapper {\n    margin-top: 14px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/comments/Comments/Edit.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport { keyBy } from 'lodash';\nimport React, { useCallback, useEffect, useMemo, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Mention, MentionsInput } from 'react-mentions';\nimport { Button, Form } from 'semantic-ui-react';\nimport { useClickAwayListener } from '../../../lib/hooks';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\nimport { isUsernameChar, mentionTextToMarkup } from '../../../utils/mentions';\nimport { focusEnd } from '../../../utils/element-helpers';\nimport { isModifierKeyPressed } from '../../../utils/event-helpers';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './Edit.module.scss';\n\nconst Edit = React.memo(({ commentId, onClose }) => {\n  const selectCommentById = useMemo(() => selectors.makeSelectCommentById(), []);\n\n  const comment = useSelector((state) => selectCommentById(state, commentId));\n  const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultData = useMemo(\n    () => ({\n      text: comment.text,\n    }),\n    [comment.text],\n  );\n\n  const [data, , setData] = useForm(() => ({\n    text: '',\n    ...defaultData,\n  }));\n\n  const textFieldRef = useRef(null);\n  const textMentionsRef = useRef(null);\n  const textInputRef = useRef(null);\n  const [submitButtonRef, handleSubmitButtonRef] = useNestedRef();\n  const [cancelButtonRef, handleCancelButtonRef] = useNestedRef();\n\n  const userByUsername = useMemo(\n    () =>\n      keyBy(\n        boardMemberships.flatMap(({ user }) => (user.username ? user : [])),\n        'username',\n      ),\n    [boardMemberships],\n  );\n\n  const submit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      text: mentionTextToMarkup(data.text.trim(), userByUsername),\n    };\n\n    if (cleanData.text && !dequal(cleanData, defaultData)) {\n      dispatch(entryActions.updateComment(commentId, cleanData));\n    }\n\n    onClose();\n  }, [commentId, onClose, dispatch, defaultData, data, userByUsername]);\n\n  const handleSubmit = useCallback(() => {\n    submit();\n  }, [submit]);\n\n  const handleFieldChange = useCallback(\n    (_, text) => {\n      setData({\n        text: !isUsernameChar(text.slice(-1)) ? mentionTextToMarkup(text, userByUsername) : text,\n      });\n    },\n    [setData, userByUsername],\n  );\n\n  const handleFieldKeyDown = useCallback(\n    (event) => {\n      if (event.key === 'Enter') {\n        if (isModifierKeyPressed(event)) {\n          submit();\n        }\n      } else if (event.key === 'Escape') {\n        if (textMentionsRef.current.isOpened()) {\n          textMentionsRef.current.clearSuggestions();\n          return;\n        }\n\n        onClose();\n      }\n    },\n    [onClose, submit],\n  );\n\n  const handleCancelClick = useCallback(() => {\n    onClose();\n  }, [onClose]);\n\n  const handleClickAwayCancel = useCallback(() => {\n    textInputRef.current.focus();\n  }, []);\n\n  const clickAwayProps = useClickAwayListener(\n    [textFieldRef, submitButtonRef, cancelButtonRef],\n    submit,\n    handleClickAwayCancel,\n  );\n\n  const suggestionRenderer = useCallback(\n    (entry, _, highlightedDisplay) => (\n      <div className={styles.suggestion}>\n        <UserAvatar id={entry.id} size=\"tiny\" />\n        {highlightedDisplay}\n      </div>\n    ),\n    [],\n  );\n\n  useEffect(() => {\n    focusEnd(textInputRef.current);\n  }, []);\n\n  return (\n    <Form onSubmit={handleSubmit}>\n      <div ref={textFieldRef} className={styles.field}>\n        <MentionsInput\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          allowSpaceInQuery\n          allowSuggestionsAboveCursor\n          ref={textMentionsRef}\n          inputRef={textInputRef}\n          value={data.text}\n          maxLength={1048576}\n          rows={3}\n          className=\"mentions-input\"\n          style={{\n            control: {\n              minHeight: '79px',\n            },\n          }}\n          onChange={handleFieldChange}\n          onKeyDown={handleFieldKeyDown}\n        >\n          <Mention\n            appendSpaceOnAdd\n            data={boardMemberships.map(({ user }) => ({\n              id: user.id,\n              display: user.username || user.name,\n            }))}\n            displayTransform={(_, display) => `@${display}`}\n            renderSuggestion={suggestionRenderer}\n            className={styles.mention}\n          />\n        </MentionsInput>\n      </div>\n      <div className={styles.controls}>\n        <Button\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          positive\n          ref={handleSubmitButtonRef}\n          content={t('action.save')}\n        />\n        <Button\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          ref={handleCancelButtonRef}\n          type=\"button\"\n          content={t('action.cancel')}\n          onClick={handleCancelClick}\n        />\n      </div>\n    </Form>\n  );\n});\n\nEdit.propTypes = {\n  commentId: PropTypes.string.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default Edit;\n"
  },
  {
    "path": "client/src/components/comments/Comments/Edit.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .controls {\n    clear: both;\n    margin-top: 6px;\n  }\n\n  .field {\n    background: #fff;\n    border-radius: 3px;\n\n    textarea {\n      border: 1px solid rgba(9, 30, 66, 0.13);\n      border-radius: 3px;\n      color: #333;\n      line-height: 1.4;\n      margin: 0 !important;\n      padding: 8px 12px;\n    }\n  }\n\n  .mention {\n    background-color: #f1f8ff;\n    border-radius: 3px;\n  }\n\n  .suggestion {\n    align-items: center;\n    display: flex;\n    gap: 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/comments/Comments/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useContext, useMemo, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { shallowEqual, useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Comment } from 'semantic-ui-react';\nimport { useDidUpdate } from '../../../lib/hooks';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { usePopupInClosableContext } from '../../../hooks';\nimport { isListArchiveOrTrash, isUserStatic } from '../../../utils/record-helpers';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\nimport { ClosableContext } from '../../../contexts';\nimport Edit from './Edit';\nimport TimeAgo from '../../common/TimeAgo';\nimport Markdown from '../../common/Markdown';\nimport ConfirmationStep from '../../common/ConfirmationStep';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id }) => {\n  const selectCommentById = useMemo(() => selectors.makeSelectCommentById(), []);\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const comment = useSelector((state) => selectCommentById(state, id));\n  const user = useSelector((state) => selectUserById(state, comment.userId));\n\n  const isCurrentUser = useSelector(\n    (state) => comment.userId === selectors.selectCurrentUserId(state),\n  );\n\n  const { canEdit, canDelete } = useSelector((state) => {\n    const { listId } = selectors.selectCurrentCard(state);\n    const list = selectListById(state, listId);\n\n    if (isListArchiveOrTrash(list)) {\n      return {\n        canEdit: false,\n        canDelete: false,\n      };\n    }\n\n    const isManager = selectors.selectIsCurrentUserManagerForCurrentProject(state);\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n\n    let isMember = false;\n    let isEditor = false;\n\n    if (boardMembership) {\n      isMember = true;\n      isEditor = boardMembership.role === BoardMembershipRoles.EDITOR;\n    }\n\n    const canEditOrDeleteAsMember =\n      isMember &&\n      comment.userId === boardMembership.userId &&\n      (isEditor || boardMembership.canComment);\n\n    return {\n      canEdit: canEditOrDeleteAsMember,\n      canDelete: isManager || canEditOrDeleteAsMember,\n    };\n  }, shallowEqual);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [isEditOpened, setIsEditOpened] = useState(false);\n  const [, , setIsClosableActive] = useContext(ClosableContext);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteComment(id));\n  }, [id, dispatch]);\n\n  const handleEditClick = useCallback(() => {\n    setIsEditOpened(true);\n  }, []);\n\n  const handleEditClose = useCallback(() => {\n    setIsEditOpened(false);\n  }, []);\n\n  useDidUpdate(() => {\n    setIsClosableActive(isEditOpened);\n  }, [isEditOpened]);\n\n  const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);\n\n  return (\n    <Comment>\n      {!isCurrentUser && (\n        <span className={styles.user}>\n          <UserAvatar id={comment.userId} />\n        </span>\n      )}\n      <div className={classNames(styles.content, isCurrentUser && styles.contentWithoutUser)}>\n        {isEditOpened ? (\n          <Edit commentId={id} onClose={handleEditClose} />\n        ) : (\n          <div className={classNames(styles.bubble, isCurrentUser && styles.bubbleRight)}>\n            <div className={styles.header}>\n              {isUserStatic(user)\n                ? t(`common.${user.name}`, {\n                    context: 'title',\n                  })\n                : user.name}\n            </div>\n            <Markdown>{comment.text}</Markdown>\n            <Comment.Actions className={styles.information}>\n              <span className={styles.date}>\n                <TimeAgo date={comment.createdAt} />\n              </span>\n              {(canEdit || canDelete) && (\n                <span className={styles.actions}>\n                  {canEdit && (\n                    <Comment.Action\n                      as=\"button\"\n                      content={t('action.edit')}\n                      disabled={!comment.isPersisted}\n                      onClick={handleEditClick}\n                    />\n                  )}\n                  {canDelete && (\n                    <ConfirmationPopup\n                      title=\"common.deleteComment\"\n                      content=\"common.areYouSureYouWantToDeleteThisComment\"\n                      buttonContent=\"action.deleteComment\"\n                      onConfirm={handleDeleteConfirm}\n                    >\n                      <Comment.Action\n                        as=\"button\"\n                        content={t('action.delete')}\n                        disabled={!comment.isPersisted}\n                      />\n                    </ConfirmationPopup>\n                  )}\n                </span>\n              )}\n            </Comment.Actions>\n          </div>\n        )}\n      </div>\n    </Comment>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/comments/Comments/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actions {\n    margin-left: 20px;\n  }\n\n  .bubble {\n    background: #fff;\n    border-radius: 0 8px 8px;\n    box-shadow: 0 1px 2px -1px rgba(9, 30, 66, 0.25),\n      0 0 0 1px rgba(9, 30, 66, 0.08);\n    box-sizing: border-box;\n    display: inline-block;\n    line-height: 1;\n    max-width: 90%;\n    min-width: 40%;\n    overflow: hidden;\n    padding: 8px 12px;\n  }\n\n  .bubbleRight {\n    background: #dee7f4;\n    border-radius: 8px 0 8px 8px;\n    float: right;\n  }\n\n  .content {\n    display: inline-block;\n    line-height: 0;\n    vertical-align: top;\n    width: calc(100% - 40px);\n  }\n\n  .contentWithoutUser {\n    margin-left: 40px;\n  }\n\n  .date {\n    color: #6b808c;\n    font-size: 12px;\n  }\n\n  .header {\n    color: #17394d;\n    display: flex;\n    font-weight: bold;\n    justify-content: space-between;\n    line-height: 20px;\n    margin-bottom: 4px;\n  }\n\n  .information {\n    display: flex;\n    justify-content: space-between;\n    margin-top: 8px;\n  }\n\n  .user {\n    display: inline-block;\n    padding: 4px 8px 4px 0;\n    position: sticky;\n    top: 0;\n    vertical-align: top;\n  }\n}\n"
  },
  {
    "path": "client/src/components/comments/Comments/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Comments from './Comments';\n\nexport default Comments;\n"
  },
  {
    "path": "client/src/components/common/AboutModal/AboutModal.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Tab } from 'semantic-ui-react';\n\nimport entryActions from '../../../entry-actions';\nimport { useClosableModal } from '../../../hooks';\nimport TermsPane from './TermsPane';\nimport AboutPane from './AboutPane';\n\nimport styles from './AboutModal.module.scss';\n\nconst AboutModal = React.memo(() => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleClose = useCallback(() => {\n    dispatch(entryActions.closeModal());\n  }, [dispatch]);\n\n  const [ClosableModal] = useClosableModal();\n\n  const panes = [\n    {\n      menuItem: t('common.aboutPlanka', {\n        context: 'title',\n      }),\n      render: () => <AboutPane />,\n    },\n    {\n      menuItem: t('common.termsOfService', {\n        context: 'title',\n      }),\n      render: () => <TermsPane />,\n    },\n  ];\n\n  return (\n    <ClosableModal\n      open\n      closeIcon\n      size=\"small\"\n      centered={false}\n      className={styles.wrapper}\n      onClose={handleClose}\n    >\n      <ClosableModal.Content>\n        <Tab\n          menu={{\n            secondary: true,\n            pointing: true,\n          }}\n          panes={panes}\n        />\n      </ClosableModal.Content>\n    </ClosableModal>\n  );\n});\n\nexport default AboutModal;\n"
  },
  {
    "path": "client/src/components/common/AboutModal/AboutModal.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    margin: 2rem auto;\n\n    @media (width < 926px) {\n      margin: 1rem auto;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AboutModal/AboutPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useEffect, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { Divider, Header, Image, Loader, Tab } from 'semantic-ui-react';\n\nimport version from '../../../version';\nimport Markdown from '../Markdown';\n\nimport aboutLogo from '../../../assets/images/about-logo.png';\nimport whatsNewUrl from '../../../assets/docs/whats-new.md?url';\n\nimport styles from './AboutPane.module.scss';\n\nconst AboutPane = React.memo(() => {\n  const [t] = useTranslation();\n  const [whatsNew, setWhatsNew] = useState(null);\n\n  useEffect(() => {\n    async function fetchWhatsNew() {\n      let response;\n      try {\n        response = await fetch(whatsNewUrl);\n      } catch {\n        return;\n      }\n\n      const text = await response.text();\n      setWhatsNew(text);\n    }\n\n    fetchWhatsNew();\n  }, []);\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <a href=\"https://github.com/plankanban/planka\" target=\"_blank\" rel=\"noreferrer\">\n        <Image centered src={aboutLogo} size=\"large\" />\n      </a>\n      <div className={styles.version}>Community v{version}</div>\n      <Divider horizontal>\n        <Header as=\"h4\">\n          {t('common.whatsNew', {\n            context: 'title',\n          })}\n        </Header>\n      </Divider>\n      {whatsNew ? (\n        <Markdown>{whatsNew}</Markdown>\n      ) : (\n        <Loader active inverted inline=\"centered\" size=\"small\" />\n      )}\n    </Tab.Pane>\n  );\n});\n\nexport default AboutPane;\n"
  },
  {
    "path": "client/src/components/common/AboutModal/AboutPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .version {\n    font-weight: bold;\n    margin-bottom: 40px;\n    text-align: center;\n  }\n\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AboutModal/TermsPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useEffect, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { Loader, Tab } from 'semantic-ui-react';\n\nimport api from '../../../api';\nimport Markdown from '../Markdown';\n\nimport styles from './TermsPane.module.scss';\n\nconst TermsPane = React.memo(() => {\n  const { i18n } = useTranslation();\n  const [content, setContent] = useState(null);\n\n  useEffect(() => {\n    async function fetchTerms() {\n      let terms;\n      try {\n        ({ item: terms } = await api.getTerms(i18n.resolvedLanguage));\n      } catch {\n        return;\n      }\n\n      setContent(terms.content);\n    }\n\n    fetchTerms();\n  }, [i18n.resolvedLanguage]);\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      {content ? (\n        <Markdown>{content}</Markdown>\n      ) : (\n        <Loader active inverted inline=\"centered\" size=\"small\" />\n      )}\n    </Tab.Pane>\n  );\n});\n\nexport default TermsPane;\n"
  },
  {
    "path": "client/src/components/common/AboutModal/TermsPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AboutModal/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AboutModal from './AboutModal';\n\nexport default AboutModal;\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/AdministrationModal.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useState } from 'react';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Modal, Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useClosableModal } from '../../../hooks';\nimport UsersPane from './UsersPane';\nimport SmtpPane from './SmtpPane';\nimport WebhooksPane from './WebhooksPane';\n\nimport styles from './AdministrationModal.module.scss';\n\nconst AdministrationModal = React.memo(() => {\n  const config = useSelector(selectors.selectConfig);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [activeTabIndex, setActiveTabIndex] = useState(0);\n\n  const handleClose = useCallback(() => {\n    dispatch(entryActions.closeModal());\n  }, [dispatch]);\n\n  const handleTabChange = useCallback((_, { activeIndex }) => {\n    setActiveTabIndex(activeIndex);\n  }, []);\n\n  const [ClosableModal] = useClosableModal();\n\n  const panes = [\n    {\n      menuItem: t('common.users', {\n        context: 'title',\n      }),\n      render: () => <UsersPane />,\n    },\n  ];\n  if (config.smtpHost !== undefined) {\n    panes.push({\n      menuItem: t('common.smtp', {\n        context: 'title',\n      }),\n      render: () => <SmtpPane />,\n    });\n  }\n  panes.push({\n    menuItem: t('common.webhooks', {\n      context: 'title',\n    }),\n    render: () => <WebhooksPane />,\n  });\n\n  const isUsersPaneActive = activeTabIndex === 0;\n\n  return (\n    <ClosableModal\n      closeIcon\n      size={isUsersPaneActive ? 'large' : 'small'}\n      centered={false}\n      className={classNames(isUsersPaneActive && styles.wrapperUsers)}\n      onClose={handleClose}\n    >\n      <Modal.Content>\n        <Tab\n          menu={{\n            secondary: true,\n            pointing: true,\n          }}\n          panes={panes}\n          onTabChange={handleTabChange}\n        />\n      </Modal.Content>\n    </ClosableModal>\n  );\n});\n\nexport default AdministrationModal;\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/AdministrationModal.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapperUsers {\n    margin: 2rem auto;\n\n    @media (width < 926px) {\n      margin: 1rem auto;\n    }\n\n    @media (768px <= width < 1160px) {\n      width: 88%;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/SmtpPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport omit from 'lodash/omit';\nimport React, { useCallback, useMemo, useRef } from 'react';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport TextareaAutosize from 'react-textarea-autosize';\nimport { Button, Checkbox, Divider, Form, Header, Tab, TextArea } from 'semantic-ui-react';\nimport { Input } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\n\nimport styles from './SmtpPane.module.scss';\n\nconst SmtpPane = React.memo(() => {\n  const config = useSelector(selectors.selectConfig);\n  const smtpTestState = useSelector(selectors.selectSmtpTestState);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const [passwordFieldRef, handlePasswordFieldRef] = useNestedRef('inputRef');\n  const isPasswordTouchedRef = useRef(false);\n\n  const defaultData = useMemo(\n    () => ({\n      smtpHost: config.smtpHost,\n      smtpPort: config.smtpPort,\n      smtpName: config.smtpName,\n      smtpSecure: config.smtpSecure,\n      smtpTlsRejectUnauthorized: config.smtpTlsRejectUnauthorized,\n      smtpUser: config.smtpUser,\n      smtpPassword: config.smtpPassword,\n      smtpFrom: config.smtpFrom,\n    }),\n    [config],\n  );\n\n  const [data, handleFieldChange] = useForm(() => ({\n    ...defaultData,\n    smtpHost: defaultData.smtpHost || '',\n    smtpPort: defaultData.smtpPort === null ? '' : `${defaultData.smtpPort}`,\n    smtpName: defaultData.smtpName || '',\n    smtpSecure: defaultData.smtpSecure,\n    smtpTlsRejectUnauthorized: defaultData.smtpTlsRejectUnauthorized,\n    smtpUser: defaultData.smtpUser || '',\n    smtpPassword: defaultData.smtpPassword || '',\n    smtpFrom: defaultData.smtpFrom || '',\n  }));\n\n  const isPasswordSet = defaultData.smtpPassword === undefined;\n\n  const cleanData = useMemo(\n    () => ({\n      ...data,\n      smtpHost: data.smtpHost.trim() || null,\n      smtpPort: parseInt(data.smtpPort, 10) || null,\n      smtpName: data.smtpName.trim() || null,\n      smtpUser: data.smtpUser.trim() || null,\n      smtpPassword: data.smtpPassword || (isPasswordSet ? undefined : null),\n      smtpFrom: data.smtpFrom.trim() || null,\n    }),\n    [data, isPasswordSet],\n  );\n\n  const isModified = useMemo(() => {\n    const cleanDataToCheck = omit(cleanData, 'smtpPassword');\n    const defaultDataToCheck = omit(defaultData, 'smtpPassword');\n\n    return !dequal(cleanDataToCheck, defaultDataToCheck) || isPasswordTouchedRef.current;\n  }, [defaultData, cleanData]);\n\n  const handleSubmit = useCallback(() => {\n    isPasswordTouchedRef.current = false;\n    dispatch(entryActions.updateConfig(cleanData));\n  }, [dispatch, cleanData]);\n\n  const handlePasswordClear = useCallback(() => {\n    dispatch(\n      entryActions.updateConfig({\n        smtpPassword: null,\n      }),\n    );\n\n    passwordFieldRef.current.focus();\n  }, [dispatch, passwordFieldRef]);\n\n  const handleTestClick = useCallback(() => {\n    dispatch(entryActions.testSmtpConfig());\n  }, [dispatch]);\n\n  const handlePasswordChange = useCallback(\n    (event, { value, ...props }) => {\n      isPasswordTouchedRef.current = value !== '';\n      handleFieldChange(event, { value, ...props });\n    },\n    [handleFieldChange],\n  );\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <Form onSubmit={handleSubmit}>\n        <div className={styles.text}>{t('common.host')}</div>\n        <Input\n          fluid\n          name=\"smtpHost\"\n          value={data.smtpHost}\n          maxLength={256}\n          className={styles.field}\n          onChange={handleFieldChange}\n        />\n        <div className={styles.text}>{t('common.port')}</div>\n        <Input\n          fluid\n          type=\"number\"\n          name=\"smtpPort\"\n          value={data.smtpPort}\n          placeholder={data.smtpSecure ? '465' : '587'}\n          min={0}\n          max={65535}\n          step={1}\n          className={styles.field}\n          onChange={handleFieldChange}\n        />\n        <div className={styles.text}>\n          {t('common.clientHostnameInEhlo')} (\n          {t('common.optional', {\n            context: 'inline',\n          })}\n          )\n        </div>\n        <Input\n          fluid\n          name=\"smtpName\"\n          value={data.smtpName}\n          maxLength={256}\n          className={styles.field}\n          onChange={handleFieldChange}\n        />\n        <Checkbox\n          name=\"smtpSecure\"\n          checked={data.smtpSecure}\n          label={t('common.useSecureConnection')}\n          className={styles.checkbox}\n          onChange={handleFieldChange}\n        />\n        <Checkbox\n          name=\"smtpTlsRejectUnauthorized\"\n          checked={data.smtpTlsRejectUnauthorized}\n          label={t('common.rejectUnauthorizedTlsCertificates')}\n          className={classNames(styles.field, styles.checkbox)}\n          onChange={handleFieldChange}\n        />\n        <div className={styles.text}>\n          {t('common.username')} (\n          {t('common.optional', {\n            context: 'inline',\n          })}\n          )\n        </div>\n        <Input\n          fluid\n          name=\"smtpUser\"\n          value={data.smtpUser}\n          maxLength={256}\n          className={styles.field}\n          onChange={handleFieldChange}\n        />\n        <div className={styles.text}>\n          {t('common.password')} (\n          {t('common.optional', {\n            context: 'inline',\n          })}\n          )\n        </div>\n        <Input.Password\n          fluid\n          ref={handlePasswordFieldRef}\n          name=\"smtpPassword\"\n          value={data.smtpPassword}\n          placeholder={isPasswordSet ? t('common.passwordIsSet') : undefined}\n          maxLength={256}\n          className={styles.field}\n          onClear={!data.smtpPassword && isPasswordSet ? handlePasswordClear : undefined}\n          onChange={handlePasswordChange}\n        />\n        <div className={styles.text}>\n          {t('common.defaultFrom')} (\n          {t('common.optional', {\n            context: 'inline',\n          })}\n          )\n        </div>\n        <Input\n          fluid\n          name=\"smtpFrom\"\n          value={data.smtpFrom}\n          maxLength={256}\n          className={styles.field}\n          onChange={handleFieldChange}\n        />\n        <div className={styles.controls}>\n          <Button positive disabled={!isModified} content={t('action.save')} />\n          {config.smtpHost && !isModified && (\n            <Button\n              type=\"button\"\n              content={t('action.sendTestEmail')}\n              loading={smtpTestState.isLoading}\n              disabled={smtpTestState.isLoading}\n              onClick={handleTestClick}\n            />\n          )}\n        </div>\n      </Form>\n      {smtpTestState.logs && (\n        <>\n          <Divider horizontal>\n            <Header as=\"h4\">\n              {t('common.testLog', {\n                context: 'title',\n              })}\n            </Header>\n          </Divider>\n          <TextArea\n            readOnly\n            as={TextareaAutosize}\n            value={smtpTestState.logs.join('\\n')}\n            className={styles.testLog}\n          />\n        </>\n      )}\n    </Tab.Pane>\n  );\n});\n\nexport default SmtpPane;\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/SmtpPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .checkbox {\n    display: block;\n    padding: 8px 0;\n  }\n\n  .controls {\n    display: flex;\n    justify-content: space-between;\n  }\n\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .testLog {\n    border: 1px solid rgba(9, 30, 66, 0.13);\n    border-radius: 3px;\n    color: #333;\n    line-height: 1.4;\n    padding: 8px 12px;\n    width: 100%;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/ActionsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../../lib/custom-ui';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { useSteps } from '../../../../hooks';\nimport SelectRoleStep from './SelectRoleStep';\nimport ApiKeyStep from './ApiKeyStep';\nimport ConfirmationStep from '../../ConfirmationStep';\nimport EditUserInformationStep from '../../../users/EditUserInformationStep';\nimport EditUserUsernameStep from '../../../users/EditUserUsernameStep';\nimport EditUserEmailStep from '../../../users/EditUserEmailStep';\nimport EditUserPasswordStep from '../../../users/EditUserPasswordStep';\n\nimport styles from './ActionsStep.module.scss';\n\nconst StepTypes = {\n  EDIT_INFORMATION: 'EDIT_INFORMATION',\n  EDIT_USERNAME: 'EDIT_USERNAME',\n  EDIT_EMAIL: 'EDIT_EMAIL',\n  EDIT_PASSWORD: 'EDIT_PASSWORD',\n  EDIT_ROLE: 'EDIT_ROLE',\n  API_KEY: 'API_KEY',\n  UNLINK_SSO: 'UNLINK_SSO',\n  ACTIVATE: 'ACTIVATE',\n  DEACTIVATE: 'DEACTIVATE',\n  DELETE: 'DELETE',\n};\n\nconst ActionsStep = React.memo(({ userId, onClose }) => {\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const activeUsersLimit = useSelector(selectors.selectActiveUsersLimit);\n  const activeUsersTotal = useSelector(selectors.selectActiveUsersTotal);\n  const user = useSelector((state) => selectUserById(state, userId));\n  const isCurrentUser = useSelector((state) => user.id === selectors.selectCurrentUserId(state));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n\n  const handleRoleSelect = useCallback(\n    (role) => {\n      dispatch(\n        entryActions.updateUser(userId, {\n          role,\n        }),\n      );\n    },\n    [userId, dispatch],\n  );\n\n  const handleUnlinkSsoConfirm = useCallback(() => {\n    dispatch(\n      entryActions.updateUser(userId, {\n        isSsoUser: false,\n      }),\n    );\n\n    onClose();\n  }, [userId, onClose, dispatch]);\n\n  const handleActivateConfirm = useCallback(() => {\n    dispatch(\n      entryActions.updateUser(userId, {\n        isDeactivated: false,\n      }),\n    );\n\n    onClose();\n  }, [userId, onClose, dispatch]);\n\n  const handleDeactivateConfirm = useCallback(() => {\n    dispatch(\n      entryActions.updateUser(userId, {\n        isDeactivated: true,\n      }),\n    );\n\n    onClose();\n  }, [userId, onClose, dispatch]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteUser(userId));\n  }, [userId, dispatch]);\n\n  const handleEditInformationClick = useCallback(() => {\n    openStep(StepTypes.EDIT_INFORMATION);\n  }, [openStep]);\n\n  const handleEditUsernameClick = useCallback(() => {\n    openStep(StepTypes.EDIT_USERNAME);\n  }, [openStep]);\n\n  const handleEditEmailClick = useCallback(() => {\n    openStep(StepTypes.EDIT_EMAIL);\n  }, [openStep]);\n\n  const handleEditPasswordClick = useCallback(() => {\n    openStep(StepTypes.EDIT_PASSWORD);\n  }, [openStep]);\n\n  const handleEditRoleClick = useCallback(() => {\n    openStep(StepTypes.EDIT_ROLE);\n  }, [openStep]);\n\n  const handleApiKeyClick = useCallback(() => {\n    openStep(StepTypes.API_KEY);\n  }, [openStep]);\n\n  const handleUnlinkSsoClick = useCallback(() => {\n    openStep(StepTypes.UNLINK_SSO);\n  }, [openStep]);\n\n  const handleActivateClick = useCallback(() => {\n    openStep(StepTypes.ACTIVATE);\n  }, [openStep]);\n\n  const handleDeactivateClick = useCallback(() => {\n    openStep(StepTypes.DEACTIVATE);\n  }, [openStep]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.EDIT_INFORMATION:\n        return <EditUserInformationStep id={userId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.EDIT_USERNAME:\n        return <EditUserUsernameStep id={userId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.EDIT_EMAIL:\n        return <EditUserEmailStep id={userId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.EDIT_PASSWORD:\n        return <EditUserPasswordStep id={userId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.EDIT_ROLE:\n        return (\n          <SelectRoleStep\n            withButton\n            defaultValue={user.role}\n            title=\"common.editRole\"\n            buttonContent=\"action.save\"\n            onSelect={handleRoleSelect}\n            onBack={handleBack}\n            onClose={onClose}\n          />\n        );\n      case StepTypes.API_KEY:\n        return <ApiKeyStep userId={userId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.UNLINK_SSO:\n        return (\n          <ConfirmationStep\n            title=\"common.unlinkSso\"\n            content=\"common.areYouSureYouWantToUnlinkSsoFromThisUser\"\n            buttonContent=\"action.unlinkSso\"\n            onConfirm={handleUnlinkSsoConfirm}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.ACTIVATE:\n        return (\n          <ConfirmationStep\n            title=\"common.activateUser\"\n            content=\"common.areYouSureYouWantToActivateThisUser\"\n            buttonType=\"positive\"\n            buttonContent=\"action.activateUser\"\n            onConfirm={handleActivateConfirm}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.DEACTIVATE:\n        return (\n          <ConfirmationStep\n            title=\"common.deactivateUser\"\n            content=\"common.areYouSureYouWantToDeactivateThisUser\"\n            buttonContent=\"action.deactivateUser\"\n            onConfirm={handleDeactivateConfirm}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.DELETE:\n        return (\n          <ConfirmationStep\n            title=\"common.deleteUser\"\n            content=\"common.areYouSureYouWantToDeleteThisUser\"\n            buttonContent=\"action.deleteUser\"\n            typeValue={user.name}\n            typeContent=\"common.typeNameToConfirm\"\n            onConfirm={handleDeleteConfirm}\n            onBack={handleBack}\n          />\n        );\n      default:\n    }\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.userActions', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Menu secondary vertical className={styles.menu}>\n          <Menu.Item className={styles.menuItem} onClick={handleEditInformationClick}>\n            <Icon name=\"info\" className={styles.menuItemIcon} />\n            {t('action.editInformation', {\n              context: 'title',\n            })}\n          </Menu.Item>\n          {!user.lockedFieldNames.includes('username') && (\n            <Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>\n              <Icon name=\"at\" className={styles.menuItemIcon} />\n              {t('action.editUsername', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {!user.lockedFieldNames.includes('email') && (\n            <Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}>\n              <Icon name=\"mail outline\" className={styles.menuItemIcon} />\n              {t('action.editEmail', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {!user.lockedFieldNames.includes('password') && (\n            <Menu.Item className={styles.menuItem} onClick={handleEditPasswordClick}>\n              <Icon name=\"keyboard outline\" className={styles.menuItemIcon} />\n              {t('action.editPassword', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {!user.lockedFieldNames.includes('role') && !isCurrentUser && (\n            <Menu.Item className={styles.menuItem} onClick={handleEditRoleClick}>\n              <Icon name=\"sun outline\" className={styles.menuItemIcon} />\n              {t('action.editRole', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          <Menu.Item className={styles.menuItem} onClick={handleApiKeyClick}>\n            <Icon name=\"key\" className={styles.menuItemIcon} />\n            {t('common.apiKey', {\n              context: 'title',\n            })}\n          </Menu.Item>\n          {user.isSsoUser && !user.lockedFieldNames.includes('isSsoUser') && !isCurrentUser && (\n            <Menu.Item className={styles.menuItem} onClick={handleUnlinkSsoClick}>\n              <Icon name=\"unlink\" className={styles.menuItemIcon} />\n              {t('action.unlinkSso', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          {!isCurrentUser && (\n            <>\n              <Menu.Item\n                disabled={\n                  user.isDeactivated &&\n                  activeUsersLimit !== null &&\n                  activeUsersTotal >= activeUsersLimit\n                }\n                className={styles.menuItem}\n                onClick={user.isDeactivated ? handleActivateClick : handleDeactivateClick}\n              >\n                <Icon\n                  name={user.isDeactivated ? 'plus' : 'close'}\n                  className={styles.menuItemIcon}\n                />\n                {user.isDeactivated\n                  ? t('action.activateUser', {\n                      context: 'title',\n                    })\n                  : t('action.deactivateUser', {\n                      context: 'title',\n                    })}\n              </Menu.Item>\n              {user.isDeactivated && !user.isDefaultAdmin && (\n                <Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>\n                  <Icon name=\"trash alternate outline\" className={styles.menuItemIcon} />\n                  {t('action.deleteUser', {\n                    context: 'title',\n                  })}\n                </Menu.Item>\n              )}\n            </>\n          )}\n        </Menu>\n      </Popup.Content>\n    </>\n  );\n});\n\nActionsStep.propTypes = {\n  userId: PropTypes.string.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default ActionsStep;\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/ActionsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menu {\n    margin: 0 -12px -5px;\n    width: calc(100% + 24px);\n  }\n\n  .menuItem {\n    margin: 0;\n    padding-left: 14px;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.5em 0 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/AddStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport isEmail from 'validator/lib/isEmail';\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Icon, Message } from 'semantic-ui-react';\nimport { useDidUpdate, usePrevious } from '../../../../lib/hooks';\nimport { Input, Popup } from '../../../../lib/custom-ui';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { useForm, useNestedRef, useSteps } from '../../../../hooks';\nimport { isPassword, isUsername } from '../../../../utils/validator';\nimport { UserRoles } from '../../../../constants/Enums';\nimport { UserRoleIcons } from '../../../../constants/Icons';\nimport SelectRoleStep from './SelectRoleStep';\n\nimport styles from './AddStep.module.scss';\n\nconst StepTypes = {\n  SELECT_ROLE: 'SELECT_ROLE',\n};\n\nconst createMessage = (error) => {\n  if (!error) {\n    return error;\n  }\n\n  switch (error.message) {\n    case 'Email already in use':\n      return {\n        type: 'error',\n        content: 'common.emailAlreadyInUse',\n      };\n    case 'Username already in use':\n      return {\n        type: 'error',\n        content: 'common.usernameAlreadyInUse',\n      };\n    default:\n      return {\n        type: 'warning',\n        content: 'common.unknownError',\n      };\n  }\n};\n\nconst AddStep = React.memo(({ onClose }) => {\n  const { data: defaultData, isSubmitting, error } = useSelector(selectors.selectUserCreateForm);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const wasSubmitting = usePrevious(isSubmitting);\n\n  const [data, handleFieldChange, setData] = useForm(() => ({\n    email: '',\n    password: '',\n    name: '',\n    username: '',\n    role: UserRoles.BOARD_USER,\n    ...defaultData,\n  }));\n\n  const [step, openStep, handleBack] = useSteps();\n  const message = useMemo(() => createMessage(error), [error]);\n\n  const [emailFieldRef, handleEmailFieldRef] = useNestedRef('inputRef');\n  const [passwordFieldRef, handlePasswordFieldRef] = useNestedRef('inputRef');\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n  const [usernameFieldRef, handleUsernameFieldRef] = useNestedRef('inputRef');\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      email: data.email.trim(),\n      name: data.name.trim(),\n      username: data.username.trim() || null,\n    };\n\n    if (!isEmail(cleanData.email)) {\n      emailFieldRef.current.select();\n      return;\n    }\n\n    if (!cleanData.password || !isPassword(cleanData.password)) {\n      passwordFieldRef.current.focus();\n      return;\n    }\n\n    if (!cleanData.name) {\n      nameFieldRef.current.select();\n      return;\n    }\n\n    if (cleanData.username && !isUsername(cleanData.username)) {\n      usernameFieldRef.current.select();\n      return;\n    }\n\n    dispatch(entryActions.createUser(cleanData));\n  }, [dispatch, data, emailFieldRef, passwordFieldRef, nameFieldRef, usernameFieldRef]);\n\n  const handleRoleSelect = useCallback(\n    (role) => {\n      setData((prevData) => ({\n        ...prevData,\n        role,\n      }));\n    },\n    [setData],\n  );\n\n  const handleMessageDismiss = useCallback(() => {\n    dispatch(entryActions.clearUserCreateError());\n  }, [dispatch]);\n\n  const handleSelectRoleClick = useCallback(() => {\n    openStep(StepTypes.SELECT_ROLE);\n  }, [openStep]);\n\n  useEffect(() => {\n    emailFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [emailFieldRef]);\n\n  useDidUpdate(() => {\n    if (wasSubmitting && !isSubmitting) {\n      if (error) {\n        switch (error.message) {\n          case 'Email already in use':\n            emailFieldRef.current.select();\n\n            break;\n          case 'Username already in use':\n            usernameFieldRef.current.select();\n\n            break;\n          default:\n        }\n      } else {\n        onClose();\n      }\n    }\n  }, [onClose, isSubmitting, wasSubmitting, error]);\n\n  if (step && step.type === StepTypes.SELECT_ROLE) {\n    return (\n      <SelectRoleStep defaultValue={data.role} onSelect={handleRoleSelect} onBack={handleBack} />\n    );\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.addUser', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        {message && (\n          <Message\n            {...{\n              [message.type]: true,\n            }}\n            visible\n            content={t(message.content)}\n            onDismiss={handleMessageDismiss}\n          />\n        )}\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.email')}</div>\n          <Input\n            fluid\n            ref={handleEmailFieldRef}\n            name=\"email\"\n            value={data.email}\n            maxLength={256}\n            readOnly={isSubmitting}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          <div className={styles.text}>{t('common.password')}</div>\n          <Input.Password\n            withStrengthBar\n            fluid\n            ref={handlePasswordFieldRef}\n            name=\"password\"\n            value={data.password}\n            maxLength={256}\n            readOnly={isSubmitting}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          <div className={styles.text}>{t('common.name')}</div>\n          <Input\n            fluid\n            ref={handleNameFieldRef}\n            name=\"name\"\n            value={data.name}\n            maxLength={128}\n            readOnly={isSubmitting}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          <div className={styles.text}>\n            {t('common.username')} (\n            {t('common.optional', {\n              context: 'inline',\n            })}\n            )\n          </div>\n          <Input\n            fluid\n            ref={handleUsernameFieldRef}\n            name=\"username\"\n            value={data.username}\n            maxLength={32}\n            readOnly={isSubmitting}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          <div className={styles.controls}>\n            <Button\n              positive\n              content={t('action.addUser')}\n              loading={isSubmitting}\n              disabled={isSubmitting}\n              className={styles.button}\n            />\n            <Button\n              type=\"button\"\n              className={classNames(styles.button, styles.selectRoleButton)}\n              onClick={handleSelectRoleClick}\n            >\n              <Icon name={UserRoleIcons[data.role]} className={styles.selectRoleButtonIcon} />\n              {t(`common.${data.role}`)}\n            </Button>\n          </div>\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nAddStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default AddStep;\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/AddStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    white-space: nowrap;\n  }\n\n  .controls {\n    display: flex;\n    max-width: 280px;\n\n    @media only screen and (width < 768px) {\n      max-width: 226px;\n    }\n  }\n\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .selectRoleButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-left: auto;\n    margin-right: 0;\n    overflow: hidden;\n    text-align: left;\n    text-decoration: underline;\n    text-overflow: ellipsis;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .selectRoleButtonIcon {\n    text-decoration: none;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/ApiKeyStep.jsx",
    "content": "/*!\n * Copyright (c) 2025 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Icon, Input, Message } from 'semantic-ui-react';\nimport { Popup } from '../../../../lib/custom-ui';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { useSteps } from '../../../../hooks';\nimport ConfirmationStep from '../../ConfirmationStep';\n\nimport styles from './ApiKeyStep.module.scss';\n\nconst StepTypes = {\n  REGENERATE: 'REGENERATE',\n  DELETE: 'DELETE',\n};\n\nconst ApiKeyStep = React.memo(({ userId, onBack, onClose }) => {\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const user = useSelector((state) => selectUserById(state, userId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n  const [isCopied, setIsCopied] = useState(false);\n\n  const handleGenerateClick = useCallback(() => {\n    if (user.apiKeyPrefix) {\n      openStep(StepTypes.REGENERATE);\n    } else {\n      dispatch(entryActions.createUserApiKey(userId));\n    }\n  }, [userId, user.apiKeyPrefix, dispatch, openStep]);\n\n  const handleRegenerateConfirm = useCallback(() => {\n    dispatch(entryActions.createUserApiKey(userId));\n    handleBack();\n  }, [userId, dispatch, handleBack]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteUserApiKey(userId));\n    onClose();\n  }, [userId, onClose, dispatch]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  const handleCopyClick = useCallback(() => {\n    if (isCopied) {\n      return;\n    }\n\n    navigator.clipboard.writeText(user.apiKeyState.value);\n\n    setIsCopied(true);\n    setTimeout(() => {\n      setIsCopied(false);\n    }, 1000);\n  }, [user.apiKeyState.value, isCopied]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.REGENERATE:\n        return (\n          <ConfirmationStep\n            title=\"common.regenerateApiKey\"\n            content=\"common.areYouSureYouWantToRegenerateThisApiKey\"\n            buttonContent=\"action.regenerateApiKey\"\n            onConfirm={handleRegenerateConfirm}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.DELETE:\n        return (\n          <ConfirmationStep\n            title=\"common.deleteApiKey\"\n            content=\"common.areYouSureYouWantToDeleteThisApiKey\"\n            buttonContent=\"action.deleteApiKey\"\n            onConfirm={handleDeleteConfirm}\n            onBack={handleBack}\n          />\n        );\n      default:\n    }\n  }\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.apiKey', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        {user.apiKeyPrefix ? (\n          <>\n            {!user.apiKeyState.isCreating &&\n              (user.apiKeyState.value ? (\n                <>\n                  <Message\n                    positive\n                    header={t('common.apiKeyCreated', {\n                      context: 'title',\n                    })}\n                    content={t('common.saveThisKeyItWillNotBeShownAgain')}\n                  />\n                  <div className={styles.valueWrapper}>\n                    <Input fluid readOnly value={user.apiKeyState.value} className={styles.value} />\n                    <Button className={styles.copyButton} onClick={handleCopyClick}>\n                      <Icon fitted name={isCopied ? 'check' : 'copy'} />\n                    </Button>\n                  </div>\n                </>\n              ) : (\n                <Message\n                  warning\n                  header={`${user.apiKeyPrefix}_...`}\n                  content={t('common.fullKeyIsHiddenForSecurityReasons')}\n                />\n              ))}\n            <Button\n              fluid\n              content={t('action.regenerateApiKey')}\n              loading={user.apiKeyState.isCreating}\n              disabled={user.apiKeyState.isCreating}\n              className={styles.actionButton}\n              onClick={handleGenerateClick}\n            />\n            <Button\n              fluid\n              content={t('action.deleteApiKey')}\n              className={styles.actionButton}\n              onClick={handleDeleteClick}\n            />\n          </>\n        ) : (\n          <>\n            <div className={styles.content}>{t('common.noApiKeyCreated')}</div>\n            <Button\n              fluid\n              positive\n              content={t('action.createApiKey')}\n              loading={user.apiKeyState.isCreating}\n              disabled={user.apiKeyState.isCreating}\n              onClick={handleGenerateClick}\n            />\n          </>\n        )}\n      </Popup.Content>\n    </>\n  );\n});\n\nApiKeyStep.propTypes = {\n  userId: PropTypes.string.isRequired,\n  onBack: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default ApiKeyStep;\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/ApiKeyStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actionButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: background 0.3s ease;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .content {\n    margin-bottom: 6px;\n  }\n\n  .copyButton {\n    background: #ebeef0;\n    box-shadow: none;\n    border-radius: 3px;\n    box-sizing: content-box;\n    color: #516b7a;\n    display: none;\n    height: 30px;\n    margin: 0;\n    min-height: auto;\n    outline: none;\n    padding: 4px;\n    position: absolute;\n    right: 0;\n    top: 0;\n    transition: background 85ms ease;\n    width: 20px;\n\n    &:hover {\n      background: #dfe3e6;\n      color: #4c4c4c;\n    }\n  }\n\n  .value input {\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n\n  .valueWrapper {\n    position: relative;\n\n    &:hover:not(:has(input:focus)) {\n      .copyButton {\n        display: block;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Icon, Table } from 'semantic-ui-react';\nimport { useEventCallback } from '../../../../lib/hooks';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { usePopupInClosableContext } from '../../../../hooks';\nimport { UserRoleIcons } from '../../../../constants/Icons';\nimport ActionsStep from './ActionsStep';\nimport UserAvatar from '../../../users/UserAvatar';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id }) => {\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const user = useSelector((state) => selectUserById(state, id));\n  const currentUserId = useSelector(selectors.selectCurrentUserId);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleActionsPopupClose = useEventCallback(() => {\n    if (user.apiKeyState.value) {\n      dispatch(entryActions.clearUserApiKeyValue(id));\n    }\n  }, [id, user.apiKeyState.value, dispatch]);\n\n  const ActionsPopup = usePopupInClosableContext(ActionsStep, {\n    onClose: handleActionsPopupClose,\n  });\n\n  return (\n    <Table.Row\n      className={classNames(styles.wrapper, user.isDeactivated && styles.wrapperDeactivated)}\n    >\n      <Table.Cell>\n        <div className={styles.user}>\n          <UserAvatar id={id} />\n          <div>\n            {user.name}\n            {user.id === currentUserId && (\n              <div className={styles.note}>{t('common.currentUser')}</div>\n            )}\n          </div>\n        </div>\n      </Table.Cell>\n      <Table.Cell>\n        {user.email}\n        {user.username && <div className={styles.note}>@{user.username}</div>}\n      </Table.Cell>\n      <Table.Cell>\n        {user.phone && (\n          <div className={styles.information}>\n            <Icon name=\"phone\" className={styles.icon} />\n            {user.phone}\n          </div>\n        )}\n        {user.organization && (\n          <div className={styles.information}>\n            <Icon name=\"building\" className={styles.icon} />\n            {user.organization}\n          </div>\n        )}\n        {user.apiKeyPrefix && (\n          <div className={classNames(styles.information, styles.informationApiKey)}>\n            <Icon name=\"key\" className={styles.icon} />\n            {user.apiKeyPrefix}_...\n          </div>\n        )}\n      </Table.Cell>\n      <Table.Cell className={styles.roleCell}>\n        <Icon name={UserRoleIcons[user.role]} className={styles.icon} />\n        {t(`common.${user.role}`)}\n      </Table.Cell>\n      <Table.Cell textAlign=\"right\">\n        <ActionsPopup userId={id}>\n          <Button className={styles.button}>\n            <Icon fitted name=\"pencil\" />\n          </Button>\n        </ActionsPopup>\n      </Table.Cell>\n    </Table.Row>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    box-shadow: 0 1px 0 #cbcccc;\n    margin-right: 0;\n  }\n\n  .icon {\n    color: #888888;\n    margin: 0 0.35714286em 0 0;\n  }\n\n  .information {\n    font-size: 13px;\n\n    &:not(:last-of-type) {\n      margin-bottom: 4px;\n    }\n  }\n\n  .informationApiKey {\n    color: #cf513d;\n\n    .icon {\n      color: inherit;\n    }\n  }\n\n  .note {\n    color: #888888;\n  }\n\n  .roleCell {\n    white-space: nowrap;\n  }\n\n  .user {\n    align-items: center;\n    display: flex;\n    gap: 8px;\n  }\n\n  .wrapper {\n    color: #212121;\n  }\n\n  .wrapperDeactivated {\n    opacity: 0.64;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/SelectRoleStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Icon, Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../../lib/custom-ui';\n\nimport { UserRoles } from '../../../../constants/Enums';\nimport { UserRoleIcons } from '../../../../constants/Icons';\n\nimport styles from './SelectRoleStep.module.scss';\n\nconst DESCRIPTION_BY_ROLE = {\n  [UserRoles.ADMIN]: 'common.canManageSystemWideSettingsAndActAsProjectOwner',\n  [UserRoles.PROJECT_OWNER]: 'common.canCreateOwnProjectsAndBeInvitedToWorkInOthers',\n  [UserRoles.BOARD_USER]: 'common.canBeInvitedToWorkInBoards',\n};\n\nconst SelectRoleStep = React.memo(\n  ({ defaultValue, title, withButton, buttonContent, onSelect, onBack, onClose }) => {\n    const [t] = useTranslation();\n    const [value, setValue] = useState(defaultValue);\n\n    const handleSelectClick = useCallback(\n      (_, { value: nextValue }) => {\n        if (withButton) {\n          setValue(nextValue);\n        } else {\n          if (nextValue !== defaultValue) {\n            onSelect(nextValue);\n          }\n\n          onBack();\n        }\n      },\n      [defaultValue, withButton, onSelect, onBack],\n    );\n\n    const handleSubmit = useCallback(() => {\n      if (value !== defaultValue) {\n        onSelect(value);\n      }\n\n      onClose();\n    }, [defaultValue, onSelect, onClose, value]);\n\n    return (\n      <>\n        <Popup.Header onBack={onBack}>\n          {t(title, {\n            context: 'title',\n          })}\n        </Popup.Header>\n        <Popup.Content>\n          <Form onSubmit={handleSubmit}>\n            <Menu secondary vertical className={styles.menu}>\n              {[UserRoles.ADMIN, UserRoles.PROJECT_OWNER, UserRoles.BOARD_USER].map((role) => (\n                <Menu.Item\n                  key={role}\n                  value={role}\n                  active={role === value}\n                  className={styles.menuItem}\n                  onClick={handleSelectClick}\n                >\n                  <Icon name={UserRoleIcons[role]} className={styles.menuItemIcon} />\n                  <div className={styles.menuItemTitle}>{t(`common.${role}`)}</div>\n                  <p className={styles.menuItemDescription}>{t(DESCRIPTION_BY_ROLE[role])}</p>\n                </Menu.Item>\n              ))}\n            </Menu>\n            {withButton && <Button positive content={t(buttonContent)} />}\n          </Form>\n        </Popup.Content>\n      </>\n    );\n  },\n);\n\nSelectRoleStep.propTypes = {\n  defaultValue: PropTypes.string.isRequired,\n  title: PropTypes.string,\n  withButton: PropTypes.bool,\n  buttonContent: PropTypes.string,\n  onSelect: PropTypes.func.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nSelectRoleStep.defaultProps = {\n  title: 'common.selectRole',\n  withButton: false,\n  buttonContent: 'action.selectRole',\n  onBack: undefined,\n};\n\nexport default SelectRoleStep;\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/SelectRoleStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menu {\n    margin: 0 auto 8px;\n    width: 100%;\n  }\n\n  .menuItem:last-child {\n    margin-bottom: 0;\n  }\n\n  .menuItemDescription {\n    opacity: 0.5;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.35714286em 0 0;\n  }\n\n  .menuItemTitle {\n    margin-bottom: 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/UsersPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo, useState } from 'react';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Divider, Tab, Table } from 'semantic-ui-react';\nimport { Input } from '../../../../lib/custom-ui';\n\nimport selectors from '../../../../selectors';\nimport { useField, useNestedRef, usePopupInClosableContext } from '../../../../hooks';\nimport Item from './Item';\nimport AddStep from './AddStep';\n\nimport styles from './UsersPane.module.scss';\n\nconst UsersPane = React.memo(() => {\n  const activeUsersLimit = useSelector(selectors.selectActiveUsersLimit);\n  const activeUsersTotal = useSelector(selectors.selectActiveUsersTotal);\n  const users = useSelector(selectors.selectUsers);\n\n  const canAdd = useSelector((state) => {\n    const oidcBootstrap = selectors.selectOidcBootstrap(state);\n    return !oidcBootstrap || !oidcBootstrap.isEnforced;\n  });\n\n  const [t] = useTranslation();\n\n  const [search, handleSearchChange] = useField('');\n  const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);\n  const [isDeactivatedVisible, setIsDeactivatedVisible] = useState(false); // TODO: refactor?\n\n  const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');\n\n  const filteredUsers = useMemo(\n    () =>\n      users.filter((user) => {\n        if (isDeactivatedVisible) {\n          if (!user.isDeactivated) {\n            return false;\n          }\n        } else if (user.isDeactivated) {\n          return false;\n        }\n\n        return (\n          user.email.includes(cleanSearch) ||\n          user.name.toLowerCase().includes(cleanSearch) ||\n          (user.username && user.username.includes(cleanSearch)) ||\n          (user.organization && user.organization.toLowerCase().includes(cleanSearch)) ||\n          (user.apiKeyPrefix && user.apiKeyPrefix.toLowerCase().includes(cleanSearch))\n        );\n      }),\n    [users, isDeactivatedVisible, cleanSearch],\n  );\n\n  const handleToggleDeactivatedClick = useCallback(() => {\n    setIsDeactivatedVisible(!isDeactivatedVisible);\n  }, [isDeactivatedVisible]);\n\n  useEffect(() => {\n    searchFieldRef.current.focus();\n  }, [searchFieldRef]);\n\n  const AddPopup = usePopupInClosableContext(AddStep);\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <Input\n        fluid\n        ref={handleSearchFieldRef}\n        value={search}\n        placeholder={t('common.searchUsers')}\n        maxLength={256}\n        icon=\"search\"\n        onChange={handleSearchChange}\n      />\n      <Divider />\n      <div className={styles.tableWrapper}>\n        <Table unstackable basic=\"very\">\n          <Table.Header>\n            <Table.Row>\n              <Table.HeaderCell />\n              <Table.HeaderCell width={4}>{t('common.identity')}</Table.HeaderCell>\n              <Table.HeaderCell width={4}>{t('common.information')}</Table.HeaderCell>\n              <Table.HeaderCell>{t('common.role')}</Table.HeaderCell>\n              <Table.HeaderCell />\n            </Table.Row>\n          </Table.Header>\n          <Table.Body>\n            {filteredUsers.map((user) => (\n              <Item key={user.id} id={user.id} />\n            ))}\n          </Table.Body>\n        </Table>\n      </div>\n      <div className={styles.actions}>\n        <Button\n          content={isDeactivatedVisible ? t('action.showActive') : t('action.showDeactivated')}\n          className={styles.toggleDeactivatedButton}\n          onClick={handleToggleDeactivatedClick}\n        />\n        {canAdd && (\n          <AddPopup>\n            <Button\n              positive\n              disabled={activeUsersLimit !== null && activeUsersTotal >= activeUsersLimit}\n              className={styles.addButton}\n            >\n              {t('action.addUser')}\n              {activeUsersLimit !== null && (\n                <span className={styles.addButtonCounter}>\n                  {activeUsersTotal}/{activeUsersLimit}\n                </span>\n              )}\n            </Button>\n          </AddPopup>\n        )}\n      </div>\n    </Tab.Pane>\n  );\n});\n\nexport default UsersPane;\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/UsersPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actions {\n    border-top: 1px solid rgba(34, 36, 38, 0.15);\n    padding-top: 1rem;\n    text-align: right;\n\n    &:after {\n      clear: both;\n      content: \"\";\n      display: table;\n    }\n  }\n\n  .addButton {\n    margin-right: 0;\n  }\n\n  .addButtonCounter {\n    margin-left: 6px;\n    opacity: 0.5;\n  }\n\n  .tableWrapper {\n    margin-right: -2.5rem;\n    max-height: calc(100vh - 338px);\n    overflow: auto;\n    padding-right: 2.5rem;\n\n    @media (width < 768px) {\n      margin-right: -2rem;\n      max-height: calc(100vh - 296px);\n      padding-right: 2rem;\n    }\n\n    @media (768px <= width < 926px) {\n      max-height: calc(100vh - 310px);\n    }\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      border-radius: 3px;\n    }\n  }\n\n  .toggleDeactivatedButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    float: left;\n    font-weight: normal;\n    margin-left: auto;\n    margin-right: 0;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: none;\n    white-space: nowrap;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/UsersPane/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport UsersPane from './UsersPane';\n\nexport default UsersPane;\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/WebhooksPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport Webhooks from '../../webhooks/Webhooks';\n\nimport styles from './WebhooksPane.module.scss';\n\nconst WebhooksPane = React.memo(() => {\n  const webhookIds = useSelector(selectors.selectWebhookIds);\n\n  const dispatch = useDispatch();\n\n  const handleCreate = useCallback(\n    (data) => {\n      dispatch(entryActions.createWebhook(data));\n    },\n    [dispatch],\n  );\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <Webhooks ids={webhookIds} onCreate={handleCreate} />\n    </Tab.Pane>\n  );\n});\n\nexport default WebhooksPane;\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/WebhooksPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/AdministrationModal/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AdministrationModal from './AdministrationModal';\n\nexport default AdministrationModal;\n"
  },
  {
    "path": "client/src/components/common/ConfirmationStep/ConfirmationStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Input } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport { useForm, useNestedRef } from '../../../hooks';\n\nimport styles from './ConfirmationStep.module.scss';\n\nconst ButtonTypes = {\n  POSITIVE: 'positive',\n  NEGATIVE: 'negative',\n};\n\nconst ConfirmationStep = React.memo(\n  ({\n    title,\n    content,\n    buttonType,\n    buttonContent,\n    typeValue,\n    typeContent,\n    onConfirm,\n    onBack,\n    onClose,\n  }) => {\n    const [t] = useTranslation();\n\n    const [data, handleFieldChange] = useForm({\n      typeValue: '',\n    });\n\n    const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n    const handleSubmit = useCallback(\n      (event) => {\n        event.stopPropagation();\n\n        if (typeValue) {\n          const cleanData = {\n            ...data,\n            typeValue: data.typeValue.trim(),\n          };\n\n          if (cleanData.typeValue.toLowerCase() !== typeValue.toLowerCase()) {\n            nameFieldRef.current.select();\n            return;\n          }\n        }\n\n        onConfirm();\n\n        if (onClose) {\n          onClose();\n        }\n      },\n      [typeValue, onConfirm, onClose, data, nameFieldRef],\n    );\n\n    useEffect(() => {\n      if (typeValue) {\n        nameFieldRef.current.select();\n      }\n    }, [typeValue, nameFieldRef]);\n\n    return (\n      <>\n        <Popup.Header onBack={onBack}>\n          {t(title, {\n            context: 'title',\n          })}\n        </Popup.Header>\n        <Popup.Content>\n          <div className={styles.content}>{t(content)}</div>\n          {typeContent && <div className={styles.content}>{t(typeContent)}</div>}\n          <Form onSubmit={handleSubmit}>\n            {typeValue && (\n              <Input\n                fluid\n                ref={handleNameFieldRef}\n                name=\"typeValue\"\n                value={data.typeValue}\n                placeholder={typeValue}\n                maxLength={128}\n                className={styles.field}\n                onChange={handleFieldChange}\n              />\n            )}\n            <Button\n              {...{\n                [buttonType]: true,\n              }}\n              fluid\n              content={t(buttonContent)}\n            />\n          </Form>\n        </Popup.Content>\n      </>\n    );\n  },\n);\n\nConfirmationStep.propTypes = {\n  title: PropTypes.string.isRequired,\n  content: PropTypes.string.isRequired,\n  buttonType: PropTypes.oneOf(Object.values(ButtonTypes)),\n  buttonContent: PropTypes.string.isRequired,\n  typeValue: PropTypes.string,\n  typeContent: PropTypes.string,\n  onConfirm: PropTypes.func.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func,\n};\n\nConfirmationStep.defaultProps = {\n  buttonType: ButtonTypes.NEGATIVE,\n  typeValue: undefined,\n  typeContent: undefined,\n  onBack: undefined,\n  onClose: undefined,\n};\n\nexport default ConfirmationStep;\n"
  },
  {
    "path": "client/src/components/common/ConfirmationStep/ConfirmationStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .content {\n    color: #212121;\n    padding-bottom: 6px;\n  }\n\n  .field {\n    margin-bottom: 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/ConfirmationStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ConfirmationStep from './ConfirmationStep';\n\nexport default ConfirmationStep;\n"
  },
  {
    "path": "client/src/components/common/Core/Core.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useRef } from 'react';\nimport { useSelector } from 'react-redux';\nimport { useTranslation, Trans } from 'react-i18next';\nimport { Loader } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport version from '../../../version';\nimport ModalTypes from '../../../constants/ModalTypes';\nimport Message from './Message';\nimport Toaster from '../Toaster';\nimport Fixed from '../Fixed';\nimport Static from '../Static';\nimport AdministrationModal from '../AdministrationModal';\nimport AboutModal from '../AboutModal';\nimport UserSettingsModal from '../../users/UserSettingsModal';\nimport ProjectBackground from '../../projects/ProjectBackground';\nimport AddProjectModal from '../../projects/AddProjectModal';\n\nconst Core = React.memo(() => {\n  const isInitializing = useSelector(selectors.selectIsInitializing);\n  const isSocketDisconnected = useSelector(selectors.selectIsSocketDisconnected);\n  const modal = useSelector(selectors.selectCurrentModal);\n  const project = useSelector(selectors.selectCurrentProject);\n  const board = useSelector(selectors.selectCurrentBoard);\n  const currentUserId = useSelector(selectors.selectCurrentUserId);\n\n  // TODO: move to selector?\n  const isNewVersionAvailable = useSelector((state) => {\n    const bootstrap = selectors.selectBootstrap(state);\n    return !!bootstrap && bootstrap.version !== version;\n  });\n\n  const [t] = useTranslation();\n\n  const defaultTitleRef = useRef(document.title);\n\n  const handleRefreshPageClick = useCallback(() => {\n    window.location.reload(true);\n  }, []);\n\n  useEffect(() => {\n    const titleParts = [];\n    if (project) {\n      if (board) {\n        titleParts.push(board.name);\n      }\n\n      titleParts.push(project.name);\n    }\n\n    document.title = titleParts.length === 0 ? defaultTitleRef.current : titleParts.join(' | ');\n  }, [project, board]);\n\n  let modalNode = null;\n  if (modal) {\n    switch (modal.type) {\n      case ModalTypes.ADMINISTRATION:\n        modalNode = <AdministrationModal />;\n\n        break;\n      case ModalTypes.ABOUT:\n        modalNode = <AboutModal />;\n\n        break;\n      case ModalTypes.USER_SETTINGS:\n        modalNode = <UserSettingsModal />;\n\n        break;\n      case ModalTypes.ADD_PROJECT:\n        modalNode = <AddProjectModal />;\n\n        break;\n      default:\n    }\n  }\n\n  let messageNode = null;\n  if (isSocketDisconnected) {\n    messageNode = (\n      <Message\n        type=\"error\"\n        header={t('common.noConnectionToServer')}\n        content={\n          <Trans i18nKey=\"common.allChangesWillBeAutomaticallySavedAfterConnectionRestored\">\n            All changes will be automatically saved\n            <br />\n            after connection restored\n          </Trans>\n        }\n      />\n    );\n  } else if (isNewVersionAvailable) {\n    messageNode = (\n      <Message\n        type=\"info\"\n        header={t('common.newVersionAvailable')}\n        content={\n          <Trans i18nKey=\"common.clickHereOrRefreshPageToUpdate\">\n            {/* eslint-disable-next-line jsx-a11y/anchor-is-valid,\n                                         jsx-a11y/click-events-have-key-events,\n                                         jsx-a11y/no-static-element-interactions */}\n            <a onClick={handleRefreshPageClick}>Click here</a> or refresh the page to update\n          </Trans>\n        }\n      />\n    );\n  }\n\n  return (\n    <>\n      {isInitializing || !currentUserId ? (\n        <Loader active size=\"massive\" />\n      ) : (\n        <>\n          <Toaster />\n          {project && project.backgroundType && <ProjectBackground />}\n          <Fixed />\n          <Static />\n          {modalNode}\n        </>\n      )}\n      {messageNode}\n    </>\n  );\n});\n\nexport default Core;\n"
  },
  {
    "path": "client/src/components/common/Core/Message.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\n\nimport styles from './Message.module.scss';\n\nconst Types = {\n  INFO: 'info',\n  ERROR: 'error',\n};\n\nconst Message = React.memo(({ type, header, content }) => (\n  <div className={classNames(styles.wrapper, styles[`wrapper${upperFirst(type)}`])}>\n    <div className={styles.header}>{header}</div>\n    <div className={styles.content}>{content}</div>\n  </div>\n));\n\nMessage.propTypes = {\n  type: PropTypes.oneOf(Object.values(Types)).isRequired,\n  header: PropTypes.node.isRequired,\n  content: PropTypes.node.isRequired,\n};\n\nexport default Message;\n"
  },
  {
    "path": "client/src/components/common/Core/Message.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .content {\n    color: #fff;\n    font-size: 16px;\n    line-height: 1.4;\n\n    a {\n      color: #fff;\n      cursor: pointer;\n      text-decoration: underline;\n    }\n  }\n\n  .header {\n    color: #fff;\n    font-size: 24px;\n    font-weight: bold;\n    line-height: 1.2;\n    margin-bottom: 8px;\n  }\n\n  .wrapper {\n    border-radius: 4px;\n    bottom: 20px;\n    max-width: calc(100% - 40px);\n    padding: 12px 18px;\n    position: fixed;\n    right: 20px;\n    width: 390px;\n    z-index: 10001;\n  }\n\n  .wrapperError {\n    background: #cf513d;\n    box-shadow: #6e2f1a 0 1px 0;\n  }\n\n  .wrapperInfo {\n    background: #2185d0;\n    box-shadow: rgba(34, 36, 38, 0.15) 0 1px 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/Core/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Core from './Core';\n\nexport default Core;\n"
  },
  {
    "path": "client/src/components/common/EditMarkdown/EditMarkdown.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useRef, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { useClickAwayListener } from '../../../lib/hooks';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useNestedRef } from '../../../hooks';\nimport MarkdownEditor from '../MarkdownEditor';\n\nimport styles from './EditMarkdown.module.scss';\n\nconst MAX_LENGTH = 1048576;\n\nconst EditMarkdown = React.memo(({ defaultValue, draftValue, onUpdate, onClose }) => {\n  const defaultMode = useSelector((state) => selectors.selectCurrentUser(state).defaultEditorMode);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [value, setValue] = useState(() => draftValue || defaultValue || '');\n\n  const fieldRef = useRef(null);\n  const [submitButtonRef, handleSubmitButtonRef] = useNestedRef();\n  const [cancelButtonRef, handleCancelButtonRef] = useNestedRef();\n\n  const handleModeChange = useCallback(\n    (mode) => {\n      dispatch(\n        entryActions.updateCurrentUser({\n          defaultEditorMode: mode,\n        }),\n      );\n    },\n    [dispatch],\n  );\n\n  const isExceeded = value.length > MAX_LENGTH;\n\n  const submit = useCallback(() => {\n    const cleanValue = value.trim() || null;\n\n    if (!isExceeded && cleanValue !== defaultValue) {\n      onUpdate(cleanValue);\n    }\n\n    onClose(isExceeded ? cleanValue : null);\n  }, [onUpdate, onClose, defaultValue, value, isExceeded]);\n\n  const handleChange = useCallback((nextValue) => {\n    setValue(nextValue);\n  }, []);\n\n  const handleSubmit = useCallback(() => {\n    submit();\n  }, [submit]);\n\n  const handleCancel = useCallback(() => {\n    submit();\n  }, [submit]);\n\n  const handleCancelClick = useCallback(() => {\n    onClose(null);\n  }, [onClose]);\n\n  const handleClickAwayCancel = useCallback(() => {\n    fieldRef.current.focus();\n  }, [fieldRef]);\n\n  const clickAwayProps = useClickAwayListener(\n    [fieldRef, submitButtonRef, cancelButtonRef],\n    submit,\n    handleClickAwayCancel,\n  );\n\n  return (\n    <>\n      <MarkdownEditor\n        {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n        ref={fieldRef}\n        defaultValue={value}\n        defaultMode={defaultMode}\n        isError={isExceeded}\n        onChange={handleChange}\n        onSubmit={handleSubmit}\n        onCancel={handleCancel}\n        onModeChange={handleModeChange}\n      />\n      <Form onSubmit={handleSubmit}>\n        <div className={styles.controls}>\n          <Button\n            {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n            positive\n            ref={handleSubmitButtonRef}\n            content={\n              isExceeded\n                ? t('common.contentExceedsLimit', {\n                    limit: '1MB',\n                  })\n                : t('action.save')\n            }\n            disabled={isExceeded}\n          />\n          <Button\n            {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n            ref={handleCancelButtonRef}\n            type=\"button\"\n            content={t('action.cancel')}\n            onClick={handleCancelClick}\n          />\n        </div>\n      </Form>\n    </>\n  );\n});\n\nEditMarkdown.propTypes = {\n  defaultValue: PropTypes.string,\n  draftValue: PropTypes.string,\n  // placeholder: PropTypes.string.isRequired, // TODO: remove?\n  onUpdate: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nEditMarkdown.defaultProps = {\n  defaultValue: undefined,\n  draftValue: undefined,\n};\n\nexport default EditMarkdown;\n"
  },
  {
    "path": "client/src/components/common/EditMarkdown/EditMarkdown.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .controls {\n    clear: both;\n    margin-top: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/EditMarkdown/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EditMarkdown from './EditMarkdown';\n\nexport default EditMarkdown;\n"
  },
  {
    "path": "client/src/components/common/ExpandableMarkdown/ExpandableMarkdown.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Icon } from 'semantic-ui-react';\nimport { useToggle } from '../../../lib/hooks';\n\nimport Markdown from '../Markdown';\n\nimport styles from './ExpandableMarkdown.module.scss';\n\nconst MAX_VISIBLE_PART_HEIGHT = 800;\n\nconst ExpandableMarkdown = React.memo(({ children }) => {\n  const [t] = useTranslation();\n  const [isOverflowing, setIsOverflowing] = useState(false);\n  const [isExpanded, toggleExpanded] = useToggle();\n\n  const contentRef = useRef(null);\n\n  const handleToggleExpandClick = useCallback(\n    (event) => {\n      event.stopPropagation();\n      event.target.blur();\n\n      toggleExpanded();\n    },\n    [toggleExpanded],\n  );\n\n  useEffect(() => {\n    const resizeObserver = new ResizeObserver(() => {\n      setIsOverflowing(contentRef.current.scrollHeight > MAX_VISIBLE_PART_HEIGHT);\n    });\n\n    resizeObserver.observe(contentRef.current);\n\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, []);\n\n  const isPartHidden = isOverflowing && !isExpanded;\n\n  return (\n    <>\n      <div\n        className={classNames(\n          styles.wrapper,\n          isExpanded && styles.wrapperExpanded,\n          isPartHidden && styles.wrapperPartHidden,\n        )}\n        style={{ maxHeight: `${MAX_VISIBLE_PART_HEIGHT}px` }}\n      >\n        <div ref={contentRef}>\n          <Markdown>{children}</Markdown>\n        </div>\n      </div>\n      {isOverflowing && (\n        <Button fluid className={styles.toggleButton} onClick={handleToggleExpandClick}>\n          <Icon name={isExpanded ? 'angle up' : 'angle down'} />\n          {isExpanded ? t('action.showLess') : t('action.showMore')}\n        </Button>\n      )}\n    </>\n  );\n});\n\nExpandableMarkdown.propTypes = {\n  children: PropTypes.string.isRequired,\n};\n\nexport default ExpandableMarkdown;\n"
  },
  {
    "path": "client/src/components/common/ExpandableMarkdown/ExpandableMarkdown.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .toggleButton {\n    box-shadow: none;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: center;\n  }\n\n  .wrapper {\n    overflow: hidden;\n  }\n\n  .wrapperExpanded {\n    max-height: none !important;\n    overflow: visible;\n  }\n\n  .wrapperPartHidden {\n    mask-image: linear-gradient(180deg, #000 80%, transparent);\n    -webkit-mask-image: linear-gradient(180deg, #000 80%, transparent);\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/ExpandableMarkdown/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ExpandableMarkdown from './ExpandableMarkdown';\n\nexport default ExpandableMarkdown;\n"
  },
  {
    "path": "client/src/components/common/Favicon/Favicon.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { Icon } from 'semantic-ui-react';\n\nimport styles from './Favicon.module.scss';\n\nconst Favicon = React.memo(({ url }) => {\n  const [isImageError, setIsImageError] = useState(false);\n\n  const handleImageError = useCallback(() => {\n    setIsImageError(true);\n  }, []);\n\n  return isImageError ? (\n    <Icon fitted name=\"linkify\" className={styles.fallbackIcon} />\n  ) : (\n    <img src={url} onError={handleImageError} /> // eslint-disable-line jsx-a11y/alt-text\n  );\n});\n\nFavicon.propTypes = {\n  url: PropTypes.string.isRequired,\n};\n\nexport default Favicon;\n"
  },
  {
    "path": "client/src/components/common/Favicon/Favicon.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .fallbackIcon {\n    color: #5e6c84;\n    font-size: 18px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/Favicon/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Favicon from './Favicon';\n\nexport default Favicon;\n"
  },
  {
    "path": "client/src/components/common/Favorites/Favorites.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useRef } from 'react';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport ProjectCard from '../../projects/ProjectCard';\n\nimport styles from './Favorites.module.scss';\n\nconst Favorites = React.memo(() => {\n  const { projectId: currentProjectId } = useSelector(selectors.selectPath);\n  const projectIds = useSelector(selectors.selectFavoriteProjectIdsForCurrentUser);\n  const isActive = useSelector(selectors.selectIsFavoritesActiveForCurrentUser);\n\n  const cardsRef = useRef(null);\n\n  const handleWheel = useCallback(({ deltaY }) => {\n    cardsRef.current.scrollBy({\n      left: deltaY,\n    });\n  }, []);\n\n  return (\n    <div\n      className={classNames(styles.wrapper, isActive && styles.wrapperActive)}\n      onWheel={handleWheel}\n    >\n      <div ref={cardsRef} className={styles.cards}>\n        {projectIds.map((projectId) => (\n          <div key={projectId} className={styles.cardWrapper}>\n            <ProjectCard\n              id={projectId}\n              size=\"small\"\n              isActive={projectId === currentProjectId}\n              className={styles.card}\n            />\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n});\n\nexport default Favorites;\n"
  },
  {
    "path": "client/src/components/common/Favorites/Favorites.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .card {\n    height: 70px;\n    width: 160px;\n  }\n\n  .cardWrapper:not(:last-child) {\n    margin-right: 20px;\n  }\n\n  .cards {\n    display: flex;\n    height: 108px;\n    overflow-x: auto;\n    overflow-y: hidden;\n    padding: 10px 20px 0 20px;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &:hover {\n      height: 90px;\n    }\n\n    &::-webkit-scrollbar {\n      height: 5px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      border-radius: 3px;\n    }\n  }\n\n  .wrapper {\n    background: rgba(0, 0, 0, 0.16);\n    height: 0;\n    overflow-y: hidden;\n    transition: height 0.2s ease;\n  }\n\n  .wrapperActive {\n    height: 90px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/Favorites/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Favorites from './Favorites';\n\nexport default Favorites;\n"
  },
  {
    "path": "client/src/components/common/Fixed/Fixed.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport Header from '../Header';\nimport Favorites from '../Favorites';\nimport HomeActions from '../HomeActions';\nimport Project from '../../projects/Project';\nimport BoardActions from '../../boards/BoardActions';\n\nimport styles from './Fixed.module.scss';\n\nconst Fixed = React.memo(() => {\n  const { projectId } = useSelector(selectors.selectPath);\n  const board = useSelector(selectors.selectCurrentBoard);\n\n  return (\n    <div className={styles.wrapper}>\n      <Header />\n      <Favorites />\n      {projectId === undefined && <HomeActions />}\n      {projectId && <Project />}\n      {board && !board.isFetching && <BoardActions />}\n    </div>\n  );\n});\n\nexport default Fixed;\n"
  },
  {
    "path": "client/src/components/common/Fixed/Fixed.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    position: fixed;\n    width: 100%;\n    z-index: 1;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/Fixed/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Fixed from './Fixed';\n\nexport default Fixed;\n"
  },
  {
    "path": "client/src/components/common/GhostError/GhostError.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Icon } from 'semantic-ui-react';\n\nimport history from '../../../history';\nimport Paths from '../../../constants/Paths';\n\nimport styles from './GhostError.module.scss';\n\nconst GhostError = React.memo(({ message }) => {\n  const [t] = useTranslation();\n\n  const eyesRef = useRef(null);\n\n  const handleBackClick = useCallback(() => {\n    history.back();\n  }, []);\n\n  const handleHomeClick = useCallback(() => {\n    history.push(Paths.ROOT);\n  }, []);\n\n  useEffect(() => {\n    let pageX = document.documentElement.clientWidth;\n    let pageY = document.documentElement.clientHeight;\n\n    const handleMouseMove = (event) => {\n      if (!eyesRef.current) {\n        return;\n      }\n\n      // Vertical axis\n      const mouseY = event.pageY;\n      const yAxis = ((pageY / 2 - mouseY) / pageY) * -300;\n\n      // Horizontal axis\n      const mouseX = event.pageX / -pageX;\n      const xAxis = -mouseX * 100 - 100;\n\n      // Apply transform to eyes\n      eyesRef.current.style.transform = `translate(${xAxis}%, ${yAxis}%)`;\n    };\n\n    const handleResize = () => {\n      pageX = document.documentElement.clientWidth;\n      pageY = document.documentElement.clientHeight;\n    };\n\n    document.addEventListener('mousemove', handleMouseMove);\n    window.addEventListener('resize', handleResize);\n\n    return () => {\n      document.removeEventListener('mousemove', handleMouseMove);\n      window.removeEventListener('resize', handleResize);\n    };\n  }, []);\n\n  return (\n    <div className={styles.wrapper}>\n      <div className={styles.symbols}>\n        <div className={styles.symbol} />\n        <div className={styles.symbol} />\n        <div className={styles.symbol} />\n        <div className={styles.symbol} />\n        <div className={styles.symbol} />\n        <div className={styles.symbol} />\n      </div>\n      <div className={styles.ghost}>\n        <div ref={eyesRef} className={styles.eyes}>\n          <div className={styles.eyeLeft} />\n          <div className={styles.eyeRight} />\n        </div>\n        <div className={styles.bottom}>\n          <div />\n          <div />\n          <div />\n          <div />\n          <div />\n        </div>\n      </div>\n      <div className={styles.shadow} />\n      <div className={styles.message}>\n        <h1 className={styles.title}>\n          {t('common.whoops', {\n            context: 'title',\n          })}\n        </h1>\n        <div className={styles.text}>\n          {t(message, {\n            context: 'title',\n          })}\n        </div>\n        {window.history.length > 1 && (\n          <Button\n            basic\n            color=\"yellow\"\n            size=\"large\"\n            className={styles.button}\n            onClick={handleBackClick}\n          >\n            <Icon name=\"arrow left\" />\n            {t('action.goBack')}\n          </Button>\n        )}\n        <Button\n          basic\n          color=\"olive\"\n          size=\"large\"\n          className={styles.button}\n          onClick={handleHomeClick}\n        >\n          <Icon name=\"home\" />\n          {t('action.goHome')}\n        </Button>\n      </div>\n    </div>\n  );\n});\n\nGhostError.propTypes = {\n  message: PropTypes.string,\n};\n\nGhostError.defaultProps = {\n  message: 'common.pageNotFound',\n};\n\nexport default GhostError;\n"
  },
  {
    "path": "client/src/components/common/GhostError/GhostError.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .bottom {\n    display: flex;\n    left: 0;\n    position: absolute;\n    right: 0;\n    top: 100%;\n\n    div {\n      background-color: #fff;\n      border-radius: 100%;\n      flex-grow: 1;\n      height: 20px;\n      position: relative;\n      top: -10px;\n\n      &:nth-child(2n) {\n        background: transparent;\n        border-top: 15px solid #22252a;\n        top: -12px;\n      }\n    }\n  }\n\n  .button {\n    padding: 0.78571429em 1.5em 0.78571429em;\n  }\n\n  .eyeLeft {\n    background: #22252a;\n    border-radius: 50%;\n    height: 12px;\n    left: 10px;\n    position: absolute;\n    width: 12px;\n  }\n\n  .eyeRight {\n    background: #22252a;\n    border-radius: 50%;\n    height: 12px;\n    position: absolute;\n    right: 10px;\n    width: 12px;\n  }\n\n  .eyes {\n    height: 12px;\n    left: 50%;\n    position: absolute;\n    top: 45%;\n    transform: translateX(-50%);\n    width: 70px;\n  }\n\n  .ghost {\n    animation: upndown 3s ease-in-out infinite;\n    background: #fff;\n    border-radius: 100px 100px 0 0;\n    height: 100px;\n    margin: 0 auto 20px;\n    position: relative;\n    width: 100px;\n    z-index: 1;\n  }\n\n  .message {\n    max-width: 400px;\n    text-align: center;\n    z-index: 1;\n  }\n\n  .shadow {\n    animation: smallnbig 3s ease-in-out infinite;\n    background: #000;\n    border-radius: 50%;\n    height: 20px;\n    margin: 0 auto 30px;\n    width: 80px;\n  }\n\n  .symbol {\n    &:nth-child(1) {\n      animation: shine 4s ease-in-out 3s infinite;\n      left: 20px;\n      opacity: 0.3;\n      position: absolute;\n      top: 20px;\n\n      &:before,\n      &:after {\n        background: #6b808c;\n        border-radius: 3px;\n        content: \"\";\n        height: 3px;\n        position: absolute;\n        width: 8px;\n      }\n\n      &:before {\n        transform: rotate(45deg);\n      }\n\n      &:after {\n        transform: rotate(-45deg);\n      }\n    }\n\n    &:nth-child(2) {\n      animation: shine 4s ease-in-out 1.3s infinite;\n      border: 2px solid;\n      border-color: #6b808c;\n      border-radius: 50%;\n      height: 12px;\n      opacity: 0.3;\n      position: absolute;\n      right: 30px;\n      top: 40px;\n      width: 12px;\n    }\n\n    &:nth-child(3) {\n      animation: shine 3s ease-in-out 0.5s infinite;\n      bottom: 30px;\n      left: 30px;\n      opacity: 0.3;\n      position: absolute;\n\n      &:before,\n      &:after {\n        background: #6b808c;\n        border-radius: 3px;\n        content: \"\";\n        height: 3px;\n        position: absolute;\n        width: 8px;\n      }\n\n      &:before {\n        transform: rotate(90deg);\n      }\n\n      &:after {\n        transform: rotate(180deg);\n      }\n    }\n\n    &:nth-child(4) {\n      animation: shine 6s ease-in-out 1.6s infinite;\n      opacity: 0.3;\n      position: absolute;\n      right: 30px;\n      top: 10px;\n\n      &:before,\n      &:after {\n        background: #6b808c;\n        border-radius: 3px;\n        content: \"\";\n        height: 3px;\n        position: absolute;\n        width: 10px;\n      }\n\n      &:before {\n        transform: rotate(45deg);\n      }\n\n      &:after {\n        transform: rotate(-45deg);\n      }\n    }\n\n    &:nth-child(5) {\n      animation: shine 1.7s ease-in-out 7s infinite;\n      border: 2px solid;\n      border-color: #6b808c;\n      border-radius: 50%;\n      height: 8px;\n      opacity: 0.3;\n      position: absolute;\n      right: 5px;\n      top: 60px;\n      width: 8px;\n    }\n\n    &:nth-child(6) {\n      animation: shine 2s ease-in-out 6s infinite;\n      bottom: 10px;\n      opacity: 0.3;\n      position: absolute;\n      right: 10px;\n\n      &:before,\n      &:after {\n        background: #6b808c;\n        border-radius: 3px;\n        content: \"\";\n        height: 3px;\n        position: absolute;\n        width: 10px;\n      }\n\n      &:before {\n        transform: rotate(90deg);\n      }\n\n      &:after {\n        transform: rotate(180deg);\n      }\n    }\n  }\n\n  .symbols {\n    height: 200px;\n    left: 50%;\n    position: absolute;\n    top: 50%;\n    transform: translate(-50%, -50%);\n    width: 200px;\n  }\n\n  .text {\n    color: #6b808c;\n    font-size: 18px;\n    line-height: 1.4;\n    margin-bottom: 30px;\n  }\n\n  .title {\n    font-size: 32px;\n    letter-spacing: 0.5px;\n  }\n\n  .wrapper {\n    align-items: center;\n    color: #fff;\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n    justify-content: center;\n    min-height: 60vh;\n    padding: 40px 20px;\n    position: relative;\n    width: 100%;\n  }\n\n  @keyframes shine {\n    0% {\n      opacity: 0.3;\n    }\n\n    25% {\n      opacity: 0.1;\n    }\n\n    50% {\n      opacity: 0.3;\n    }\n\n    100% {\n      opacity: 0.3;\n    }\n  }\n\n  @keyframes smallnbig {\n    0% {\n      width: 80px;\n    }\n\n    50% {\n      width: 90px;\n    }\n\n    100% {\n      width: 80px;\n    }\n  }\n\n  @keyframes upndown {\n    0% {\n      transform: translateY(5px);\n    }\n\n    50% {\n      transform: translateY(15px);\n    }\n\n    100% {\n      transform: translateY(5px);\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/GhostError/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport GhostError from './GhostError';\n\nexport default GhostError;\n"
  },
  {
    "path": "client/src/components/common/Header/Header.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport classNames from 'classnames';\nimport { shallowEqual, useDispatch, useSelector } from 'react-redux';\nimport { Link } from 'react-router';\nimport { Button, Icon, Menu } from 'semantic-ui-react';\nimport { usePopup } from '../../../lib/popup';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport Paths from '../../../constants/Paths';\nimport { BoardMembershipRoles, BoardViews, UserRoles } from '../../../constants/Enums';\nimport UserAvatar from '../../users/UserAvatar';\nimport UserActionsStep from '../../users/UserActionsStep';\nimport NotificationsStep from '../../notifications/NotificationsStep';\n\nimport styles from './Header.module.scss';\n\nconst POPUP_PROPS = {\n  position: 'bottom right',\n};\n\nconst Header = React.memo(() => {\n  const user = useSelector(selectors.selectCurrentUser);\n  const project = useSelector(selectors.selectCurrentProject);\n  const board = useSelector(selectors.selectCurrentBoard);\n  const notificationIds = useSelector(selectors.selectNotificationIdsForCurrentUser);\n  const isFavoritesEnabled = useSelector(selectors.selectIsFavoritesEnabled);\n  const isEditModeEnabled = useSelector(selectors.selectIsEditModeEnabled);\n\n  const withFavoritesToggler = useSelector(\n    // TODO: use selector instead?\n    (state) => selectors.selectFavoriteProjectIdsForCurrentUser(state).length > 0,\n  );\n\n  const { withEditModeToggler, canEditProject } = useSelector((state) => {\n    if (!project) {\n      return {\n        withEditModeToggler: false,\n        canEditProject: false,\n      };\n    }\n\n    const isAdminInSharedProject = user.role === UserRoles.ADMIN && !project.ownerProjectManagerId;\n    const isManager = selectors.selectIsCurrentUserManagerForCurrentProject(state);\n\n    if (isAdminInSharedProject || isManager) {\n      return {\n        withEditModeToggler: true,\n        canEditProject: isEditModeEnabled,\n      };\n    }\n\n    if (!board) {\n      return {\n        withEditModeToggler: false,\n        canEditProject: false,\n      };\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    const isEditor = !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n\n    return {\n      withEditModeToggler: board.view === BoardViews.KANBAN && isEditor,\n      canEditProject: false,\n    };\n  }, shallowEqual);\n\n  const dispatch = useDispatch();\n\n  const handleToggleFavoritesClick = useCallback(() => {\n    dispatch(entryActions.toggleFavorites(!isFavoritesEnabled));\n  }, [isFavoritesEnabled, dispatch]);\n\n  const handleToggleEditModeClick = useCallback(() => {\n    dispatch(entryActions.toggleEditMode(!isEditModeEnabled));\n  }, [isEditModeEnabled, dispatch]);\n\n  const handleProjectSettingsClick = useCallback(() => {\n    if (!canEditProject) {\n      return;\n    }\n\n    dispatch(entryActions.openProjectSettingsModal());\n  }, [canEditProject, dispatch]);\n\n  const NotificationsPopup = usePopup(NotificationsStep, POPUP_PROPS);\n  const UserActionsPopup = usePopup(UserActionsStep, POPUP_PROPS);\n\n  return (\n    <div className={styles.wrapper}>\n      {!project && (\n        <Link to={Paths.ROOT} className={classNames(styles.logo, styles.title)}>\n          PLANKA\n        </Link>\n      )}\n      <Menu inverted size=\"large\" className={styles.menu}>\n        {project && (\n          <Menu.Menu position=\"left\">\n            <Menu.Item\n              as={Link}\n              to={Paths.ROOT}\n              className={classNames(styles.item, styles.itemHoverable)}\n            >\n              <Icon fitted name=\"arrow left\" />\n            </Menu.Item>\n            <Menu.Item className={classNames(styles.item, styles.title)}>\n              {project.name}\n              {canEditProject && (\n                <Button className={styles.editButton} onClick={handleProjectSettingsClick}>\n                  <Icon fitted name=\"pencil\" size=\"small\" />\n                </Button>\n              )}\n            </Menu.Item>\n          </Menu.Menu>\n        )}\n        <Menu.Menu position=\"right\">\n          {withFavoritesToggler && (\n            <Menu.Item\n              className={classNames(styles.item, styles.itemHoverable)}\n              onClick={handleToggleFavoritesClick}\n            >\n              <Icon\n                fitted\n                name={isFavoritesEnabled ? 'star' : 'star outline'}\n                className={classNames(isFavoritesEnabled && styles.itemIconEnabled)}\n              />\n            </Menu.Item>\n          )}\n          {withEditModeToggler && (\n            <Menu.Item\n              className={classNames(styles.item, styles.itemHoverable)}\n              onClick={handleToggleEditModeClick}\n            >\n              <Icon\n                fitted\n                name={isEditModeEnabled ? 'unlock' : 'lock'}\n                className={classNames(isEditModeEnabled && styles.itemIconEnabled)}\n              />\n            </Menu.Item>\n          )}\n          <NotificationsPopup>\n            <Menu.Item className={classNames(styles.item, styles.itemHoverable)}>\n              <Icon fitted name=\"bell\" />\n              {notificationIds.length > 0 && (\n                <span className={styles.notification}>{notificationIds.length}</span>\n              )}\n            </Menu.Item>\n          </NotificationsPopup>\n          <UserActionsPopup>\n            <Menu.Item className={classNames(styles.item, styles.itemHoverable)}>\n              <span className={styles.userName}>{user.name}</span>\n              <UserAvatar id={user.id} size=\"small\" />\n            </Menu.Item>\n          </UserActionsPopup>\n        </Menu.Menu>\n      </Menu>\n    </div>\n  );\n});\n\nexport default Header;\n"
  },
  {
    "path": "client/src/components/common/Header/Header.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .editButton {\n    background: transparent;\n    box-shadow: none;\n    color: #fff;\n    font-size: 15px;\n    line-height: 34px;\n    margin-left: 8px;\n    opacity: 0;\n    padding: 0;\n    width: 34px;\n\n    &:hover {\n      background: rgba(255, 255, 255, 0.08);\n    }\n  }\n\n  .item {\n    cursor: auto;\n    user-select: auto;\n\n    &:before {\n      background: none;\n    }\n\n    &:active,\n    &:hover {\n      background: transparent;\n      color: rgba(255, 255, 255, 0.9);\n\n      .editButton {\n        opacity: 1;\n      }\n    }\n  }\n\n  .itemHoverable:hover {\n    cursor: pointer;\n    background: rgba(0, 0, 0, 0.32);\n  }\n\n  .itemIconEnabled {\n    color: #bdff22;\n  }\n\n  .logo {\n    color: #fff;\n    flex: 0 0 auto;\n    letter-spacing: 3.5px;\n    line-height: 50px;\n    padding: 0 16px;\n    text-transform: uppercase;\n\n    &:before {\n      background: none;\n    }\n  }\n\n  .menu {\n    background: transparent;\n    border: none;\n    border-radius: 0;\n    box-shadow: none;\n    color: #fff;\n    flex: 1 1 auto;\n    height: 50px;\n    margin: 0;\n    width: 100%;\n  }\n\n  .notification {\n    background: #eb5a46;\n    border-radius: 8px;\n    color: #fff;\n    display: inline-block;\n    font-size: 14px;\n    font-weight: bold;\n    height: 16px;\n    left: 22px;\n    line-height: 16px;\n    min-width: 16px;\n    position: absolute;\n    text-align: center;\n    top: 8px;\n  }\n\n  .title {\n    font-size: 20px;\n    font-weight: bold;\n  }\n\n  .userName {\n    margin-right: 10px;\n\n    @media only screen and (width < 768px) {\n      display: none;\n    }\n  }\n\n  .wrapper {\n    background: rgba(0, 0, 0, 0.24);\n    display: flex;\n    flex: 0 0 auto;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/Header/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Header from './Header';\n\nexport default Header;\n"
  },
  {
    "path": "client/src/components/common/Home/GridProjectsView.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport Projects from './Projects';\n\nconst GridProjectsView = React.memo(() => {\n  const projectIds = useSelector(selectors.selectFilteredProjectIdsForCurrentUser);\n\n  const dispatch = useDispatch();\n\n  const handleAdd = useCallback(() => {\n    dispatch(entryActions.openAddProjectModal());\n  }, [dispatch]);\n\n  return <Projects ids={projectIds} onAdd={handleAdd} />;\n});\n\nexport default GridProjectsView;\n"
  },
  {
    "path": "client/src/components/common/Home/GroupedProjectsView.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';\nimport { ProjectGroups, ProjectTypes } from '../../../constants/Enums';\nimport { ProjectGroupIcons } from '../../../constants/Icons';\nimport Projects from './Projects';\n\nconst TITLE_BY_GROUP = {\n  [ProjectGroups.MY_OWN]: 'common.myOwn',\n  [ProjectGroups.TEAM]: 'common.team',\n  [ProjectGroups.SHARED_WITH_ME]: 'common.sharedWithMe',\n  [ProjectGroups.OTHERS]: 'common.others',\n};\n\nconst DEFAULT_TYPE_BY_GROUP = {\n  [ProjectGroups.MY_OWN]: ProjectTypes.PRIVATE,\n  [ProjectGroups.TEAM]: ProjectTypes.SHARED,\n};\n\nconst GroupedProjectsView = React.memo(() => {\n  const projectIdsByGroup = useSelector(selectors.selectFilteredProjctIdsByGroupForCurrentUser);\n\n  const canAdd = useSelector((state) => {\n    const user = selectors.selectCurrentUser(state);\n    return isUserAdminOrProjectOwner(user);\n  });\n\n  const dispatch = useDispatch();\n\n  const handleAdd = useCallback(\n    (defaultType) => {\n      dispatch(entryActions.openAddProjectModal(defaultType));\n    },\n    [dispatch],\n  );\n\n  return (\n    <>\n      {[ProjectGroups.MY_OWN, ProjectGroups.TEAM].map(\n        (group) =>\n          (projectIdsByGroup[group].length > 0 || canAdd) && (\n            <Projects\n              key={group}\n              ids={projectIdsByGroup[group]}\n              title={TITLE_BY_GROUP[group]}\n              titleIcon={ProjectGroupIcons[group]}\n              onAdd={() => handleAdd(DEFAULT_TYPE_BY_GROUP[group])}\n            />\n          ),\n      )}\n      {[ProjectGroups.SHARED_WITH_ME, ProjectGroups.OTHERS].map(\n        (group) =>\n          projectIdsByGroup[group].length > 0 && (\n            <Projects\n              withTypeIndicator\n              key={group}\n              ids={projectIdsByGroup[group]}\n              title={TITLE_BY_GROUP[group]}\n              titleIcon={ProjectGroupIcons[group]}\n            />\n          ),\n      )}\n    </>\n  );\n});\n\nexport default GroupedProjectsView;\n"
  },
  {
    "path": "client/src/components/common/Home/Home.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport { HomeViews } from '../../../constants/Enums';\nimport GridProjectsView from './GridProjectsView';\nimport GroupedProjectsView from './GroupedProjectsView';\n\nimport styles from './Home.module.scss';\n\nconst Home = React.memo(() => {\n  const view = useSelector(selectors.selectHomeView);\n\n  let View;\n  switch (view) {\n    case HomeViews.GRID_PROJECTS:\n      View = GridProjectsView;\n\n      break;\n    case HomeViews.GROUPED_PROJECTS:\n      View = GroupedProjectsView;\n\n      break;\n    default:\n  }\n\n  return (\n    <div className={styles.wrapper}>\n      <View />\n    </div>\n  );\n});\n\nexport default Home;\n"
  },
  {
    "path": "client/src/components/common/Home/Home.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    height: 100%;\n    margin-left: auto !important;\n    margin-right: auto !important;\n    overflow-y: auto;\n    padding: 0 20px;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.08);\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/Home/Projects.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Grid, Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';\nimport ProjectCard from '../../projects/ProjectCard';\nimport PlusIcon from '../../../assets/images/plus-icon.svg?react';\n\nimport styles from './Projects.module.scss';\n\nconst Projects = React.memo(({ ids, title, titleIcon, withTypeIndicator, onAdd }) => {\n  const canAdd = useSelector((state) => {\n    const user = selectors.selectCurrentUser(state);\n    return isUserAdminOrProjectOwner(user);\n  });\n\n  const [t] = useTranslation();\n\n  return (\n    <div className={classNames(styles.wrapper, !title && styles.wrapperWithoutTitle)}>\n      {title && (\n        <div className={styles.title}>\n          {titleIcon && <Icon name={titleIcon} className={styles.titleIcon} />}\n          {t(title, {\n            context: 'title',\n          })}\n        </div>\n      )}\n      <Grid>\n        {ids.map((id) => (\n          <Grid.Column key={id} className={styles.column}>\n            <ProjectCard\n              withDescription\n              withFavoriteButton\n              id={id}\n              withTypeIndicator={withTypeIndicator}\n              className={styles.card}\n            />\n          </Grid.Column>\n        ))}\n        {onAdd && canAdd && (\n          <Grid.Column className={styles.column}>\n            <button\n              type=\"button\"\n              className={classNames(styles.card, styles.addButton)}\n              onClick={onAdd}\n            >\n              <div className={styles.addButtonCover} />\n              <div className={styles.addButtonTitleWrapper}>\n                <div className={styles.addButtonTitle}>\n                  <PlusIcon className={styles.addButtonTitleIcon} />\n                  {t('action.createProject')}\n                </div>\n              </div>\n            </button>\n          </Grid.Column>\n        )}\n      </Grid>\n    </div>\n  );\n});\n\nProjects.propTypes = {\n  ids: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types\n  title: PropTypes.string,\n  titleIcon: PropTypes.string,\n  withTypeIndicator: PropTypes.bool, // TODO: use plural form?\n  onAdd: PropTypes.func,\n};\n\nProjects.defaultProps = {\n  title: undefined,\n  titleIcon: undefined,\n  withTypeIndicator: false,\n  onAdd: undefined,\n};\n\nexport default Projects;\n"
  },
  {
    "path": "client/src/components/common/Home/Projects.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .addButton {\n    background: transparent;\n    border: none;\n    border-radius: 4px;\n    color: rgba(255, 255, 255, 0.48);\n    cursor: pointer;\n    fill: rgba(255, 255, 255, 0.48);\n    overflow: hidden;\n    padding: 0;\n    position: relative;\n    text-align: center;\n    width: 100%;\n\n    &:hover {\n      .addButtonCover {\n        filter: brightness(0.8);\n      }\n    }\n  }\n\n  .addButtonCover {\n    background: rgba(107, 128, 140, 0.08);\n    bottom: 0;\n    content: \"\";\n    left: 0;\n    position: absolute;\n    right: 0;\n    top: 0;\n    transition: filter 0.2s ease;\n  }\n\n  .addButtonTitle {\n    display: table-cell;\n    font-size: 16px;\n    line-height: 1;\n    vertical-align: middle;\n  }\n\n  .addButtonTitleIcon {\n    display: block;\n    margin: 0 auto 12px;\n    width: 36px;\n  }\n\n  .addButtonTitleWrapper {\n    display: table;\n    height: 100%;\n    padding: 35px 21px;\n    width: 100%;\n  }\n\n  .card {\n    box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1), 0 6px 12px rgba(0, 0, 0, 0.1);\n    height: 150px;\n    transition: box-shadow 0.2s ease;\n\n    &:hover {\n      box-shadow: 0 16px 32px rgba(0, 0, 0, 0.1), 0 32px 64px rgba(0, 0, 0, 0.1);\n    }\n  }\n\n  .column {\n    @media only screen and (width < 840px) {\n      width: 50% !important;\n    }\n\n    @media only screen and (840px <= width < 1120px) {\n      width: 33.3333333333% !important;\n    }\n\n    @media only screen and (1120px <= width < 1400px) {\n      width: 25% !important;\n    }\n\n    @media only screen and (1400px <= width < 1680px) {\n      width: 20% !important;\n    }\n\n    @media only screen and (1680px <= width < 1960px) {\n      width: 16.6666666667% !important;\n    }\n\n    @media only screen and (1960px <= width < 2240px) {\n      width: 14.2857142857% !important;\n    }\n\n    @media only screen and (width >= 2240px) {\n      width: 12.5% !important;\n    }\n  }\n\n  .title {\n    color: #6b808c;\n    font-size: 16px;\n    padding: 14px 0 14px 2px;\n  }\n\n  .titleIcon {\n    margin-right: 10px;\n  }\n\n  .wrapper:not(:last-child) {\n    margin-bottom: 20px;\n  }\n\n  .wrapperWithoutTitle {\n    margin-top: 14px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/Home/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Home from './Home';\n\nexport default Home;\n"
  },
  {
    "path": "client/src/components/common/HomeActions/Filters.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport debounce from 'lodash/debounce';\nimport React, { useCallback, useMemo, useState } from 'react';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon } from 'semantic-ui-react';\nimport { useDidUpdate } from '../../../lib/hooks';\nimport { Input } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useNestedRef } from '../../../hooks';\n\nimport styles from './Filters.module.scss';\n\nconst Filters = React.memo(() => {\n  const defaultSearch = useSelector(selectors.selectProjectsSearch);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [search, setSearch] = useState(defaultSearch);\n  const [isSearchFocused, setIsSearchFocused] = useState(false);\n\n  const debouncedSearch = useMemo(\n    () =>\n      debounce((nextSearch) => {\n        dispatch(entryActions.searchProjects(nextSearch));\n      }, 400),\n    [dispatch],\n  );\n\n  const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');\n\n  const cancelSearch = useCallback(() => {\n    debouncedSearch.cancel();\n    setSearch('');\n    dispatch(entryActions.searchProjects(''));\n    searchFieldRef.current.blur();\n  }, [dispatch, debouncedSearch, searchFieldRef]);\n\n  const handleSearchChange = useCallback(\n    (_, { value }) => {\n      setSearch(value);\n      debouncedSearch(value);\n    },\n    [debouncedSearch],\n  );\n\n  const handleSearchFocus = useCallback(() => {\n    setIsSearchFocused(true);\n  }, []);\n\n  const handleSearchKeyDown = useCallback(\n    (event) => {\n      if (event.key === 'Escape') {\n        cancelSearch();\n      }\n    },\n    [cancelSearch],\n  );\n\n  const handleSearchBlur = useCallback(() => {\n    setIsSearchFocused(false);\n  }, []);\n\n  const handleCancelSearchClick = useCallback(() => {\n    cancelSearch();\n  }, [cancelSearch]);\n\n  useDidUpdate(() => {\n    setSearch(defaultSearch);\n  }, [defaultSearch]);\n\n  const isSearchActive = search || isSearchFocused;\n\n  return (\n    <Input\n      ref={handleSearchFieldRef}\n      value={search}\n      placeholder={t('common.searchProjects')}\n      maxLength={128}\n      icon={\n        isSearchActive ? <Icon link name=\"cancel\" onClick={handleCancelSearchClick} /> : 'search'\n      }\n      className={classNames(styles.search, !isSearchActive && styles.searchInactive)}\n      onFocus={handleSearchFocus}\n      onKeyDown={handleSearchKeyDown}\n      onChange={handleSearchChange}\n      onBlur={handleSearchBlur}\n    />\n  );\n});\n\nexport default Filters;\n"
  },
  {
    "path": "client/src/components/common/HomeActions/Filters.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .search {\n    height: 36px;\n    transition: max-width 0.2s ease;\n    max-width: 500px;\n    width: 100%;\n\n    input {\n      font-size: 13px;\n    }\n  }\n\n  .searchInactive {\n    color: rgba(255, 255, 255, 0.72);\n    max-width: 400px;\n\n    input {\n      background: rgba(0, 0, 0, 0.24);\n      border: none;\n      color: rgba(255, 255, 255, 0.72) !important;\n\n      &::placeholder {\n        color: rgba(255, 255, 255, 0.72);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/HomeActions/HomeActions.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport classNames from 'classnames';\n\nimport Filters from './Filters';\nimport RightSide from './RightSide';\n\nimport styles from './HomeActions.module.scss';\n\nconst HomeActions = React.memo(() => (\n  <div className={styles.wrapper}>\n    <div className={styles.content}>\n      <div className={styles.actions}>\n        <div className={classNames(styles.action, styles.actionFilters)}>\n          <Filters />\n        </div>\n        <div className={classNames(styles.action, styles.actionRightSide)}>\n          <RightSide />\n        </div>\n      </div>\n    </div>\n  </div>\n));\n\nexport default HomeActions;\n"
  },
  {
    "path": "client/src/components/common/HomeActions/HomeActions.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .action {\n    align-items: center;\n    display: flex;\n    flex: 0 0 auto;\n\n    &:first-child {\n      padding-left: 20px;\n    }\n\n    &:last-child {\n      padding-right: 20px;\n    }\n\n    &:not(:last-child) {\n      margin-right: 20px;\n    }\n  }\n\n  .actionFilters {\n    flex-grow: 1;\n  }\n\n  .actionRightSide {\n    margin-left: auto;\n  }\n\n  .actions {\n    display: flex;\n  }\n\n  .content {\n    width: 100%;\n  }\n\n  .wrapper {\n    display: flex;\n    justify-content: center;\n    overflow-x: auto;\n    overflow-y: hidden;\n    -ms-overflow-style: none;\n    padding: 15px 0;\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/HomeActions/RightSide/RightSide.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Icon } from 'semantic-ui-react';\nimport { usePopup } from '../../../../lib/popup';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { HomeViews } from '../../../../constants/Enums';\nimport { HomeViewIcons, ProjectOrderIcons } from '../../../../constants/Icons';\nimport SelectOrderStep from './SelectOrderStep';\n\nimport styles from './RightSide.module.scss';\n\nconst RightSide = React.memo(() => {\n  const currentView = useSelector(selectors.selectHomeView); // TODO: rename?\n  const currentOrder = useSelector(selectors.selectProjectsOrder); // TODO: rename?\n  const isHiddenVisible = useSelector(selectors.selectIsHiddenProjectsVisible);\n\n  const dispatch = useDispatch();\n\n  const handleSelectViewClick = useCallback(\n    ({ currentTarget: { value: view } }) => {\n      dispatch(entryActions.updateHomeView(view));\n    },\n    [dispatch],\n  );\n\n  const handleOrderSelect = useCallback(\n    (order) => {\n      dispatch(entryActions.updateProjectsOrder(order));\n    },\n    [dispatch],\n  );\n\n  const handleToggleHiddenClick = useCallback(() => {\n    dispatch(entryActions.toggleHiddenProjects(!isHiddenVisible));\n  }, [isHiddenVisible, dispatch]);\n\n  const SelectOrderPopup = usePopup(SelectOrderStep);\n\n  return (\n    <>\n      <div className={styles.action}>\n        <button\n          type=\"button\"\n          className={classNames(styles.button)}\n          onClick={handleToggleHiddenClick}\n        >\n          <Icon fitted name={isHiddenVisible ? 'eye slash' : 'eye'} />\n        </button>\n      </div>\n      <div className={styles.action}>\n        <SelectOrderPopup value={currentOrder} onSelect={handleOrderSelect}>\n          <button type=\"button\" className={styles.button}>\n            <Icon fitted name={ProjectOrderIcons[currentOrder]} />\n          </button>\n        </SelectOrderPopup>\n      </div>\n      <div className={styles.action}>\n        <div className={styles.buttonGroup}>\n          {[HomeViews.GRID_PROJECTS, HomeViews.GROUPED_PROJECTS].map((view) => (\n            <button\n              key={view}\n              type=\"button\"\n              value={view}\n              disabled={view === currentView}\n              className={styles.button}\n              onClick={handleSelectViewClick}\n            >\n              <Icon fitted name={HomeViewIcons[view]} />\n            </button>\n          ))}\n        </div>\n      </div>\n    </>\n  );\n});\n\nexport default RightSide;\n"
  },
  {
    "path": "client/src/components/common/HomeActions/RightSide/RightSide.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .action:not(:last-child) {\n    margin-right: 10px;\n  }\n\n  .button {\n    background: rgba(0, 0, 0, 0.24);\n    border: none;\n    border-radius: 3px;\n    color: rgba(255, 255, 255, 0.72);\n    cursor: pointer;\n    display: inline-block;\n    font-size: 13px;\n    line-height: 20px;\n    outline: none;\n    padding: 8px 15px;\n    width: 43px;\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.32);\n    }\n  }\n\n  .buttonGroup {\n    .button {\n      border-radius: 0;\n\n      &:enabled {\n        background: rgba(0, 0, 0, 0.08);\n\n        &:hover {\n          background: rgba(0, 0, 0, 0.12);\n        }\n      }\n\n      &:first-child {\n        border-top-left-radius: 3px;\n        border-bottom-left-radius: 3px;\n      }\n\n      &:last-child {\n        border-top-right-radius: 3px;\n        border-bottom-right-radius: 3px;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/HomeActions/RightSide/SelectOrderStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../../lib/custom-ui';\n\nimport { ProjectOrders } from '../../../../constants/Enums';\nimport { ProjectOrderIcons } from '../../../../constants/Icons';\n\nimport styles from './SelectOrderStep.module.scss';\n\nconst SelectOrderStep = React.memo(({ value, onSelect, onClose }) => {\n  const [t] = useTranslation();\n\n  const handleSelectClick = useCallback(\n    (_, { value: nextValue }) => {\n      if (nextValue !== value) {\n        onSelect(nextValue);\n      }\n\n      onClose();\n    },\n    [value, onSelect, onClose],\n  );\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.selectOrder', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Menu secondary vertical className={styles.menu}>\n          {[\n            ProjectOrders.BY_DEFAULT,\n            ProjectOrders.ALPHABETICALLY,\n            ProjectOrders.BY_CREATION_TIME,\n          ].map((order) => (\n            <Menu.Item\n              key={order}\n              value={order}\n              active={order === value}\n              className={styles.menuItem}\n              onClick={handleSelectClick}\n            >\n              <Icon name={ProjectOrderIcons[order]} className={styles.menuItemIcon} />\n              {t(`common.${order}`)}\n            </Menu.Item>\n          ))}\n        </Menu>\n      </Popup.Content>\n    </>\n  );\n});\n\nSelectOrderStep.propTypes = {\n  value: PropTypes.string.isRequired,\n  onSelect: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default SelectOrderStep;\n"
  },
  {
    "path": "client/src/components/common/HomeActions/RightSide/SelectOrderStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menu {\n    margin: 0 -12px -5px;\n    width: calc(100% + 24px);\n  }\n\n  .menuItem {\n    margin: 0;\n    padding-left: 14px;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.5em 0 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/HomeActions/RightSide/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport RightSide from './RightSide';\n\nexport default RightSide;\n"
  },
  {
    "path": "client/src/components/common/HomeActions/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport HomeActions from './HomeActions';\n\nexport default HomeActions;\n"
  },
  {
    "path": "client/src/components/common/Linkify/Link.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\n\nimport history from '../../../history';\nimport selectors from '../../../selectors';\nimport matchPaths from '../../../utils/match-paths';\nimport Config from '../../../constants/Config';\nimport Paths from '../../../constants/Paths';\n\nconst Link = React.memo(({ href, content, stopPropagation, ...props }) => {\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n\n  const url = useMemo(() => {\n    try {\n      return new URL(href, window.location);\n    } catch {\n      return null;\n    }\n  }, [href]);\n\n  const isSameSite =\n    !!url && url.origin === window.location.origin && url.pathname.startsWith(Config.BASE_PATH);\n\n  const cardsPathMatch = useMemo(() => {\n    if (!isSameSite) {\n      return null;\n    }\n\n    return matchPaths(url.pathname, [Paths.CARDS]);\n  }, [url.pathname, isSameSite]);\n\n  const card = useSelector((state) => {\n    if (!cardsPathMatch) {\n      return null;\n    }\n\n    return selectCardById(state, cardsPathMatch.params.id);\n  });\n\n  const handleClick = useCallback(\n    (event) => {\n      if (stopPropagation) {\n        event.stopPropagation();\n      }\n\n      if (isSameSite) {\n        event.preventDefault();\n        history.push(event.target.href);\n      }\n    },\n    [stopPropagation, isSameSite],\n  );\n\n  return (\n    <a\n      {...props} // eslint-disable-line react/jsx-props-no-spreading\n      href={href}\n      target={isSameSite ? undefined : '_blank'}\n      rel={isSameSite ? undefined : 'noreferrer'}\n      onClick={handleClick}\n    >\n      {card ? card.name : content}\n    </a>\n  );\n});\n\nLink.propTypes = {\n  href: PropTypes.string.isRequired,\n  content: PropTypes.string.isRequired,\n  stopPropagation: PropTypes.bool,\n};\n\nLink.defaultProps = {\n  stopPropagation: false,\n};\n\nexport default Link;\n"
  },
  {
    "path": "client/src/components/common/Linkify/Linkify.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport LinkifyReact from 'linkify-react';\n\nimport Link from './Link';\n\nconst Linkify = React.memo(({ children, linkStopPropagation, ...props }) => {\n  const linkRenderer = useCallback(\n    ({ attributes: { href, ...linkProps }, content }) => (\n      // eslint-disable-next-line react/jsx-props-no-spreading\n      <Link {...linkProps} href={href} content={content} stopPropagation={linkStopPropagation} />\n    ),\n    [linkStopPropagation],\n  );\n\n  return (\n    <LinkifyReact\n      {...props} // eslint-disable-line react/jsx-props-no-spreading\n      options={{\n        defaultProtocol: 'https',\n        render: linkRenderer,\n      }}\n    >\n      {children}\n    </LinkifyReact>\n  );\n});\n\nLinkify.propTypes = {\n  children: PropTypes.string.isRequired,\n  linkStopPropagation: PropTypes.bool,\n};\n\nLinkify.defaultProps = {\n  linkStopPropagation: undefined,\n};\n\nexport default Linkify;\n"
  },
  {
    "path": "client/src/components/common/Linkify/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Linkify from './Linkify';\n\nexport default Linkify;\n"
  },
  {
    "path": "client/src/components/common/Login/Content.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport isEmail from 'validator/lib/isEmail';\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation, Trans } from 'react-i18next';\nimport TextareaAutosize from 'react-textarea-autosize';\nimport { Button, Divider, Form, Grid, Header, Message, TextArea } from 'semantic-ui-react';\nimport { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';\nimport { Input } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\nimport { isUsername } from '../../../utils/validator';\nimport AccessTokenSteps from '../../../constants/AccessTokenSteps';\nimport TermsModal from './TermsModal';\n\nimport logo from '../../../assets/images/logo.png';\n\nimport styles from './Content.module.scss';\n\nconst createMessage = (error, isDebug) => {\n  if (!error) {\n    return error;\n  }\n\n  switch (error.message) {\n    case 'Invalid credentials':\n      return {\n        type: 'error',\n        content: 'common.invalidCredentials',\n      };\n    case 'Invalid email or username':\n      return {\n        type: 'error',\n        content: 'common.invalidEmailOrUsername',\n      };\n    case 'Invalid password':\n      return {\n        type: 'error',\n        content: 'common.invalidPassword',\n      };\n    case 'Use single sign-on':\n      return {\n        type: 'error',\n        content: 'common.useSingleSignOn',\n      };\n    case 'Admin login required to initialize instance':\n      return {\n        type: 'error',\n        content: 'common.adminLoginRequiredToInitializeInstance',\n      };\n    case 'Email already in use':\n      return {\n        type: 'error',\n        content: 'common.emailAlreadyInUse',\n      };\n    case 'Username already in use':\n      return {\n        type: 'error',\n        content: 'common.usernameAlreadyInUse',\n      };\n    case 'Active users limit reached':\n      return {\n        type: 'error',\n        content: 'common.activeUsersLimitReached',\n      };\n    case 'Failed to fetch':\n      return {\n        type: 'warning',\n        content: 'common.noInternetConnection',\n      };\n    case 'Network request failed':\n      return {\n        type: 'warning',\n        content: 'common.serverConnectionFailed',\n      };\n    default:\n      return {\n        type: 'warning',\n        content: isDebug ? error.message : 'common.unknownError',\n      };\n  }\n};\n\nconst Content = React.memo(() => {\n  const bootstrap = useSelector(selectors.selectBootstrap);\n\n  const {\n    data: defaultData,\n    isSubmitting,\n    isSubmittingWithOidc,\n    error,\n    debugLogs,\n    step,\n  } = useSelector(selectors.selectAuthenticateForm);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const wasSubmitting = usePrevious(isSubmitting);\n\n  const [data, handleFieldChange, setData] = useForm(() => {\n    const initialData = {\n      emailOrUsername: '',\n      password: '',\n      ...defaultData,\n    };\n\n    if (bootstrap.isDemoMode) {\n      const params = new URLSearchParams(window.location.hash.slice(1));\n\n      Object.keys(initialData).forEach((fieldName) => {\n        const value = params.get(fieldName);\n\n        if (value !== null) {\n          initialData[fieldName] = value;\n        }\n      });\n    }\n\n    return initialData;\n  });\n\n  const withOidc = !!bootstrap.oidc;\n  const isOidcEnforced = withOidc && bootstrap.oidc.isEnforced;\n  const isOidcDebug = withOidc && bootstrap.oidc.debug;\n\n  const message = useMemo(() => createMessage(error, isOidcDebug), [error, isOidcDebug]);\n  const [focusPasswordFieldState, focusPasswordField] = useToggle();\n\n  const [emailOrUsernameFieldRef, handleEmailOrUsernameFieldRef] = useNestedRef('inputRef');\n  const [passwordFieldRef, handlePasswordFieldRef] = useNestedRef('inputRef');\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      emailOrUsername: data.emailOrUsername.trim(),\n    };\n\n    if (!isEmail(cleanData.emailOrUsername) && !isUsername(cleanData.emailOrUsername)) {\n      emailOrUsernameFieldRef.current.select();\n      return;\n    }\n\n    if (!cleanData.password) {\n      passwordFieldRef.current.focus();\n      return;\n    }\n\n    dispatch(entryActions.authenticate(cleanData));\n  }, [dispatch, data, emailOrUsernameFieldRef, passwordFieldRef]);\n\n  const handleAuthenticateWithOidcClick = useCallback(() => {\n    dispatch(entryActions.authenticateWithOidc());\n  }, [dispatch]);\n\n  const handleMessageDismiss = useCallback(() => {\n    dispatch(entryActions.clearAuthenticateError());\n  }, [dispatch]);\n\n  useEffect(() => {\n    if (!isOidcEnforced) {\n      emailOrUsernameFieldRef.current.focus();\n    }\n  }, [isOidcEnforced, emailOrUsernameFieldRef]);\n\n  useDidUpdate(() => {\n    if (wasSubmitting && !isSubmitting && error) {\n      switch (error.message) {\n        case 'Invalid credentials':\n        case 'Invalid email or username':\n          emailOrUsernameFieldRef.current.select();\n\n          break;\n        case 'Invalid password':\n          setData((prevData) => ({\n            ...prevData,\n            password: '',\n          }));\n          focusPasswordField();\n\n          break;\n        default:\n      }\n    }\n  }, [isSubmitting, wasSubmitting, error]);\n\n  useDidUpdate(() => {\n    passwordFieldRef.current.focus();\n  }, [focusPasswordFieldState]);\n\n  return (\n    <div className={classNames(styles.wrapper, styles.fullHeight)}>\n      <Grid verticalAlign=\"middle\" className={styles.grid}>\n        <Grid.Column computer={6} tablet={16} mobile={16} className={styles.gridItem}>\n          <div className={styles.login}>\n            <div className={styles.form}>\n              <div className={styles.logoWrapper}>\n                <img src={logo} alt=\"\" className={styles.logo} />\n              </div>\n              <Header\n                as=\"h1\"\n                textAlign=\"center\"\n                content={bootstrap.instanceName || 'PLANKA'}\n                className={styles.formTitle}\n              />\n              <Header\n                as=\"h2\"\n                textAlign=\"center\"\n                content={t('common.logIn', {\n                  context: 'title',\n                })}\n                className={styles.formSubtitle}\n              />\n              {message && (\n                <Message\n                  {...{\n                    [message.type]: true,\n                  }}\n                  visible\n                  content={t(message.content)}\n                  onDismiss={handleMessageDismiss}\n                />\n              )}\n              {!isOidcEnforced && (\n                <>\n                  <Form size=\"large\" onSubmit={handleSubmit}>\n                    <div className={styles.inputWrapper}>\n                      <div className={styles.inputLabel}>{t('common.emailOrUsername')}</div>\n                      <Input\n                        fluid\n                        ref={handleEmailOrUsernameFieldRef}\n                        name=\"emailOrUsername\"\n                        value={data.emailOrUsername}\n                        maxLength={256}\n                        readOnly={isSubmitting}\n                        className={styles.input}\n                        onChange={handleFieldChange}\n                      />\n                    </div>\n                    <div className={styles.inputWrapper}>\n                      <div className={styles.inputLabel}>{t('common.password')}</div>\n                      <Input.Password\n                        fluid\n                        ref={handlePasswordFieldRef}\n                        name=\"password\"\n                        value={data.password}\n                        maxLength={256}\n                        readOnly={isSubmitting}\n                        className={styles.input}\n                        onChange={handleFieldChange}\n                      />\n                    </div>\n                    <Form.Button\n                      fluid\n                      primary\n                      icon=\"right arrow\"\n                      labelPosition=\"right\"\n                      content={t('action.logIn')}\n                      loading={isSubmitting}\n                      disabled={isSubmitting || isSubmittingWithOidc}\n                    />\n                  </Form>\n                  {withOidc && (\n                    <Divider horizontal content={t('common.or')} className={styles.divider} />\n                  )}\n                </>\n              )}\n              {withOidc && (\n                <>\n                  <Button\n                    fluid\n                    primary={isOidcDebug ? undefined : isOidcEnforced}\n                    color={isOidcDebug ? 'orange' : undefined}\n                    icon={isOidcEnforced ? 'right arrow' : undefined}\n                    labelPosition={isOidcEnforced ? 'right' : undefined}\n                    content={isOidcDebug ? t('action.debugSso') : t('action.logInWithSso')}\n                    loading={isSubmittingWithOidc}\n                    disabled={isSubmitting || isSubmittingWithOidc}\n                    onClick={handleAuthenticateWithOidcClick}\n                  />\n                  {debugLogs && (\n                    <TextArea\n                      readOnly\n                      as={TextareaAutosize}\n                      value={debugLogs.join('\\n')}\n                      className={styles.debugLog}\n                    />\n                  )}\n                </>\n              )}\n            </div>\n            <div className={styles.poweredBy}>\n              <p className={styles.poweredByText}>\n                <Trans i18nKey=\"common.poweredByPlanka\">\n                  {'Powered by '}\n                  <a href=\"https://github.com/plankanban/planka\" target=\"_blank\" rel=\"noreferrer\">\n                    PLANKA\n                  </a>\n                </Trans>\n              </p>\n            </div>\n          </div>\n        </Grid.Column>\n        <Grid.Column\n          computer={10}\n          only=\"computer\"\n          className={classNames(styles.gridItem, styles.cover)}\n        >\n          <div className={styles.coverOverlay} />\n        </Grid.Column>\n      </Grid>\n      {step === AccessTokenSteps.ACCEPT_TERMS && <TermsModal />}\n    </div>\n  );\n});\n\nexport default Content;\n"
  },
  {
    "path": "client/src/components/common/Login/Content.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .cover {\n    background: url(\"../../../assets/images/login-cover.jpg\") left / cover;\n  }\n\n  .coverOverlay {\n    background: rgba(33, 33, 33, 0.5);\n    bottom: 0;\n    height: 100%;\n    left: 0;\n    position: absolute;\n    right: 0;\n    top: 0;\n    width: 100%;\n  }\n\n  .debugLog {\n    border: 1px solid rgba(9, 30, 66, 0.13);\n    border-radius: 3px;\n    color: #333;\n    line-height: 1.4;\n    margin-top: 16px;\n    padding: 8px 12px;\n    width: 100%;\n  }\n\n  .divider {\n    font-weight: normal;\n  }\n\n  .form {\n    display: flex;\n    flex: 1;\n    flex-direction: column;\n    justify-content: center;\n  }\n\n  .formTitle {\n    font-size: 36px;\n    font-weight: bold;\n  }\n\n  .formSubtitle {\n    font-size: 28px;\n    font-weight: normal;\n    margin-bottom: 32px;\n  }\n\n  .grid {\n    height: 100%;\n    margin: 0;\n  }\n\n  .gridItem {\n    height: 100%;\n    padding: 0;\n  }\n\n  .inputLabel {\n    font-size: 16px;\n    line-height: 20px;\n    margin-bottom: 4px;\n  }\n\n  .inputWrapper {\n    margin-bottom: 16px;\n  }\n\n  .login {\n    display: flex;\n    flex-direction: column;\n    margin: 0 auto;\n    max-width: 440px;\n    min-height: 100vh;\n    padding: 40px;\n    row-gap: 40px;\n  }\n\n  .logo {\n    max-height: 144px;\n    max-width: 80%;\n  }\n\n  .logoWrapper {\n    display: flex;\n    justify-content: center;\n    margin-bottom: 40px;\n  }\n\n  .poweredBy {\n    align-items: center;\n    display: flex;\n    flex-direction: column;\n    gap: 10px;\n    justify-content: center;\n    margin-top: auto;\n  }\n\n  .poweredByText {\n    color: #6b808c;\n    font-weight: lighter;\n    text-align: center;\n  }\n\n  .wrapper {\n    background: #fff;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/Login/Login.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useSelector } from 'react-redux';\nimport { Loader } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport Content from './Content';\n\nconst Login = React.memo(() => {\n  const isInitializing = useSelector(selectors.selectIsInitializing);\n\n  return isInitializing ? <Loader active size=\"massive\" /> : <Content />;\n});\n\nexport default Login;\n"
  },
  {
    "path": "client/src/components/common/Login/TermsModal.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Checkbox, Dropdown, Modal, Segment } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { localeByLanguage } from '../../../locales';\nimport Markdown from '../Markdown';\n\nimport styles from './TermsModal.module.scss';\n\nconst splitTermsAndConfirmations = (content) => {\n  const separator = '\\n[confirmations]::\\n---\\n';\n  const index = content.lastIndexOf(separator);\n\n  if (index === -1) {\n    return [content.trim(), []];\n  }\n\n  const terms = content.slice(0, index).trim();\n\n  const confirmations = content\n    .slice(index + separator.length)\n    .split('\\n')\n    .map((confirmation) => confirmation.replace(/^✔️\\s*/, '').replace(/\\*\\*(.*?)\\*\\*/, '$1'))\n    .filter(Boolean);\n\n  return [terms, confirmations];\n};\n\nconst TermsModal = React.memo(() => {\n  const { termsLanguages } = useSelector(selectors.selectBootstrap);\n\n  const {\n    termsForm: { payload: terms, isSubmitting, isCancelling, isLanguageUpdating },\n  } = useSelector(selectors.selectAuthenticateForm);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [acceptedConfirmationsSet, setAcceptedConfirmationsSet] = useState(new Set());\n\n  const locales = useMemo(\n    () =>\n      termsLanguages.map(\n        (language) =>\n          localeByLanguage[language] || {\n            language,\n            country: language.split('-')[1]?.toLowerCase(),\n            name: language,\n          },\n      ),\n    [termsLanguages],\n  );\n\n  const [content, confirmations] = useMemo(\n    () => splitTermsAndConfirmations(terms.content),\n    [terms.content],\n  );\n\n  const handleContinueClick = useCallback(() => {\n    dispatch(entryActions.acceptTerms(terms.signature));\n  }, [terms.signature, dispatch]);\n\n  const handleCancelClick = useCallback(() => {\n    dispatch(entryActions.cancelTerms());\n  }, [dispatch]);\n\n  const handleLanguageChange = useCallback(\n    (_, { value }) => {\n      dispatch(entryActions.updateTermsLanguage(value));\n    },\n    [dispatch],\n  );\n\n  const handleToggleConfirmationAccept = useCallback((index) => {\n    setAcceptedConfirmationsSet((prevAcceptedConfirmationsSet) => {\n      const nextAcceptedConfirmationsSet = new Set(prevAcceptedConfirmationsSet);\n\n      if (nextAcceptedConfirmationsSet.has(index)) {\n        nextAcceptedConfirmationsSet.delete(index);\n      } else {\n        nextAcceptedConfirmationsSet.add(index);\n      }\n\n      return nextAcceptedConfirmationsSet;\n    });\n  }, []);\n\n  const isAllConfirmationsAccepted = acceptedConfirmationsSet.size === confirmations.length;\n\n  return (\n    <Modal open centered={false}>\n      <Modal.Content>\n        <Dropdown\n          fluid\n          selection\n          options={locales.map((locale) => ({\n            value: locale.language,\n            flag: locale.country,\n            text: locale.name,\n          }))}\n          value={terms.language}\n          loading={isLanguageUpdating}\n          disabled={isLanguageUpdating}\n          className={styles.language}\n          onChange={handleLanguageChange}\n        />\n        <Markdown>{content}</Markdown>\n        {confirmations.length > 0 && (\n          <Segment size=\"massive\" className={styles.confirmations}>\n            {confirmations.map((confirmation, index) => (\n              <Checkbox\n                key={confirmation}\n                checked={acceptedConfirmationsSet.has(index)}\n                label={confirmation}\n                className={styles.confirmationCheckbox}\n                onChange={() => handleToggleConfirmationAccept(index)}\n              />\n            ))}\n          </Segment>\n        )}\n      </Modal.Content>\n      <Modal.Actions>\n        <Button\n          content={t('action.cancelAndClose')}\n          floated=\"left\"\n          loading={isCancelling}\n          disabled={isSubmitting || isCancelling}\n          className={styles.cancelButton}\n          onClick={handleCancelClick}\n        />\n        <Button\n          positive\n          content={t('action.continue')}\n          loading={isSubmitting}\n          disabled={!isAllConfirmationsAccepted || isSubmitting || isCancelling}\n          onClick={handleContinueClick}\n        />\n      </Modal.Actions>\n    </Modal>\n  );\n});\n\nexport default TermsModal;\n"
  },
  {
    "path": "client/src/components/common/Login/TermsModal.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .cancelButton {\n    margin-left: 0;\n  }\n\n  .confirmations {\n    border-color: #5aac44;\n    border-width: 2px;\n    box-shadow: none;\n    display: flex;\n    flex-direction: column;\n    row-gap: 12px;\n  }\n\n  .confirmationCheckbox {\n    font-weight: bold;\n  }\n\n  .language {\n    margin-bottom: 20px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/Login/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Login from './Login';\n\nexport default Login;\n"
  },
  {
    "path": "client/src/components/common/Markdown.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport transform from '@diplodoc/transform';\nimport { defaultOptions as defaultSanitizeOptions } from '@diplodoc/transform/lib/sanitize';\nimport { colorClassName } from '@gravity-ui/markdown-editor';\n\nimport plugins from '../../configs/markdown-plugins';\n\nconst Markdown = React.memo(({ children }) => {\n  const html = useMemo(() => {\n    try {\n      return transform(children, {\n        plugins,\n        breaks: true,\n        linkify: true,\n        linkifyTlds: null,\n        sanitizeOptions: {\n          ...defaultSanitizeOptions,\n          allowedSchemesByTag: { img: ['http', 'https', 'data'] },\n        },\n        defaultClassName: colorClassName,\n      }).result.html;\n    } catch (error) {\n      return error.toString();\n    }\n  }, [children]);\n\n  // eslint-disable-next-line react/no-danger\n  return <div dangerouslySetInnerHTML={{ __html: html }} className=\"yfm\" />;\n});\n\nMarkdown.propTypes = {\n  children: PropTypes.string.isRequired,\n};\n\nexport default Markdown;\n"
  },
  {
    "path": "client/src/components/common/MarkdownEditor/MarkdownEditor.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport {\n  useMarkdownEditor,\n  wysiwygToolbarConfigs,\n  MarkdownEditorView,\n} from '@gravity-ui/markdown-editor';\n/* eslint-disable import/no-unresolved */\nimport { full as toolbarsPreset } from '@gravity-ui/markdown-editor/_/modules/toolbars/presets';\nimport { ActionName } from '@gravity-ui/markdown-editor/_/bundle/config/action-names';\n/* eslint-enable import/no-unresolved */\n\nimport { EditorModes } from '../../../constants/Enums';\n\nimport styles from './MarkdownEditor.module.scss';\n\nconst removedActionNamesSet = new Set([\n  ActionName.checkbox,\n  ActionName.file,\n  ActionName.filePopup,\n  ActionName.tabs,\n]);\n\nremovedActionNamesSet.forEach((actionName) => {\n  delete toolbarsPreset.items[actionName];\n\n  Object.entries(toolbarsPreset.orders).forEach(([orderName, order]) => {\n    order.forEach((actions, actionsIndex) => {\n      toolbarsPreset.orders[orderName][actionsIndex] = actions.filter(\n        (action) => action.id || action !== actionName,\n      );\n    });\n  });\n});\n\nconst commandMenuActions = wysiwygToolbarConfigs.wCommandMenuConfig.filter(\n  (action) => !removedActionNamesSet.has(action.id),\n);\n\nexport const fileToBase64Data = (file) =>\n  new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.readAsDataURL(file);\n    reader.onload = () => resolve(reader.result);\n    reader.onerror = reject;\n  });\n\nconst fileUploadHandler = async (file) => {\n  const base64Data = await fileToBase64Data(file);\n  return { url: base64Data };\n};\n\nconst MarkdownEditor = React.forwardRef(\n  (\n    { defaultValue, defaultMode, isError, onChange, onSubmit, onCancel, onModeChange, ...props },\n    ref,\n  ) => {\n    const wrapperRef = useRef(null);\n\n    const handleWrapperRef = useCallback(\n      (element) => {\n        wrapperRef.current = element;\n\n        if (typeof ref === 'function') {\n          ref(element);\n        } else if (ref) {\n          ref.current = element; // eslint-disable-line no-param-reassign\n        }\n      },\n      [ref],\n    );\n\n    const editor = useMarkdownEditor({\n      md: {\n        breaks: true,\n        linkify: true,\n        linkifyTlds: null,\n      },\n      handlers: {\n        uploadFile: fileUploadHandler,\n      },\n      wysiwygConfig: {\n        extensionOptions: {\n          commandMenu: {\n            actions: commandMenuActions,\n          },\n        },\n        searchPanel: false, // TODO: cancel event does not fire when enabled\n      },\n      // TODO: remove once both search panels are enabled and locales are synced\n      markupConfig: {\n        searchPanel: false,\n      },\n      initial: {\n        markup: defaultValue,\n        mode: defaultMode,\n      },\n    });\n\n    useEffect(() => {\n      const handleChange = () => {\n        onChange(editor.getValue());\n      };\n\n      const handleSubmit = () => {\n        onSubmit();\n      };\n\n      const handleCancel = () => {\n        onCancel();\n      };\n\n      const handleModeChange = ({ mode: nextMode }) => {\n        if (onModeChange) {\n          onModeChange(nextMode);\n        }\n      };\n\n      editor.on('change', handleChange);\n      editor.on('submit', handleSubmit);\n      editor.on('cancel', handleCancel);\n      editor.on('change-editor-mode', handleModeChange);\n\n      return () => {\n        editor.off('change', handleChange);\n        editor.off('submit', handleSubmit);\n        editor.off('cancel', handleCancel);\n        editor.off('change-editor-mode', handleModeChange);\n      };\n    }, [onChange, onSubmit, onCancel, onModeChange, editor]);\n\n    useEffect(() => {\n      const { current: wrapperElement } = wrapperRef;\n\n      const handlePaste = (event) => {\n        event.stopPropagation();\n      };\n\n      wrapperElement.addEventListener('paste', handlePaste);\n\n      return () => {\n        wrapperElement.removeEventListener('paste', handlePaste);\n      };\n    }, []);\n\n    return (\n      <div\n        {...props} // eslint-disable-line react/jsx-props-no-spreading\n        ref={handleWrapperRef}\n        className={classNames(styles.wrapper, isError && styles.wrapperError)}\n      >\n        <MarkdownEditorView\n          autofocus\n          stickyToolbar\n          editor={editor}\n          toolbarsPreset={toolbarsPreset}\n          className={styles.editor}\n        />\n      </div>\n    );\n  },\n);\n\nMarkdownEditor.propTypes = {\n  defaultValue: PropTypes.string.isRequired,\n  defaultMode: PropTypes.oneOf(Object.values(EditorModes)),\n  isError: PropTypes.bool,\n  onChange: PropTypes.func.isRequired,\n  onSubmit: PropTypes.func.isRequired,\n  onCancel: PropTypes.func.isRequired,\n  onModeChange: PropTypes.func,\n};\n\nMarkdownEditor.defaultProps = {\n  defaultMode: EditorModes.WYSIWYG,\n  isError: false,\n  onModeChange: undefined,\n};\n\nexport default React.memo(MarkdownEditor);\n"
  },
  {
    "path": "client/src/components/common/MarkdownEditor/MarkdownEditor.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .editor {\n    min-height: 200px;\n\n    :global {\n\n      .g-md-markup-editor__editor,\n      .g-md-wysiwyg-editor__editor {\n        background: #fff;\n      }\n\n      .g-md-markup-editor__toolbar,\n      .g-md-wysiwyg-editor__toolbar {\n        background: #f5f6f7;\n        z-index: 2000;\n      }\n\n      .yfm-editor {\n        height: 100%;\n      }\n    }\n  }\n\n  .wrapper {\n    --g-md-editor-padding: 6px 12px;\n    --g-md-toolbar-padding: 6px 12px;\n    --g-md-toolbar-sticky-offset: -21px;\n\n    border: 1px solid rgba(9, 30, 66, 0.13);\n    border-radius: 3px;\n  }\n\n  .wrapperError {\n    border: 1px solid #cf513d;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/MarkdownEditor/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport MarkdownEditor from './MarkdownEditor';\n\nexport default MarkdownEditor;\n"
  },
  {
    "path": "client/src/components/common/Root.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport { Provider } from 'react-redux';\nimport { Route, Routes } from 'react-router';\nimport { ThemeProvider, ToasterProvider } from '@gravity-ui/uikit';\n// eslint-disable-next-line import/no-unresolved\nimport { toaster } from '@gravity-ui/uikit/toaster-singleton';\nimport { ReduxRouter } from '../../lib/redux-router';\n\nimport Paths from '../../constants/Paths';\nimport Login from './Login';\nimport Core from './Core';\nimport GhostError from './GhostError';\n\nimport 'react-datepicker/dist/react-datepicker.css';\nimport 'photoswipe/dist/photoswipe.css';\nimport '@gravity-ui/uikit/styles/styles.css';\nimport '../../lib/custom-ui/styles.css';\n\nimport '../../styles.module.scss';\n\nfunction Root({ store, history }) {\n  return (\n    <Provider store={store}>\n      <ReduxRouter history={history}>\n        <ThemeProvider theme=\"light\">\n          <ToasterProvider toaster={toaster}>\n            <Routes>\n              <Route path={Paths.LOGIN} element={<Login />} />\n              <Route path={Paths.OIDC_CALLBACK} element={<Login />} />\n              <Route path={Paths.ROOT} element={<Core />} />\n              <Route path={Paths.PROJECTS} element={<Core />} />\n              <Route path={Paths.BOARDS} element={<Core />} />\n              <Route path={Paths.CARDS} element={<Core />} />\n              <Route path=\"*\" element={<GhostError />} />\n            </Routes>\n          </ToasterProvider>\n        </ThemeProvider>\n      </ReduxRouter>\n    </Provider>\n  );\n}\n\nRoot.propTypes = {\n  /* eslint-disable react/forbid-prop-types */\n  store: PropTypes.object.isRequired,\n  history: PropTypes.object.isRequired,\n  /* eslint-enable react/forbid-prop-types */\n};\n\nexport default Root;\n"
  },
  {
    "path": "client/src/components/common/Static/Static.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useRef } from 'react';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { useTranslation, Trans } from 'react-i18next';\nimport { Button, Icon, Loader } from 'semantic-ui-react';\nimport { useTransitioning } from '../../../lib/hooks';\nimport { usePopup } from '../../../lib/popup';\n\nimport selectors from '../../../selectors';\nimport { BoardViews } from '../../../constants/Enums';\nimport Home from '../Home';\nimport GhostError from '../GhostError';\nimport Board from '../../boards/Board';\nimport AddBoardStep from '../../boards/AddBoardStep';\n\nimport styles from './Static.module.scss';\n\nconst Static = React.memo(() => {\n  const { cardId, projectId } = useSelector(selectors.selectPath);\n  const board = useSelector(selectors.selectCurrentBoard);\n  const isFetching = useSelector(selectors.selectIsContentFetching);\n  const isFavoritesActive = useSelector(selectors.selectIsFavoritesActiveForCurrentUser);\n\n  const canAddBoard = useSelector((state) =>\n    selectors.selectIsCurrentUserManagerForCurrentProject(state),\n  );\n\n  const [t] = useTranslation();\n\n  const wrapperRef = useRef(null);\n\n  const handleTransitionEnd = useTransitioning(wrapperRef, styles.wrapperTransitioning, [\n    isFavoritesActive,\n  ]);\n\n  const AddBoardPopup = usePopup(AddBoardStep);\n\n  let wrapperClassNames;\n  let contentNode;\n\n  if (isFetching) {\n    wrapperClassNames = [styles.wrapperLoader];\n    contentNode = <Loader active size=\"huge\" />;\n  } else if (projectId === undefined) {\n    wrapperClassNames = [isFavoritesActive && styles.wrapperWithFavorites, styles.wrapperVertical];\n    contentNode = <Home />;\n  } else if (cardId === null) {\n    wrapperClassNames = [isFavoritesActive && styles.wrapperWithFavorites, styles.wrapperFlex];\n    contentNode = <GhostError message=\"common.cardNotFound\" />;\n  } else if (board === null) {\n    wrapperClassNames = [isFavoritesActive && styles.wrapperWithFavorites, styles.wrapperFlex];\n    contentNode = <GhostError message=\"common.boardNotFound\" />;\n  } else if (projectId === null) {\n    wrapperClassNames = [isFavoritesActive && styles.wrapperWithFavorites, styles.wrapperFlex];\n    contentNode = <GhostError message=\"common.projectNotFound\" />;\n  } else if (board === undefined) {\n    wrapperClassNames = [\n      isFavoritesActive ? styles.wrapperProjectWithFavorites : styles.wrapperProject,\n      styles.wrapperFlex,\n    ];\n\n    contentNode = (\n      <div className={styles.message}>\n        <Icon inverted name=\"hand point up outline\" size=\"huge\" className={styles.messageIcon} />\n        <h1 className={styles.messageTitle}>\n          {t('common.openBoard', {\n            context: 'title',\n          })}\n        </h1>\n        <div className={styles.messageContent}>\n          <Trans i18nKey=\"common.createNewOneOrSelectExistingOne\" />\n        </div>\n        {canAddBoard && (\n          <AddBoardPopup>\n            <Button basic positive size=\"large\" className={styles.button}>\n              <Icon name=\"plus\" />\n              {t('action.createBoard')}\n            </Button>\n          </AddBoardPopup>\n        )}\n      </div>\n    );\n  } else if (board.isFetching) {\n    wrapperClassNames = [\n      styles.wrapperLoader,\n      isFavoritesActive ? styles.wrapperProjectWithFavorites : styles.wrapperProject,\n    ];\n\n    contentNode = <Loader active size=\"big\" />;\n  } else {\n    wrapperClassNames = [\n      isFavoritesActive ? styles.wrapperBoardWithFavorites : styles.wrapperBoard,\n      [BoardViews.GRID, BoardViews.LIST].includes(board.view) && styles.wrapperVertical,\n      styles.wrapperFlex,\n    ];\n\n    contentNode = <Board />;\n  }\n\n  return (\n    <div\n      ref={wrapperRef}\n      className={classNames(styles.wrapper, ...wrapperClassNames)}\n      onTransitionEnd={handleTransitionEnd}\n    >\n      {contentNode}\n    </div>\n  );\n});\n\nexport default Static;\n"
  },
  {
    "path": "client/src/components/common/Static/Static.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    margin-top: 24px;\n    padding: 0.78571429em 1.5em 0.78571429em;\n  }\n\n  .message {\n    align-content: space-between;\n    align-items: center;\n    color: #fff;\n    display: flex;\n    flex: 1 1 auto;\n    flex-direction: column;\n    justify-content: center;\n  }\n\n  .messageIcon {\n    margin-top: -84px;\n  }\n\n  .messageTitle {\n    font-size: 32px;\n    margin: 24px 0 8px;\n  }\n\n  .messageContent {\n    font-size: 18px;\n    line-height: 1.4;\n    margin: 4px 0 0;\n    text-align: center;\n  }\n\n  .wrapper {\n    height: 100%;\n    margin-top: 116px;\n  }\n\n  .wrapperBoard {\n    margin-top: 174px;\n  }\n\n  .wrapperBoardWithFavorites {\n    margin-top: 264px;\n  }\n\n  .wrapperFlex {\n    display: flex;\n  }\n\n  .wrapperLoader {\n    position: relative;\n  }\n\n  .wrapperProject {\n    margin-top: 98px;\n  }\n\n  .wrapperProjectWithFavorites {\n    margin-top: 188px;\n  }\n\n  .wrapperTransitioning {\n    transition: margin-top 0.2s ease;\n  }\n\n  .wrapperVertical {\n    overflow: hidden;\n  }\n\n  .wrapperWithFavorites {\n    margin-top: 206px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/Static/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Static from './Static';\n\nexport default Static;\n"
  },
  {
    "path": "client/src/components/common/TimeAgo/ExpirableTime.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\n\nimport styles from './ExpirableTime.module.scss';\n\nconst DAY = 1000 * 60 * 60 * 24;\n\nconst isExpired = (value) => value <= Date.now() - DAY;\n\nconst ExpirableTime = React.memo(({ children, date, verboseDate, tooltip, ...props }) => (\n  <time\n    {...props} // eslint-disable-line react/jsx-props-no-spreading\n    dateTime={date.toISOString()}\n    title={tooltip ? verboseDate : undefined}\n    className={classNames(isExpired(date) && styles.expired)}\n  >\n    {children}\n  </time>\n));\n\nExpirableTime.propTypes = {\n  children: PropTypes.string.isRequired,\n  date: PropTypes.instanceOf(Date).isRequired,\n  verboseDate: PropTypes.string,\n  tooltip: PropTypes.bool.isRequired,\n};\n\nExpirableTime.defaultProps = {\n  verboseDate: undefined,\n};\n\nexport default ExpirableTime;\n"
  },
  {
    "path": "client/src/components/common/TimeAgo/ExpirableTime.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .expired {\n    color: #cf513d;\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/TimeAgo/TimeAgo.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport ReactTimeAgo from 'react-time-ago';\n\nimport getDateFormat from '../../../utils/get-date-format';\nimport ExpirableTime from './ExpirableTime';\n\nconst TimeAgo = React.memo(({ date, withExpiration }) => {\n  const [t, i18n] = useTranslation();\n\n  const verboseDateFormatter = useCallback(\n    (value) =>\n      t(`format:${getDateFormat(value)}`, {\n        value,\n        postProcess: 'formatDate',\n      }),\n    [t],\n  );\n\n  return (\n    <ReactTimeAgo\n      date={date}\n      timeStyle=\"round-minute\"\n      locale={i18n.resolvedLanguage}\n      component={withExpiration ? ExpirableTime : undefined}\n      formatVerboseDate={verboseDateFormatter}\n    />\n  );\n});\n\nTimeAgo.propTypes = {\n  date: PropTypes.instanceOf(Date).isRequired,\n  withExpiration: PropTypes.bool,\n};\n\nTimeAgo.defaultProps = {\n  withExpiration: false,\n};\n\nexport default TimeAgo;\n"
  },
  {
    "path": "client/src/components/common/TimeAgo/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport TimeAgo from './TimeAgo';\n\nexport default TimeAgo;\n"
  },
  {
    "path": "client/src/components/common/Toaster/EmptyTrashToast.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { toast } from 'react-hot-toast';\nimport { Button, Icon, Message } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { BoardContexts } from '../../../constants/Enums';\nimport { BoardContextIcons } from '../../../constants/Icons';\n\nimport styles from './EmptyTrashToast.module.scss';\n\nconst EmptyTrashToast = React.memo(({ id, listId }) => {\n  const isCurrentList = useSelector((state) => listId === selectors.selectCurrentListId(state));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleReturnClick = useCallback(() => {\n    dispatch(entryActions.updateContextInCurrentBoard(BoardContexts.BOARD));\n    toast.dismiss(id);\n  }, [id, dispatch]);\n\n  return (\n    <Message visible positive size=\"tiny\">\n      <Icon name=\"checkmark\" />\n      {t('common.trashHasBeenSuccessfullyEmptied')}\n      {isCurrentList && (\n        <Button\n          content={t(`action.returnToBoard`)}\n          icon={BoardContextIcons[BoardContexts.BOARD]}\n          size=\"mini\"\n          className={styles.button}\n          onClick={handleReturnClick}\n        />\n      )}\n    </Message>\n  );\n});\n\nEmptyTrashToast.propTypes = {\n  id: PropTypes.string.isRequired,\n  listId: PropTypes.string.isRequired,\n};\n\nexport default EmptyTrashToast;\n"
  },
  {
    "path": "client/src/components/common/Toaster/EmptyTrashToast.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-left: 6px;\n    margin-right: 0;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/common/Toaster/FileIsTooBigToast.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Message } from 'semantic-ui-react';\n\nconst FileIsTooBigToast = React.memo(() => {\n  const [t] = useTranslation();\n\n  return (\n    <Message visible negative size=\"tiny\">\n      <Icon name=\"file\" />\n      {t('common.uploadFailedFileIsTooBig')}\n    </Message>\n  );\n});\n\nexport default FileIsTooBigToast;\n"
  },
  {
    "path": "client/src/components/common/Toaster/NotEnoughStorageToast.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Message } from 'semantic-ui-react';\n\nconst NotEnoughStorageToast = React.memo(() => {\n  const [t] = useTranslation();\n\n  return (\n    <Message visible negative size=\"tiny\">\n      <Icon name=\"hdd\" />\n      {t('common.uploadFailedNotEnoughStorageSpace')}\n    </Message>\n  );\n});\n\nexport default NotEnoughStorageToast;\n"
  },
  {
    "path": "client/src/components/common/Toaster/SourceCardNotCopyableToast.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Message } from 'semantic-ui-react';\n\nconst SourceCardNotCopyableToast = React.memo(() => {\n  const [t] = useTranslation();\n\n  return (\n    <Message visible negative size=\"tiny\">\n      <Icon name=\"paste\" />\n      {t('common.sourceCardIsNoLongerAvailableForCopying')}\n    </Message>\n  );\n});\n\nexport default SourceCardNotCopyableToast;\n"
  },
  {
    "path": "client/src/components/common/Toaster/SourceCardNotMovableToast.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Message } from 'semantic-ui-react';\n\nconst SourceCardNotMovableToast = React.memo(() => {\n  const [t] = useTranslation();\n\n  return (\n    <Message visible negative size=\"tiny\">\n      <Icon name=\"paste\" />\n      {t('common.sourceCardIsNoLongerAvailableForMoving')}\n    </Message>\n  );\n});\n\nexport default SourceCardNotMovableToast;\n"
  },
  {
    "path": "client/src/components/common/Toaster/Toaster.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { Toaster as HotToaster, ToastBar as HotToastBar } from 'react-hot-toast';\n\nimport ToastTypes from '../../../constants/ToastTypes';\nimport FileIsTooBigToast from './FileIsTooBigToast';\nimport NotEnoughStorageToast from './NotEnoughStorageToast';\nimport EmptyTrashToast from './EmptyTrashToast';\nimport SourceCardNotCopyableToast from './SourceCardNotCopyableToast';\nimport SourceCardNotMovableToast from './SourceCardNotMovableToast';\n\nconst TOAST_BY_TYPE = {\n  [ToastTypes.FILE_IS_TOO_BIG]: FileIsTooBigToast,\n  [ToastTypes.NOT_ENOUGH_STORAGE]: NotEnoughStorageToast,\n  [ToastTypes.EMPTY_TRASH]: EmptyTrashToast,\n  [ToastTypes.SOURCE_CARD_NOT_COPYABLE]: SourceCardNotCopyableToast,\n  [ToastTypes.SOURCE_CARD_NOT_MOVABLE]: SourceCardNotMovableToast,\n};\n\nconst Toaster = React.memo(() => (\n  <HotToaster>\n    {(toast) => (\n      <HotToastBar\n        toast={toast}\n        style={{\n          background: 'transparent',\n          borderRadius: 0,\n          maxWidth: '90%',\n          padding: 0,\n        }}\n      >\n        {() => {\n          const Toast = TOAST_BY_TYPE[toast.message.type];\n\n          // eslint-disable-next-line react/jsx-props-no-spreading\n          return <Toast {...toast.message.params} id={toast.id} />;\n        }}\n      </HotToastBar>\n    )}\n  </HotToaster>\n));\n\nexport default Toaster;\n"
  },
  {
    "path": "client/src/components/common/Toaster/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Toaster from './Toaster';\n\nexport default Toaster;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/AddCustomFieldGroupStep/AddCustomFieldGroupStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Dropdown, Form } from 'semantic-ui-react';\nimport { useDidUpdate, useToggle } from '../../../lib/hooks';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport { useForm } from '../../../hooks';\n\nimport styles from './AddCustomFieldGroupStep.module.scss';\nimport CustomFieldGroupEditor from '../CustomFieldGroupEditor/CustomFieldGroupEditor';\n\nconst AddCustomFieldGroupStep = React.memo(({ onCreate, onBack, onClose }) => {\n  const baseCustomFieldGroups = useSelector(selectors.selectBaseCustomFieldGroupsForCurrentProject);\n\n  const [t] = useTranslation();\n\n  const [data, handleFieldChange, setData] = useForm({\n    baseCustomFieldGroupId: null,\n    name: '',\n  });\n\n  const selectedBaseCustomFieldGroup = useMemo(() => {\n    if (data.baseCustomFieldGroupId === 'without') {\n      return null;\n    }\n\n    return baseCustomFieldGroups.find(\n      (baseCustomFieldGroup) => baseCustomFieldGroup.id === data.baseCustomFieldGroupId,\n    );\n  }, [baseCustomFieldGroups, data.baseCustomFieldGroupId]);\n\n  const [focusNameFieldState, focusNameField] = useToggle();\n\n  const customFieldGroupEditorRef = useRef(null);\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n    };\n\n    if (selectedBaseCustomFieldGroup) {\n      if (!cleanData.name || cleanData.name === selectedBaseCustomFieldGroup.name) {\n        cleanData.name = null;\n      }\n    } else {\n      if (!cleanData.name) {\n        customFieldGroupEditorRef.current.selectNameField();\n        return;\n      }\n\n      delete cleanData.baseCustomFieldGroupId;\n    }\n\n    onCreate(cleanData);\n    onClose();\n  }, [onCreate, onClose, data, selectedBaseCustomFieldGroup, customFieldGroupEditorRef]);\n\n  const handleBaseCustomFieldGroupIdChange = useCallback(\n    (_, { value }) => {\n      setData((prevData) => {\n        const baseCustomFieldGroupId = value === 'without' ? null : value; // FIXME: hack\n\n        const nextSelectedBaseCustomFieldGroup =\n          baseCustomFieldGroupId &&\n          baseCustomFieldGroups.find(\n            (baseCustomFieldGroup) => baseCustomFieldGroup.id === baseCustomFieldGroupId,\n          );\n\n        return {\n          ...prevData,\n          baseCustomFieldGroupId,\n          name: nextSelectedBaseCustomFieldGroup ? nextSelectedBaseCustomFieldGroup.name : '',\n        };\n      });\n\n      focusNameField();\n    },\n    [baseCustomFieldGroups, setData, focusNameField],\n  );\n\n  useDidUpdate(() => {\n    customFieldGroupEditorRef.current.focusNameField();\n  }, [focusNameFieldState]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.addCustomFieldGroup', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.baseGroup')}</div>\n          <Dropdown\n            fluid\n            selection\n            name=\"baseCustomFieldGroupId\"\n            options={[\n              {\n                value: 'without',\n                text: t('common.withoutBaseGroup'),\n              },\n              ...baseCustomFieldGroups.map((baseCustomFieldGroup) => ({\n                value: baseCustomFieldGroup.id,\n                text: baseCustomFieldGroup.name,\n                disabled: !baseCustomFieldGroup.isPersisted,\n              })),\n            ]}\n            value={data.baseCustomFieldGroupId || 'without'}\n            className={styles.field}\n            onChange={handleBaseCustomFieldGroupIdChange}\n          />\n          <CustomFieldGroupEditor\n            ref={customFieldGroupEditorRef}\n            data={data}\n            onFieldChange={handleFieldChange}\n          />\n          <Button positive content={t('action.addCustomFieldGroup')} />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nAddCustomFieldGroupStep.propTypes = {\n  onCreate: PropTypes.func.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nAddCustomFieldGroupStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default AddCustomFieldGroupStep;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/AddCustomFieldGroupStep/AddCustomFieldGroupStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-groups/AddCustomFieldGroupStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AddCustomFieldGroupStep from './AddCustomFieldGroupStep';\n\nexport default AddCustomFieldGroupStep;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroup/CustomFieldGroup.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport CustomField from '../../custom-fields/CustomField';\n\nimport styles from './CustomFieldGroup.module.scss';\n\nconst CustomFieldGroup = React.memo(({ id }) => {\n  const selectCustomFieldIdsByGroupId = useMemo(\n    () => selectors.makeSelectCustomFieldIdsByGroupId(),\n    [],\n  );\n\n  const customFieldIds = useSelector((state) => selectCustomFieldIdsByGroupId(state, id));\n\n  return (\n    <div className={styles.wrapper}>\n      {customFieldIds.map((customFieldId) => (\n        <CustomField key={customFieldId} id={customFieldId} customFieldGroupId={id} />\n      ))}\n    </div>\n  );\n});\n\nCustomFieldGroup.propTypes = {\n  id: PropTypes.string.isRequired,\n};\n\nexport default CustomFieldGroup;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroup/CustomFieldGroup.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    display: grid;\n    gap: 1rem;\n    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroup/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport CustomFieldGroup from './CustomFieldGroup';\n\nexport default CustomFieldGroup;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupEditor/CustomFieldGroupEditor.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useImperativeHandle } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Input } from '../../../lib/custom-ui';\n\nimport { useNestedRef } from '../../../hooks';\n\nimport styles from './CustomFieldGroupEditor.module.scss';\n\nconst CustomFieldGroupEditor = React.forwardRef(({ data, onFieldChange }, ref) => {\n  const [t] = useTranslation();\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const focusNameField = useCallback(() => {\n    nameFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [nameFieldRef]);\n\n  const selectNameField = useCallback(() => {\n    nameFieldRef.current.select();\n  }, [nameFieldRef]);\n\n  useImperativeHandle(\n    ref,\n    () => ({\n      focusNameField,\n      selectNameField,\n    }),\n    [focusNameField, selectNameField],\n  );\n\n  useEffect(() => {\n    focusNameField();\n  }, [focusNameField]);\n\n  return (\n    <>\n      <div className={styles.text}>{t('common.title')}</div>\n      <Input\n        fluid\n        ref={handleNameFieldRef}\n        name=\"name\"\n        value={data.name}\n        maxLength={128}\n        className={styles.field}\n        onChange={onFieldChange}\n      />\n    </>\n  );\n});\n\nCustomFieldGroupEditor.propTypes = {\n  data: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  onFieldChange: PropTypes.func.isRequired,\n};\n\nexport default React.memo(CustomFieldGroupEditor);\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupEditor/CustomFieldGroupEditor.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupEditor/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport CustomFieldGroupEditor from './CustomFieldGroupEditor';\n\nexport default CustomFieldGroupEditor;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/CustomField.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport ReactDOM from 'react-dom';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { Draggable } from 'react-beautiful-dnd';\nimport { Button, Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\n\nimport styles from './CustomField.module.scss';\n\nconst CustomField = React.memo(({ id, index, onEdit }) => {\n  const selectCustomFieldById = useMemo(() => selectors.makeSelectCustomFieldById(), []);\n\n  const customField = useSelector((state) => selectCustomFieldById(state, id));\n\n  const handleEditClick = useCallback(() => {\n    onEdit(id);\n  }, [id, onEdit]);\n\n  return (\n    <Draggable draggableId={id} index={index} isDragDisabled={!customField.isPersisted}>\n      {({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {\n        const contentNode = (\n          // eslint-disable-next-line react/jsx-props-no-spreading\n          <div {...draggableProps} ref={innerRef} className={styles.wrapper}>\n            <span\n              {...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading\n              className={styles.name}\n            >\n              {customField.showOnFrontOfCard && <Icon name=\"pin\" className={styles.nameIcon} />}\n              {customField.name}\n            </span>\n            <Button\n              icon=\"pencil\"\n              size=\"small\"\n              floated=\"right\"\n              disabled={!customField.isPersisted}\n              className={styles.editButton}\n              onClick={handleEditClick}\n            />\n          </div>\n        );\n\n        return isDragging ? ReactDOM.createPortal(contentNode, document.body) : contentNode;\n      }}\n    </Draggable>\n  );\n});\n\nCustomField.propTypes = {\n  id: PropTypes.string.isRequired,\n  index: PropTypes.number.isRequired,\n  onEdit: PropTypes.func.isRequired,\n};\n\nexport default CustomField;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/CustomField.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .editButton {\n    background: transparent;\n    box-shadow: none;\n    flex: 0 0 auto;\n    font-weight: normal;\n    padding: 8px 10px;\n    text-decoration: underline;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .name {\n    background: rgba(9, 30, 66, 0.04);\n    border-radius: 3px;\n    color: #17394d;\n    flex: 1 1 auto;\n    font-size: 14px;\n    overflow: hidden;\n    padding: 8px 32px 8px 10px;\n    position: relative;\n    text-overflow: ellipsis;\n  }\n\n  .nameIcon {\n    color: rgba(9, 30, 66, 0.24);\n    font-size: 12px;\n    margin: 0 8px 0 0;\n    width: 14px;\n  }\n\n  .wrapper {\n    display: flex;\n    margin-bottom: 4px;\n    max-width: 280px;\n    white-space: nowrap;\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/CustomFieldAddStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport entryActions from '../../../entry-actions';\nimport { useForm } from '../../../hooks';\nimport CustomFieldEditor from './CustomFieldEditor';\n\nimport styles from './CustomFieldAddStep.module.scss';\n\nconst CustomFieldAddStep = React.memo(({ customFieldGroupId, defaultData, onBack }) => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: '',\n    showOnFrontOfCard: false,\n    ...defaultData,\n  }));\n\n  const customFieldEditorRef = useRef(null);\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim() || null,\n    };\n\n    if (!cleanData.name) {\n      customFieldEditorRef.current.selectNameField();\n      return;\n    }\n\n    dispatch(entryActions.createCustomFieldInGroup(customFieldGroupId, cleanData));\n    onBack();\n  }, [customFieldGroupId, onBack, dispatch, data]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.addCustomField', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <CustomFieldEditor\n            ref={customFieldEditorRef}\n            data={data}\n            onFieldChange={handleFieldChange}\n          />\n          <Button positive content={t('action.addCustomField')} className={styles.submitButton} />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nCustomFieldAddStep.propTypes = {\n  customFieldGroupId: PropTypes.string.isRequired,\n  defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  onBack: PropTypes.func.isRequired,\n};\n\nexport default CustomFieldAddStep;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/CustomFieldAddStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .submitButton {\n    margin-top: 12px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/CustomFieldEditStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport React, { useCallback, useMemo, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useSteps } from '../../../hooks';\nimport CustomFieldEditor from './CustomFieldEditor';\nimport ConfirmationStep from '../../common/ConfirmationStep';\n\nimport styles from './CustomFieldEditStep.module.scss';\n\nconst StepTypes = {\n  DELETE: 'DELETE',\n};\n\nconst CustomFieldEditStep = React.memo(({ id, onBack }) => {\n  const selectCustomFieldById = useMemo(() => selectors.makeSelectCustomFieldById(), []);\n\n  const customField = useSelector((state) => selectCustomFieldById(state, id));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultData = useMemo(\n    () => ({\n      name: customField.name,\n      showOnFrontOfCard: customField.showOnFrontOfCard,\n    }),\n    [customField.name, customField.showOnFrontOfCard],\n  );\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: '',\n    showOnFrontOfCard: false,\n    ...defaultData,\n  }));\n\n  const [step, openStep, handleBack] = useSteps();\n\n  const customFieldEditorRef = useRef(null);\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim() || null,\n    };\n\n    if (!cleanData.name) {\n      customFieldEditorRef.current.selectNameField();\n      return;\n    }\n\n    if (!dequal(cleanData, defaultData)) {\n      dispatch(entryActions.updateCustomField(id, cleanData));\n    }\n\n    onBack();\n  }, [id, onBack, dispatch, defaultData, data]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteCustomField(id));\n    onBack();\n  }, [id, onBack, dispatch]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  if (step && step.type === StepTypes.DELETE) {\n    return (\n      <ConfirmationStep\n        title=\"common.deleteCustomField\"\n        content=\"common.areYouSureYouWantToDeleteThisCustomField\"\n        buttonContent=\"action.deleteCustomField\"\n        onConfirm={handleDeleteConfirm}\n        onBack={handleBack}\n      />\n    );\n  }\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editCustomField', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <CustomFieldEditor\n            ref={customFieldEditorRef}\n            data={data}\n            onFieldChange={handleFieldChange}\n          />\n          <Button positive content={t('action.save')} className={styles.submitButton} />\n        </Form>\n        <Button\n          content={t('action.delete')}\n          className={styles.deleteButton}\n          onClick={handleDeleteClick}\n        />\n      </Popup.Content>\n    </>\n  );\n});\n\nCustomFieldEditStep.propTypes = {\n  id: PropTypes.string.isRequired,\n  onBack: PropTypes.func.isRequired,\n};\n\nexport default CustomFieldEditStep;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/CustomFieldEditStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .deleteButton {\n    bottom: 12px;\n    box-shadow: 0 1px 0 #cbcccc;\n    position: absolute;\n    right: 9px;\n  }\n\n  .submitButton {\n    margin-top: 12px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/CustomFieldEditor.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useImperativeHandle } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { Radio } from 'semantic-ui-react';\nimport { Input } from '../../../lib/custom-ui';\n\nimport { useNestedRef } from '../../../hooks';\n\nimport styles from './CustomFieldEditor.module.scss';\n\nconst CustomFieldEditor = React.forwardRef(({ data, onFieldChange }, ref) => {\n  const [t] = useTranslation();\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const selectNameField = useCallback(() => {\n    nameFieldRef.current.select();\n  }, [nameFieldRef]);\n\n  useImperativeHandle(\n    ref,\n    () => ({\n      selectNameField,\n    }),\n    [selectNameField],\n  );\n\n  useEffect(() => {\n    nameFieldRef.current.focus();\n  }, [nameFieldRef]);\n\n  return (\n    <>\n      <div className={styles.text}>{t('common.title')}</div>\n      <Input\n        fluid\n        ref={handleNameFieldRef}\n        name=\"name\"\n        value={data.name}\n        maxLength={128}\n        className={styles.fieldName}\n        onChange={onFieldChange}\n      />\n      <Radio\n        toggle\n        name=\"showOnFrontOfCard\"\n        checked={data.showOnFrontOfCard}\n        label={t('common.showOnFrontOfCard')}\n        className={classNames(styles.field, styles.fieldRadio)}\n        onChange={onFieldChange}\n      />\n    </>\n  );\n});\n\nCustomFieldEditor.propTypes = {\n  data: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  onFieldChange: PropTypes.func.isRequired,\n};\n\nexport default React.memo(CustomFieldEditor);\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/CustomFieldEditor.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .fieldName {\n    margin-bottom: 16px;\n  }\n\n  .fieldRadio {\n    width: 100%;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/CustomFieldGroupStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport UnbasedContent from './UnbasedContent';\nimport EditCustomFieldGroupStep from '../EditCustomFieldGroupStep';\n\nconst CustomFieldGroupStep = React.memo(({ id, onBack, onClose }) => {\n  const selectCustomFielGroupdById = useMemo(() => selectors.makeSelectCustomFieldGroupById(), []);\n\n  const customFieldGroup = useSelector((state) => selectCustomFielGroupdById(state, id));\n\n  if (customFieldGroup.baseCustomFieldGroupId) {\n    return (\n      <EditCustomFieldGroupStep\n        withDeleteButton\n        id={id}\n        onBack={onBack}\n        onClose={onBack || onClose}\n      />\n    );\n  }\n\n  return <UnbasedContent id={id} onBack={onBack || onClose} />;\n});\n\nCustomFieldGroupStep.propTypes = {\n  id: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nCustomFieldGroupStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default CustomFieldGroupStep;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/UnbasedContent.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { DragDropContext, Droppable } from 'react-beautiful-dnd';\nimport { Button } from 'semantic-ui-react';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useField, useNestedRef, useSteps } from '../../../hooks';\nimport DroppableTypes from '../../../constants/DroppableTypes';\nimport CustomFieldAddStep from './CustomFieldAddStep';\nimport CustomFieldEditStep from './CustomFieldEditStep';\nimport CustomField from './CustomField';\nimport EditCustomFieldGroupStep from '../EditCustomFieldGroupStep';\nimport ConfirmationStep from '../../common/ConfirmationStep';\n\nimport styles from './UnbasedContent.module.scss';\n\nconst StepTypes = {\n  EDIT: 'EDIT',\n  DELETE: 'DELETE',\n  ADD_CUSTOM_FIELD: 'ADD_CUSTOM_FIELD',\n  EDIT_CUSTOM_FIELD: 'EDIT_CUSTOM_FIELD',\n};\n\nconst UnbasedContent = React.memo(({ id, onBack }) => {\n  const selectCustomFielGroupdById = useMemo(() => selectors.makeSelectCustomFieldGroupById(), []);\n\n  const customFieldGroup = useSelector((state) => selectCustomFielGroupdById(state, id));\n\n  const selectCustomFieldsByGroupId = useMemo(\n    () => selectors.makeSelectCustomFieldsByGroupId(),\n    [],\n  );\n\n  const customFields = useSelector((state) => selectCustomFieldsByGroupId(state, id));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n  const [search, handleSearchChange] = useField('');\n  const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);\n\n  const filteredCustomFields = useMemo(\n    () =>\n      customFields.filter((customField) => customField.name.toLowerCase().includes(cleanSearch)),\n    [customFields, cleanSearch],\n  );\n\n  const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteCustomFieldGroup(id));\n  }, [id, dispatch]);\n\n  const handleCustomFieldDragEnd = useCallback(\n    ({ draggableId, source, destination }) => {\n      if (!destination || source.index === destination.index) {\n        return;\n      }\n\n      dispatch(entryActions.moveCustomField(draggableId, destination.index));\n    },\n    [dispatch],\n  );\n\n  const handleEditClick = useCallback(() => {\n    openStep(StepTypes.EDIT);\n  }, [openStep]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  const handleCustomFieldAddClick = useCallback(() => {\n    openStep(StepTypes.ADD_CUSTOM_FIELD);\n  }, [openStep]);\n\n  const handleCustomFieldEdit = useCallback(\n    (customFieldId) => {\n      openStep(StepTypes.EDIT_CUSTOM_FIELD, {\n        id: customFieldId,\n      });\n    },\n    [openStep],\n  );\n\n  useEffect(() => {\n    searchFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [searchFieldRef]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.EDIT:\n        return <EditCustomFieldGroupStep id={id} onBack={handleBack} onClose={handleBack} />;\n      case StepTypes.DELETE:\n        return (\n          <ConfirmationStep\n            title=\"common.deleteCustomFieldGroup\"\n            content=\"common.areYouSureYouWantToDeleteThisCustomFieldGroup\"\n            buttonContent=\"action.deleteCustomFieldGroup\"\n            typeValue={customFieldGroup.boardId ? customFieldGroup.name : undefined}\n            typeContent={customFieldGroup.boardId ? 'common.typeTitleToConfirm' : undefined}\n            onConfirm={handleDeleteConfirm}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.ADD_CUSTOM_FIELD:\n        return (\n          <CustomFieldAddStep\n            customFieldGroupId={id}\n            // TODO: memoize?\n            defaultData={{\n              name: search,\n            }}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.EDIT_CUSTOM_FIELD: {\n        const currentCustomField = customFields.find(\n          (customField) => customField.id === step.params.id,\n        );\n\n        if (currentCustomField) {\n          return <CustomFieldEditStep id={currentCustomField.id} onBack={handleBack} />;\n        }\n\n        openStep(null);\n\n        break;\n      }\n      default:\n    }\n  }\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.customFieldGroup', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Input\n          fluid\n          ref={handleSearchFieldRef}\n          value={search}\n          placeholder={t('common.searchCustomFields')}\n          maxLength={128}\n          icon=\"search\"\n          onChange={handleSearchChange}\n        />\n        {filteredCustomFields.length > 0 && (\n          <DragDropContext onDragEnd={handleCustomFieldDragEnd}>\n            <Droppable droppableId=\"customFields\" type={DroppableTypes.CUSTOM_FIELD}>\n              {({ innerRef, droppableProps, placeholder }) => (\n                <div\n                  {...droppableProps} // eslint-disable-line react/jsx-props-no-spreading\n                  ref={innerRef}\n                  className={styles.items}\n                >\n                  {filteredCustomFields.map((customField, index) => (\n                    <CustomField\n                      key={customField.id}\n                      id={customField.id}\n                      index={index}\n                      onEdit={handleCustomFieldEdit}\n                    />\n                  ))}\n                  {placeholder}\n                </div>\n              )}\n            </Droppable>\n            <Droppable droppableId=\"customFields:hack\" type={DroppableTypes.CUSTOM_FIELD}>\n              {({ innerRef, droppableProps, placeholder }) => (\n                <div\n                  {...droppableProps} // eslint-disable-line react/jsx-props-no-spreading\n                  ref={innerRef}\n                  className={styles.droppableHack}\n                >\n                  {placeholder}\n                </div>\n              )}\n            </Droppable>\n          </DragDropContext>\n        )}\n        <Button\n          fluid\n          content={t('action.addCustomField')}\n          className={styles.actionButton}\n          onClick={handleCustomFieldAddClick}\n        />\n        <Button\n          fluid\n          content={t('action.editGroup')}\n          className={styles.actionButton}\n          onClick={handleEditClick}\n        />\n        <Button\n          fluid\n          content={t('action.deleteGroup')}\n          className={styles.actionButton}\n          onClick={handleDeleteClick}\n        />\n      </Popup.Content>\n    </>\n  );\n});\n\nUnbasedContent.propTypes = {\n  id: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n};\n\nUnbasedContent.defaultProps = {\n  onBack: undefined,\n};\n\nexport default UnbasedContent;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/UnbasedContent.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actionButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: background 0.3s ease;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .droppableHack {\n    display: none;\n    position: fixed;\n  }\n\n  .items {\n    margin-top: 8px;\n    max-height: 60vh;\n    overflow-x: hidden;\n    overflow-y: auto;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &::-webkit-scrollbar {\n      width: 9px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background-clip: padding-box;\n      border-left: 0.25em transparent solid;\n      border-radius: 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport CustomFieldGroupStep from './CustomFieldGroupStep';\n\nexport default CustomFieldGroupStep;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupsStep/CustomFieldGroupsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { DragDropContext, Droppable } from 'react-beautiful-dnd';\nimport { Button } from 'semantic-ui-react';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useField, useNestedRef, useSteps } from '../../../hooks';\nimport DroppableTypes from '../../../constants/DroppableTypes';\nimport Item from './Item';\nimport CustomFieldGroupStep from '../CustomFieldGroupStep';\nimport AddCustomFieldGroupStep from '../AddCustomFieldGroupStep';\n\nimport styles from './CustomFieldGroupsStep.module.scss';\n\nconst StepTypes = {\n  ADD: 'ADD',\n  EDIT: 'EDIT',\n};\n\nconst CustomFieldGroupsStep = React.memo(({ onBack }) => {\n  const customFieldGroups = useSelector(selectors.selectCustomFieldGroupsForCurrentBoard);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n  const [search, handleSearchChange] = useField('');\n  const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);\n\n  const filteredCustomFieldGroups = useMemo(\n    () =>\n      customFieldGroups.filter((customFieldGroup) =>\n        customFieldGroup.name.toLowerCase().includes(cleanSearch),\n      ),\n    [customFieldGroups, cleanSearch],\n  );\n\n  const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');\n\n  const handleCreate = useCallback(\n    (data) => {\n      dispatch(entryActions.createCustomFieldGroupInCurrentBoard(data));\n    },\n    [dispatch],\n  );\n\n  const handleDragEnd = useCallback(\n    ({ draggableId, source, destination }) => {\n      if (!destination || source.index === destination.index) {\n        return;\n      }\n\n      dispatch(entryActions.moveCustomFieldGroup(draggableId, destination.index));\n    },\n    [dispatch],\n  );\n\n  const handleAddClick = useCallback(() => {\n    openStep(StepTypes.ADD);\n  }, [openStep]);\n\n  const handleEdit = useCallback(\n    (id) => {\n      openStep(StepTypes.EDIT, {\n        id,\n      });\n    },\n    [openStep],\n  );\n\n  useEffect(() => {\n    searchFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [searchFieldRef]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.ADD:\n        return (\n          <AddCustomFieldGroupStep\n            onCreate={handleCreate}\n            onBack={handleBack}\n            onClose={handleBack}\n          />\n        );\n      case StepTypes.EDIT: {\n        const currentCustomFieldGroup = customFieldGroups.find(\n          (customFieldGroup) => customFieldGroup.id === step.params.id,\n        );\n\n        if (currentCustomFieldGroup) {\n          return (\n            <CustomFieldGroupStep\n              id={currentCustomFieldGroup.id}\n              onBack={handleBack}\n              onClose={handleBack}\n            />\n          );\n        }\n\n        openStep(null);\n\n        break;\n      }\n      default:\n    }\n  }\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.customFieldGroups', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Input\n          fluid\n          ref={handleSearchFieldRef}\n          value={search}\n          placeholder={t('common.searchCustomFieldGroups')}\n          maxLength={128}\n          icon=\"search\"\n          onChange={handleSearchChange}\n        />\n        {filteredCustomFieldGroups.length > 0 && (\n          <DragDropContext onDragEnd={handleDragEnd}>\n            <Droppable droppableId=\"customFieldGroups\" type={DroppableTypes.CUSTOM_FIELD_GROUP}>\n              {({ innerRef, droppableProps, placeholder }) => (\n                <div\n                  {...droppableProps} // eslint-disable-line react/jsx-props-no-spreading\n                  ref={innerRef}\n                  className={styles.items}\n                >\n                  {filteredCustomFieldGroups.map((customFieldGroup, index) => (\n                    <Item\n                      key={customFieldGroup.id}\n                      id={customFieldGroup.id}\n                      index={index}\n                      onEdit={handleEdit}\n                    />\n                  ))}\n                  {placeholder}\n                </div>\n              )}\n            </Droppable>\n            <Droppable\n              droppableId=\"customFieldGroups:hack\"\n              type={DroppableTypes.CUSTOM_FIELD_GROUP}\n            >\n              {({ innerRef, droppableProps, placeholder }) => (\n                <div\n                  {...droppableProps} // eslint-disable-line react/jsx-props-no-spreading\n                  ref={innerRef}\n                  className={styles.droppableHack}\n                >\n                  {placeholder}\n                </div>\n              )}\n            </Droppable>\n          </DragDropContext>\n        )}\n        <Button\n          fluid\n          content={t('action.addCustomFieldGroup')}\n          className={styles.actionButton}\n          onClick={handleAddClick}\n        />\n      </Popup.Content>\n    </>\n  );\n});\n\nCustomFieldGroupsStep.propTypes = {\n  onBack: PropTypes.func,\n};\n\nCustomFieldGroupsStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default CustomFieldGroupsStep;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupsStep/CustomFieldGroupsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actionButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: background 0.3s ease;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .droppableHack {\n    display: none;\n    position: fixed;\n  }\n\n  .items {\n    margin-top: 8px;\n    max-height: 60vh;\n    overflow-x: hidden;\n    overflow-y: auto;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &::-webkit-scrollbar {\n      width: 9px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background-clip: padding-box;\n      border-left: 0.25em transparent solid;\n      border-radius: 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupsStep/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport ReactDOM from 'react-dom';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { Draggable } from 'react-beautiful-dnd';\nimport { Button } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id, index, onEdit }) => {\n  const selectCustomFieldGroupById = useMemo(() => selectors.makeSelectCustomFieldGroupById(), []);\n\n  const customFieldGroup = useSelector((state) => selectCustomFieldGroupById(state, id));\n\n  const handleEditClick = useCallback(() => {\n    onEdit(id);\n  }, [id, onEdit]);\n\n  return (\n    <Draggable draggableId={id} index={index} isDragDisabled={!customFieldGroup.isPersisted}>\n      {({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {\n        const contentNode = (\n          // eslint-disable-next-line react/jsx-props-no-spreading\n          <div {...draggableProps} ref={innerRef} className={styles.wrapper}>\n            <span\n              {...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading\n              className={styles.name}\n            >\n              {customFieldGroup.name}\n            </span>\n            <Button\n              icon=\"pencil\"\n              size=\"small\"\n              floated=\"right\"\n              disabled={!customFieldGroup.isPersisted}\n              className={styles.editButton}\n              onClick={handleEditClick}\n            />\n          </div>\n        );\n\n        return isDragging ? ReactDOM.createPortal(contentNode, document.body) : contentNode;\n      }}\n    </Draggable>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n  index: PropTypes.number.isRequired,\n  onEdit: PropTypes.func.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupsStep/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .editButton {\n    background: transparent;\n    box-shadow: none;\n    flex: 0 0 auto;\n    font-weight: normal;\n    padding: 8px 10px;\n    text-decoration: underline;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .name {\n    background: rgba(9, 30, 66, 0.04);\n    border-radius: 3px;\n    color: #17394d;\n    flex: 1 1 auto;\n    font-size: 14px;\n    overflow: hidden;\n    padding: 8px 32px 8px 10px;\n    position: relative;\n    text-overflow: ellipsis;\n  }\n\n  .wrapper {\n    display: flex;\n    margin-bottom: 4px;\n    max-width: 280px;\n    white-space: nowrap;\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-groups/CustomFieldGroupsStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport CustomFieldGroupsStep from './CustomFieldGroupsStep';\n\nexport default CustomFieldGroupsStep;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/EditCustomFieldGroupStep/EditCustomFieldGroupStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport React, { useCallback, useMemo, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useSteps } from '../../../hooks';\nimport CustomFieldGroupEditor from '../CustomFieldGroupEditor';\nimport ConfirmationStep from '../../common/ConfirmationStep';\n\nimport styles from './EditCustomFieldGroupStep.module.scss';\n\nconst StepTypes = {\n  DELETE: 'DELETE',\n};\n\nconst EditCustomFieldGroupStep = React.memo(({ id, withDeleteButton, onBack, onClose }) => {\n  const selectCustomFieldGroupById = useMemo(() => selectors.makeSelectCustomFieldGroupById(), []);\n\n  const customFieldGroup = useSelector((state) => selectCustomFieldGroupById(state, id));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultData = useMemo(\n    () => ({\n      name: customFieldGroup.name,\n    }),\n    [customFieldGroup.name],\n  );\n\n  const [data, handleFieldChange] = useForm({\n    name: '',\n    ...defaultData,\n  });\n\n  const [step, openStep, handleBack] = useSteps();\n\n  const customFieldGroupEditorRef = useRef(null);\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n    };\n\n    if (!cleanData.name) {\n      customFieldGroupEditorRef.current.selectNameField();\n      return;\n    }\n\n    if (!dequal(cleanData, defaultData)) {\n      dispatch(entryActions.updateCustomFieldGroup(id, cleanData));\n    }\n\n    onClose();\n  }, [id, onClose, dispatch, defaultData, data, customFieldGroupEditorRef]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteCustomFieldGroup(id));\n  }, [id, dispatch]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  if (step && step.type === StepTypes.DELETE) {\n    return (\n      <ConfirmationStep\n        title=\"common.deleteCustomFieldGroup\"\n        content=\"common.areYouSureYouWantToDeleteThisCustomFieldGroup\"\n        buttonContent=\"action.deleteCustomFieldGroup\"\n        typeValue={customFieldGroup.boardId ? customFieldGroup.name : undefined}\n        typeContent={customFieldGroup.boardId ? 'common.typeTitleToConfirm' : undefined}\n        onConfirm={handleDeleteConfirm}\n        onBack={handleBack}\n      />\n    );\n  }\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editCustomFieldGroup', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <CustomFieldGroupEditor data={data} onFieldChange={handleFieldChange} />\n          <Button positive content={t('action.save')} />\n        </Form>\n        {withDeleteButton && (\n          <Button\n            content={t('action.delete')}\n            className={styles.deleteButton}\n            onClick={handleDeleteClick}\n          />\n        )}\n      </Popup.Content>\n    </>\n  );\n});\n\nEditCustomFieldGroupStep.propTypes = {\n  id: PropTypes.string.isRequired,\n  withDeleteButton: PropTypes.bool,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nEditCustomFieldGroupStep.defaultProps = {\n  withDeleteButton: false,\n  onBack: undefined,\n};\n\nexport default EditCustomFieldGroupStep;\n"
  },
  {
    "path": "client/src/components/custom-field-groups/EditCustomFieldGroupStep/EditCustomFieldGroupStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .deleteButton {\n    bottom: 12px;\n    box-shadow: 0 1px 0 #cbcccc;\n    position: absolute;\n    right: 9px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-groups/EditCustomFieldGroupStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EditCustomFieldGroupStep from './EditCustomFieldGroupStep';\n\nexport default EditCustomFieldGroupStep;\n"
  },
  {
    "path": "client/src/components/custom-field-values/CustomFieldValueChip/CustomFieldValueChip.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\n\nimport styles from './CustomFieldValueChip.module.scss';\n\nconst Sizes = {\n  TINY: 'tiny',\n  SMALL: 'small',\n  MEDIUM: 'medium',\n};\n\nconst CustomFieldValueChip = React.memo(({ id, size, onClick }) => {\n  const selectCustomFieldValueById = useMemo(() => selectors.makeSelectCustomFieldValueById(), []);\n  const selectCustomFieldById = useMemo(() => selectors.makeSelectCustomFieldById(), []);\n\n  const customFieldValue = useSelector((state) => selectCustomFieldValueById(state, id));\n\n  const customField = useSelector((state) =>\n    selectCustomFieldById(state, customFieldValue.customFieldId),\n  );\n\n  const contentNode = (\n    <span\n      title={`${customField.name}: ${customFieldValue.content}`}\n      className={classNames(\n        styles.wrapper,\n        styles[`wrapper${upperFirst(size)}`],\n        onClick && styles.wrapperHoverable,\n      )}\n    >\n      {!Number.isNaN(parseFloat(customFieldValue.content)) && `${customField.name}: `}\n      {customFieldValue.content}\n    </span>\n  );\n\n  return onClick ? (\n    <button\n      type=\"button\"\n      disabled={customField.isDisabled}\n      className={styles.button}\n      onClick={onClick}\n    >\n      {contentNode}\n    </button>\n  ) : (\n    contentNode\n  );\n});\n\nCustomFieldValueChip.propTypes = {\n  id: PropTypes.string.isRequired,\n  size: PropTypes.oneOf(Object.values(Sizes)),\n  onClick: PropTypes.func,\n};\n\nCustomFieldValueChip.defaultProps = {\n  size: Sizes.MEDIUM,\n  onClick: undefined,\n};\n\nexport default CustomFieldValueChip;\n"
  },
  {
    "path": "client/src/components/custom-field-values/CustomFieldValueChip/CustomFieldValueChip.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    background: transparent;\n    border: none;\n    cursor: pointer;\n    display: inline-block;\n    max-width: 100%;\n    outline: none;\n    padding: 0;\n  }\n\n  .wrapper {\n    background: #dce0e4;\n    border-radius: 3px;\n    color: #6a808b;\n    display: inline-block;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    transition: background 0.3s ease;\n    vertical-align: top;\n    white-space: nowrap;\n  }\n\n  .wrapperHoverable:hover {\n    background: #d2d8dc;\n    color: #17394d;\n  }\n\n  /* Sizes */\n\n  .wrapperTiny {\n    font-size: 12px;\n    line-height: 20px;\n    max-width: 176px;\n    padding: 0px 6px;\n  }\n\n  .wrapperSmall {\n    font-size: 12px;\n    line-height: 20px;\n    max-width: 176px;\n    padding: 2px 8px;\n  }\n\n  .wrapperMedium {\n    line-height: 20px;\n    max-width: 230px;\n    padding: 6px 12px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-field-values/CustomFieldValueChip/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport CustomFieldValueChip from './CustomFieldValueChip';\n\nexport default CustomFieldValueChip;\n"
  },
  {
    "path": "client/src/components/custom-fields/CustomField/CustomField.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Button, Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { buildCustomFieldValueId } from '../../../models/CustomFieldValue';\nimport { isListArchiveOrTrash } from '../../../utils/record-helpers';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\nimport ValueField from './ValueField';\n\nimport styles from './CustomField.module.scss';\n\nconst CustomField = React.memo(({ id, customFieldGroupId }) => {\n  const selectCustomFieldById = useMemo(() => selectors.makeSelectCustomFieldById(), []);\n  const selectCustomFieldValueById = useMemo(() => selectors.makeSelectCustomFieldValueById(), []);\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const { cardId } = useSelector(selectors.selectPath);\n  const customField = useSelector((state) => selectCustomFieldById(state, id));\n\n  const customFieldValue = useSelector((state) =>\n    selectCustomFieldValueById(\n      state,\n      buildCustomFieldValueId({\n        cardId,\n        customFieldGroupId,\n        customFieldId: id,\n      }),\n    ),\n  );\n\n  const canEdit = useSelector((state) => {\n    const { listId } = selectors.selectCurrentCard(state);\n    const list = selectListById(state, listId);\n\n    if (isListArchiveOrTrash(list)) {\n      return false;\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  const dispatch = useDispatch();\n  const [isCopied, setIsCopied] = useState(false);\n\n  const handleValueUpdate = useCallback(\n    (content) => {\n      if (content) {\n        dispatch(\n          entryActions.updateCustomFieldValue(cardId, customFieldGroupId, id, {\n            content,\n          }),\n        );\n      } else {\n        dispatch(entryActions.deleteCustomFieldValue(cardId, customFieldGroupId, id));\n      }\n    },\n    [id, customFieldGroupId, cardId, dispatch],\n  );\n\n  const handleCopyClick = useCallback(() => {\n    if (isCopied) {\n      return;\n    }\n\n    navigator.clipboard.writeText(customFieldValue.content);\n\n    setIsCopied(true);\n    setTimeout(() => {\n      setIsCopied(false);\n    }, 1000);\n  }, [customFieldValue, isCopied]);\n\n  return (\n    <div>\n      <div className={styles.name}>{customField.name}</div>\n      <div className={styles.valueWrapper}>\n        {canEdit ? (\n          <ValueField\n            defaultValue={customFieldValue && customFieldValue.content}\n            disabled={!customField.isPersisted}\n            onUpdate={handleValueUpdate}\n          />\n        ) : (\n          <div className={styles.value}>\n            {customFieldValue ? customFieldValue.content : '\\u00A0'}\n          </div>\n        )}\n        {customFieldValue && customFieldValue.content && (\n          <Button className={styles.copyButton} onClick={handleCopyClick}>\n            <Icon fitted name={isCopied ? 'check' : 'copy'} />\n          </Button>\n        )}\n      </div>\n    </div>\n  );\n});\n\nCustomField.propTypes = {\n  id: PropTypes.string.isRequired,\n  customFieldGroupId: PropTypes.string.isRequired,\n};\n\nexport default CustomField;\n"
  },
  {
    "path": "client/src/components/custom-fields/CustomField/CustomField.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .copyButton {\n    background: #ebeef0;\n    box-shadow: none;\n    border-radius: 3px;\n    box-sizing: content-box;\n    color: #516b7a;\n    display: none;\n    height: 30px;\n    margin: 0;\n    min-height: auto;\n    outline: none;\n    padding: 4px;\n    position: absolute;\n    right: 0;\n    top: 0;\n    transition: background 85ms ease;\n    width: 20px;\n\n    &:hover {\n      background: #dfe3e6;\n      color: #4c4c4c;\n    }\n  }\n\n  .name {\n    color: #6b808c;\n    font-size: 13px;\n    line-height: 1;\n    margin-bottom: 6px;\n    padding: 1px;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .value {\n    background: rgba(9, 30, 66, 0.04);\n    border: 1px solid transparent;\n    border-radius: 3px;\n    color: #17394d;\n    line-height: 20px;\n    opacity: 0.45;\n    overflow: hidden;\n    padding: 8px 12px;\n    text-overflow: ellipsis;\n  }\n\n  .valueWrapper {\n    position: relative;\n\n    &:hover:not(:has(input:focus)) {\n      .copyButton {\n        display: block;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-fields/CustomField/ValueField.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';\nimport { Input } from '../../../lib/custom-ui';\n\nimport { useEscapeInterceptor, useField, useNestedRef } from '../../../hooks';\n\nimport styles from './ValueField.module.scss';\n\nconst ValueField = React.memo(({ defaultValue, onUpdate, ...props }) => {\n  const prevDefaultValue = usePrevious(defaultValue);\n  const [value, handleChange, setValue] = useField(defaultValue || '');\n  const [blurFieldState, blurField] = useToggle();\n\n  const [fieldRef, handleFieldRef] = useNestedRef('inputRef');\n  const isFocusedRef = useRef(false);\n\n  const handleEscape = useCallback(() => {\n    setValue(defaultValue || '');\n    blurField();\n  }, [defaultValue, setValue, blurField]);\n\n  const [activateEscapeInterceptor, deactivateEscapeInterceptor] =\n    useEscapeInterceptor(handleEscape);\n\n  const handleFocus = useCallback(() => {\n    activateEscapeInterceptor();\n    isFocusedRef.current = true;\n  }, [activateEscapeInterceptor]);\n\n  const handleKeyDown = useCallback(\n    (event) => {\n      if (event.key === 'Enter') {\n        event.preventDefault();\n        fieldRef.current.blur();\n      }\n    },\n    [fieldRef],\n  );\n\n  const handleBlur = useCallback(() => {\n    deactivateEscapeInterceptor();\n    isFocusedRef.current = false;\n\n    const cleanValue = value.trim() || null;\n\n    if (cleanValue !== defaultValue) {\n      onUpdate(cleanValue);\n    }\n  }, [defaultValue, onUpdate, value, deactivateEscapeInterceptor]);\n\n  useDidUpdate(() => {\n    if (!isFocusedRef.current && defaultValue !== prevDefaultValue) {\n      setValue(defaultValue || '');\n    }\n  }, [defaultValue, prevDefaultValue]);\n\n  useDidUpdate(() => {\n    fieldRef.current.blur();\n  }, [blurFieldState]);\n\n  return (\n    <Input\n      {...props} // eslint-disable-line react/jsx-props-no-spreading\n      fluid\n      ref={handleFieldRef}\n      value={value}\n      maxLength={512}\n      className={styles.field}\n      onFocus={handleFocus}\n      onKeyDown={handleKeyDown}\n      onChange={handleChange}\n      onBlur={handleBlur}\n    />\n  );\n});\n\nValueField.propTypes = {\n  defaultValue: PropTypes.string,\n  onUpdate: PropTypes.func.isRequired,\n};\n\nValueField.defaultProps = {\n  defaultValue: undefined,\n};\n\nexport default ValueField;\n"
  },
  {
    "path": "client/src/components/custom-fields/CustomField/ValueField.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field input {\n    background: rgba(9, 30, 66, 0.04);\n    border: 1px solid transparent;\n    overflow: hidden;\n    text-overflow: ellipsis;\n\n    &:focus {\n      background: #fff;\n      border-color: #5ba4cf;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/custom-fields/CustomField/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport CustomField from './CustomField';\n\nexport default CustomField;\n"
  },
  {
    "path": "client/src/components/labels/LabelChip/LabelChip.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport camelCase from 'lodash/camelCase';\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\n\nimport styles from './LabelChip.module.scss';\nimport globalStyles from '../../../styles.module.scss';\n\nconst Sizes = {\n  TINY: 'tiny',\n  SMALL: 'small',\n  MEDIUM: 'medium',\n};\n\nconst LabelChip = React.memo(({ id, size, onClick }) => {\n  const selectLabelById = useMemo(() => selectors.makeSelectLabelById(), []);\n\n  const label = useSelector((state) => selectLabelById(state, id));\n\n  const contentNode = (\n    <span\n      title={label.name}\n      className={classNames(\n        styles.wrapper,\n        !label.name && styles.wrapperNameless,\n        styles[`wrapper${upperFirst(size)}`],\n        onClick && styles.wrapperHoverable,\n        globalStyles[`background${upperFirst(camelCase(label.color))}`],\n      )}\n    >\n      {label.name || '\\u00A0'}\n    </span>\n  );\n\n  return onClick ? (\n    <button\n      data-id={id}\n      type=\"button\"\n      disabled={label.isDisabled}\n      className={styles.button}\n      onClick={onClick}\n    >\n      {contentNode}\n    </button>\n  ) : (\n    contentNode\n  );\n});\n\nLabelChip.propTypes = {\n  id: PropTypes.string.isRequired,\n  size: PropTypes.oneOf(Object.values(Sizes)),\n  onClick: PropTypes.func,\n};\n\nLabelChip.defaultProps = {\n  size: Sizes.MEDIUM,\n  onClick: undefined,\n};\n\nexport default LabelChip;\n"
  },
  {
    "path": "client/src/components/labels/LabelChip/LabelChip.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    background: transparent;\n    border: none;\n    cursor: pointer;\n    display: inline-block;\n    max-width: 100%;\n    outline: none;\n    padding: 0;\n  }\n\n  .wrapper {\n    border-radius: 3px;\n    color: #fff;\n    display: inline-block;\n    font-weight: normal;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);\n    vertical-align: top;\n    white-space: nowrap;\n  }\n\n  .wrapperNameless {\n    width: 40px;\n  }\n\n  .wrapperHoverable:hover {\n    opacity: 0.75;\n  }\n\n  /* Sizes */\n\n  .wrapperTiny {\n    font-size: 12px;\n    line-height: 20px;\n    max-width: 176px;\n    padding: 0 6px;\n  }\n\n  .wrapperSmall {\n    font-size: 12px;\n    line-height: 20px;\n    max-width: 176px;\n    padding: 2px 8px;\n  }\n\n  .wrapperMedium {\n    font-size: 14px;\n    line-height: 32px;\n    max-width: 230px;\n    padding: 0 12px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/labels/LabelChip/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport LabelChip from './LabelChip';\n\nexport default LabelChip;\n"
  },
  {
    "path": "client/src/components/labels/LabelsStep/AddStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport entryActions from '../../../entry-actions';\nimport { useForm } from '../../../hooks';\nimport LABEL_COLORS from '../../../constants/LabelColors';\nimport Editor from './Editor';\n\nconst AddStep = React.memo(({ cardId, defaultData, onBack }) => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: '',\n    color: LABEL_COLORS[0],\n    ...defaultData,\n  }));\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim() || null,\n    };\n\n    dispatch(\n      cardId\n        ? entryActions.createLabelFromCard(cardId, cleanData)\n        : entryActions.createLabelInCurrentBoard(cleanData),\n    );\n\n    onBack();\n  }, [cardId, onBack, data, dispatch]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.createLabel', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <Editor data={data} onFieldChange={handleFieldChange} />\n          <Button positive content={t('action.createLabel')} />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nAddStep.propTypes = {\n  cardId: PropTypes.string,\n  defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  onBack: PropTypes.func.isRequired,\n};\n\nAddStep.defaultProps = {\n  cardId: undefined,\n};\n\nexport default AddStep;\n"
  },
  {
    "path": "client/src/components/labels/LabelsStep/EditStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useSteps } from '../../../hooks';\nimport LABEL_COLORS from '../../../constants/LabelColors';\nimport Editor from './Editor';\nimport ConfirmationStep from '../../common/ConfirmationStep';\n\nimport styles from './EditStep.module.scss';\n\nconst StepTypes = {\n  DELETE: 'DELETE',\n};\n\nconst EditStep = React.memo(({ labelId, onBack }) => {\n  const selectLabelById = useMemo(() => selectors.makeSelectLabelById(), []);\n\n  const label = useSelector((state) => selectLabelById(state, labelId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultData = useMemo(\n    () => ({\n      name: label.name,\n      color: label.color,\n    }),\n    [label.name, label.color],\n  );\n\n  const [data, handleFieldChange] = useForm(() => ({\n    color: LABEL_COLORS[0],\n    ...defaultData,\n    name: defaultData.name || '',\n  }));\n\n  const [step, openStep, handleBack] = useSteps();\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim() || null,\n    };\n\n    if (!dequal(cleanData, defaultData)) {\n      dispatch(entryActions.updateLabel(labelId, cleanData));\n    }\n\n    onBack();\n  }, [labelId, onBack, defaultData, dispatch, data]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteLabel(labelId));\n  }, [labelId, dispatch]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  if (step && step.type === StepTypes.DELETE) {\n    return (\n      <ConfirmationStep\n        title=\"common.deleteLabel\"\n        content=\"common.areYouSureYouWantToDeleteThisLabel\"\n        buttonContent=\"action.deleteLabel\"\n        onConfirm={handleDeleteConfirm}\n        onBack={handleBack}\n      />\n    );\n  }\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editLabel', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <Editor data={data} onFieldChange={handleFieldChange} />\n          <div className={styles.actions}>\n            <Button positive content={t('action.save')} />\n            <Button\n              type=\"button\"\n              content={t('action.delete')}\n              className={styles.deleteButton}\n              onClick={handleDeleteClick}\n            />\n          </div>\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nEditStep.propTypes = {\n  labelId: PropTypes.string.isRequired,\n  onBack: PropTypes.func.isRequired,\n};\n\nexport default EditStep;\n"
  },
  {
    "path": "client/src/components/labels/LabelsStep/EditStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actions {\n    align-items: center;\n    display: flex;\n    gap: 12px;\n    justify-content: space-between;\n  }\n\n  .deleteButton {\n    box-shadow: 0 1px 0 #cbcccc;\n  }\n}\n"
  },
  {
    "path": "client/src/components/labels/LabelsStep/Editor.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport camelCase from 'lodash/camelCase';\nimport React, { useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { Button } from 'semantic-ui-react';\nimport { Input } from '../../../lib/custom-ui';\n\nimport { useNestedRef } from '../../../hooks';\nimport LABEL_COLORS from '../../../constants/LabelColors';\n\nimport styles from './Editor.module.scss';\nimport globalStyles from '../../../styles.module.scss';\n\nconst Editor = React.memo(({ data, onFieldChange }) => {\n  const [t] = useTranslation();\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  useEffect(() => {\n    nameFieldRef.current.focus();\n  }, [nameFieldRef]);\n\n  return (\n    <>\n      <div className={styles.text}>{t('common.title')}</div>\n      <Input\n        fluid\n        ref={handleNameFieldRef}\n        name=\"name\"\n        value={data.name}\n        maxLength={128}\n        className={styles.field}\n        onChange={onFieldChange}\n      />\n      <div className={styles.text}>{t('common.color')}</div>\n      <div className={styles.colorButtons}>\n        {LABEL_COLORS.map((color) => (\n          <Button\n            key={color}\n            type=\"button\"\n            name=\"color\"\n            value={color}\n            className={classNames(\n              styles.colorButton,\n              color === data.color && styles.colorButtonActive,\n              globalStyles[`background${upperFirst(camelCase(color))}`],\n            )}\n            onClick={onFieldChange}\n          />\n        ))}\n      </div>\n    </>\n  );\n});\n\nEditor.propTypes = {\n  data: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  onFieldChange: PropTypes.func.isRequired,\n};\n\nexport default Editor;\n"
  },
  {
    "path": "client/src/components/labels/LabelsStep/Editor.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .colorButton {\n    margin: 0;\n    position: relative;\n\n    &:hover {\n      opacity: 0.9;\n    }\n  }\n\n  .colorButtonActive:before {\n    color: #ffffff;\n    content: \"Г\";\n    font-size: 16px;\n    line-height: 32px;\n    position: absolute;\n    right: calc(50% - 5px);\n    text-align: center;\n    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);\n    top: calc(50% - 17px);\n    transform: rotate(-135deg);\n  }\n\n  .colorButtons {\n    display: grid;\n    gap: 6px;\n    grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));\n    margin-bottom: 12px;\n    max-height: 60vh;\n    overflow-y: auto;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &::-webkit-scrollbar {\n      width: 9px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background-clip: padding-box;\n      border-left: 0.25em transparent solid;\n      border-radius: 3px;\n    }\n  }\n\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/labels/LabelsStep/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport camelCase from 'lodash/camelCase';\nimport React, { useCallback, useMemo } from 'react';\nimport ReactDOM from 'react-dom';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { Draggable } from 'react-beautiful-dnd';\nimport { Button } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\n\nimport styles from './Item.module.scss';\nimport globalStyles from '../../../styles.module.scss';\n\nconst Item = React.memo(({ id, index, isActive, onSelect, onDeselect, onEdit }) => {\n  const selectLabelById = useMemo(() => selectors.makeSelectLabelById(), []);\n\n  const label = useSelector((state) => selectLabelById(state, id));\n\n  const canEdit = useSelector((state) => {\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  const handleToggleClick = useCallback(() => {\n    if (label.isPersisted) {\n      if (isActive) {\n        onDeselect(id);\n      } else {\n        onSelect(id);\n      }\n    }\n  }, [id, isActive, onSelect, onDeselect, label.isPersisted]);\n\n  const handleEditClick = useCallback(() => {\n    onEdit(id);\n  }, [id, onEdit]);\n\n  return (\n    <Draggable draggableId={id} index={index} isDragDisabled={!label.isPersisted || !canEdit}>\n      {({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {\n        const contentNode = (\n          // eslint-disable-next-line react/jsx-props-no-spreading\n          <div {...draggableProps} ref={innerRef} className={styles.wrapper}>\n            {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                         jsx-a11y/no-static-element-interactions */}\n            <span\n              {...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading\n              className={classNames(\n                styles.name,\n                isActive && styles.nameActive,\n                globalStyles[`background${upperFirst(camelCase(label.color))}`],\n              )}\n              onClick={handleToggleClick}\n            >\n              {label.name}\n            </span>\n            {canEdit && (\n              <Button\n                icon=\"pencil\"\n                size=\"small\"\n                floated=\"right\"\n                disabled={!label.isPersisted}\n                className={styles.editButton}\n                onClick={handleEditClick}\n              />\n            )}\n          </div>\n        );\n\n        return isDragging ? ReactDOM.createPortal(contentNode, document.body) : contentNode;\n      }}\n    </Draggable>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n  index: PropTypes.number.isRequired,\n  isActive: PropTypes.bool.isRequired,\n  onSelect: PropTypes.func.isRequired,\n  onDeselect: PropTypes.func.isRequired,\n  onEdit: PropTypes.func.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/labels/LabelsStep/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .editButton {\n    background: transparent;\n    box-shadow: none;\n    flex: 0 0 auto;\n    font-weight: normal;\n    padding: 8px 10px;\n    text-decoration: underline;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .name {\n    border-radius: 3px;\n    color: #fff;\n    cursor: pointer;\n    flex: 1 1 auto;\n    font-weight: bold;\n    overflow: hidden;\n    padding: 8px 32px 8px 10px;\n    position: relative;\n    text-overflow: ellipsis;\n    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);\n\n    &:hover {\n      opacity: 0.9;\n    }\n  }\n\n  .nameActive:before {\n    bottom: 1px;\n    content: \"Г\";\n    font-size: 18px;\n    font-weight: normal;\n    line-height: 36px;\n    position: absolute;\n    right: 2px;\n    text-align: center;\n    transform: rotate(-135deg);\n    width: 36px;\n  }\n\n  .wrapper {\n    display: flex;\n    margin-bottom: 4px;\n    max-width: 280px;\n    white-space: nowrap;\n  }\n}\n"
  },
  {
    "path": "client/src/components/labels/LabelsStep/LabelsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { DragDropContext, Droppable } from 'react-beautiful-dnd';\nimport { Button } from 'semantic-ui-react';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useField, useNestedRef, useSteps } from '../../../hooks';\nimport DroppableTypes from '../../../constants/DroppableTypes';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\nimport Item from './Item';\nimport AddStep from './AddStep';\nimport EditStep from './EditStep';\n\nimport styles from './LabelsStep.module.scss';\nimport globalStyles from '../../../styles.module.scss';\n\nconst StepTypes = {\n  ADD: 'ADD',\n  EDIT: 'EDIT',\n};\n\nconst LabelsStep = React.memo(({ currentIds, cardId, title, onSelect, onDeselect, onBack }) => {\n  const labels = useSelector(selectors.selectLabelsForCurrentBoard);\n\n  const canAdd = useSelector((state) => {\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n  const [search, handleSearchChange] = useField('');\n  const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);\n\n  const filteredLabels = useMemo(\n    () =>\n      labels.filter(\n        (label) =>\n          (label.name && label.name.toLowerCase().includes(cleanSearch)) ||\n          label.color.includes(cleanSearch),\n      ),\n    [labels, cleanSearch],\n  );\n\n  const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');\n\n  const handleDragStart = useCallback(() => {\n    document.body.classList.add(globalStyles.dragging);\n  }, []);\n\n  const handleDragEnd = useCallback(\n    ({ draggableId, source, destination }) => {\n      document.body.classList.remove(globalStyles.dragging);\n\n      if (!destination || source.index === destination.index) {\n        return;\n      }\n\n      dispatch(entryActions.moveLabel(draggableId, destination.index));\n    },\n    [dispatch],\n  );\n\n  const handleAddClick = useCallback(() => {\n    openStep(StepTypes.ADD);\n  }, [openStep]);\n\n  const handleEdit = useCallback(\n    (id) => {\n      openStep(StepTypes.EDIT, {\n        id,\n      });\n    },\n    [openStep],\n  );\n\n  useEffect(() => {\n    searchFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [searchFieldRef]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.ADD:\n        return (\n          <AddStep\n            cardId={cardId}\n            // TODO: memoize?\n            defaultData={{\n              name: search,\n            }}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.EDIT: {\n        const currentLabel = labels.find((label) => label.id === step.params.id);\n\n        if (currentLabel) {\n          return <EditStep labelId={currentLabel.id} onBack={handleBack} />;\n        }\n\n        openStep(null);\n\n        break;\n      }\n      default:\n    }\n  }\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t(title, {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Input\n          fluid\n          ref={handleSearchFieldRef}\n          value={search}\n          placeholder={t('common.searchLabels')}\n          maxLength={128}\n          icon=\"search\"\n          onChange={handleSearchChange}\n        />\n        {filteredLabels.length > 0 && (\n          <DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>\n            <Droppable droppableId=\"labels\" type={DroppableTypes.LABEL}>\n              {({ innerRef, droppableProps, placeholder }) => (\n                <div\n                  {...droppableProps} // eslint-disable-line react/jsx-props-no-spreading\n                  ref={innerRef}\n                  className={styles.items}\n                >\n                  {filteredLabels.map((item, index) => (\n                    <Item\n                      key={item.id}\n                      id={item.id}\n                      index={index}\n                      isActive={currentIds.includes(item.id)}\n                      onSelect={onSelect}\n                      onDeselect={onDeselect}\n                      onEdit={handleEdit}\n                    />\n                  ))}\n                  {placeholder}\n                </div>\n              )}\n            </Droppable>\n            <Droppable droppableId=\"labels:hack\" type={DroppableTypes.LABEL}>\n              {({ innerRef, droppableProps, placeholder }) => (\n                <div\n                  {...droppableProps} // eslint-disable-line react/jsx-props-no-spreading\n                  ref={innerRef}\n                  className={styles.droppableHack}\n                >\n                  {placeholder}\n                </div>\n              )}\n            </Droppable>\n          </DragDropContext>\n        )}\n        {canAdd && (\n          <Button\n            fluid\n            content={t('action.createNewLabel')}\n            className={styles.addButton}\n            onClick={handleAddClick}\n          />\n        )}\n      </Popup.Content>\n    </>\n  );\n});\n\nLabelsStep.propTypes = {\n  currentIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types\n  cardId: PropTypes.string,\n  title: PropTypes.string,\n  onSelect: PropTypes.func.isRequired,\n  onDeselect: PropTypes.func.isRequired,\n  onBack: PropTypes.func,\n};\n\nLabelsStep.defaultProps = {\n  cardId: undefined,\n  title: 'common.labels',\n  onBack: undefined,\n};\n\nexport default LabelsStep;\n"
  },
  {
    "path": "client/src/components/labels/LabelsStep/LabelsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .addButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: background 0.3s ease;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .droppableHack {\n    display: none;\n    position: fixed;\n  }\n\n  .items {\n    margin-top: 8px;\n    max-height: 60vh;\n    overflow-x: hidden;\n    overflow-y: auto;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &::-webkit-scrollbar {\n      width: 9px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background-clip: padding-box;\n      border-left: 0.25em transparent solid;\n      border-radius: 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/labels/LabelsStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport LabelsStep from './LabelsStep';\n\nexport default LabelsStep;\n"
  },
  {
    "path": "client/src/components/lists/List/ActionsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useSteps } from '../../../hooks';\nimport { ListTypes } from '../../../constants/Enums';\nimport EditColorStep from './EditColorStep';\nimport SortStep from './SortStep';\nimport MoveStep from './MoveStep';\nimport SelectListTypeStep from '../SelectListTypeStep';\nimport ConfirmationStep from '../../common/ConfirmationStep';\nimport ArchiveCardsStep from '../../cards/ArchiveCardsStep';\n\nimport styles from './ActionsStep.module.scss';\n\nconst StepTypes = {\n  EDIT_TYPE: 'EDIT_TYPE',\n  EDIT_COLOR: 'EDIT_COLOR',\n  SORT: 'SORT',\n  MOVE: 'MOVE',\n  ARCHIVE_CARDS: 'ARCHIVE_CARDS',\n  DELETE: 'DELETE',\n};\n\nconst ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const list = useSelector((state) => selectListById(state, listId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n\n  const handleTypeSelect = useCallback(\n    (type) => {\n      dispatch(\n        entryActions.updateList(listId, {\n          type,\n        }),\n      );\n    },\n    [listId, dispatch],\n  );\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteList(listId));\n  }, [listId, dispatch]);\n\n  const handleEditNameClick = useCallback(() => {\n    onNameEdit();\n    onClose();\n  }, [onNameEdit, onClose]);\n\n  const handleAddCardClick = useCallback(() => {\n    onCardAdd();\n    onClose();\n  }, [onCardAdd, onClose]);\n\n  const handleEditTypeClick = useCallback(() => {\n    openStep(StepTypes.EDIT_TYPE);\n  }, [openStep]);\n\n  const handleEditColorClick = useCallback(() => {\n    openStep(StepTypes.EDIT_COLOR);\n  }, [openStep]);\n\n  const handleSortClick = useCallback(() => {\n    openStep(StepTypes.SORT);\n  }, [openStep]);\n\n  const handleMoveClick = useCallback(() => {\n    openStep(StepTypes.MOVE);\n  }, [openStep]);\n\n  const handleArchiveCardsClick = useCallback(() => {\n    openStep(StepTypes.ARCHIVE_CARDS);\n  }, [openStep]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.EDIT_TYPE:\n        return (\n          <SelectListTypeStep\n            withButton\n            defaultValue={list.type}\n            title=\"common.editType\"\n            buttonContent=\"action.save\"\n            onSelect={handleTypeSelect}\n            onBack={handleBack}\n            onClose={onClose}\n          />\n        );\n      case StepTypes.EDIT_COLOR:\n        return <EditColorStep listId={listId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.SORT:\n        return <SortStep listId={listId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.MOVE:\n        return <MoveStep id={listId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.ARCHIVE_CARDS:\n        return <ArchiveCardsStep listId={listId} onBack={handleBack} onClose={onClose} />;\n      case StepTypes.DELETE:\n        return (\n          <ConfirmationStep\n            title=\"common.deleteList\"\n            content=\"common.areYouSureYouWantToDeleteThisList\"\n            buttonContent=\"action.deleteList\"\n            onConfirm={handleDeleteConfirm}\n            onBack={handleBack}\n          />\n        );\n      default:\n    }\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.listActions', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Menu secondary vertical className={styles.menu}>\n          <Menu.Item className={styles.menuItem} onClick={handleEditNameClick}>\n            <Icon name=\"edit outline\" className={styles.menuItemIcon} />\n            {t('action.editTitle', {\n              context: 'title',\n            })}\n          </Menu.Item>\n          <Menu.Item className={styles.menuItem} onClick={handleEditTypeClick}>\n            <Icon name=\"map outline\" className={styles.menuItemIcon} />\n            {t('action.editType', {\n              context: 'title',\n            })}\n          </Menu.Item>\n          <Menu.Item className={styles.menuItem} onClick={handleEditColorClick}>\n            <Icon name=\"dot circle outline\" className={styles.menuItemIcon} />\n            {t('action.editColor', {\n              context: 'title',\n            })}\n          </Menu.Item>\n          <Menu.Item className={styles.menuItem} onClick={handleAddCardClick}>\n            <Icon name=\"list alternate outline\" className={styles.menuItemIcon} />\n            {t('action.addCard', {\n              context: 'title',\n            })}\n          </Menu.Item>\n          <Menu.Item className={styles.menuItem} onClick={handleSortClick}>\n            <Icon name=\"sort amount down\" className={styles.menuItemIcon} />\n            {t('action.sortList', {\n              context: 'title',\n            })}\n          </Menu.Item>\n          <Menu.Item className={styles.menuItem} onClick={handleMoveClick}>\n            <Icon name=\"share square outline\" className={styles.menuItemIcon} />\n            {t('action.moveList', {\n              context: 'title',\n            })}\n          </Menu.Item>\n          {list.type === ListTypes.CLOSED && (\n            <Menu.Item className={styles.menuItem} onClick={handleArchiveCardsClick}>\n              <Icon name=\"folder open outline\" className={styles.menuItemIcon} />\n              {t('action.archiveCards', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          <Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>\n            <Icon name=\"trash alternate outline\" className={styles.menuItemIcon} />\n            {t('action.deleteList', {\n              context: 'title',\n            })}\n          </Menu.Item>\n        </Menu>\n      </Popup.Content>\n    </>\n  );\n});\n\nActionsStep.propTypes = {\n  listId: PropTypes.string.isRequired,\n  onNameEdit: PropTypes.func.isRequired,\n  onCardAdd: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default ActionsStep;\n"
  },
  {
    "path": "client/src/components/lists/List/ActionsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menu {\n    margin: 0 -12px -5px;\n    width: calc(100% + 24px);\n  }\n\n  .menuItem {\n    margin: 0;\n    padding-left: 14px;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.5em 0 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/lists/List/EditColorStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport camelCase from 'lodash/camelCase';\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport LIST_COLORS from '../../../constants/ListColors';\n\nimport styles from './EditColorStep.module.scss';\nimport globalStyles from '../../../styles.module.scss';\n\nconst EditColorStep = React.memo(({ listId, onBack, onClose }) => {\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const defaultValue = useSelector((state) => selectListById(state, listId).color);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleSelectClick = useCallback(\n    (_, { value: color }) => {\n      dispatch(\n        entryActions.updateList(listId, {\n          color,\n        }),\n      );\n    },\n    [listId, dispatch],\n  );\n\n  const handleClearClick = useCallback(() => {\n    dispatch(\n      entryActions.updateList(listId, {\n        color: null,\n      }),\n    );\n\n    onClose();\n  }, [listId, onClose, dispatch]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editColor', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <div className={styles.colorButtons}>\n          {LIST_COLORS.map((color) => (\n            <Button\n              key={color}\n              type=\"button\"\n              value={color}\n              className={classNames(\n                styles.colorButton,\n                color === defaultValue && styles.colorButtonActive,\n                globalStyles[`background${upperFirst(camelCase(color))}`],\n              )}\n              onClick={handleSelectClick}\n            />\n          ))}\n        </div>\n        {defaultValue && (\n          <Button\n            fluid\n            content={t('action.removeColor')}\n            className={styles.clearButton}\n            onClick={handleClearClick}\n          />\n        )}\n      </Popup.Content>\n    </>\n  );\n});\n\nEditColorStep.propTypes = {\n  listId: PropTypes.string.isRequired,\n  onBack: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default EditColorStep;\n"
  },
  {
    "path": "client/src/components/lists/List/EditColorStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .clearButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: background 0.3s ease;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .colorButton {\n    float: left;\n    height: 40px;\n    margin: 4px;\n    padding: 0;\n    position: relative;\n    width: 49.6px;\n\n    &:hover {\n      opacity: 0.9;\n    }\n  }\n\n  .colorButtonActive:before {\n    bottom: 3px;\n    color: #ffffff;\n    content: \"Г\";\n    font-size: 18px;\n    line-height: 36px;\n    position: absolute;\n    right: 6px;\n    text-align: center;\n    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);\n    top: 0;\n    transform: rotate(-135deg);\n    width: 36px;\n  }\n\n  .colorButtons {\n    margin: -4px;\n\n    &:after {\n      clear: both;\n      content: \"\";\n      display: table;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/lists/List/EditName.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport TextareaAutosize from 'react-textarea-autosize';\nimport { TextArea } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useField, useNestedRef } from '../../../hooks';\nimport { focusEnd } from '../../../utils/element-helpers';\n\nimport styles from './EditName.module.scss';\n\nconst EditName = React.memo(({ listId, onClose }) => {\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const defaultValue = useSelector((state) => selectListById(state, listId).name);\n\n  const dispatch = useDispatch();\n  const [value, handleFieldChange] = useField(defaultValue);\n\n  const [fieldRef, handleFieldRef] = useNestedRef();\n\n  const submit = useCallback(() => {\n    const cleanValue = value.trim();\n\n    if (cleanValue && cleanValue !== defaultValue) {\n      dispatch(\n        entryActions.updateList(listId, {\n          name: cleanValue,\n        }),\n      );\n    }\n\n    onClose();\n  }, [listId, defaultValue, dispatch, value, onClose]);\n\n  const handleFieldClick = useCallback((event) => {\n    event.stopPropagation();\n  }, []);\n\n  const handleFieldKeyDown = useCallback(\n    (event) => {\n      switch (event.key) {\n        case 'Enter':\n          event.preventDefault();\n          submit();\n\n          break;\n        case 'Escape':\n          submit();\n\n          break;\n        default:\n      }\n    },\n    [submit],\n  );\n\n  const handleFieldBlur = useCallback(() => {\n    submit();\n  }, [submit]);\n\n  useEffect(() => {\n    focusEnd(fieldRef.current);\n  }, [fieldRef]);\n\n  return (\n    <TextArea\n      ref={handleFieldRef}\n      as={TextareaAutosize}\n      value={value}\n      maxLength={128}\n      className={styles.field}\n      onClick={handleFieldClick}\n      onKeyDown={handleFieldKeyDown}\n      onChange={handleFieldChange}\n      onBlur={handleFieldBlur}\n    />\n  );\n});\n\nEditName.propTypes = {\n  listId: PropTypes.string.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default EditName;\n"
  },
  {
    "path": "client/src/components/lists/List/EditName.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    border: none;\n    border-color: #bbb;\n    border-radius: 3px;\n    color: #17394d;\n    display: block;\n    font-weight: bold;\n    line-height: 20px;\n    margin: 0;\n    outline: none;\n    overflow: hidden;\n    padding: 4px 8px;\n    resize: none;\n    width: 100%;\n\n    &:focus {\n      background: #fff;\n      border-color: #5ba4cf;\n      box-shadow: 0 0 0 1px #5ba4cf;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/lists/List/List.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport camelCase from 'lodash/camelCase';\nimport React, { useCallback, useContext, useMemo, useRef, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { shallowEqual, useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Draggable, Droppable } from 'react-beautiful-dnd';\nimport { Button, Icon } from 'semantic-ui-react';\nimport { useDidUpdate, useToggle, useTransitioning } from '../../../lib/hooks';\nimport { usePopup } from '../../../lib/popup';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { BoardShortcutsContext } from '../../../contexts';\nimport DroppableTypes from '../../../constants/DroppableTypes';\nimport { BoardMembershipRoles, ListTypes } from '../../../constants/Enums';\nimport { ListTypeIcons } from '../../../constants/Icons';\nimport EditName from './EditName';\nimport ActionsStep from './ActionsStep';\nimport DraggableCard from '../../cards/DraggableCard';\nimport AddCard from '../../cards/AddCard';\nimport ArchiveCardsStep from '../../cards/ArchiveCardsStep';\nimport PlusMathIcon from '../../../assets/images/plus-math-icon.svg?react';\n\nimport styles from './List.module.scss';\nimport globalStyles from '../../../styles.module.scss';\n\nconst AddCardPositions = {\n  TOP: 'top',\n  BOTTOM: 'bottom',\n};\n\nconst INDEX_BY_ADD_CARD_POSITION = {\n  [AddCardPositions.TOP]: 0,\n};\n\nconst List = React.memo(({ id, index }) => {\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const selectFilteredCardIdsByListId = useMemo(\n    () => selectors.makeSelectFilteredCardIdsByListId(),\n    [],\n  );\n\n  const clipboard = useSelector(selectors.selectClipboard);\n  const isFavoritesActive = useSelector(selectors.selectIsFavoritesActiveForCurrentUser);\n\n  const list = useSelector((state) => selectListById(state, id));\n  const cardIds = useSelector((state) => selectFilteredCardIdsByListId(state, id));\n\n  const { canEdit, canArchiveCards, canAddCard, canPasteCard, canDropCard } = useSelector(\n    (state) => {\n      const isEditModeEnabled = selectors.selectIsEditModeEnabled(state); // TODO: move out?\n\n      const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n      const isEditor = !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n\n      return {\n        canEdit: isEditModeEnabled && isEditor,\n        canArchiveCards: list.type === ListTypes.CLOSED && isEditor,\n        canAddCard: isEditor,\n        canPasteCard: isEditor,\n        canDropCard: isEditor,\n      };\n    },\n    shallowEqual,\n  );\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [isEditNameOpened, setIsEditNameOpened] = useState(false);\n  const [addCardPosition, setAddCardPosition] = useState(null);\n  const [scrollBottomState, scrollBottom] = useToggle();\n  const [handleListMouseEnter, handleListMouseLeave] = useContext(BoardShortcutsContext);\n\n  const wrapperRef = useRef(null);\n  const cardsWrapperRef = useRef(null);\n\n  const handleCardCreate = useCallback(\n    (data, autoOpen) => {\n      dispatch(\n        entryActions.createCard(id, data, INDEX_BY_ADD_CARD_POSITION[addCardPosition], autoOpen),\n      );\n    },\n    [id, dispatch, addCardPosition],\n  );\n\n  const handlePasteCardClick = useCallback(() => {\n    dispatch(entryActions.pasteCard(id));\n    scrollBottom();\n  }, [id, dispatch, scrollBottom]);\n\n  const handleMouseEnter = useCallback(() => {\n    handleListMouseEnter(id, () => {\n      scrollBottom();\n    });\n  }, [id, scrollBottom, handleListMouseEnter]);\n\n  const handleHeaderClick = useCallback(() => {\n    if (list.isPersisted && canEdit) {\n      setIsEditNameOpened(true);\n    }\n  }, [list.isPersisted, canEdit]);\n\n  const handleAddCardClick = useCallback(() => {\n    setAddCardPosition(AddCardPositions.BOTTOM);\n  }, []);\n\n  const handleAddCardClose = useCallback(() => {\n    setAddCardPosition(null);\n  }, []);\n\n  const handleCardAdd = useCallback(() => {\n    setAddCardPosition(AddCardPositions.TOP);\n  }, []);\n\n  const handleNameEdit = useCallback(() => {\n    setIsEditNameOpened(true);\n  }, []);\n\n  const handleEditNameClose = useCallback(() => {\n    setIsEditNameOpened(false);\n  }, []);\n\n  const handleWrapperTransitionEnd = useTransitioning(\n    wrapperRef,\n    styles.outerWrapperTransitioning,\n    [isFavoritesActive],\n  );\n\n  useDidUpdate(() => {\n    if (!addCardPosition) {\n      return;\n    }\n\n    cardsWrapperRef.current.scrollTop =\n      addCardPosition === AddCardPositions.TOP ? 0 : cardsWrapperRef.current.scrollHeight;\n  }, [cardIds, addCardPosition]);\n\n  useDidUpdate(() => {\n    cardsWrapperRef.current.scrollTop = cardsWrapperRef.current.scrollHeight;\n  }, [scrollBottomState]);\n\n  const ActionsPopup = usePopup(ActionsStep);\n  const ArchiveCardsPopup = usePopup(ArchiveCardsStep);\n\n  const addCardNode = canAddCard && (\n    <AddCard\n      isOpened={!!addCardPosition}\n      className={styles.addCard}\n      onCreate={handleCardCreate}\n      onClose={handleAddCardClose}\n    />\n  );\n\n  const cardsNode = (\n    <Droppable\n      droppableId={`list:${id}`}\n      type={DroppableTypes.CARD}\n      isDropDisabled={!list.isPersisted || !canDropCard}\n    >\n      {({ innerRef, droppableProps, placeholder }) => (\n        // eslint-disable-next-line react/jsx-props-no-spreading\n        <div {...droppableProps} ref={innerRef}>\n          <div className={styles.cards}>\n            {addCardPosition === AddCardPositions.TOP && addCardNode}\n            {cardIds.map((cardId, cardIndex) => (\n              <DraggableCard key={cardId} id={cardId} index={cardIndex} className={styles.card} />\n            ))}\n            {placeholder}\n            {addCardPosition === AddCardPositions.BOTTOM && addCardNode}\n          </div>\n        </div>\n      )}\n    </Droppable>\n  );\n\n  return (\n    <Draggable\n      draggableId={`list:${id}`}\n      index={index}\n      isDragDisabled={!list.isPersisted || !canEdit || isEditNameOpened}\n    >\n      {({ innerRef, draggableProps, dragHandleProps }) => (\n        <div\n          {...draggableProps} // eslint-disable-line react/jsx-props-no-spreading\n          data-drag-scroller\n          ref={innerRef}\n          className={styles.innerWrapper}\n          onMouseEnter={handleMouseEnter}\n          onMouseLeave={handleListMouseLeave}\n        >\n          <div\n            ref={wrapperRef}\n            className={classNames(\n              styles.outerWrapper,\n              isFavoritesActive && styles.outerWrapperWithFavorites,\n              list.color && globalStyles[`background${upperFirst(camelCase(list.color))}Soft`],\n            )}\n            onTransitionEnd={handleWrapperTransitionEnd}\n          >\n            {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                         jsx-a11y/no-static-element-interactions */}\n            <div\n              {...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading\n              className={classNames(styles.header, canEdit && styles.headerEditable)}\n              onClick={handleHeaderClick}\n            >\n              {isEditNameOpened ? (\n                <EditName listId={id} onClose={handleEditNameClose} />\n              ) : (\n                <div className={styles.headerName}>\n                  {list.color && (\n                    <Icon\n                      name=\"circle\"\n                      className={classNames(\n                        styles.headerNameColor,\n                        globalStyles[`color${upperFirst(camelCase(list.color))}`],\n                      )}\n                    />\n                  )}\n                  {list.name}\n                </div>\n              )}\n              {list.type !== ListTypes.ACTIVE && (\n                <Icon\n                  name={ListTypeIcons[list.type]}\n                  className={classNames(\n                    styles.headerIcon,\n                    list.isPersisted && (canEdit || canArchiveCards) && styles.headerIconHidable,\n                  )}\n                />\n              )}\n              {list.isPersisted &&\n                (canEdit ? (\n                  <ActionsPopup listId={id} onNameEdit={handleNameEdit} onCardAdd={handleCardAdd}>\n                    <Button className={styles.headerButton}>\n                      <Icon fitted name=\"pencil\" size=\"small\" />\n                    </Button>\n                  </ActionsPopup>\n                ) : (\n                  canArchiveCards && (\n                    <ArchiveCardsPopup listId={id}>\n                      <Button className={styles.headerButton}>\n                        <Icon fitted name=\"archive\" size=\"small\" />\n                      </Button>\n                    </ArchiveCardsPopup>\n                  )\n                ))}\n            </div>\n            <div ref={cardsWrapperRef} className={styles.cardsInnerWrapper}>\n              <div className={styles.cardsOuterWrapper}>{cardsNode}</div>\n            </div>\n            {!addCardPosition && canAddCard && (\n              <div className={styles.addCardButtonWrapper}>\n                <button\n                  type=\"button\"\n                  disabled={!list.isPersisted}\n                  className={classNames(\n                    styles.addCardButton,\n                    list.color &&\n                      globalStyles[`background${upperFirst(camelCase(list.color))}Soft`],\n                  )}\n                  onClick={handleAddCardClick}\n                >\n                  <PlusMathIcon className={styles.addCardButtonIcon} />\n                  <span className={styles.addCardButtonText}>\n                    {cardIds.length > 0 ? t('action.addAnotherCard') : t('action.addCard')}\n                  </span>\n                </button>\n                {clipboard && canPasteCard && (\n                  <button\n                    type=\"button\"\n                    disabled={!list.isPersisted}\n                    className={classNames(styles.addCardButton, styles.paste)}\n                    onClick={handlePasteCardClick}\n                  >\n                    <Icon name=\"paste\" />\n                  </button>\n                )}\n              </div>\n            )}\n          </div>\n        </div>\n      )}\n    </Draggable>\n  );\n});\n\nList.propTypes = {\n  id: PropTypes.string.isRequired,\n  index: PropTypes.number.isRequired,\n};\n\nexport default List;\n"
  },
  {
    "path": "client/src/components/lists/List/List.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .addCard {\n    margin-bottom: 8px;\n  }\n\n  .addCardButton {\n    background: #dfe3e6;\n    border: none;\n    color: #6b808c;\n    cursor: pointer;\n    display: block;\n    fill: #6b808c;\n    flex: 1;\n    font-weight: normal;\n    height: 36px;\n    outline: none;\n    padding: 8px;\n    text-align: left;\n\n    &:hover {\n      background: #c3cbd0;\n      color: #17394d;\n      fill: #17394d;\n    }\n\n    &.paste {\n      flex: 0 0 auto;\n    }\n  }\n\n  .addCardButtonIcon {\n    height: 20px;\n    padding: 0.64px;\n    width: 20px;\n  }\n\n  .addCardButtonText {\n    display: inline-block;\n    font-size: 14px;\n    line-height: 20px;\n    margin-left: 2px;\n    vertical-align: top;\n  }\n\n  .addCardButtonWrapper {\n    display: flex;\n  }\n\n  .card {\n    margin-bottom: 8px;\n  }\n\n  .cards {\n    min-height: 1px;\n  }\n\n  .cardsInnerWrapper {\n    overflow-x: hidden;\n    overflow-y: auto;\n    width: 290px;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &:hover {\n      width: 272px;\n    }\n\n    &::-webkit-scrollbar {\n      width: 5px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      border-radius: 3px;\n    }\n  }\n\n  .cardsOuterWrapper {\n    padding: 0 8px;\n    white-space: normal;\n    width: 272px;\n  }\n\n  .header {\n    outline: none;\n    padding: 6px 36px 6px 8px;\n    position: relative;\n\n    &:hover {\n      .headerButton {\n        opacity: 1;\n      }\n\n      .headerIconHidable {\n        opacity: 0;\n      }\n    }\n  }\n\n  .headerEditable {\n    cursor: pointer;\n  }\n\n  .headerButton {\n    background: none;\n    box-shadow: none;\n    color: #798d99;\n    line-height: 32px;\n    margin: 0;\n    opacity: 0;\n    padding: 0;\n    position: absolute;\n    right: 4px;\n    top: 4px;\n    width: 32px;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.13);\n      color: #516b7a;\n    }\n  }\n\n  .headerIcon {\n    color: #798d99;\n    position: absolute;\n    right: 8px;\n    top: 10px;\n  }\n\n  .headerName {\n    color: #17394d;\n    font-weight: bold;\n    line-height: 20px;\n    max-height: 256px;\n    outline: none;\n    overflow: hidden;\n    overflow-wrap: break-word;\n    padding: 4px 8px;\n    word-break: break-word;\n  }\n\n  .headerNameColor {\n    line-height: 1;\n    margin-right: 6px;\n  }\n\n  .innerWrapper {\n    margin-right: 8px;\n    width: 272px;\n  }\n\n  .outerWrapper {\n    background: #dfe3e6;\n    border-radius: 3px;\n    display: flex;\n    flex-direction: column;\n    max-height: calc(100vh - 198px);\n    overflow: hidden;\n  }\n\n  .outerWrapperTransitioning {\n    transition: max-height 0.2s ease;\n  }\n\n  .outerWrapperWithFavorites {\n    max-height: calc(100vh - 288px);\n  }\n}\n"
  },
  {
    "path": "client/src/components/lists/List/MoveStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useMemo, useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Dropdown, Form } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm } from '../../../hooks';\n\nimport styles from './MoveStep.module.scss';\n\nconst MoveStep = React.memo(({ id, onBack, onClose }) => {\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n  const selectBoardById = useMemo(() => selectors.makeSelectBoardById(), []);\n\n  const projectsToBoards = useSelector(\n    selectors.selectProjectsToBoardsWithEditorRightsForCurrentUser,\n  );\n\n  const list = useSelector((state) => selectListById(state, id));\n  const projectId = useSelector((state) => selectBoardById(state, list.boardId).projectId);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultPath = useMemo(\n    () => ({\n      projectId,\n      boardId: list.boardId,\n    }),\n    [list.boardId, projectId],\n  );\n\n  const [path, handleFieldChange] = useForm(() => ({\n    projectId: null,\n    boardId: null,\n    ...defaultPath,\n  }));\n\n  const selectedProject = useMemo(\n    () => projectsToBoards.find((project) => project.id === path.projectId) || null,\n    [projectsToBoards, path.projectId],\n  );\n\n  const selectedBoard = useMemo(\n    () =>\n      (selectedProject && selectedProject.boards.find((board) => board.id === path.boardId)) ||\n      null,\n    [selectedProject, path.boardId],\n  );\n\n  const handleSubmit = useCallback(() => {\n    if (selectedBoard.id !== defaultPath.boardId) {\n      dispatch(entryActions.transferList(id, selectedBoard.id));\n    }\n\n    onClose();\n  }, [id, onClose, dispatch, defaultPath, selectedBoard]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.moveList', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.project')}</div>\n          <Dropdown\n            fluid\n            selection\n            name=\"projectId\"\n            options={projectsToBoards.map((project) => ({\n              text: project.name,\n              value: project.id,\n            }))}\n            value={selectedProject && selectedProject.id}\n            placeholder={\n              projectsToBoards.length === 0 ? t('common.noProjects') : t('common.selectProject')\n            }\n            disabled={projectsToBoards.length === 0}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          {selectedProject && (\n            <>\n              <div className={styles.text}>{t('common.board')}</div>\n              <Dropdown\n                fluid\n                selection\n                name=\"boardId\"\n                options={selectedProject.boards.map((board) => ({\n                  text: board.name,\n                  value: board.id,\n                }))}\n                value={selectedBoard && selectedBoard.id}\n                placeholder={\n                  selectedProject.boards.length === 0\n                    ? t('common.noBoards')\n                    : t('common.selectBoard')\n                }\n                disabled={selectedProject.boards.length === 0}\n                className={styles.field}\n                onChange={handleFieldChange}\n              />\n            </>\n          )}\n          <Button positive content={t('action.move')} disabled={!selectedBoard} />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nMoveStep.propTypes = {\n  id: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nMoveStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default MoveStep;\n"
  },
  {
    "path": "client/src/components/lists/List/MoveStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/lists/List/SortStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport entryActions from '../../../entry-actions';\nimport { ListSortFieldNames, SortOrders } from '../../../constants/Enums';\n\nimport styles from './ActionsStep.module.scss';\n\nconst Types = {\n  ALPHABETICALLY: 'alphabetically',\n  BY_DUE_DATE: 'byDueDate',\n  OLDEST_FIRST: 'oldestFirst',\n  NEWEST_FIRST: 'newestFirst',\n};\n\nconst DATA_BY_TYPE = {\n  [Types.ALPHABETICALLY]: {\n    fieldName: ListSortFieldNames.NAME,\n  },\n  [Types.BY_DUE_DATE]: {\n    fieldName: ListSortFieldNames.DUE_DATE,\n  },\n  [Types.OLDEST_FIRST]: {\n    fieldName: ListSortFieldNames.CREATED_AT,\n  },\n  [Types.NEWEST_FIRST]: {\n    fieldName: ListSortFieldNames.CREATED_AT,\n    order: SortOrders.DESC,\n  },\n};\n\nconst SortStep = React.memo(({ listId, onBack, onClose }) => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleSelectTypeClick = useCallback(\n    (_, { value: type }) => {\n      dispatch(entryActions.sortList(listId, DATA_BY_TYPE[type]));\n      onClose();\n    },\n    [listId, onClose, dispatch],\n  );\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.sortList', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Menu secondary vertical className={styles.menu}>\n          {[Types.ALPHABETICALLY, Types.BY_DUE_DATE, Types.OLDEST_FIRST, Types.NEWEST_FIRST].map(\n            (type) => (\n              <Menu.Item\n                key={type}\n                value={type}\n                className={styles.menuItem}\n                onClick={handleSelectTypeClick}\n              >\n                {t(`common.${type}`)}\n              </Menu.Item>\n            ),\n          )}\n        </Menu>\n      </Popup.Content>\n    </>\n  );\n});\n\nSortStep.propTypes = {\n  listId: PropTypes.string.isRequired,\n  onBack: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default SortStep;\n"
  },
  {
    "path": "client/src/components/lists/List/SortStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menu {\n    margin: 0 -12px -5px;\n    width: calc(100% + 24px);\n  }\n\n  .menuItem {\n    margin: 0;\n    padding-left: 14px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/lists/List/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport List from './List';\n\nexport default List;\n"
  },
  {
    "path": "client/src/components/lists/ListsStep/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport { ListTypes } from '../../../constants/Enums';\nimport { ListTypeIcons } from '../../../constants/Icons';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id, isActive, onSelect }) => {\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const list = useSelector((state) => selectListById(state, id));\n\n  const [t] = useTranslation();\n\n  const handleSelectClick = useCallback(() => {\n    if (!isActive && list.isPersisted) {\n      onSelect(id);\n    }\n  }, [id, isActive, onSelect, list.isPersisted]);\n\n  return (\n    <div className={styles.wrapper}>\n      {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                   jsx-a11y/no-static-element-interactions */}\n      <span\n        className={classNames(styles.name, isActive && styles.nameActive)}\n        onClick={handleSelectClick}\n      >\n        {list.type !== ListTypes.ACTIVE && (\n          <Icon name={ListTypeIcons[list.type]} className={styles.nameIcon} />\n        )}\n        {list.name || t(`common.${list.type}`)}\n      </span>\n    </div>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n  isActive: PropTypes.bool.isRequired,\n  onSelect: PropTypes.func.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/lists/ListsStep/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .name {\n    background: rgba(9, 30, 66, 0.04);\n    border-radius: 3px;\n    color: #17394d;\n    cursor: pointer;\n    flex: 1 1 auto;\n    font-size: 14px;\n    overflow: hidden;\n    padding: 8px 32px 8px 10px;\n    position: relative;\n    text-overflow: ellipsis;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n    }\n  }\n\n  .nameActive {\n    opacity: 0.45;\n\n    &:before {\n      bottom: 1px;\n      content: \"Г\";\n      font-size: 18px;\n      font-weight: normal;\n      line-height: 36px;\n      position: absolute;\n      right: 2px;\n      text-align: center;\n      transform: rotate(-135deg);\n      width: 36px;\n    }\n  }\n\n  .nameIcon {\n    color: rgba(9, 30, 66, 0.24);\n    font-size: 12px;\n    margin: 0 8px 0 0;\n    width: 14px;\n  }\n\n  .wrapper {\n    display: flex;\n    margin-bottom: 4px;\n    max-width: 280px;\n    white-space: nowrap;\n  }\n}\n"
  },
  {
    "path": "client/src/components/lists/ListsStep/ListsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport { useField, useNestedRef } from '../../../hooks';\nimport Item from './Item';\n\nimport styles from './ListsStep.module.scss';\n\nconst ListsStep = React.memo(({ currentId, onSelect }) => {\n  const lists = useSelector(selectors.selectAvailableListsForCurrentBoard);\n\n  const [t] = useTranslation();\n  const [search, handleSearchChange] = useField('');\n  const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);\n\n  const filteredLists = useMemo(\n    () =>\n      lists.filter((list) =>\n        (list.name ? list.name.toLowerCase() : list.type).includes(cleanSearch),\n      ),\n    [lists, cleanSearch],\n  );\n\n  const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');\n\n  useEffect(() => {\n    searchFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [searchFieldRef]);\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.lists', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Input\n          fluid\n          ref={handleSearchFieldRef}\n          value={search}\n          placeholder={t('common.searchLists')}\n          maxLength={128}\n          icon=\"search\"\n          onChange={handleSearchChange}\n        />\n        {filteredLists.length > 0 && (\n          <div className={styles.items}>\n            {filteredLists.map((list) => (\n              <Item\n                key={list.id}\n                id={list.id}\n                isActive={list.id === currentId}\n                onSelect={onSelect}\n              />\n            ))}\n          </div>\n        )}\n      </Popup.Content>\n    </>\n  );\n});\n\nListsStep.propTypes = {\n  currentId: PropTypes.string.isRequired,\n  onSelect: PropTypes.func.isRequired,\n};\n\nexport default ListsStep;\n"
  },
  {
    "path": "client/src/components/lists/ListsStep/ListsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .items {\n    margin-top: 8px;\n    max-height: 60vh;\n    overflow-x: hidden;\n    overflow-y: auto;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &::-webkit-scrollbar {\n      width: 9px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background-clip: padding-box;\n      border-left: 0.25em transparent solid;\n      border-radius: 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/lists/ListsStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ListsStep from './ListsStep';\n\nexport default ListsStep;\n"
  },
  {
    "path": "client/src/components/lists/SelectListTypeStep/SelectListTypeStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Icon, Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport { ListTypes } from '../../../constants/Enums';\nimport { ListTypeIcons } from '../../../constants/Icons';\n\nimport styles from './SelectListTypeStep.module.scss';\n\nconst DESCRIPTION_BY_TYPE = {\n  [ListTypes.ACTIVE]: 'common.cardsOnThisListAreReadyToBeWorkedOn',\n  [ListTypes.CLOSED]: 'common.cardsOnThisListAreCompleteAndReadyToBeArchived',\n};\n\nconst SelectListTypeStep = React.memo(\n  ({ defaultValue, title, withButton, buttonContent, onSelect, onBack, onClose }) => {\n    const [t] = useTranslation();\n    const [value, setValue] = useState(defaultValue);\n\n    const handleSelectClick = useCallback(\n      (_, { value: nextValue }) => {\n        if (withButton) {\n          setValue(nextValue);\n        } else {\n          if (nextValue !== defaultValue) {\n            onSelect(nextValue);\n          }\n\n          onClose();\n        }\n      },\n      [defaultValue, withButton, onSelect, onClose],\n    );\n\n    const handleSubmit = useCallback(() => {\n      if (value !== defaultValue) {\n        onSelect(value);\n      }\n\n      onClose();\n    }, [defaultValue, onSelect, onClose, value]);\n\n    return (\n      <>\n        <Popup.Header onBack={onBack}>\n          {t(title, {\n            context: 'title',\n          })}\n        </Popup.Header>\n        <Popup.Content>\n          <Form onSubmit={handleSubmit}>\n            <Menu secondary vertical className={styles.menu}>\n              {[ListTypes.ACTIVE, ListTypes.CLOSED].map((type) => (\n                <Menu.Item\n                  key={type}\n                  value={type}\n                  active={type === value}\n                  className={styles.menuItem}\n                  onClick={handleSelectClick}\n                >\n                  <Icon name={ListTypeIcons[type]} className={styles.menuItemIcon} />\n                  <div className={styles.menuItemTitle}>{t(`common.${type}`)}</div>\n                  <p className={styles.menuItemDescription}>{t(DESCRIPTION_BY_TYPE[type])}</p>\n                </Menu.Item>\n              ))}\n            </Menu>\n            {withButton && <Button positive content={t(buttonContent)} />}\n          </Form>\n        </Popup.Content>\n      </>\n    );\n  },\n);\n\nSelectListTypeStep.propTypes = {\n  defaultValue: PropTypes.string.isRequired,\n  title: PropTypes.string,\n  withButton: PropTypes.bool,\n  buttonContent: PropTypes.string,\n  onSelect: PropTypes.func.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nSelectListTypeStep.defaultProps = {\n  title: 'common.selectType',\n  withButton: false,\n  buttonContent: 'action.selectType',\n  onBack: undefined,\n};\n\nexport default SelectListTypeStep;\n"
  },
  {
    "path": "client/src/components/lists/SelectListTypeStep/SelectListTypeStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menu {\n    margin: 0 auto 8px;\n    width: 100%;\n  }\n\n  .menuItem:last-child {\n    margin-bottom: 0;\n  }\n\n  .menuItemDescription {\n    opacity: 0.5;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.35714286em 0 0;\n  }\n\n  .menuItemTitle {\n    margin-bottom: 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/lists/SelectListTypeStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport SelectListTypeStep from './SelectListTypeStep';\n\nexport default SelectListTypeStep;\n"
  },
  {
    "path": "client/src/components/notification-services/NotificationServices/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Button, Dropdown, Form, Icon, Input } from 'semantic-ui-react';\nimport { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport {\n  useEscapeInterceptor,\n  useForm,\n  useNestedRef,\n  usePopupInClosableContext,\n} from '../../../hooks';\nimport { NotificationServiceFormats } from '../../../constants/Enums';\nimport ConfirmationStep from '../../common/ConfirmationStep';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id }) => {\n  const selectNotificationServiceById = useMemo(\n    () => selectors.makeSelectNotificationServiceById(),\n    [],\n  );\n\n  const notificationService = useSelector((state) => selectNotificationServiceById(state, id));\n\n  const dispatch = useDispatch();\n\n  const defaultData = useMemo(\n    () => ({\n      url: notificationService.url,\n      format: notificationService.format,\n    }),\n    [notificationService.url, notificationService.format],\n  );\n\n  const prevDefaultData = usePrevious(defaultData);\n\n  const [data, handleFieldChange, setData] = useForm(() => ({\n    url: '',\n    format: NotificationServiceFormats.MARKDOWN,\n    ...defaultData,\n  }));\n\n  const [blurUrlFieldState, blurUrlField] = useToggle();\n\n  const [urlFieldRef, handleUrlFieldRef] = useNestedRef('inputRef');\n  const isUrlFocusedRef = useRef(false);\n\n  const resetUrl = useCallback(() => {\n    setData((prevData) => ({\n      ...prevData,\n      url: defaultData.url,\n    }));\n  }, [defaultData.url, setData]);\n\n  const handleUrlEscape = useCallback(() => {\n    resetUrl();\n    blurUrlField();\n  }, [blurUrlField, resetUrl]);\n\n  const [activateEscapeInterceptor, deactivateEscapeInterceptor] =\n    useEscapeInterceptor(handleUrlEscape);\n\n  const handleUrlFocus = useCallback(() => {\n    activateEscapeInterceptor();\n    isUrlFocusedRef.current = true;\n  }, [activateEscapeInterceptor]);\n\n  const handleUrlBlur = useCallback(() => {\n    deactivateEscapeInterceptor();\n    isUrlFocusedRef.current = false;\n\n    const cleanUrl = data.url.trim();\n\n    if (!cleanUrl) {\n      resetUrl();\n      return;\n    }\n\n    if (cleanUrl !== defaultData.url) {\n      dispatch(\n        entryActions.updateNotificationService(id, {\n          url: cleanUrl,\n        }),\n      );\n    }\n  }, [\n    id,\n    dispatch,\n    defaultData.url,\n    data.url,\n    isUrlFocusedRef,\n    resetUrl,\n    deactivateEscapeInterceptor,\n  ]);\n\n  const handleFormatChange = useCallback(\n    (_, { value: format }) => {\n      setData((prevData) => ({\n        ...prevData,\n        format,\n      }));\n\n      dispatch(\n        entryActions.updateNotificationService(id, {\n          format,\n        }),\n      );\n    },\n    [id, dispatch, setData],\n  );\n\n  const handleTestClick = useCallback(() => {\n    dispatch(entryActions.testNotificationService(id));\n  }, [id, dispatch]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteNotificationService(id));\n  }, [id, dispatch]);\n\n  const handleSubmit = useCallback(() => {\n    urlFieldRef.current.blur();\n  }, [urlFieldRef]);\n\n  useDidUpdate(() => {\n    const nextData = {};\n    if (!isUrlFocusedRef.current && defaultData.url !== prevDefaultData.url) {\n      nextData.url = defaultData.url;\n    }\n    if (defaultData.format !== prevDefaultData.format) {\n      nextData.format = defaultData.format;\n    }\n\n    if (Object.keys(nextData).length > 0) {\n      setData((prevData) => ({\n        ...prevData,\n        ...nextData,\n      }));\n    }\n  }, [defaultData, prevDefaultData]);\n\n  useDidUpdate(() => {\n    urlFieldRef.current.blur();\n  }, [blurUrlFieldState]);\n\n  const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);\n\n  return (\n    <Form className={styles.wrapper} onSubmit={handleSubmit}>\n      <Input\n        ref={handleUrlFieldRef}\n        name=\"url\"\n        value={data.url}\n        maxLength={512}\n        readOnly={!notificationService.isPersisted}\n        label={\n          <Dropdown\n            basic\n            compact\n            value={data.format}\n            options={[\n              NotificationServiceFormats.TEXT,\n              NotificationServiceFormats.MARKDOWN,\n              NotificationServiceFormats.HTML,\n            ].map((format) => ({\n              text: format,\n              value: format,\n            }))}\n            disabled={!notificationService.isPersisted}\n            onChange={handleFormatChange}\n          />\n        }\n        labelPosition=\"right\"\n        className={styles.field}\n        onFocus={handleUrlFocus}\n        onChange={handleFieldChange}\n        onBlur={handleUrlBlur}\n      />\n      <Button\n        type=\"button\"\n        loading={notificationService.isTesting}\n        disabled={!notificationService.isPersisted || notificationService.isTesting}\n        className={styles.button}\n        onClick={handleTestClick}\n      >\n        <Icon fitted name=\"paper plane outline\" />\n      </Button>\n      <ConfirmationPopup\n        title=\"common.deleteNotificationService\"\n        content=\"common.areYouSureYouWantToDeleteThisNotificationService\"\n        buttonContent=\"action.deleteNotificationService\"\n        onConfirm={handleDeleteConfirm}\n      >\n        <Button type=\"button\" disabled={!notificationService.isPersisted} className={styles.button}>\n          <Icon fitted name=\"trash alternate outline\" />\n        </Button>\n      </ConfirmationPopup>\n    </Form>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/notification-services/NotificationServices/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    margin: 0 0 0 8px;\n  }\n\n  .field {\n    flex: auto;\n\n    input {\n      width: auto;\n    }\n  }\n\n  .wrapper {\n    display: flex;\n  }\n}\n"
  },
  {
    "path": "client/src/components/notification-services/NotificationServices/NotificationServices.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { Trans } from 'react-i18next';\nimport { Button, Dropdown, Form, Icon, Input, Message } from 'semantic-ui-react';\nimport { useDidUpdate, useToggle } from '../../../lib/hooks';\n\nimport { useEscapeInterceptor, useForm, useNestedRef } from '../../../hooks';\nimport { NotificationServiceFormats } from '../../../constants/Enums';\nimport Item from './Item';\n\nimport styles from './NotificationServices.module.scss';\n\nconst DEFAULT_DATA = {\n  url: '',\n  format: NotificationServiceFormats.MARKDOWN,\n};\n\nconst NotificationServices = React.memo(({ ids, onCreate }) => {\n  const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);\n  const [focusUrlFieldState, focusUrlField] = useToggle();\n\n  const [urlFieldRef, handleUrlFieldRef] = useNestedRef('inputRef');\n\n  const handleUrlEscape = useCallback(() => {\n    urlFieldRef.current.blur();\n  }, [urlFieldRef]);\n\n  const [activateEscapeInterceptor, deactivateEscapeInterceptor] =\n    useEscapeInterceptor(handleUrlEscape);\n\n  const handleCreateSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      url: data.url.trim(),\n    };\n\n    if (!cleanData.url) {\n      urlFieldRef.current.select();\n      return;\n    }\n\n    onCreate(cleanData);\n    setData(DEFAULT_DATA);\n    focusUrlField();\n  }, [onCreate, data, setData, focusUrlField, urlFieldRef]);\n\n  const handleUrlFocus = useCallback(() => {\n    activateEscapeInterceptor();\n  }, [activateEscapeInterceptor]);\n\n  const handleUrlBlur = useCallback(() => {\n    deactivateEscapeInterceptor();\n  }, [deactivateEscapeInterceptor]);\n\n  useEffect(() => {\n    if (urlFieldRef.current) {\n      urlFieldRef.current.focus();\n    }\n  }, [urlFieldRef]);\n\n  useDidUpdate(() => {\n    if (urlFieldRef.current) {\n      urlFieldRef.current.focus();\n    }\n  }, [focusUrlFieldState]);\n\n  return (\n    <>\n      <Message>\n        <Trans i18nKey=\"common.plankaUsesAppriseToSendNotificationsToOver100PopularServices\">\n          {'PLANKA uses '}\n          <a href=\"https://appriseit.com/services/\" target=\"_blank\" rel=\"noreferrer\">\n            <b>Apprise</b>\n          </a>\n          {' to send notifications to over 100 popular services.'}\n        </Trans>\n      </Message>\n      {ids.map((id) => (\n        <div key={id} className={styles.item}>\n          <Item id={id} />\n        </div>\n      ))}\n      {ids.length < 5 && (\n        <Form className={classNames(styles.item, styles.addItem)} onSubmit={handleCreateSubmit}>\n          <Input\n            ref={handleUrlFieldRef}\n            name=\"url\"\n            value={data.url}\n            placeholder=\"service://hostname/token\"\n            maxLength={512}\n            label={\n              <Dropdown\n                basic\n                compact\n                name=\"format\"\n                value={data.format}\n                options={[\n                  NotificationServiceFormats.TEXT,\n                  NotificationServiceFormats.MARKDOWN,\n                  NotificationServiceFormats.HTML,\n                ].map((format) => ({\n                  text: format,\n                  value: format,\n                }))}\n                onChange={handleFieldChange}\n              />\n            }\n            labelPosition=\"right\"\n            className={styles.field}\n            onFocus={handleUrlFocus}\n            onChange={handleFieldChange}\n            onBlur={handleUrlBlur}\n          />\n          <Button positive className={styles.button}>\n            <Icon fitted name=\"plus\" />\n          </Button>\n        </Form>\n      )}\n    </>\n  );\n});\n\nNotificationServices.propTypes = {\n  ids: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types\n  onCreate: PropTypes.func.isRequired,\n};\n\nexport default NotificationServices;\n"
  },
  {
    "path": "client/src/components/notification-services/NotificationServices/NotificationServices.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .addItem {\n    display: flex;\n  }\n\n  .button {\n    margin: 0 0 0 8px;\n  }\n\n  .field {\n    flex: auto;\n\n    input {\n      width: auto;\n    }\n  }\n\n  .item:not(:last-child) {\n    margin-bottom: 12px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/notification-services/NotificationServices/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport NotificationServices from './NotificationServices';\n\nexport default NotificationServices;\n"
  },
  {
    "path": "client/src/components/notifications/NotificationsStep/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport truncate from 'lodash/truncate';\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation, Trans } from 'react-i18next';\nimport { Link } from 'react-router';\nimport { Button } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { mentionMarkupToText } from '../../../utils/mentions';\nimport { isUserStatic } from '../../../utils/record-helpers';\nimport Paths from '../../../constants/Paths';\nimport { NotificationTypes } from '../../../constants/Enums';\nimport TimeAgo from '../../common/TimeAgo';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id, onClose }) => {\n  const selectNotificationById = useMemo(() => selectors.makeSelectNotificationById(), []);\n  const selectCreatorUserById = useMemo(() => selectors.makeSelectUserById(), []);\n  const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);\n\n  const notification = useSelector((state) => selectNotificationById(state, id));\n\n  const creatorUser = useSelector((state) =>\n    selectCreatorUserById(state, notification.creatorUserId),\n  );\n\n  const card = useSelector((state) => selectCardById(state, notification.cardId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleDeleteClick = useCallback(() => {\n    dispatch(entryActions.deleteNotification(id));\n  }, [id, dispatch]);\n\n  const creatorUserName = isUserStatic(creatorUser)\n    ? t(`common.${creatorUser.name}`, {\n        context: 'title',\n      })\n    : creatorUser.name;\n\n  const cardName = card ? card.name : notification.data.card.name;\n\n  let contentNode;\n  switch (notification.type) {\n    case NotificationTypes.MOVE_CARD: {\n      const { fromList, toList } = notification.data;\n\n      const fromListName = fromList.name || t(`common.${fromList.type}`);\n      const toListName = toList.name || t(`common.${toList.type}`);\n\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userMovedCardFromListToList\"\n          values={{\n            user: creatorUserName,\n            card: cardName,\n            fromList: fromListName,\n            toList: toListName,\n          }}\n        >\n          <span className={styles.author}>{creatorUserName}</span>\n          {' moved '}\n          <Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>\n            {cardName}\n          </Link>\n          {' from '}\n          {fromListName}\n          {' to '}\n          {toListName}\n        </Trans>\n      );\n\n      break;\n    }\n    case NotificationTypes.COMMENT_CARD: {\n      const commentText = truncate(mentionMarkupToText(notification.data.text));\n\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userLeftNewCommentToCard\"\n          values={{\n            user: creatorUserName,\n            comment: commentText,\n            card: cardName,\n          }}\n        >\n          <span className={styles.author}>{creatorUserName}</span>\n          {` left a new comment «${commentText}» to `}\n          <Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>\n            {cardName}\n          </Link>\n        </Trans>\n      );\n\n      break;\n    }\n    case NotificationTypes.ADD_MEMBER_TO_CARD:\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userAddedYouToCard\"\n          values={{\n            user: creatorUserName,\n            card: cardName,\n          }}\n        >\n          <span className={styles.author}>{creatorUserName}</span>\n          {` added you to `}\n          <Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>\n            {cardName}\n          </Link>\n        </Trans>\n      );\n\n      break;\n    case NotificationTypes.MENTION_IN_COMMENT: {\n      const commentText = truncate(mentionMarkupToText(notification.data.text));\n\n      contentNode = (\n        <Trans\n          i18nKey=\"common.userMentionedYouInCommentOnCard\"\n          values={{\n            user: creatorUserName,\n            comment: commentText,\n            card: cardName,\n          }}\n        >\n          <span className={styles.author}>{creatorUserName}</span>\n          {` mentioned you in «${commentText}» on `}\n          <Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>\n            {cardName}\n          </Link>\n        </Trans>\n      );\n\n      break;\n    }\n    default:\n      contentNode = null;\n  }\n\n  return (\n    <div className={styles.wrapper}>\n      <UserAvatar id={notification.creatorUserId} size=\"large\" />\n      <span className={styles.content}>\n        <div>{contentNode}</div>\n        <span className={styles.date}>\n          <TimeAgo date={notification.createdAt} />\n        </span>\n      </span>\n      <Button\n        type=\"button\"\n        icon=\"trash alternate outline\"\n        className={styles.button}\n        onClick={handleDeleteClick}\n      />\n    </div>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/notifications/NotificationsStep/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .author {\n    color: #17394d;\n    font-weight: bold;\n  }\n\n  .button {\n    background: transparent;\n    box-shadow: none;\n    float: right;\n    height: 20px;\n    line-height: 20px;\n    margin: 0;\n    min-height: auto;\n    padding: 0;\n    transition: background 0.3s ease;\n    width: 20px;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .content {\n    display: inline-block;\n    font-size: 13px;\n    line-height: 20px;\n    min-height: 36px;\n    overflow: hidden;\n    padding: 0 4px 0 8px;\n    vertical-align: top;\n    width: calc(100% - 56px);\n    word-break: break-word;\n  }\n\n  .date {\n    color: #6b808c;\n    font-size: 12px;\n  }\n\n  .wrapper {\n    padding: 12px;\n\n    &:hover {\n      background: #f0f0f0;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/notifications/NotificationsStep/NotificationsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport Item from './Item';\n\nimport styles from './NotificationsStep.module.scss';\n\nconst NotificationsStep = React.memo(({ onClose }) => {\n  const notificationIds = useSelector(selectors.selectNotificationIdsForCurrentUser);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleDeleteAllClick = useCallback(() => {\n    dispatch(entryActions.deleteAllNotifications());\n  }, [dispatch]);\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.notifications', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        {notificationIds.length > 0 ? (\n          <>\n            <div className={styles.items}>\n              {notificationIds.map((notificationId) => (\n                <Item key={notificationId} id={notificationId} onClose={onClose} />\n              ))}\n            </div>\n            <Button\n              fluid\n              content={t('action.dismissAll')}\n              className={styles.deleteAllButton}\n              onClick={handleDeleteAllClick}\n            />\n          </>\n        ) : (\n          t('common.noUnreadNotifications')\n        )}\n      </Popup.Content>\n    </>\n  );\n});\n\nNotificationsStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default NotificationsStep;\n"
  },
  {
    "path": "client/src/components/notifications/NotificationsStep/NotificationsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .deleteAllButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-decoration: underline;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .items {\n    margin: 0 -12px;\n    max-height: 60vh;\n    overflow-x: hidden;\n    overflow-y: auto;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &::-webkit-scrollbar {\n      width: 5px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      border-radius: 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/notifications/NotificationsStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport NotificationsStep from './NotificationsStep';\n\nexport default NotificationsStep;\n"
  },
  {
    "path": "client/src/components/project-managers/ProjectManagers/ActionsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { shallowEqual, useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useSteps } from '../../../hooks';\nimport { UserRoles } from '../../../constants/Enums';\nimport ConfirmationStep from '../../common/ConfirmationStep';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './ActionsStep.module.scss';\n\nconst StepTypes = {\n  DELETE: 'DELETE',\n  ASSIGN_AS_OWNER: 'ASSIGN_AS_OWNER',\n};\n\nconst ActionsStep = React.memo(({ projectManagerId, onClose }) => {\n  const selectProjectManagerById = useMemo(() => selectors.makeSelectProjectManagerById(), []);\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const projectManager = useSelector((state) => selectProjectManagerById(state, projectManagerId));\n  const user = useSelector((state) => selectUserById(state, projectManager.userId));\n\n  const isCurrentUser = useSelector(\n    (state) => projectManager.userId === selectors.selectCurrentUserId(state),\n  );\n\n  const { canDelete, canAssignAsOwner } = useSelector((state) => {\n    const currentUser = selectors.selectCurrentUser(state);\n\n    const isLastProjectManager =\n      selectors.selectManagerUserIdsForCurrentProject(state).length === 1;\n\n    if (currentUser.role !== UserRoles.ADMIN) {\n      return {\n        canDelete: !isLastProjectManager,\n        canAssignAsOwner: false,\n      };\n    }\n\n    const isInSharedProject = !selectors.selectCurrentProject(state).ownerProjectManagerId;\n\n    return {\n      canDelete: !isLastProjectManager,\n      canAssignAsOwner: isLastProjectManager && isInSharedProject,\n    };\n  }, shallowEqual);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n\n  const handleAssignAsOwnerConfirm = useCallback(() => {\n    dispatch(\n      entryActions.updateCurrentProject({\n        ownerProjectManagerId: projectManagerId,\n      }),\n    );\n\n    onClose();\n  }, [projectManagerId, onClose, dispatch]);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteProjectManager(projectManagerId));\n  }, [projectManagerId, dispatch]);\n\n  const handleAssignAsOwnerClick = useCallback(() => {\n    openStep(StepTypes.ASSIGN_AS_OWNER);\n  }, [openStep]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  if (step) {\n    switch (step.type) {\n      case StepTypes.ASSIGN_AS_OWNER:\n        return (\n          <ConfirmationStep\n            title=\"common.assignAsOwner\"\n            content=\"common.areYouSureYouWantToAssignThisProjectManagerAsOwner\"\n            buttonType=\"positive\"\n            buttonContent=\"action.assignAsOwner\"\n            onConfirm={handleAssignAsOwnerConfirm}\n            onBack={handleBack}\n          />\n        );\n      case StepTypes.DELETE:\n        return (\n          <ConfirmationStep\n            title={isCurrentUser ? 'common.leaveProject' : 'common.removeManager'}\n            content={\n              isCurrentUser\n                ? 'common.areYouSureYouWantToLeaveProject'\n                : 'common.areYouSureYouWantToRemoveThisManagerFromProject'\n            }\n            buttonContent={isCurrentUser ? 'action.leaveProject' : 'action.removeManager'}\n            onConfirm={handleDeleteConfirm}\n            onBack={handleBack}\n          />\n        );\n      default:\n    }\n  }\n\n  return (\n    <>\n      <span className={styles.user}>\n        <UserAvatar id={projectManager.userId} size=\"large\" />\n      </span>\n      <span className={styles.content}>\n        <div className={styles.name}>{user.name}</div>\n        {user.username && <div className={styles.username}>@{user.username}</div>}\n      </span>\n      {canAssignAsOwner && (\n        <Button\n          fluid\n          content={t('action.assignAsOwner')}\n          className={styles.button}\n          onClick={handleAssignAsOwnerClick}\n        />\n      )}\n      {canDelete && (\n        <Button\n          fluid\n          content={isCurrentUser ? t('action.leaveProject') : t('action.removeFromProject')}\n          className={styles.button}\n          onClick={handleDeleteClick}\n        />\n      )}\n    </>\n  );\n});\n\nActionsStep.propTypes = {\n  projectManagerId: PropTypes.string.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default ActionsStep;\n"
  },
  {
    "path": "client/src/components/project-managers/ProjectManagers/ActionsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-top: 8px;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .content {\n    display: inline-block;\n    width: calc(100% - 44px);\n  }\n\n  .name {\n    color: #212121;\n    font-size: 16px;\n    font-weight: bold;\n    line-height: 1.2;\n    padding: 9px 28px 0 2px;\n  }\n\n  .user {\n    display: inline-block;\n    padding-right: 8px;\n    padding-top: 10px;\n    vertical-align: top;\n  }\n\n  .username {\n    color: #888888;\n    font-size: 14px;\n    line-height: 1.2;\n    padding: 2px 0 2px 2px;\n    word-wrap: break-word;\n  }\n}\n"
  },
  {
    "path": "client/src/components/project-managers/ProjectManagers/AddStep/AddStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Input, Popup } from '../../../../lib/custom-ui';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { useField, useNestedRef } from '../../../../hooks';\nimport User from './User';\n\nimport styles from './AddStep.module.scss';\n\nconst AddStep = React.memo(({ onClose }) => {\n  const users = useSelector(selectors.selectActiveAdminOrProjectOwnerUsers);\n  const currentUserIds = useSelector(selectors.selectManagerUserIdsForCurrentProject);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [search, handleSearchChange] = useField('');\n  const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);\n\n  const filteredUsers = useMemo(\n    () =>\n      users.filter(\n        (user) =>\n          user.name.toLowerCase().includes(cleanSearch) ||\n          (user.username && user.username.includes(cleanSearch)),\n      ),\n    [users, cleanSearch],\n  );\n\n  const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');\n\n  const handleUserSelect = useCallback(\n    (userId) => {\n      dispatch(\n        entryActions.createManagerInCurrentProject({\n          userId,\n        }),\n      );\n\n      onClose();\n    },\n    [onClose, dispatch],\n  );\n\n  useEffect(() => {\n    searchFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [searchFieldRef]);\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.addManager', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Input\n          fluid\n          ref={handleSearchFieldRef}\n          value={search}\n          placeholder={t('common.searchUsers')}\n          maxLength={128}\n          icon=\"search\"\n          onChange={handleSearchChange}\n        />\n        {filteredUsers.length > 0 && (\n          <div className={styles.users}>\n            {filteredUsers.map((user) => (\n              <User\n                key={user.id}\n                id={user.id}\n                isActive={currentUserIds.includes(user.id)}\n                onSelect={handleUserSelect}\n              />\n            ))}\n          </div>\n        )}\n      </Popup.Content>\n    </>\n  );\n});\n\nAddStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default AddStep;\n"
  },
  {
    "path": "client/src/components/project-managers/ProjectManagers/AddStep/AddStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .users {\n    margin-top: 8px;\n    max-height: 60vh;\n    overflow-x: hidden;\n    overflow-y: auto;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &::-webkit-scrollbar {\n      width: 9px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background-clip: padding-box;\n      border-left: 0.25em transparent solid;\n      border-radius: 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/project-managers/ProjectManagers/AddStep/User.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../../selectors';\nimport UserAvatar from '../../../users/UserAvatar';\n\nimport styles from './User.module.scss';\n\nconst User = React.memo(({ id, isActive, onSelect }) => {\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const user = useSelector((state) => selectUserById(state, id));\n\n  const handleClick = useCallback(() => {\n    onSelect(id);\n  }, [id, onSelect]);\n\n  return (\n    <button type=\"button\" disabled={isActive} className={styles.menuItem} onClick={handleClick}>\n      <span className={styles.user}>\n        <UserAvatar id={id} />\n      </span>\n      <div className={classNames(styles.menuItemText, isActive && styles.menuItemTextActive)}>\n        {user.name}\n      </div>\n    </button>\n  );\n});\n\nUser.propTypes = {\n  id: PropTypes.string.isRequired,\n  isActive: PropTypes.bool.isRequired,\n  onSelect: PropTypes.func.isRequired,\n};\n\nexport default User;\n"
  },
  {
    "path": "client/src/components/project-managers/ProjectManagers/AddStep/User.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menuItem {\n    background: transparent;\n    border: none;\n    border-radius: 0.28571429rem;\n    display: block;\n    margin: 0;\n    outline: 0;\n    overflow: hidden;\n    padding: 4px;\n    text-align: left;\n    width: 100%;\n\n    &:enabled {\n      cursor: pointer;\n\n      &:hover {\n        background: rgba(0, 0, 0, 0.05);\n      }\n    }\n  }\n\n  .menuItemText {\n    display: inline-block;\n    line-height: 32px;\n    position: relative;\n    width: calc(100% - 40px);\n  }\n\n  .menuItemTextActive:before {\n    bottom: 2px;\n    color: #798d99;\n    content: \"Г\";\n    font-size: 18px;\n    font-weight: normal;\n    line-height: 36px;\n    position: absolute;\n    right: 2px;\n    text-align: center;\n    transform: rotate(-135deg);\n    width: 36px;\n  }\n\n  .user {\n    display: inline-block;\n    line-height: 32px;\n    padding-right: 8px;\n    width: 40px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/project-managers/ProjectManagers/AddStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AddStep from './AddStep';\n\nexport default AddStep;\n"
  },
  {
    "path": "client/src/components/project-managers/ProjectManagers/ProjectManagers.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useSelector } from 'react-redux';\nimport { Button } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport { usePopupInClosableContext } from '../../../hooks';\nimport { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';\nimport AddStep from './AddStep';\nimport ActionsStep from './ActionsStep';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './ProjectManagers.module.scss';\n\nconst ProjectManagers = React.memo(() => {\n  const projectManagers = useSelector(selectors.selectManagersForCurrentProject);\n\n  const canAdd = useSelector((state) => {\n    const user = selectors.selectCurrentUser(state);\n\n    if (!isUserAdminOrProjectOwner(user)) {\n      return false;\n    }\n\n    return !selectors.selectCurrentProject(state).ownerProjectManagerId;\n  });\n\n  const AddPopup = usePopupInClosableContext(AddStep);\n  const ActionsPopup = usePopupInClosableContext(ActionsStep);\n\n  return (\n    <div className={styles.wrapper}>\n      {projectManagers.map((projectManager) => (\n        <span key={projectManager.id} className={styles.user}>\n          <ActionsPopup projectManagerId={projectManager.id}>\n            <UserAvatar\n              id={projectManager.user.id}\n              size=\"large\"\n              isDisabled={!projectManager.isPersisted}\n            />\n          </ActionsPopup>\n        </span>\n      ))}\n      {canAdd && (\n        <AddPopup>\n          <Button icon=\"add user\" className={styles.addButton} />\n        </AddPopup>\n      )}\n    </div>\n  );\n});\n\nexport default ProjectManagers;\n"
  },
  {
    "path": "client/src/components/project-managers/ProjectManagers/ProjectManagers.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .addButton {\n    background: rgba(0, 0, 0, 0.24);\n    border-radius: 50%;\n    box-shadow: none;\n    color: #fff;\n    line-height: 36px;\n    margin: 0;\n    padding: 0;\n    transition: all 0.1s ease 0s;\n    vertical-align: top;\n    width: 36px;\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.32);\n    }\n  }\n\n  .user {\n    line-height: 1;\n    margin: 0 -4px 0 0;\n  }\n\n  .wrapper {\n    display: flex;\n  }\n}\n"
  },
  {
    "path": "client/src/components/project-managers/ProjectManagers/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ProjectManagers from './ProjectManagers';\n\nexport default ProjectManagers;\n"
  },
  {
    "path": "client/src/components/projects/AddProjectModal/AddProjectModal.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport TextareaAutosize from 'react-textarea-autosize';\nimport { Button, Form, Header, Icon, TextArea } from 'semantic-ui-react';\nimport { usePopup } from '../../../lib/popup';\nimport { Input } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useClosableModal, useForm, useNestedRef } from '../../../hooks';\nimport { isModifierKeyPressed } from '../../../utils/event-helpers';\nimport { ProjectTypes } from '../../../constants/Enums';\nimport { ProjectTypeIcons } from '../../../constants/Icons';\nimport SelectTypeStep from './SelectTypeStep';\n\nimport styles from './AddProjectModal.module.scss';\n\nconst AddProjectModal = React.memo(() => {\n  const defaultType = useSelector(\n    (state) => selectors.selectCurrentModal(state).params.defaultType,\n  );\n\n  const { data: defaultData, isSubmitting } = useSelector(selectors.selectProjectCreateForm);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const [data, handleFieldChange, setData] = useForm(() => ({\n    name: '',\n    description: '',\n    type: ProjectTypes.PRIVATE,\n    ...defaultData,\n    ...(defaultType && {\n      type: defaultType,\n    }),\n  }));\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const submit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n      description: data.description.trim() || null,\n    };\n\n    if (!cleanData.name) {\n      nameFieldRef.current.select();\n      return;\n    }\n\n    dispatch(entryActions.createProject(cleanData));\n  }, [dispatch, data, nameFieldRef]);\n\n  const handleClose = useCallback(() => {\n    dispatch(entryActions.closeModal());\n  }, [dispatch]);\n\n  const handleSubmit = useCallback(() => {\n    submit();\n  }, [submit]);\n\n  const handleDescriptionKeyDown = useCallback(\n    (event) => {\n      if (isModifierKeyPressed(event) && event.key === 'Enter') {\n        submit();\n      }\n    },\n    [submit],\n  );\n\n  const handleTypeSelect = useCallback(\n    (type) => {\n      setData((prevData) => ({\n        ...prevData,\n        type,\n      }));\n    },\n    [setData],\n  );\n\n  const [ClosableModal, , activateClosable, deactivateClosable] = useClosableModal();\n\n  const handleSelectTypeClose = useCallback(() => {\n    deactivateClosable();\n    nameFieldRef.current.focus();\n  }, [deactivateClosable, nameFieldRef]);\n\n  useEffect(() => {\n    nameFieldRef.current.focus();\n  }, [nameFieldRef]);\n\n  const SelectTypePopup = usePopup(SelectTypeStep, {\n    onOpen: activateClosable,\n    onClose: handleSelectTypeClose,\n  });\n\n  return (\n    <ClosableModal basic closeIcon size=\"tiny\" onClose={handleClose}>\n      <ClosableModal.Content>\n        <Header inverted size=\"huge\">\n          {t('common.createProject', {\n            context: 'title',\n          })}\n        </Header>\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.title')}</div>\n          <Input\n            fluid\n            inverted\n            ref={handleNameFieldRef}\n            name=\"name\"\n            value={data.name}\n            maxLength={128}\n            readOnly={isSubmitting}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          <div className={styles.text}>{t('common.description')}</div>\n          <TextArea\n            as={TextareaAutosize}\n            name=\"description\"\n            value={data.description}\n            maxLength={1024}\n            minRows={2}\n            className={styles.field}\n            onKeyDown={handleDescriptionKeyDown}\n            onChange={handleFieldChange}\n          />\n          <Button\n            inverted\n            color=\"green\"\n            icon=\"checkmark\"\n            content={t('action.createProject')}\n            loading={isSubmitting}\n            disabled={isSubmitting}\n          />\n          <SelectTypePopup value={data.type} onSelect={handleTypeSelect}>\n            <Button type=\"button\" className={styles.selectTypeButton}>\n              <Icon name={ProjectTypeIcons[data.type]} className={styles.selectTypeButtonIcon} />\n              {t(`common.${data.type}`)}\n            </Button>\n          </SelectTypePopup>\n        </Form>\n      </ClosableModal.Content>\n    </ClosableModal>\n  );\n});\n\nexport default AddProjectModal;\n"
  },
  {
    "path": "client/src/components/projects/AddProjectModal/AddProjectModal.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 14px;\n  }\n\n  .selectTypeButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    float: right;\n    font-weight: normal;\n    margin-left: auto;\n    margin-right: 0;\n    padding: 6px 11px;\n    text-align: left;\n    text-decoration: underline;\n    transition: none;\n    white-space: nowrap;\n\n    &:hover {\n      background: rgba(246, 225, 189, 0.08);\n      color: #fff;\n    }\n  }\n\n  .selectTypeButtonIcon {\n    text-decoration: none;\n  }\n\n  .text {\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/AddProjectModal/SelectTypeStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport { ProjectTypes } from '../../../constants/Enums';\nimport { ProjectTypeIcons } from '../../../constants/Icons';\n\nimport styles from './SelectTypeStep.module.scss';\n\nconst DESCRIPTION_BY_TYPE = {\n  [ProjectTypes.PRIVATE]: 'common.forPersonalProjects',\n  [ProjectTypes.SHARED]: 'common.forTeamBasedProjects',\n};\n\nconst SelectTypeStep = React.memo(({ value, onSelect, onClose }) => {\n  const [t] = useTranslation();\n\n  const handleSelectClick = useCallback(\n    (_, { value: nextValue }) => {\n      if (nextValue !== value) {\n        onSelect(nextValue);\n      }\n\n      onClose();\n    },\n    [value, onSelect, onClose],\n  );\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.selectType', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Menu secondary vertical className={styles.menu}>\n          {[ProjectTypes.PRIVATE, ProjectTypes.SHARED].map((type) => (\n            <Menu.Item\n              key={type}\n              value={type}\n              active={type === value}\n              className={styles.menuItem}\n              onClick={handleSelectClick}\n            >\n              <Icon name={ProjectTypeIcons[type]} className={styles.menuItemIcon} />\n              <div className={styles.menuItemTitle}>{t(`common.${type}`)}</div>\n              <p className={styles.menuItemDescription}>{t(DESCRIPTION_BY_TYPE[type])}</p>\n            </Menu.Item>\n          ))}\n        </Menu>\n      </Popup.Content>\n    </>\n  );\n});\n\nSelectTypeStep.propTypes = {\n  value: PropTypes.string.isRequired,\n  onSelect: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default SelectTypeStep;\n"
  },
  {
    "path": "client/src/components/projects/AddProjectModal/SelectTypeStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menu {\n    margin: 0 auto 8px;\n    width: 100%;\n  }\n\n  .menuItem:last-child {\n    margin-bottom: 0;\n  }\n\n  .menuItemDescription {\n    opacity: 0.5;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.35714286em 0 0;\n  }\n\n  .menuItemTitle {\n    margin-bottom: 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/AddProjectModal/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AddProjectModal from './AddProjectModal';\n\nexport default AddProjectModal;\n"
  },
  {
    "path": "client/src/components/projects/Project/Project.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport ModalTypes from '../../../constants/ModalTypes';\nimport ProjectSettingsModal from '../ProjectSettingsModal';\nimport Boards from '../../boards/Boards';\nimport BoardSettingsModal from '../../boards/BoardSettingsModal';\n\nimport styles from './Project.module.scss';\n\nconst Project = React.memo(() => {\n  const modal = useSelector(selectors.selectCurrentModal);\n\n  let modalNode = null;\n  if (modal) {\n    switch (modal.type) {\n      case ModalTypes.PROJECT_SETTINGS:\n        modalNode = <ProjectSettingsModal />;\n\n        break;\n      case ModalTypes.BOARD_SETTINGS:\n        modalNode = <BoardSettingsModal />;\n\n        break;\n      default:\n    }\n  }\n\n  return (\n    <>\n      <div className={styles.wrapper}>\n        <Boards />\n      </div>\n      {modalNode}\n    </>\n  );\n});\n\nexport default Project;\n"
  },
  {
    "path": "client/src/components/projects/Project/Project.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    background: rgba(0, 0, 0, 0.16);\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n    padding: 10px 20px 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/Project/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Project from './Project';\n\nexport default Project;\n"
  },
  {
    "path": "client/src/components/projects/ProjectBackground/ProjectBackground.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport camelCase from 'lodash/camelCase';\nimport React, { useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\n\nimport selectors from '../../../selectors';\nimport { ProjectBackgroundTypes } from '../../../constants/Enums';\n\nimport styles from './ProjectBackground.module.scss';\nimport globalStyles from '../../../styles.module.scss';\n\nconst ProjectBackground = React.memo(() => {\n  const selectBackgroundImageById = useMemo(() => selectors.makeSelectBackgroundImageById(), []);\n\n  const { backgroundImageId, backgroundType, backgroundGradient } = useSelector(\n    selectors.selectCurrentProject,\n  );\n\n  const backgroundImageUrl = useSelector((state) => {\n    if (!backgroundType || backgroundType !== ProjectBackgroundTypes.IMAGE) {\n      return null;\n    }\n\n    const backgroundImage = selectBackgroundImageById(state, backgroundImageId);\n\n    if (!backgroundImage) {\n      return null;\n    }\n\n    return backgroundImage.url;\n  });\n\n  return (\n    <div\n      className={classNames(\n        styles.wrapper,\n        backgroundType === ProjectBackgroundTypes.GRADIENT &&\n          globalStyles[`background${upperFirst(camelCase(backgroundGradient))}`],\n      )}\n      style={{\n        background: backgroundImageUrl && `url(\"${backgroundImageUrl}\") center / cover`,\n      }}\n    />\n  );\n});\n\nexport default ProjectBackground;\n"
  },
  {
    "path": "client/src/components/projects/ProjectBackground/ProjectBackground.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    height: 100%;\n    position: fixed;\n    width: 100%;\n    z-index: -1;\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectBackground/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ProjectBackground from './ProjectBackground';\n\nexport default ProjectBackground;\n"
  },
  {
    "path": "client/src/components/projects/ProjectCard/ProjectCard.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport camelCase from 'lodash/camelCase';\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Link } from 'react-router';\nimport { Button, Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport Paths from '../../../constants/Paths';\nimport { ProjectBackgroundTypes } from '../../../constants/Enums';\nimport UserAvatar from '../../users/UserAvatar';\n\nimport styles from './ProjectCard.module.scss';\nimport globalStyles from '../../../styles.module.scss';\n\nconst Sizes = {\n  SMALL: 'small',\n  LARGE: 'large',\n};\n\nconst ProjectCard = React.memo(\n  ({ id, size, isActive, withDescription, withTypeIndicator, withFavoriteButton, className }) => {\n    const selectProjectById = useMemo(() => selectors.makeSelectProjectById(), []);\n\n    const selectFirstBoardIdByProjectId = useMemo(\n      () => selectors.makeSelectFirstBoardIdByProjectId(),\n      [],\n    );\n\n    const selectNotificationsTotalByProjectId = useMemo(\n      () => selectors.makeSelectNotificationsTotalByProjectId(),\n      [],\n    );\n\n    const selectProjectManagerById = useMemo(() => selectors.makeSelectProjectManagerById(), []);\n    const selectBackgroundImageById = useMemo(() => selectors.makeSelectBackgroundImageById(), []);\n\n    const project = useSelector((state) => selectProjectById(state, id));\n    const firstBoardId = useSelector((state) => selectFirstBoardIdByProjectId(state, id));\n\n    const notificationsTotal = useSelector((state) =>\n      selectNotificationsTotalByProjectId(state, id),\n    );\n\n    const ownerProjectManager = useSelector(\n      (state) =>\n        project.ownerProjectManagerId &&\n        selectProjectManagerById(state, project.ownerProjectManagerId),\n    );\n\n    const backgroundImageUrl = useSelector((state) => {\n      if (!project.backgroundType || project.backgroundType !== ProjectBackgroundTypes.IMAGE) {\n        return null;\n      }\n\n      const backgroundImage = selectBackgroundImageById(state, project.backgroundImageId);\n\n      if (!backgroundImage) {\n        return null;\n      }\n\n      return backgroundImage.thumbnailUrls.outside360;\n    });\n\n    const dispatch = useDispatch();\n\n    const handleToggleFavoriteClick = useCallback(() => {\n      dispatch(\n        entryActions.updateProject(project.id, {\n          isFavorite: !project.isFavorite,\n        }),\n      );\n    }, [project, dispatch]);\n\n    const withSidebar = withTypeIndicator || (withFavoriteButton && !project.isHidden);\n\n    return (\n      <div\n        className={classNames(\n          className,\n          styles.wrapper,\n          styles[`wrapper${upperFirst(size)}`],\n          project.isHidden && styles.wrapperHidden,\n        )}\n      >\n        <Link\n          to={\n            firstBoardId\n              ? Paths.BOARDS.replace(':id', firstBoardId)\n              : Paths.PROJECTS.replace(':id', id)\n          }\n          className={styles.content}\n        >\n          <div\n            className={classNames(\n              styles.cover,\n              project.backgroundType === ProjectBackgroundTypes.GRADIENT &&\n                globalStyles[`background${upperFirst(camelCase(project.backgroundGradient))}`],\n            )}\n            style={{\n              background: backgroundImageUrl && `url(\"${backgroundImageUrl}\") center / cover`,\n            }}\n          />\n          {notificationsTotal > 0 && (\n            <span className={styles.notifications}>{notificationsTotal}</span>\n          )}\n          <div\n            className={classNames(styles.information, withSidebar && styles.informationWithSidebar)}\n          >\n            <div\n              className={classNames(\n                styles.title,\n                isActive !== undefined && styles.titleActivatable,\n                isActive && styles.titleActive,\n              )}\n            >\n              {project.name}\n            </div>\n            {withDescription && project.description && (\n              <div className={styles.description}>{project.description}</div>\n            )}\n          </div>\n          {withTypeIndicator && (\n            <div\n              className={classNames(\n                styles.typeIndicator,\n                ownerProjectManager && styles.typeIndicatorWithUser,\n              )}\n            >\n              {ownerProjectManager ? (\n                <UserAvatar id={ownerProjectManager.userId} size=\"small\" />\n              ) : (\n                <Icon\n                  fitted\n                  name=\"group\"\n                  className={classNames(styles.icon, styles.typeIndicatorIcon)}\n                />\n              )}\n            </div>\n          )}\n        </Link>\n        {withFavoriteButton && !project.isHidden && (\n          <Button\n            className={classNames(\n              styles.favoriteButton,\n              !project.isFavorite && styles.favoriteButtonAppearable,\n            )}\n            onClick={handleToggleFavoriteClick}\n          >\n            <Icon\n              fitted\n              name={project.isFavorite ? 'star' : 'star outline'}\n              className={classNames(styles.icon, styles.favoriteButtonIcon)}\n            />\n          </Button>\n        )}\n      </div>\n    );\n  },\n);\n\nProjectCard.propTypes = {\n  id: PropTypes.string.isRequired,\n  size: PropTypes.oneOf(Object.values(Sizes)),\n  isActive: PropTypes.bool,\n  withDescription: PropTypes.bool,\n  withTypeIndicator: PropTypes.bool,\n  withFavoriteButton: PropTypes.bool,\n  className: PropTypes.string.isRequired,\n};\n\nProjectCard.defaultProps = {\n  size: Sizes.LARGE,\n  isActive: undefined,\n  withDescription: false,\n  withTypeIndicator: false,\n  withFavoriteButton: false,\n};\n\nexport default ProjectCard;\n"
  },
  {
    "path": "client/src/components/projects/ProjectCard/ProjectCard.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .content {\n    color: #fff;\n  }\n\n  .cover {\n    background: #555;\n    bottom: 0;\n    left: 0;\n    position: absolute;\n    right: 0;\n    top: 0;\n    transition: filter 0.2s ease;\n\n    &::after {\n      background: #000;\n      bottom: 0;\n      content: \"\";\n      left: 0;\n      opacity: 0.3;\n      position: absolute;\n      right: 0;\n      top: 0;\n    }\n  }\n\n  .description {\n    border-left: 1px solid;\n    display: -webkit-box;\n    font-weight: lighter;\n    hyphens: auto;\n    left: 0;\n    line-height: 1.2;\n    opacity: 0;\n    overflow: hidden;\n    position: absolute;\n    top: 0;\n    transition: opacity 0.3s, transform 0.3s;\n    transform: translate(-40px, 0);\n    white-space: pre-line;\n    word-break: break-word;\n    -webkit-box-orient: vertical;\n  }\n\n  .favoriteButton {\n    background: none;\n    box-shadow: none;\n    height: 36px;\n    line-height: 1;\n    margin: 0;\n    padding: 0;\n    position: absolute;\n    right: 4px;\n    top: 4px;\n    width: 36px;\n\n    &:hover {\n      background: rgba(0, 0, 0, 0.12);\n    }\n  }\n\n  .favoriteButtonAppearable {\n    opacity: 0;\n  }\n\n  .icon {\n    color: rgba(255, 255, 255, 0.8);\n    font-size: 18px;\n  }\n\n  .information {\n    height: 100%;\n    position: relative;\n  }\n\n  .notifications {\n    background: #eb5a46;\n    border: none;\n    border-radius: 3px;\n    outline: none;\n    padding: 0px 6px;\n    position: absolute;\n    transition: background 0.3s ease;\n  }\n\n  .title {\n    bottom: 0;\n    display: -webkit-box;\n    hyphens: auto;\n    left: 0;\n    line-height: 1.1;\n    overflow: hidden;\n    position: absolute;\n    word-break: break-word;\n    -webkit-box-orient: vertical;\n  }\n\n  .titleActivatable {\n    color: rgba(255, 255, 255, 0.72);\n  }\n\n  .titleActive {\n    color: #fff;\n    font-weight: bold;\n  }\n\n  .typeIndicator {\n    bottom: 12px;\n    margin-top: auto;\n    position: absolute;\n    right: 12px;\n  }\n\n  .typeIndicatorIcon {\n    opacity: 0.8;\n  }\n\n  .typeIndicatorWithUser {\n    bottom: 12px;\n    right: 8px;\n  }\n\n  .wrapper {\n    border-radius: 4px;\n    overflow: hidden;\n    position: relative;\n\n    &:hover {\n      .cover {\n        filter: brightness(0.8);\n      }\n\n      .description {\n        opacity: 1;\n        transform: translate(0, 0);\n      }\n\n      .favoriteButtonAppearable {\n        opacity: 1;\n      }\n\n      .titleActivatable {\n        color: #fff;\n      }\n    }\n  }\n\n  .wrapperHidden {\n    opacity: 0.5;\n  }\n\n  /* Sizes */\n\n  .wrapperSmall {\n    .notifications {\n      font-size: 10px;\n      line-height: 18px;\n    }\n\n    .title {\n      line-clamp: 2;\n      -webkit-line-clamp: 2;\n      margin: 0 12px 10px 12px;\n    }\n  }\n\n  .wrapperLarge {\n    .description {\n      line-clamp: 3;\n      -webkit-line-clamp: 3;\n      margin: 24px 20px 0 20px;\n      padding-left: 10px;\n    }\n\n    .informationWithSidebar {\n      margin-right: 20px;\n    }\n\n    .notifications {\n      font-size: 12px;\n      line-height: 20px;\n    }\n\n    .title {\n      font-size: 22px;\n      line-clamp: 2;\n      -webkit-line-clamp: 2;\n      margin: 0 20px 14px 20px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectCard/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ProjectCard from './ProjectCard';\n\nexport default ProjectCard;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/AddImageZone.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { useDropzone } from 'react-dropzone';\n\nimport styles from './AddImageZone.module.scss';\n\nconst AddImageZone = React.memo(({ children, onCreate }) => {\n  const [t] = useTranslation();\n\n  const handleDropAccepted = useCallback(\n    (files) => {\n      onCreate(files[0]);\n    },\n    [onCreate],\n  );\n\n  const { getRootProps, getInputProps, isDragActive } = useDropzone({\n    accept: {\n      'image/*': [],\n    },\n    multiple: false,\n    noClick: true,\n    noKeyboard: true,\n    onDropAccepted: handleDropAccepted,\n  });\n\n  useEffect(() => {\n    const handlePaste = (event) => {\n      if (!event.clipboardData) {\n        return;\n      }\n\n      const file = event.clipboardData.files[0];\n\n      if (file) {\n        if (!file.type.startsWith('image/')) {\n          return;\n        }\n\n        onCreate(file);\n        return;\n      }\n\n      const item = event.clipboardData.items[0];\n\n      if (!item || !item.type.startsWith('image/')) {\n        return;\n      }\n\n      if (item.kind === 'file') {\n        onCreate(item.getAsFile());\n      }\n    };\n\n    window.addEventListener('paste', handlePaste);\n\n    return () => {\n      window.removeEventListener('paste', handlePaste);\n    };\n  }, [onCreate]);\n\n  return (\n    /* eslint-disable-next-line react/jsx-props-no-spreading */\n    <div {...getRootProps()}>\n      {isDragActive && (\n        <div className={styles.dropzone}>\n          <div className={styles.dropzoneText}>{t('common.dropFileToUpload')}</div>\n        </div>\n      )}\n      {children}\n      {/* eslint-disable-next-line react/jsx-props-no-spreading */}\n      <input {...getInputProps()} />\n    </div>\n  );\n});\n\nAddImageZone.propTypes = {\n  children: PropTypes.node.isRequired,\n  onCreate: PropTypes.func.isRequired,\n};\n\nexport default AddImageZone;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/AddImageZone.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .dropzone {\n    background: white;\n    font-size: 20px;\n    font-weight: bold;\n    inset: 0;\n    line-height: 30px;\n    opacity: 0.7;\n    position: absolute;\n    text-align: center;\n    z-index: 2001;\n  }\n\n  .dropzoneText {\n    left: 50%;\n    position: absolute;\n    top: min(200px, 50%);\n    transform: translateX(-50%) translateY(-50%);\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/BackgroundPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { ProjectBackgroundTypes } from '../../../../constants/Enums';\nimport Gradients from './Gradients';\nimport Images from './Images';\nimport AddImageZone from './AddImageZone';\n\nimport styles from './BackgroundPane.module.scss';\n\nconst TITLE_BY_TYPE = {\n  [ProjectBackgroundTypes.GRADIENT]: 'common.gradients',\n  [ProjectBackgroundTypes.IMAGE]: 'common.uploadedImages',\n};\n\nconst BackgroundPane = React.memo(() => {\n  const { backgroundType: currentType } = useSelector(selectors.selectCurrentProject);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const [activeType, setActiveType] = useState(\n    () => currentType || ProjectBackgroundTypes.GRADIENT,\n  );\n\n  const handleImageCreate = useCallback(\n    (file) => {\n      dispatch(\n        entryActions.createBackgroundImageInCurrentProject({\n          file,\n        }),\n      );\n\n      setActiveType(ProjectBackgroundTypes.IMAGE);\n    },\n    [dispatch],\n  );\n\n  const handleActiveTypeChange = useCallback((_, { value }) => {\n    setActiveType(value);\n  }, []);\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <AddImageZone onCreate={handleImageCreate}>\n        <Button.Group fluid basic className={styles.activeTypeButtonGroup}>\n          {[ProjectBackgroundTypes.GRADIENT, ProjectBackgroundTypes.IMAGE].map((type) => (\n            <Button\n              key={type}\n              type=\"button\"\n              value={type}\n              active={type === activeType}\n              onClick={handleActiveTypeChange}\n            >\n              {t(TITLE_BY_TYPE[type])}\n            </Button>\n          ))}\n        </Button.Group>\n        {activeType === ProjectBackgroundTypes.GRADIENT && <Gradients />}\n        {activeType === ProjectBackgroundTypes.IMAGE && <Images />}\n      </AddImageZone>\n    </Tab.Pane>\n  );\n});\n\nexport default BackgroundPane;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/BackgroundPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .activeTypeButtonGroup {\n    margin-bottom: 12px;\n  }\n\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Gradients/Gradients.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\n\nimport BACKGROUND_GRADIENTS from '../../../../../constants/BackgroundGradients';\nimport Item from './Item';\n\nimport styles from './Gradients.module.scss';\n\nconst Gradients = React.memo(() => (\n  <div className={styles.wrapper}>\n    {BACKGROUND_GRADIENTS.map((backgroundGradient) => (\n      <Item key={backgroundGradient} name={backgroundGradient} />\n    ))}\n  </div>\n));\n\nexport default Gradients;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Gradients/Gradients.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    display: grid;\n    gap: 10px;\n    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));\n    max-height: 60vh;\n    overflow-y: auto;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &:after {\n      clear: both;\n      content: \"\";\n      display: table;\n    }\n\n    &::-webkit-scrollbar {\n      width: 9px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background-clip: padding-box;\n      border-left: 0.25em transparent solid;\n      border-radius: 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Gradients/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport camelCase from 'lodash/camelCase';\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Button, Label } from 'semantic-ui-react';\n\nimport selectors from '../../../../../selectors';\nimport entryActions from '../../../../../entry-actions';\nimport { ProjectBackgroundTypes } from '../../../../../constants/Enums';\n\nimport styles from './Item.module.scss';\nimport globalStyles from '../../../../../styles.module.scss';\n\nconst Item = React.memo(({ name }) => {\n  const isActive = useSelector((state) => {\n    const { backgroundType, backgroundGradient } = selectors.selectCurrentProject(state);\n    return backgroundType === ProjectBackgroundTypes.GRADIENT && name === backgroundGradient;\n  });\n\n  const dispatch = useDispatch();\n\n  const handleClick = useCallback(() => {\n    dispatch(\n      entryActions.updateCurrentProject(\n        isActive\n          ? {\n              backgroundType: null,\n              backgroundGradient: null,\n            }\n          : {\n              backgroundType: ProjectBackgroundTypes.GRADIENT,\n              backgroundGradient: name,\n            },\n      ),\n    );\n  }, [name, isActive, dispatch]);\n\n  return (\n    <Button\n      type=\"button\"\n      className={classNames(\n        styles.wrapper,\n        globalStyles[`background${upperFirst(camelCase(name))}`],\n      )}\n      onClick={handleClick}\n    >\n      {isActive && (\n        <Label\n          corner=\"left\"\n          size=\"mini\"\n          icon={{\n            name: 'checkmark',\n            color: 'grey',\n            inverted: true,\n          }}\n          className={styles.label}\n        />\n      )}\n    </Button>\n  );\n});\n\nItem.propTypes = {\n  name: PropTypes.string.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Gradients/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .label {\n    border-color: rgba(29, 46, 63, 0.8);\n\n    i {\n      cursor: inherit;\n    }\n  }\n\n  .wrapper {\n    height: 74px;\n    margin: 0;\n    padding: 0;\n    position: relative;\n\n    &:hover {\n      opacity: 0.9;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Gradients/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Gradients from './Gradients';\n\nexport default Gradients;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Image.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { Button, Icon, Label } from 'semantic-ui-react';\n\nimport { usePopupInClosableContext } from '../../../../hooks';\nimport ConfirmationStep from '../../../common/ConfirmationStep';\n\nimport styles from './Image.module.scss';\n\nconst Image = React.memo(({ url, isActive, onSelect, onDeselect, onDelete }) => {\n  const handleClick = useCallback(() => {\n    if (isActive) {\n      onDeselect();\n    } else {\n      onSelect();\n    }\n  }, [isActive, onSelect, onDeselect]);\n\n  const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);\n\n  return (\n    /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                jsx-a11y/no-static-element-interactions */\n    <div\n      className={styles.wrapper}\n      style={{\n        background: `url(\"${url}\") center / cover`,\n      }}\n      onClick={handleClick}\n    >\n      {isActive && (\n        <Label\n          corner=\"left\"\n          size=\"mini\"\n          icon={{\n            name: 'checkmark',\n            color: 'grey',\n            inverted: true,\n          }}\n          className={styles.label}\n        />\n      )}\n      {onDelete && (\n        <ConfirmationPopup\n          title=\"common.deleteBackgroundImage\"\n          content=\"common.areYouSureYouWantToDeleteThisBackgroundImage\"\n          buttonContent=\"action.deleteBackgroundImage\"\n          onConfirm={onDelete}\n        >\n          <Button className={styles.deleteButton}>\n            <Icon fitted name=\"trash alternate\" size=\"small\" />\n          </Button>\n        </ConfirmationPopup>\n      )}\n    </div>\n  );\n});\n\nImage.propTypes = {\n  url: PropTypes.string.isRequired,\n  isActive: PropTypes.bool.isRequired,\n  onSelect: PropTypes.func.isRequired,\n  onDeselect: PropTypes.func.isRequired,\n  onDelete: PropTypes.func,\n};\n\nImage.defaultProps = {\n  onDelete: undefined,\n};\n\nexport default Image;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Image.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .deleteButton {\n    background: rgba(255, 255, 255, 0.9);\n    box-shadow: none;\n    line-height: 1;\n    margin: 0;\n    min-height: 24px;\n    opacity: 0;\n    padding: 0;\n    position: absolute;\n    right: 4px;\n    top: 4px;\n    width: 24px;\n  }\n\n  .label {\n    border-color: rgba(29, 46, 63, 0.8);\n\n    i {\n      cursor: inherit;\n    }\n  }\n\n  .wrapper {\n    border-radius: 3px;\n    cursor: pointer;\n    height: 74px;\n    position: relative;\n\n    &:hover {\n      opacity: 0.9;\n\n      .deleteButton {\n        opacity: 1;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Images/Images.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useRef } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button } from 'semantic-ui-react';\nimport { FilePicker } from '../../../../../lib/custom-ui';\n\nimport selectors from '../../../../../selectors';\nimport entryActions from '../../../../../entry-actions';\nimport Item from './Item';\n\nimport styles from './Images.module.scss';\n\nconst Images = React.memo(() => {\n  const backgroundImageIds = useSelector(selectors.selectBackgroundImageIdsForCurrentProject);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const fieldRef = useRef(null);\n\n  const handleFileSelect = useCallback(\n    (file) => {\n      dispatch(\n        entryActions.createBackgroundImageInCurrentProject({\n          file,\n        }),\n      );\n    },\n    [dispatch],\n  );\n\n  useEffect(() => {\n    fieldRef.current.focus();\n  }, []);\n\n  return (\n    <>\n      <div className={styles.images}>\n        {backgroundImageIds.map((backgroundImageId) => (\n          <Item key={backgroundImageId} id={backgroundImageId} />\n        ))}\n      </div>\n      <div className={styles.actions}>\n        <div className={styles.action}>\n          <FilePicker accept=\"image/*\" onSelect={handleFileSelect}>\n            <Button\n              ref={fieldRef}\n              content={t('action.uploadNewImage', {\n                context: 'title',\n              })}\n              className={styles.actionButton}\n            />\n          </FilePicker>\n        </div>\n      </div>\n    </>\n  );\n});\n\nexport default Images;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Images/Images.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .action {\n    border: none;\n    border-radius: 0.28571429rem;\n    display: inline-block;\n    height: 36px;\n    overflow: hidden;\n    position: relative;\n    transition: background 0.3s ease;\n    width: 100%;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .actions {\n    margin-top: 20px;\n  }\n\n  .actionButton {\n    background: transparent;\n    color: #6b808c;\n    font-weight: normal;\n    height: 36px;\n    line-height: 24px;\n    padding: 6px 12px;\n    text-align: left;\n    text-decoration: underline;\n    width: 100%;\n  }\n\n  .images {\n    display: grid;\n    gap: 10px;\n    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));\n    max-height: 60vh;\n    overflow-y: auto;\n\n    @supports (-moz-appearance: none) {\n      scrollbar-color: rgba(0, 0, 0, 0.32) transparent;\n      scrollbar-width: thin;\n    }\n\n    &::-webkit-scrollbar {\n      width: 9px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background-clip: padding-box;\n      border-left: 0.25em transparent solid;\n      border-radius: 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Images/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Loader } from 'semantic-ui-react';\n\nimport selectors from '../../../../../selectors';\nimport entryActions from '../../../../../entry-actions';\nimport { ProjectBackgroundTypes } from '../../../../../constants/Enums';\nimport Image from '../Image';\n\nimport styles from './Item.module.scss';\n\nconst Item = React.memo(({ id }) => {\n  const selectBackgroundImageById = useMemo(() => selectors.makeSelectBackgroundImageById(), []);\n\n  const backgroundImage = useSelector((state) => selectBackgroundImageById(state, id));\n\n  const isActive = useSelector((state) => {\n    const { backgroundType, backgroundImageId } = selectors.selectCurrentProject(state);\n    return backgroundType === ProjectBackgroundTypes.IMAGE && id === backgroundImageId;\n  });\n\n  const dispatch = useDispatch();\n\n  const handleSelect = useCallback(() => {\n    dispatch(\n      entryActions.updateCurrentProject({\n        backgroundType: ProjectBackgroundTypes.IMAGE,\n        backgroundImageId: id,\n      }),\n    );\n  }, [id, dispatch]);\n\n  const handleDeselect = useCallback(() => {\n    dispatch(\n      entryActions.updateCurrentProject({\n        backgroundType: null,\n        backgroundImageId: null,\n      }),\n    );\n  }, [dispatch]);\n\n  const handleDelete = useCallback(() => {\n    dispatch(entryActions.deleteBackgroundImage(id));\n  }, [id, dispatch]);\n\n  if (!backgroundImage.isPersisted) {\n    return (\n      <div className={styles.wrapperSubmitting}>\n        <Loader inverted />\n      </div>\n    );\n  }\n\n  return (\n    <Image\n      url={backgroundImage.thumbnailUrls.outside360}\n      isActive={isActive}\n      onSelect={handleSelect}\n      onDeselect={handleDeselect}\n      onDelete={handleDelete}\n    />\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Images/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapperSubmitting {\n    background: rgba(9, 30, 66, 0.04);\n    border-radius: 3px;\n    height: 74px;\n    position: relative;\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/Images/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Images from './Images';\n\nexport default Images;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BackgroundPane/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport BackgroundPane from './BackgroundPane';\n\nexport default BackgroundPane;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BaseCustomFieldGroupsPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { Icon, Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport { usePopupInClosableContext } from '../../../hooks';\nimport BaseCustomFieldGroupChip from '../../base-custom-field-groups/BaseCustomFieldGroupChip';\nimport BaseCustomFieldGroupStep from '../../base-custom-field-groups/BaseCustomFieldGroupStep';\nimport AddBaseCustomFieldGroupStep from '../../base-custom-field-groups/AddBaseCustomFieldGroupStep';\n\nimport styles from './BaseCustomFieldGroupsPane.module.scss';\n\nconst BaseCustomFieldGroupsPane = React.memo(() => {\n  const baseCustomFieldGroupIds = useSelector(\n    selectors.selectBaseCustomFieldGroupIdsForCurrentProject,\n  );\n\n  const BaseCustomFieldGroupPopup = usePopupInClosableContext(BaseCustomFieldGroupStep);\n  const AddBaseCustomFieldGroupPopup = usePopupInClosableContext(AddBaseCustomFieldGroupStep);\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <div className={styles.attachments}>\n        {baseCustomFieldGroupIds.map((baseCustomFieldGroupId) => (\n          <span key={baseCustomFieldGroupId} className={styles.attachment}>\n            <BaseCustomFieldGroupPopup id={baseCustomFieldGroupId}>\n              <BaseCustomFieldGroupChip id={baseCustomFieldGroupId} />\n            </BaseCustomFieldGroupPopup>\n          </span>\n        ))}\n        <AddBaseCustomFieldGroupPopup>\n          <button\n            type=\"button\"\n            className={classNames(styles.attachment, styles.addAttachmentButton)}\n          >\n            <Icon name=\"plus\" size=\"small\" className={styles.addAttachmentButtonIcon} />\n          </button>\n        </AddBaseCustomFieldGroupPopup>\n      </div>\n    </Tab.Pane>\n  );\n});\n\nexport default BaseCustomFieldGroupsPane;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/BaseCustomFieldGroupsPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .addAttachmentButton {\n    background: rgba(9, 30, 66, 0.04);\n    border: none;\n    border-radius: 3px;\n    color: #6b808c;\n    cursor: pointer;\n    line-height: 20px;\n    padding: 6px 14px;\n    transition: background 0.3s ease;\n    vertical-align: top;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #17394d;\n    }\n  }\n\n  .addAttachmentButtonIcon {\n    margin: 0 -4.3px;\n  }\n\n  .attachment {\n    display: inline-block;\n    margin: 0 4px 4px 0;\n    max-width: 100%;\n  }\n\n  .attachments {\n    display: inline-block;\n    line-height: 1;\n    margin: 0 8px 8px 0;\n    max-width: 100%;\n    vertical-align: top;\n  }\n\n  .wrapper {\n    border: none;\n    box-shadow: none;\n    display: flex;\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/GeneralPane/EditInformation.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport React, { useCallback, useMemo } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport TextareaAutosize from 'react-textarea-autosize';\nimport { Button, Form, Input, TextArea } from 'semantic-ui-react';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../../hooks';\nimport { isModifierKeyPressed } from '../../../../utils/event-helpers';\n\nimport styles from './EditInformation.module.scss';\n\nconst EditInformation = React.memo(() => {\n  const project = useSelector(selectors.selectCurrentProject);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultData = useMemo(\n    () => ({\n      name: project.name,\n      description: project.description,\n    }),\n    [project.name, project.description],\n  );\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: '',\n    ...defaultData,\n    description: defaultData.description || '',\n  }));\n\n  const cleanData = useMemo(\n    () => ({\n      ...data,\n      name: data.name.trim(),\n      description: data.description.trim() || null,\n    }),\n    [data],\n  );\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const submit = useCallback(() => {\n    if (!cleanData.name) {\n      nameFieldRef.current.select();\n      return;\n    }\n\n    dispatch(entryActions.updateCurrentProject(cleanData));\n  }, [dispatch, cleanData, nameFieldRef]);\n\n  const handleSubmit = useCallback(() => {\n    submit();\n  }, [submit]);\n\n  const handleDescriptionKeyDown = useCallback(\n    (event) => {\n      if (isModifierKeyPressed(event) && event.key === 'Enter') {\n        submit();\n      }\n    },\n    [submit],\n  );\n\n  return (\n    <Form onSubmit={handleSubmit}>\n      <div className={styles.text}>{t('common.title')}</div>\n      <Input\n        fluid\n        ref={handleNameFieldRef}\n        name=\"name\"\n        value={data.name}\n        maxLength={128}\n        className={styles.field}\n        onChange={handleFieldChange}\n      />\n      <div className={styles.text}>{t('common.description')}</div>\n      <TextArea\n        as={TextareaAutosize}\n        name=\"description\"\n        value={data.description}\n        maxLength={1024}\n        minRows={2}\n        className={styles.field}\n        onKeyDown={handleDescriptionKeyDown}\n        onChange={handleFieldChange}\n      />\n      <Button positive disabled={dequal(cleanData, defaultData)} content={t('action.save')} />\n    </Form>\n  );\n});\n\nexport default EditInformation;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/GeneralPane/EditInformation.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/GeneralPane/GeneralPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Divider, Header, Radio, Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { usePopupInClosableContext } from '../../../../hooks';\nimport EditInformation from './EditInformation';\nimport ConfirmationStep from '../../../common/ConfirmationStep';\n\nimport styles from './GeneralPane.module.scss';\n\nconst GeneralPane = React.memo(() => {\n  const project = useSelector(selectors.selectCurrentProject);\n\n  const hasBoards = useSelector(\n    (state) => selectors.selectBoardIdsForCurrentProject(state).length > 0,\n  );\n\n  const canEdit = useSelector(selectors.selectIsCurrentUserManagerForCurrentProject);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleToggleChange = useCallback(\n    (_, { name: fieldName, checked }) => {\n      dispatch(\n        entryActions.updateCurrentProject({\n          [fieldName]: checked,\n        }),\n      );\n    },\n    [dispatch],\n  );\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteCurrentProject());\n  }, [dispatch]);\n\n  const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      {canEdit && (\n        <>\n          <EditInformation />\n          <Divider horizontal section>\n            <Header as=\"h4\">\n              {t('common.display', {\n                context: 'title',\n              })}\n            </Header>\n          </Divider>\n        </>\n      )}\n      <Radio\n        toggle\n        name=\"isHidden\"\n        checked={project.isHidden}\n        label={t('common.hideFromProjectListAndFavorites')}\n        className={styles.radio}\n        onChange={handleToggleChange}\n      />\n      {canEdit && (\n        <>\n          <Divider horizontal section>\n            <Header as=\"h4\">\n              {t('common.dangerZone', {\n                context: 'title',\n              })}\n            </Header>\n          </Divider>\n          <div className={styles.action}>\n            <ConfirmationPopup\n              title=\"common.deleteProject\"\n              content=\"common.areYouSureYouWantToDeleteThisProject\"\n              buttonContent=\"action.deleteProject\"\n              onConfirm={handleDeleteConfirm}\n            >\n              <Button disabled={hasBoards} className={styles.actionButton}>\n                {hasBoards\n                  ? t('common.deleteAllBoardsToBeAbleToDeleteThisProject')\n                  : t('action.deleteProject', {\n                      context: 'title',\n                    })}\n              </Button>\n            </ConfirmationPopup>\n          </div>\n        </>\n      )}\n    </Tab.Pane>\n  );\n});\n\nexport default GeneralPane;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/GeneralPane/GeneralPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .action {\n    border: none;\n    border-radius: 0.28571429rem;\n    display: inline-block;\n    height: 36px;\n    overflow: hidden;\n    position: relative;\n    transition: background 0.3s ease;\n    width: 100%;\n\n    &:has(:enabled):hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .actionButton {\n    background: transparent;\n    color: #6b808c;\n    font-weight: normal;\n    height: 36px;\n    line-height: 24px;\n    padding: 6px 12px;\n    text-align: left;\n    text-decoration: underline;\n    width: 100%;\n  }\n\n  .radio {\n    margin-bottom: 16px;\n    width: 100%;\n\n    &:last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/GeneralPane/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport GeneralPane from './GeneralPane';\n\nexport default GeneralPane;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/ManagersPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Divider, Header, Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { usePopupInClosableContext } from '../../../hooks';\nimport ConfirmationStep from '../../common/ConfirmationStep';\nimport ProjectManagers from '../../project-managers/ProjectManagers';\n\nimport styles from './ManagersPane.module.scss';\n\nconst ManagersPane = React.memo(() => {\n  const projectManagers = useSelector(selectors.selectManagersForCurrentProject);\n\n  // TODO: rename?\n  const isShared = useSelector(\n    (state) => !selectors.selectCurrentProject(state).ownerProjectManagerId,\n  );\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const firstProjectManager = projectManagers[0];\n\n  const handleToggleSharedConfirm = useCallback(() => {\n    dispatch(\n      entryActions.updateCurrentProject({\n        ownerProjectManagerId: isShared ? firstProjectManager.id : null,\n      }),\n    );\n  }, [firstProjectManager, isShared, dispatch]);\n\n  const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);\n\n  let toggleSharedButtonContent;\n  if (isShared) {\n    toggleSharedButtonContent =\n      projectManagers.length === 1\n        ? t('action.makeProjectPrivate', {\n            context: 'title',\n          })\n        : t('common.onlyOneManagerShouldRemainToMakeThisProjectPrivate');\n  } else {\n    toggleSharedButtonContent = t('action.makeProjectShared', {\n      context: 'title',\n    });\n  }\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <ProjectManagers />\n      <Divider horizontal section>\n        <Header as=\"h4\">\n          {t('common.dangerZone', {\n            context: 'title',\n          })}\n        </Header>\n      </Divider>\n      <div className={styles.action}>\n        <ConfirmationPopup\n          title={isShared ? 'common.makeProjectPrivate' : 'common.makeProjectShared'}\n          content={\n            isShared\n              ? 'common.areYouSureYouWantToMakeThisProjectPrivate'\n              : 'common.areYouSureYouWantToMakeThisProjectShared'\n          }\n          buttonType=\"positive\"\n          buttonContent={isShared ? 'action.makeProjectPrivate' : 'action.makeProjectShared'}\n          onConfirm={handleToggleSharedConfirm}\n        >\n          <Button\n            disabled={isShared && projectManagers.length !== 1}\n            className={styles.actionButton}\n          >\n            {toggleSharedButtonContent}\n          </Button>\n        </ConfirmationPopup>\n      </div>\n    </Tab.Pane>\n  );\n});\n\nexport default ManagersPane;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/ManagersPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .action {\n    border: none;\n    border-radius: 0.28571429rem;\n    display: inline-block;\n    height: 36px;\n    overflow: hidden;\n    position: relative;\n    transition: background 0.3s ease;\n    width: 100%;\n\n    &:has(:enabled):hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .actionButton {\n    background: transparent;\n    color: #6b808c;\n    font-weight: normal;\n    height: 36px;\n    line-height: 24px;\n    padding: 6px 12px;\n    text-align: left;\n    text-decoration: underline;\n    width: 100%;\n  }\n\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/ProjectSettingsModal.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useClosableModal } from '../../../hooks';\nimport GeneralPane from './GeneralPane';\nimport ManagersPane from './ManagersPane';\nimport BackgroundPane from './BackgroundPane';\nimport BaseCustomFieldGroupsPane from './BaseCustomFieldGroupsPane';\n\nimport styles from './ProjectSettingsModal.module.scss';\n\nconst ProjectSettingsModal = React.memo(() => {\n  const withManagablePanes = useSelector(selectors.selectIsCurrentUserManagerForCurrentProject);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [activeTabIndex, setActiveTabIndex] = useState(0);\n\n  const handleClose = useCallback(() => {\n    dispatch(entryActions.closeModal());\n  }, [dispatch]);\n\n  const handleTabChange = useCallback((_, { activeIndex }) => {\n    setActiveTabIndex(activeIndex);\n  }, []);\n\n  const [ClosableModal] = useClosableModal();\n\n  const panes = [\n    {\n      menuItem: t('common.general', {\n        context: 'title',\n      }),\n      render: () => <GeneralPane />,\n    },\n    {\n      menuItem: t('common.managers', {\n        context: 'title',\n      }),\n      render: () => <ManagersPane />,\n    },\n  ];\n\n  if (withManagablePanes) {\n    panes.push(\n      {\n        menuItem: t('common.background', {\n          context: 'title',\n        }),\n        render: () => <BackgroundPane />,\n      },\n      {\n        menuItem: t('common.baseCustomFields', {\n          context: 'title',\n        }),\n        render: () => <BaseCustomFieldGroupsPane />,\n      },\n    );\n  }\n\n  const isBackgroundPaneActive = withManagablePanes && activeTabIndex === 2;\n\n  return (\n    <ClosableModal\n      closeIcon\n      size=\"small\"\n      centered={false}\n      dimmer={isBackgroundPaneActive && { className: styles.dimmerTransparent }}\n      onClose={handleClose}\n    >\n      <ClosableModal.Content>\n        <Tab\n          menu={{\n            secondary: true,\n            pointing: true,\n          }}\n          panes={panes}\n          onTabChange={handleTabChange}\n        />\n      </ClosableModal.Content>\n    </ClosableModal>\n  );\n});\n\nexport default ProjectSettingsModal;\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/ProjectSettingsModal.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .dimmerTransparent {\n    background-color: transparent;\n  }\n}\n"
  },
  {
    "path": "client/src/components/projects/ProjectSettingsModal/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ProjectSettingsModal from './ProjectSettingsModal';\n\nexport default ProjectSettingsModal;\n"
  },
  {
    "path": "client/src/components/task-lists/AddTaskListStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form } from 'semantic-ui-react';\nimport { Popup } from '../../lib/custom-ui';\n\nimport entryActions from '../../entry-actions';\nimport { useForm } from '../../hooks';\nimport TaskListEditor from './TaskListEditor';\n\nconst AddTaskListStep = React.memo(({ onClose }) => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const [data, handleFieldChange] = useForm({\n    name: t('common.taskList', {\n      context: 'title',\n    }),\n    showOnFrontOfCard: true,\n    hideCompletedTasks: false,\n  });\n\n  const taskListEditorRef = useRef(null);\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n    };\n\n    if (!cleanData.name) {\n      taskListEditorRef.current.selectNameField();\n      return;\n    }\n\n    dispatch(entryActions.createTaskListInCurrentCard(cleanData));\n\n    onClose();\n  }, [onClose, dispatch, data, taskListEditorRef]);\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.addTaskList', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Form onSubmit={handleSubmit}>\n          <TaskListEditor ref={taskListEditorRef} data={data} onFieldChange={handleFieldChange} />\n          <Button positive content={t('action.addTaskList')} />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nAddTaskListStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default AddTaskListStep;\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/AddTask.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport TextareaAutosize from 'react-textarea-autosize';\nimport { Button, Dropdown, Form, Icon, TextArea } from 'semantic-ui-react';\nimport { useClickAwayListener, useDidUpdate, useToggle } from '../../../lib/hooks';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\nimport { focusEnd } from '../../../utils/element-helpers';\nimport { isModifierKeyPressed } from '../../../utils/event-helpers';\n\nimport styles from './AddTask.module.scss';\n\nconst DEFAULT_DATA = {\n  name: '',\n  linkedCardId: null,\n};\n\nconst MULTIPLE_REGEX = /\\s*\\r?\\n\\s*/;\n\nconst AddTask = React.memo(({ children, taskListId, isOpened, onClose }) => {\n  const cards = useSelector(selectors.selectCardsExceptCurrentForCurrentBoard);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);\n  const [isLinkingToCard, toggleLinkingToCard] = useToggle();\n  const [focusFieldState, focusField] = useToggle();\n\n  const [fieldRef, handleFieldRef] = useNestedRef();\n  const [submitButtonRef, handleSubmitButtonRef] = useNestedRef();\n  const [toggleLinkingButtonRef, handleToggleLinkingButtonRef] = useNestedRef();\n\n  const submit = useCallback(\n    (isMultiple = false) => {\n      const cleanData = {\n        ...data,\n        name: data.name.trim(),\n      };\n\n      if (isLinkingToCard) {\n        if (!cleanData.linkedCardId) {\n          fieldRef.current.querySelector('.search').focus();\n          return;\n        }\n\n        delete cleanData.name;\n      } else {\n        if (!cleanData.name) {\n          fieldRef.current.select();\n          return;\n        }\n\n        delete cleanData.linkedCardId;\n      }\n\n      if (!isLinkingToCard && isMultiple) {\n        cleanData.name.split(MULTIPLE_REGEX).forEach((name) => {\n          dispatch(\n            entryActions.createTask(taskListId, {\n              ...cleanData,\n              name,\n            }),\n          );\n        });\n      } else {\n        dispatch(entryActions.createTask(taskListId, cleanData));\n      }\n\n      setData(DEFAULT_DATA);\n      focusField();\n    },\n    [taskListId, dispatch, data, setData, isLinkingToCard, focusField, fieldRef],\n  );\n\n  const handleSubmit = useCallback(() => {\n    submit();\n  }, [submit]);\n\n  const handleFieldKeyDown = useCallback(\n    (event) => {\n      if (event.key === 'Enter') {\n        if (!isLinkingToCard) {\n          event.preventDefault();\n          submit(isModifierKeyPressed(event));\n        }\n      } else if (event.key === 'Escape') {\n        onClose();\n      }\n    },\n    [onClose, isLinkingToCard, submit],\n  );\n\n  const handleToggleLinkingClick = useCallback(() => {\n    toggleLinkingToCard();\n  }, [toggleLinkingToCard]);\n\n  const handleClickAwayCancel = useCallback(() => {\n    if (isLinkingToCard) {\n      fieldRef.current.querySelector('.search').focus();\n    } else {\n      focusEnd(fieldRef.current);\n    }\n  }, [isLinkingToCard, fieldRef]);\n\n  const clickAwayProps = useClickAwayListener(\n    [fieldRef, submitButtonRef, toggleLinkingButtonRef],\n    onClose,\n    handleClickAwayCancel,\n  );\n\n  useEffect(() => {\n    if (isOpened) {\n      if (isLinkingToCard) {\n        fieldRef.current.querySelector('.search').focus();\n      } else {\n        focusEnd(fieldRef.current);\n      }\n    }\n  }, [isOpened, isLinkingToCard, fieldRef]);\n\n  useDidUpdate(() => {\n    fieldRef.current.focus();\n  }, [focusFieldState]);\n\n  if (!isOpened) {\n    return children;\n  }\n\n  return (\n    <Form className={styles.wrapper} onSubmit={handleSubmit}>\n      {isLinkingToCard ? (\n        <Dropdown\n          fluid\n          selection\n          search\n          ref={handleFieldRef}\n          name=\"linkedCardId\"\n          options={cards.map((card) => ({\n            text: card.name,\n            value: card.id,\n          }))}\n          value={data.linkedCardId}\n          placeholder={t('common.searchCards')}\n          minCharacters={1}\n          closeOnBlur={false}\n          closeOnEscape={false}\n          noResultsMessage={t('common.noCardsFound')}\n          className={styles.field}\n          onKeyDown={handleFieldKeyDown}\n          onChange={handleFieldChange}\n        />\n      ) : (\n        <TextArea\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          ref={handleFieldRef}\n          as={TextareaAutosize}\n          name=\"name\"\n          value={data.name}\n          placeholder={t('common.enterTaskDescription')}\n          maxLength={1024}\n          minRows={2}\n          className={styles.field}\n          onKeyDown={handleFieldKeyDown}\n          onChange={handleFieldChange}\n        />\n      )}\n      <div className={styles.controls}>\n        <Button\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          positive\n          ref={handleSubmitButtonRef}\n          content={t('action.addTask')}\n        />\n        <Button\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          ref={handleToggleLinkingButtonRef}\n          type=\"button\"\n          className={styles.toggleLinkingButton}\n          onClick={handleToggleLinkingClick}\n        >\n          <Icon\n            name={isLinkingToCard ? 'align left' : 'exchange'}\n            className={styles.toggleLinkingButtonIcon}\n          />\n          {isLinkingToCard ? t('common.description') : t('common.linkToCard')}\n        </Button>\n      </div>\n    </Form>\n  );\n});\n\nAddTask.propTypes = {\n  children: PropTypes.element.isRequired,\n  taskListId: PropTypes.string.isRequired,\n  isOpened: PropTypes.bool.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default AddTask;\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/AddTask.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .controls {\n    display: flex;\n    margin-top: 6px;\n  }\n\n  .field {\n    background: #fff;\n    border: 1px solid rgba(9, 30, 66, 0.08);\n    border-radius: 3px;\n    color: #17394d;\n    display: block;\n    line-height: 1.5;\n    font-size: 14px;\n    margin-bottom: 4px;\n    padding: 8px 12px;\n    resize: none;\n\n    :global {\n      .menu {\n        border: 1px solid rgba(9, 30, 66, 0.08);\n      }\n\n      .search {\n        left: 0;\n      }\n    }\n  }\n\n  .toggleLinkingButton {\n    background: transparent;\n    box-shadow: none;\n    color: #6b808c;\n    font-weight: normal;\n    margin-left: auto;\n    margin-right: 0;\n    overflow: hidden;\n    text-align: left;\n    text-decoration: underline;\n    text-overflow: ellipsis;\n    transition: none;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n      color: #092d42;\n    }\n  }\n\n  .toggleLinkingButtonIcon {\n    text-decoration: none;\n  }\n\n  .wrapper {\n    margin-top: 6px;\n    padding-bottom: 8px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/Task/ActionsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Icon, Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../../lib/custom-ui';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { useSteps } from '../../../../hooks';\nimport ConfirmationStep from '../../../common/ConfirmationStep';\n\nimport styles from './ActionsStep.module.scss';\n\nconst StepTypes = {\n  DELETE: 'DELETE',\n};\n\nconst ActionsStep = React.memo(({ taskId, onNameEdit, onClose }) => {\n  const selectTaskById = useMemo(() => selectors.makeSelectTaskById(), []);\n\n  const task = useSelector((state) => selectTaskById(state, taskId));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [step, openStep, handleBack] = useSteps();\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteTask(taskId));\n  }, [taskId, dispatch]);\n\n  const handleEditNameClick = useCallback(() => {\n    onNameEdit();\n    onClose();\n  }, [onNameEdit, onClose]);\n\n  const handleDeleteClick = useCallback(() => {\n    openStep(StepTypes.DELETE);\n  }, [openStep]);\n\n  if (step && step.type === StepTypes.DELETE) {\n    return (\n      <ConfirmationStep\n        title=\"common.deleteTask\"\n        content=\"common.areYouSureYouWantToDeleteThisTask\"\n        buttonContent=\"action.deleteTask\"\n        onConfirm={handleDeleteConfirm}\n        onBack={handleBack}\n      />\n    );\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.taskActions', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Menu secondary vertical className={styles.menu}>\n          {!task.linkedCardId && (\n            <Menu.Item className={styles.menuItem} onClick={handleEditNameClick}>\n              <Icon name=\"align left\" className={styles.menuItemIcon} />\n              {t('action.editDescription', {\n                context: 'title',\n              })}\n            </Menu.Item>\n          )}\n          <Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>\n            <Icon name=\"trash alternate outline\" className={styles.menuItemIcon} />\n            {t('action.deleteTask', {\n              context: 'title',\n            })}\n          </Menu.Item>\n        </Menu>\n      </Popup.Content>\n    </>\n  );\n});\n\nActionsStep.propTypes = {\n  taskId: PropTypes.string.isRequired,\n  onNameEdit: PropTypes.func.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default ActionsStep;\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/Task/ActionsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .menu {\n    margin: 0 -12px -5px;\n    width: calc(100% + 24px);\n  }\n\n  .menuItem {\n    margin: 0;\n    padding-left: 14px;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.5em 0 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/Task/EditName.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport TextareaAutosize from 'react-textarea-autosize';\nimport { Button, Form, TextArea } from 'semantic-ui-react';\nimport { useClickAwayListener } from '../../../../lib/hooks';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { useField, useNestedRef } from '../../../../hooks';\nimport { focusEnd } from '../../../../utils/element-helpers';\n\nimport styles from './EditName.module.scss';\n\nconst EditName = React.memo(({ taskId, onClose }) => {\n  const selectTaskById = useMemo(() => selectors.makeSelectTaskById(), []);\n\n  const defaultValue = useSelector((state) => selectTaskById(state, taskId).name);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [value, handleFieldChange] = useField(defaultValue);\n\n  const [fieldRef, handleFieldRef] = useNestedRef();\n  const [buttonRef, handleButtonRef] = useNestedRef();\n\n  const submit = useCallback(() => {\n    const cleanValue = value.trim();\n\n    if (cleanValue && cleanValue !== defaultValue) {\n      dispatch(\n        entryActions.updateTask(taskId, {\n          name: cleanValue,\n        }),\n      );\n    }\n\n    onClose();\n  }, [taskId, onClose, defaultValue, dispatch, value]);\n\n  const handleSubmit = useCallback(() => {\n    submit();\n  }, [submit]);\n\n  const handleFieldKeyDown = useCallback(\n    (event) => {\n      if (event.key === 'Enter') {\n        event.preventDefault();\n        submit();\n      } else if (event.key === 'Escape') {\n        onClose();\n      }\n    },\n    [onClose, submit],\n  );\n\n  const handleClickAwayCancel = useCallback(() => {\n    fieldRef.current.focus();\n  }, [fieldRef]);\n\n  const clickAwayProps = useClickAwayListener([fieldRef, buttonRef], submit, handleClickAwayCancel);\n\n  useEffect(() => {\n    focusEnd(fieldRef.current);\n  }, [fieldRef]);\n\n  return (\n    <Form onSubmit={handleSubmit} className={styles.wrapper}>\n      <TextArea\n        {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n        ref={handleFieldRef}\n        as={TextareaAutosize}\n        value={value}\n        maxLength={1024}\n        minRows={2}\n        className={styles.field}\n        onKeyDown={handleFieldKeyDown}\n        onChange={handleFieldChange}\n      />\n      <div className={styles.controls}>\n        <Button\n          {...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading\n          ref={handleButtonRef}\n          positive\n          content={t('action.save')}\n        />\n      </div>\n    </Form>\n  );\n});\n\nEditName.propTypes = {\n  taskId: PropTypes.string.isRequired,\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default EditName;\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/Task/EditName.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .controls {\n    clear: both;\n    margin-top: 6px;\n  }\n\n  .field {\n    background: #fff;\n    border: 1px solid rgba(9, 30, 66, 0.13);\n    border-radius: 3px;\n    box-sizing: border-box;\n    color: #17394d;\n    display: block;\n    font-size: 14px;\n    line-height: 1.5;\n    overflow: hidden;\n    padding: 8px 12px;\n    resize: none;\n\n    &:focus {\n      outline: none;\n    }\n  }\n\n  .wrapper {\n    padding: 9px 0 16px 40px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/Task/SelectAssigneeStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\n\nimport BoardMembershipsStep from '../../../board-memberships/BoardMembershipsStep';\n\nconst SelectAssigneeStep = React.memo(\n  ({ currentUserId, onUserSelect, onUserDeselect, onBack, onClose }) => {\n    const deselectUser = useCallback(() => {\n      onUserDeselect();\n      onClose();\n    }, [onUserDeselect, onClose]);\n\n    const handleUserSelect = useCallback(\n      (userId) => {\n        onUserSelect(userId);\n        onClose();\n      },\n      [onUserSelect, onClose],\n    );\n\n    const handleUserDeselect = useCallback(() => {\n      deselectUser();\n    }, [deselectUser]);\n\n    const handleClear = useCallback(() => {\n      deselectUser();\n    }, [deselectUser]);\n\n    return (\n      <BoardMembershipsStep\n        currentUserIds={currentUserId ? [currentUserId] : []}\n        title=\"common.selectAssignee\"\n        clearButtonContent=\"action.removeAssignee\"\n        onUserSelect={handleUserSelect}\n        onUserDeselect={handleUserDeselect}\n        onClear={handleClear}\n        onBack={onBack}\n      />\n    );\n  },\n);\n\nSelectAssigneeStep.propTypes = {\n  currentUserId: PropTypes.string,\n  onUserSelect: PropTypes.func.isRequired,\n  onUserDeselect: PropTypes.func.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nSelectAssigneeStep.defaultProps = {\n  currentUserId: undefined,\n  onBack: undefined,\n};\n\nexport default SelectAssigneeStep;\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/Task/Task.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useContext, useMemo, useState } from 'react';\nimport ReactDOM from 'react-dom';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { shallowEqual, useDispatch, useSelector } from 'react-redux';\nimport { Link } from 'react-router';\nimport { Draggable } from 'react-beautiful-dnd';\nimport { Button, Checkbox, Icon } from 'semantic-ui-react';\nimport { useDidUpdate } from '../../../../lib/hooks';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { usePopupInClosableContext } from '../../../../hooks';\nimport { isListArchiveOrTrash } from '../../../../utils/record-helpers';\nimport { BoardMembershipRoles } from '../../../../constants/Enums';\nimport { ClosableContext } from '../../../../contexts';\nimport Paths from '../../../../constants/Paths';\nimport EditName from './EditName';\nimport SelectAssigneeStep from './SelectAssigneeStep';\nimport ActionsStep from './ActionsStep';\nimport Linkify from '../../../common/Linkify';\nimport UserAvatar from '../../../users/UserAvatar';\n\nimport styles from './Task.module.scss';\n\nconst Task = React.memo(({ id, index }) => {\n  const selectTaskById = useMemo(() => selectors.makeSelectTaskById(), []);\n  const selectLinkedCardById = useMemo(() => selectors.makeSelectCardById(), []);\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n\n  const task = useSelector((state) => selectTaskById(state, id));\n\n  const linkedCard = useSelector(\n    (state) => task.linkedCardId && selectLinkedCardById(state, task.linkedCardId),\n  );\n\n  const { canEdit, canToggle } = useSelector((state) => {\n    const { listId } = selectors.selectCurrentCard(state);\n    const list = selectListById(state, listId);\n\n    if (isListArchiveOrTrash(list)) {\n      return {\n        canEdit: false,\n        canToggle: false,\n      };\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    const isEditor = !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n\n    return {\n      canEdit: isEditor,\n      canToggle: isEditor,\n    };\n  }, shallowEqual);\n\n  const dispatch = useDispatch();\n  const [isEditNameOpened, setIsEditNameOpened] = useState(false);\n  const [, , setIsClosableActive] = useContext(ClosableContext);\n\n  const handleToggleChange = useCallback(() => {\n    dispatch(\n      entryActions.updateTask(id, {\n        isCompleted: !task.isCompleted,\n      }),\n    );\n  }, [id, task.isCompleted, dispatch]);\n\n  const handleUserSelect = useCallback(\n    (userId) => {\n      dispatch(\n        entryActions.updateTask(id, {\n          assigneeUserId: userId,\n        }),\n      );\n    },\n    [id, dispatch],\n  );\n\n  const handleUserDeselect = useCallback(() => {\n    dispatch(\n      entryActions.updateTask(id, {\n        assigneeUserId: null,\n      }),\n    );\n  }, [id, dispatch]);\n\n  const isEditable = task.isPersisted && canEdit;\n\n  const handleClick = useCallback(() => {\n    if (!task.linkedCardId && isEditable) {\n      setIsEditNameOpened(true);\n    }\n  }, [task.linkedCardId, isEditable]);\n\n  const handleNameEdit = useCallback(() => {\n    setIsEditNameOpened(true);\n  }, []);\n\n  const handleEditNameClose = useCallback(() => {\n    setIsEditNameOpened(false);\n  }, []);\n\n  const handleLinkClick = useCallback((event) => {\n    event.stopPropagation();\n  }, []);\n\n  useDidUpdate(() => {\n    setIsClosableActive(isEditNameOpened);\n  }, [isEditNameOpened]);\n\n  const SelectAssigneePopup = usePopupInClosableContext(SelectAssigneeStep);\n  const ActionsPopup = usePopupInClosableContext(ActionsStep);\n\n  return (\n    <Draggable\n      draggableId={`task:${id}`}\n      index={index}\n      isDragDisabled={isEditNameOpened || !isEditable}\n    >\n      {({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {\n        const contentNode = (\n          <div\n            {...draggableProps} // eslint-disable-line react/jsx-props-no-spreading\n            {...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading\n            ref={innerRef}\n            className={classNames(styles.wrapper, isDragging && styles.wrapperDragging)}\n          >\n            <span className={styles.checkboxWrapper}>\n              <Checkbox\n                checked={task.isCompleted}\n                disabled={!!task.linkedCardId || !task.isPersisted || !canToggle}\n                className={styles.checkbox}\n                onChange={handleToggleChange}\n              />\n            </span>\n            {isEditNameOpened ? (\n              <EditName taskId={id} onClose={handleEditNameClose} />\n            ) : (\n              <div className={classNames(canEdit && styles.contentHoverable)}>\n                {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,\n                                             jsx-a11y/no-static-element-interactions */}\n                <span\n                  className={classNames(\n                    styles.text,\n                    task.linkedCardId && styles.textLinked,\n                    canEdit && styles.textEditable,\n                    canEdit && !task.linkedCardId && styles.textPointable,\n                  )}\n                  onClick={handleClick}\n                >\n                  <span\n                    className={classNames(styles.task, task.isCompleted && styles.taskCompleted)}\n                  >\n                    {task.linkedCardId ? (\n                      <>\n                        <Icon name=\"exchange\" size=\"small\" className={styles.icon} />\n                        <span\n                          className={classNames(\n                            styles.name,\n                            task.isCompleted && styles.nameCompleted,\n                          )}\n                        >\n                          <Link\n                            to={Paths.CARDS.replace(':id', task.linkedCardId)}\n                            onClick={handleLinkClick}\n                          >\n                            {linkedCard ? linkedCard.name : task.name}\n                          </Link>\n                        </span>\n                      </>\n                    ) : (\n                      <span\n                        className={classNames(\n                          styles.name,\n                          task.isCompleted && styles.nameCompleted,\n                        )}\n                      >\n                        <Linkify linkStopPropagation>{task.name}</Linkify>\n                      </span>\n                    )}\n                  </span>\n                </span>\n                {(task.assigneeUserId || isEditable) && (\n                  <div className={classNames(styles.actions, isEditable && styles.actionsEditable)}>\n                    {isEditable ? (\n                      <>\n                        {!task.linkedCardId && (\n                          <SelectAssigneePopup\n                            currentUserId={task.assigneeUserId}\n                            onUserSelect={handleUserSelect}\n                            onUserDeselect={handleUserDeselect}\n                          >\n                            {task.assigneeUserId ? (\n                              <UserAvatar\n                                id={task.assigneeUserId}\n                                size=\"tiny\"\n                                className={styles.assigneeUserAvatar}\n                              />\n                            ) : (\n                              <Button className={styles.button}>\n                                <Icon fitted name=\"add user\" size=\"small\" />\n                              </Button>\n                            )}\n                          </SelectAssigneePopup>\n                        )}\n                        <ActionsPopup taskId={id} onNameEdit={handleNameEdit}>\n                          <Button className={styles.button}>\n                            <Icon fitted name=\"pencil\" size=\"small\" />\n                          </Button>\n                        </ActionsPopup>\n                      </>\n                    ) : (\n                      <UserAvatar\n                        id={task.assigneeUserId}\n                        size=\"tiny\"\n                        className={styles.assigneeUserAvatar}\n                      />\n                    )}\n                  </div>\n                )}\n              </div>\n            )}\n          </div>\n        );\n\n        return isDragging ? ReactDOM.createPortal(contentNode, document.body) : contentNode;\n      }}\n    </Draggable>\n  );\n});\n\nTask.propTypes = {\n  id: PropTypes.string.isRequired,\n  index: PropTypes.number.isRequired,\n};\n\nexport default Task;\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/Task/Task.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .actions {\n    display: flex;\n    gap: 2px;\n    position: absolute;\n    right: 0;\n    top: 4px;\n  }\n\n  .actionsEditable {\n    right: 4px;\n  }\n\n  .assigneeUserAvatar {\n    padding: 2px;\n  }\n\n  .button {\n    background: transparent;\n    box-shadow: none;\n    line-height: 28px;\n    margin: 0;\n    min-height: auto;\n    opacity: 0;\n    padding: 0;\n    width: 28px;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.08);\n    }\n  }\n\n  .checkboxWrapper {\n    display: inline-block;\n    padding: 10px 15px 0px 8px;\n    position: absolute;\n    text-align: center;\n    top: 0;\n    left: 0;\n    vertical-align: top;\n    z-index: 2000;\n    line-height: 1;\n    height: 100%;\n  }\n\n  .contentHoverable:hover {\n    background: rgba(9, 30, 66, 0.04);\n\n    .button {\n      opacity: 1;\n    }\n  }\n\n  .icon {\n    color: rgba(9, 30, 66, 0.24);\n    margin-right: 8px;\n  }\n\n  .name {\n    a:hover {\n      text-decoration: underline;\n    }\n  }\n\n  .nameCompleted {\n    color: #aaa;\n    text-decoration: line-through;\n  }\n\n  .task {\n    display: inline-block;\n    overflow: hidden;\n    overflow-wrap: break-word;\n    padding: 8px 0;\n    vertical-align: top;\n    white-space: pre-wrap;\n    width: 100%;\n  }\n\n  .text {\n    background: transparent;\n    border-radius: 3px;\n    color: #17394d;\n    display: inline-block;\n    font-size: 15px;\n    line-height: 1.5;\n    min-height: 32px;\n    padding: 0 34px 0 40px;\n    width: 100%;\n\n    &.textLinked {\n      padding-right: 0px;\n    }\n  }\n\n  .textEditable {\n    padding-right: 68px;\n\n    &.textLinked {\n      padding-right: 34px;\n    }\n  }\n\n  .textPointable {\n    cursor: pointer;\n  }\n\n  .wrapper {\n    border-radius: 3px;\n    cursor: auto;\n    margin-left: -40px;\n    min-height: 32px;\n    position: relative;\n    width: calc(100% + 40px);\n  }\n\n  .wrapperDragging {\n    background: rgba(247, 246, 247, 0.8);\n  }\n}\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/Task/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Task from './Task';\n\nexport default Task;\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/TaskList.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useContext, useMemo, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Droppable } from 'react-beautiful-dnd';\nimport { Progress } from 'semantic-ui-react';\nimport { useDidUpdate } from '../../../lib/hooks';\n\nimport selectors from '../../../selectors';\nimport { isListArchiveOrTrash } from '../../../utils/record-helpers';\nimport DroppableTypes from '../../../constants/DroppableTypes';\nimport { BoardMembershipRoles } from '../../../constants/Enums';\nimport { ClosableContext } from '../../../contexts';\nimport Task from './Task';\nimport AddTask from './AddTask';\n\nimport styles from './TaskList.module.scss';\n\nconst TaskList = React.memo(({ id, isCompletedVisible }) => {\n  const selectTaskListById = useMemo(() => selectors.makeSelectTaskListById(), []);\n  const selectListById = useMemo(() => selectors.makeSelectListById(), []);\n  const selectTasksByTaskListId = useMemo(() => selectors.makeSelectTasksByTaskListId(), []);\n\n  const taskList = useSelector((state) => selectTaskListById(state, id));\n  const tasks = useSelector((state) => selectTasksByTaskListId(state, id));\n\n  const canEdit = useSelector((state) => {\n    const { listId } = selectors.selectCurrentCard(state);\n    const list = selectListById(state, listId);\n\n    if (isListArchiveOrTrash(list)) {\n      return false;\n    }\n\n    const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);\n    return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;\n  });\n\n  const [t] = useTranslation();\n  const [isAddOpened, setIsAddOpened] = useState(false);\n  const [, , setIsClosableActive] = useContext(ClosableContext);\n\n  const filteredTasks = useMemo(\n    () =>\n      !isCompletedVisible && taskList.hideCompletedTasks\n        ? tasks.filter((task) => !task.isCompleted)\n        : tasks,\n    [isCompletedVisible, taskList.hideCompletedTasks, tasks],\n  );\n\n  // TODO: move to selector?\n  const completedTasksTotal = useMemo(\n    () => tasks.reduce((result, task) => (task.isCompleted ? result + 1 : result), 0),\n    [tasks],\n  );\n\n  const handleAddClick = useCallback(() => {\n    setIsAddOpened(true);\n  }, []);\n\n  const handleAddClose = useCallback(() => {\n    setIsAddOpened(false);\n  }, []);\n\n  useDidUpdate(() => {\n    setIsClosableActive(isAddOpened);\n  }, [isAddOpened]);\n\n  return (\n    <>\n      {tasks.length > 0 && (\n        <div className={styles.progressRow}>\n          <span className={styles.progressWrapper}>\n            <Progress\n              autoSuccess\n              value={completedTasksTotal}\n              total={tasks.length}\n              color=\"blue\"\n              size=\"tiny\"\n              className={styles.progress}\n            />\n          </span>\n          <span className={styles.count}>\n            {completedTasksTotal}/{tasks.length}\n          </span>\n        </div>\n      )}\n      <Droppable\n        droppableId={`task-list:${id}`}\n        type={DroppableTypes.TASK}\n        isDropDisabled={!taskList.isPersisted || !canEdit}\n      >\n        {({ innerRef, droppableProps, placeholder }) => (\n          // eslint-disable-next-line react/jsx-props-no-spreading\n          <div {...droppableProps} ref={innerRef} className={styles.tasks}>\n            {filteredTasks.map((task, index) => (\n              <Task key={task.id} id={task.id} index={index} />\n            ))}\n            {placeholder}\n          </div>\n        )}\n      </Droppable>\n      {canEdit && (\n        <AddTask taskListId={id} isOpened={isAddOpened} onClose={handleAddClose}>\n          <button\n            type=\"button\"\n            disabled={!taskList.isPersisted}\n            className={styles.taskButton}\n            onClick={handleAddClick}\n          >\n            <span className={styles.taskButtonText}>\n              {tasks.length > 0 ? t('action.addAnotherTask') : t('action.addTask')}\n            </span>\n          </button>\n        </AddTask>\n      )}\n    </>\n  );\n});\n\nTaskList.propTypes = {\n  id: PropTypes.string.isRequired,\n  isCompletedVisible: PropTypes.bool.isRequired,\n};\n\nexport default TaskList;\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/TaskList.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .count {\n    color: #8c8c8c;\n    font-size: 14px;\n    line-height: 14px;\n  }\n\n  .progress {\n    margin: 0;\n  }\n\n  .progressRow {\n    display: flex;\n    gap: 12px;\n    justify-content: space-between;\n    margin-bottom: 16px;\n  }\n\n  .progressWrapper {\n    padding: 3px 0;\n    width: 100%;\n  }\n\n  .tasks {\n    min-height: 1px;\n  }\n\n  .taskButton {\n    background: transparent;\n    border: none;\n    border-radius: 3px;\n    color: #6b808c;\n    cursor: pointer;\n    display: block;\n    margin-top: 6px;\n    min-height: 54px;\n    outline: none;\n    padding: 8px 12px;\n    position: relative;\n    text-align: left;\n    text-decoration: none;\n    width: 100%;\n\n    &:hover {\n      background: rgba(9, 30, 66, 0.04);\n      color: #092d42;\n    }\n  }\n\n  .taskButtonText {\n    font-size: 14px;\n    position: absolute;\n    top: 12px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/task-lists/TaskList/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport TaskList from './TaskList';\n\nexport default TaskList;\n"
  },
  {
    "path": "client/src/components/task-lists/TaskListEditor/TaskListEditor.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useImperativeHandle } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Radio } from 'semantic-ui-react';\nimport { Input } from '../../../lib/custom-ui';\n\nimport { useNestedRef } from '../../../hooks';\n\nimport styles from './TaskListEditor.module.scss';\n\nconst TaskListEditor = React.forwardRef(({ data, onFieldChange }, ref) => {\n  const [t] = useTranslation();\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const selectNameField = useCallback(() => {\n    nameFieldRef.current.select();\n  }, [nameFieldRef]);\n\n  useImperativeHandle(\n    ref,\n    () => ({\n      selectNameField,\n    }),\n    [selectNameField],\n  );\n\n  useEffect(() => {\n    nameFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [nameFieldRef]);\n\n  return (\n    <>\n      <div className={styles.text}>{t('common.title')}</div>\n      <Input\n        fluid\n        ref={handleNameFieldRef}\n        name=\"name\"\n        value={data.name}\n        maxLength={128}\n        className={styles.field}\n        onChange={onFieldChange}\n      />\n      <Radio\n        toggle\n        name=\"showOnFrontOfCard\"\n        checked={data.showOnFrontOfCard}\n        label={t('common.showOnFrontOfCard')}\n        className={styles.fieldRadio}\n        onChange={onFieldChange}\n      />\n      <Radio\n        toggle\n        name=\"hideCompletedTasks\"\n        checked={data.hideCompletedTasks}\n        label={t('common.hideCompletedTasks')}\n        className={styles.fieldRadio}\n        onChange={onFieldChange}\n      />\n    </>\n  );\n});\n\nTaskListEditor.propTypes = {\n  data: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  onFieldChange: PropTypes.func.isRequired,\n};\n\nexport default React.memo(TaskListEditor);\n"
  },
  {
    "path": "client/src/components/task-lists/TaskListEditor/TaskListEditor.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .fieldRadio {\n    margin-top: 8px;\n    margin-bottom: 8px;\n    width: 100%;\n\n    &:last-of-type {\n      margin-bottom: 16px;\n    }\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/task-lists/TaskListEditor/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport TaskListEditor from './TaskListEditor';\n\nexport default TaskListEditor;\n"
  },
  {
    "path": "client/src/components/users/EditUserEmailStep/EditUserEmailStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport isEmail from 'validator/lib/isEmail';\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Message } from 'semantic-ui-react';\nimport { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\n\nimport styles from './EditUserEmailStep.module.scss';\n\nconst createMessage = (error) => {\n  if (!error) {\n    return error;\n  }\n\n  switch (error.message) {\n    case 'Email already in use':\n      return {\n        type: 'error',\n        content: 'common.emailAlreadyInUse',\n      };\n    case 'Invalid current password':\n      return {\n        type: 'error',\n        content: 'common.invalidCurrentPassword',\n      };\n    default:\n      return {\n        type: 'warning',\n        content: 'common.unknownError',\n      };\n  }\n};\n\nconst EditUserEmailStep = React.memo(({ id, onBack, onClose }) => {\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const {\n    email,\n    isSsoUser,\n    emailUpdateForm: { data: defaultData, isSubmitting, error },\n  } = useSelector((state) => selectUserById(state, id));\n\n  const withPasswordConfirmation = useSelector(\n    (state) => id === selectors.selectCurrentUserId(state) && !isSsoUser,\n  );\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const wasSubmitting = usePrevious(isSubmitting);\n\n  const [data, handleFieldChange, setData] = useForm({\n    email: '',\n    currentPassword: '',\n    ...defaultData,\n  });\n\n  const message = useMemo(() => createMessage(error), [error]);\n  const [focusCurrentPasswordFieldState, focusCurrentPasswordField] = useToggle();\n\n  const [emailFieldRef, handleEmailFieldRef] = useNestedRef('inputRef');\n  const [currentPasswordFieldRef, handleCurrentPasswordFieldRef] = useNestedRef('inputRef');\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      email: data.email.trim(),\n    };\n\n    if (!isEmail(cleanData.email)) {\n      emailFieldRef.current.select();\n      return;\n    }\n\n    if (cleanData.email === email) {\n      onClose();\n      return;\n    }\n\n    if (withPasswordConfirmation) {\n      if (!cleanData.currentPassword) {\n        currentPasswordFieldRef.current.focus();\n        return;\n      }\n    } else {\n      delete cleanData.currentPassword;\n    }\n\n    dispatch(entryActions.updateUserEmail(id, cleanData));\n  }, [\n    id,\n    withPasswordConfirmation,\n    onClose,\n    email,\n    dispatch,\n    data,\n    emailFieldRef,\n    currentPasswordFieldRef,\n  ]);\n\n  const handleMessageDismiss = useCallback(() => {\n    dispatch(entryActions.clearUserEmailUpdateError(id));\n  }, [id, dispatch]);\n\n  useEffect(() => {\n    emailFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [emailFieldRef]);\n\n  useDidUpdate(() => {\n    if (wasSubmitting && !isSubmitting) {\n      if (error) {\n        switch (error.message) {\n          case 'Email already in use':\n            emailFieldRef.current.select();\n\n            break;\n          case 'Invalid current password':\n            setData((prevData) => ({\n              ...prevData,\n              currentPassword: '',\n            }));\n            focusCurrentPasswordField();\n\n            break;\n          default:\n        }\n      } else {\n        onClose();\n      }\n    }\n  }, [isSubmitting, wasSubmitting, error, onClose]);\n\n  useDidUpdate(() => {\n    currentPasswordFieldRef.current.focus();\n  }, [focusCurrentPasswordFieldState]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editEmail', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        {message && (\n          <Message\n            {...{\n              [message.type]: true,\n            }}\n            visible\n            content={t(message.content)}\n            onDismiss={handleMessageDismiss}\n          />\n        )}\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.newEmail')}</div>\n          <Input\n            fluid\n            ref={handleEmailFieldRef}\n            name=\"email\"\n            value={data.email}\n            placeholder={email}\n            maxLength={256}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          {withPasswordConfirmation && (\n            <>\n              <div className={styles.text}>{t('common.currentPassword')}</div>\n              <Input.Password\n                fluid\n                ref={handleCurrentPasswordFieldRef}\n                name=\"currentPassword\"\n                value={data.currentPassword}\n                maxLength={256}\n                className={styles.field}\n                onChange={handleFieldChange}\n              />\n            </>\n          )}\n          <Button\n            positive\n            content={t('action.save')}\n            loading={isSubmitting}\n            disabled={isSubmitting}\n          />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nEditUserEmailStep.propTypes = {\n  id: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nEditUserEmailStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default EditUserEmailStep;\n"
  },
  {
    "path": "client/src/components/users/EditUserEmailStep/EditUserEmailStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/users/EditUserEmailStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EditUserEmailStep from './EditUserEmailStep';\n\nexport default EditUserEmailStep;\n"
  },
  {
    "path": "client/src/components/users/EditUserInformation/EditUserInformation.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport omit from 'lodash/omit';\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Input } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\n\nimport styles from './EditUserInformation.module.scss';\n\nconst EditUserInformation = React.memo(({ id, onUpdate }) => {\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const user = useSelector((state) => selectUserById(state, id));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const defaultData = useMemo(\n    () => ({\n      name: user.name,\n      phone: user.phone,\n      organization: user.organization,\n    }),\n    [user.name, user.phone, user.organization],\n  );\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: '',\n    ...defaultData,\n    phone: defaultData.phone || '',\n    organization: defaultData.organization || '',\n  }));\n\n  const cleanData = useMemo(\n    () => ({\n      ...data,\n      name: data.name.trim(),\n      phone: data.phone.trim() || null,\n      organization: data.organization.trim() || null,\n    }),\n    [data],\n  );\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n\n  const isNameEditable = !user.lockedFieldNames.includes('name');\n\n  const handleSubmit = useCallback(() => {\n    if (isNameEditable && !cleanData.name) {\n      nameFieldRef.current.select();\n      return;\n    }\n\n    dispatch(entryActions.updateUser(id, isNameEditable ? cleanData : omit(cleanData, 'name')));\n\n    if (onUpdate) {\n      onUpdate();\n    }\n  }, [id, onUpdate, dispatch, cleanData, nameFieldRef, isNameEditable]);\n\n  return (\n    <Form onSubmit={handleSubmit}>\n      <div className={styles.text}>{t('common.name')}</div>\n      <Input\n        fluid\n        ref={handleNameFieldRef}\n        name=\"name\"\n        value={data.name}\n        maxLength={128}\n        disabled={!isNameEditable}\n        className={styles.field}\n        onChange={handleFieldChange}\n      />\n      <div className={styles.text}>{t('common.phone')}</div>\n      <Input\n        fluid\n        name=\"phone\"\n        value={data.phone}\n        maxLength={128}\n        className={styles.field}\n        onChange={handleFieldChange}\n      />\n      <div className={styles.text}>{t('common.organization')}</div>\n      <Input\n        fluid\n        name=\"organization\"\n        value={data.organization}\n        maxLength={128}\n        className={styles.field}\n        onChange={handleFieldChange}\n      />\n      <Button positive disabled={dequal(cleanData, defaultData)} content={t('action.save')} />\n    </Form>\n  );\n});\n\nEditUserInformation.propTypes = {\n  id: PropTypes.string.isRequired,\n  onUpdate: PropTypes.func,\n};\n\nEditUserInformation.defaultProps = {\n  onUpdate: undefined,\n};\n\nexport default EditUserInformation;\n"
  },
  {
    "path": "client/src/components/users/EditUserInformation/EditUserInformation.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/users/EditUserInformation/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EditUserInformation from './EditUserInformation';\n\nexport default EditUserInformation;\n"
  },
  {
    "path": "client/src/components/users/EditUserInformationStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Popup } from '../../lib/custom-ui';\n\nimport EditUserInformation from './EditUserInformation';\n\nconst EditUserInformationStep = React.memo(({ id, onBack, onClose }) => {\n  const [t] = useTranslation();\n\n  const handleUpdate = useCallback(() => {\n    onClose();\n  }, [onClose]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editInformation', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <EditUserInformation id={id} onUpdate={handleUpdate} />\n      </Popup.Content>\n    </>\n  );\n});\n\nEditUserInformationStep.propTypes = {\n  id: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nEditUserInformationStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default EditUserInformationStep;\n"
  },
  {
    "path": "client/src/components/users/EditUserPasswordStep/EditUserPasswordStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport omit from 'lodash/omit';\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Message } from 'semantic-ui-react';\nimport { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\nimport { isPassword } from '../../../utils/validator';\n\nimport styles from './EditUserPasswordStep.module.scss';\n\nconst createMessage = (error) => {\n  if (!error) {\n    return error;\n  }\n\n  switch (error.message) {\n    case 'Invalid current password':\n      return {\n        type: 'error',\n        content: 'common.invalidCurrentPassword',\n      };\n    default:\n      return {\n        type: 'warning',\n        content: 'common.unknownError',\n      };\n  }\n};\n\nconst EditUserPasswordStep = React.memo(({ id, onBack, onClose }) => {\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const {\n    isSsoUser,\n    passwordUpdateForm: { data: defaultData, isSubmitting, error },\n  } = useSelector((state) => selectUserById(state, id));\n\n  const withPasswordConfirmation = useSelector(\n    (state) => id === selectors.selectCurrentUserId(state) && !isSsoUser,\n  );\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const wasSubmitting = usePrevious(isSubmitting);\n\n  const [data, handleFieldChange, setData] = useForm({\n    password: '',\n    currentPassword: '',\n    ...defaultData,\n  });\n\n  const message = useMemo(() => createMessage(error), [error]);\n  const [focusCurrentPasswordFieldState, focusCurrentPasswordField] = useToggle();\n\n  const [passwordFieldRef, handlePasswordFieldRef] = useNestedRef('inputRef');\n  const [currentPasswordFieldRef, handleCurrentPasswordFieldRef] = useNestedRef('inputRef');\n\n  const handleSubmit = useCallback(() => {\n    if (!data.password || !isPassword(data.password)) {\n      passwordFieldRef.current.select();\n      return;\n    }\n\n    if (withPasswordConfirmation && !data.currentPassword) {\n      currentPasswordFieldRef.current.focus();\n      return;\n    }\n\n    dispatch(\n      entryActions.updateUserPassword(\n        id,\n        withPasswordConfirmation ? data : omit(data, 'currentPassword'),\n      ),\n    );\n  }, [id, withPasswordConfirmation, dispatch, data, passwordFieldRef, currentPasswordFieldRef]);\n\n  const handleMessageDismiss = useCallback(() => {\n    dispatch(entryActions.clearUserPasswordUpdateError(id));\n  }, [id, dispatch]);\n\n  useEffect(() => {\n    passwordFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [passwordFieldRef]);\n\n  useDidUpdate(() => {\n    if (wasSubmitting && !isSubmitting) {\n      if (!error) {\n        onClose();\n      } else if (error.message === 'Invalid current password') {\n        setData((prevData) => ({\n          ...prevData,\n          currentPassword: '',\n        }));\n        focusCurrentPasswordField();\n      }\n    }\n  }, [isSubmitting, wasSubmitting, error, onClose]);\n\n  useDidUpdate(() => {\n    currentPasswordFieldRef.current.focus();\n  }, [focusCurrentPasswordFieldState]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editPassword', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        {message && (\n          <Message\n            {...{\n              [message.type]: true,\n            }}\n            visible\n            content={t(message.content)}\n            onDismiss={handleMessageDismiss}\n          />\n        )}\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.newPassword')}</div>\n          <Input.Password\n            withStrengthBar\n            fluid\n            ref={handlePasswordFieldRef}\n            name=\"password\"\n            value={data.password}\n            maxLength={256}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          {withPasswordConfirmation && (\n            <>\n              <div className={styles.text}>{t('common.currentPassword')}</div>\n              <Input.Password\n                fluid\n                ref={handleCurrentPasswordFieldRef}\n                name=\"currentPassword\"\n                value={data.currentPassword}\n                maxLength={256}\n                className={styles.field}\n                onChange={handleFieldChange}\n              />\n            </>\n          )}\n          <Button\n            positive\n            content={t('action.save')}\n            loading={isSubmitting}\n            disabled={isSubmitting}\n          />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nEditUserPasswordStep.propTypes = {\n  id: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nEditUserPasswordStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default EditUserPasswordStep;\n"
  },
  {
    "path": "client/src/components/users/EditUserPasswordStep/EditUserPasswordStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/users/EditUserPasswordStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EditUserPasswordStep from './EditUserPasswordStep';\n\nexport default EditUserPasswordStep;\n"
  },
  {
    "path": "client/src/components/users/EditUserUsernameStep/EditUserUsernameStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Form, Message } from 'semantic-ui-react';\nimport { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';\nimport { Input, Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, useNestedRef } from '../../../hooks';\nimport { isUsername } from '../../../utils/validator';\n\nimport styles from './EditUserUsernameStep.module.scss';\n\nconst createMessage = (error) => {\n  if (!error) {\n    return error;\n  }\n\n  switch (error.message) {\n    case 'Username already in use':\n      return {\n        type: 'error',\n        content: 'common.usernameAlreadyInUse',\n      };\n    case 'Invalid current password':\n      return {\n        type: 'error',\n        content: 'common.invalidCurrentPassword',\n      };\n    default:\n      return {\n        type: 'warning',\n        content: 'common.unknownError',\n      };\n  }\n};\n\nconst EditUserUsernameStep = React.memo(({ id, onBack, onClose }) => {\n  const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n  const {\n    username,\n    isSsoUser,\n    usernameUpdateForm: { data: defaultData, isSubmitting, error },\n  } = useSelector((state) => selectUserById(state, id));\n\n  const withPasswordConfirmation = useSelector(\n    (state) => id === selectors.selectCurrentUserId(state) && !isSsoUser,\n  );\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const wasSubmitting = usePrevious(isSubmitting);\n\n  const [data, handleFieldChange, setData] = useForm({\n    username: '',\n    currentPassword: '',\n    ...defaultData,\n  });\n\n  const message = useMemo(() => createMessage(error), [error]);\n  const [focusCurrentPasswordFieldState, focusCurrentPasswordField] = useToggle();\n\n  const [usernameFieldRef, handleUsernameFieldRef] = useNestedRef('inputRef');\n  const [currentPasswordFieldRef, handleCurrentPasswordFieldRef] = useNestedRef('inputRef');\n\n  const handleSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      username: data.username.trim() || null,\n    };\n\n    if (!cleanData.username || !isUsername(cleanData.username)) {\n      usernameFieldRef.current.select();\n      return;\n    }\n\n    if (cleanData.username === username) {\n      onClose();\n      return;\n    }\n\n    if (withPasswordConfirmation) {\n      if (!cleanData.currentPassword) {\n        currentPasswordFieldRef.current.focus();\n        return;\n      }\n    } else {\n      delete cleanData.currentPassword;\n    }\n\n    dispatch(entryActions.updateUserUsername(id, cleanData));\n  }, [\n    id,\n    withPasswordConfirmation,\n    onClose,\n    username,\n    dispatch,\n    data,\n    usernameFieldRef,\n    currentPasswordFieldRef,\n  ]);\n\n  const handleMessageDismiss = useCallback(() => {\n    dispatch(entryActions.clearUserUsernameUpdateError(id));\n  }, [id, dispatch]);\n\n  useEffect(() => {\n    usernameFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [usernameFieldRef]);\n\n  useDidUpdate(() => {\n    if (wasSubmitting && !isSubmitting) {\n      if (error) {\n        switch (error.message) {\n          case 'Username already in use':\n            usernameFieldRef.current.select();\n\n            break;\n          case 'Invalid current password':\n            setData((prevData) => ({\n              ...prevData,\n              currentPassword: '',\n            }));\n            focusCurrentPasswordField();\n\n            break;\n          default:\n        }\n      } else {\n        onClose();\n      }\n    }\n  }, [isSubmitting, wasSubmitting, error, onClose]);\n\n  useDidUpdate(() => {\n    currentPasswordFieldRef.current.focus();\n  }, [focusCurrentPasswordFieldState]);\n\n  return (\n    <>\n      <Popup.Header onBack={onBack}>\n        {t('common.editUsername', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        {message && (\n          <Message\n            {...{\n              [message.type]: true,\n            }}\n            visible\n            content={t(message.content)}\n            onDismiss={handleMessageDismiss}\n          />\n        )}\n        <Form onSubmit={handleSubmit}>\n          <div className={styles.text}>{t('common.newUsername')}</div>\n          <Input\n            fluid\n            ref={handleUsernameFieldRef}\n            name=\"username\"\n            value={data.username}\n            placeholder={username}\n            maxLength={32}\n            className={styles.field}\n            onChange={handleFieldChange}\n          />\n          {withPasswordConfirmation && (\n            <>\n              <div className={styles.text}>{t('common.currentPassword')}</div>\n              <Input.Password\n                fluid\n                ref={handleCurrentPasswordFieldRef}\n                name=\"currentPassword\"\n                value={data.currentPassword}\n                maxLength={256}\n                className={styles.field}\n                onChange={handleFieldChange}\n              />\n            </>\n          )}\n          <Button\n            positive\n            content={t('action.save')}\n            loading={isSubmitting}\n            disabled={isSubmitting}\n          />\n        </Form>\n      </Popup.Content>\n    </>\n  );\n});\n\nEditUserUsernameStep.propTypes = {\n  id: PropTypes.string.isRequired,\n  onBack: PropTypes.func,\n  onClose: PropTypes.func.isRequired,\n};\n\nEditUserUsernameStep.defaultProps = {\n  onBack: undefined,\n};\n\nexport default EditUserUsernameStep;\n"
  },
  {
    "path": "client/src/components/users/EditUserUsernameStep/EditUserUsernameStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/users/EditUserUsernameStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EditUserUsernameStep from './EditUserUsernameStep';\n\nexport default EditUserUsernameStep;\n"
  },
  {
    "path": "client/src/components/users/UserActionsStep/UserActionsStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Icon, Menu } from 'semantic-ui-react';\nimport { Popup } from '../../../lib/custom-ui';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { UserRoles } from '../../../constants/Enums';\n\nimport styles from './UserActionsStep.module.scss';\n\nconst UserActionsStep = React.memo(({ onClose }) => {\n  const isLogouting = useSelector(selectors.selectIsLogouting);\n\n  const customerPanelUrl = useSelector(\n    (state) => selectors.selectBootstrap(state).customerPanelUrl,\n  );\n\n  const withAdministration = useSelector(\n    (state) => selectors.selectCurrentUser(state).role === UserRoles.ADMIN,\n  );\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleSettingsClick = useCallback(() => {\n    dispatch(entryActions.openUserSettingsModal());\n    onClose();\n  }, [onClose, dispatch]);\n\n  const handleAdministrationClick = useCallback(() => {\n    dispatch(entryActions.openAdministrationModal());\n    onClose();\n  }, [onClose, dispatch]);\n\n  const handleAboutClick = useCallback(() => {\n    dispatch(entryActions.openAboutModal());\n    onClose();\n  }, [onClose, dispatch]);\n\n  const handleLogoutClick = useCallback(() => {\n    dispatch(entryActions.logout());\n  }, [dispatch]);\n\n  let logoutMenuItemProps;\n  if (isLogouting) {\n    logoutMenuItemProps = {\n      as: Button,\n      fluid: true,\n      basic: true,\n      loading: true,\n      disabled: true,\n    };\n  }\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.userActions', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <Menu secondary vertical className={styles.menu}>\n          <Menu.Item className={styles.menuItem} onClick={handleSettingsClick}>\n            <Icon name=\"user circle\" className={styles.menuItemIcon} />\n            {t('common.settings', {\n              context: 'title',\n            })}\n          </Menu.Item>\n          {withAdministration && (\n            <>\n              <Menu.Item className={styles.menuItem} onClick={handleAdministrationClick}>\n                <Icon name=\"setting\" className={styles.menuItemIcon} />\n                {t('common.administration', {\n                  context: 'title',\n                })}\n              </Menu.Item>\n              {customerPanelUrl && (\n                <Menu.Item\n                  href={customerPanelUrl}\n                  target=\"_blank\"\n                  rel=\"noreferrer\"\n                  className={styles.menuItem}\n                >\n                  <Icon name=\"shield alternate\" className={styles.menuItemIcon} />\n                  {t('common.customerPanel', {\n                    context: 'title',\n                  })}\n                </Menu.Item>\n              )}\n            </>\n          )}\n          <Menu.Item className={styles.menuItem} onClick={handleAboutClick}>\n            <Icon name=\"info circle\" className={styles.menuItemIcon} />\n            {t('common.aboutApp', {\n              context: 'title',\n            })}\n          </Menu.Item>\n          <hr className={styles.divider} />\n          <Menu.Item\n            {...logoutMenuItemProps} // eslint-disable-line react/jsx-props-no-spreading\n            className={styles.menuItem}\n            onClick={handleLogoutClick}\n          >\n            <Icon name=\"log out\" className={styles.menuItemIcon} />\n            {t('action.logOut', {\n              context: 'title',\n            })}\n          </Menu.Item>\n        </Menu>\n      </Popup.Content>\n    </>\n  );\n});\n\nUserActionsStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default UserActionsStep;\n"
  },
  {
    "path": "client/src/components/users/UserActionsStep/UserActionsStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .divider {\n    background: #eee;\n    border: 0;\n    height: 1px;\n    margin-bottom: 8px;\n  }\n\n  .menu {\n    margin: 0 -12px -5px;\n    width: calc(100% + 24px);\n  }\n\n  .menuItem {\n    margin: 0;\n    padding-left: 14px;\n  }\n\n  .menuItemIcon {\n    float: left;\n    margin: 0 0.5em 0 0;\n  }\n}\n"
  },
  {
    "path": "client/src/components/users/UserActionsStep/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport UserActionsStep from './UserActionsStep';\n\nexport default UserActionsStep;\n"
  },
  {
    "path": "client/src/components/users/UserAvatar/UserAvatar.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport upperFirst from 'lodash/upperFirst';\nimport camelCase from 'lodash/camelCase';\nimport initials from 'initials';\nimport React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport classNames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\n\nimport selectors from '../../../selectors';\nimport { isUserStatic } from '../../../utils/record-helpers';\n\nimport styles from './UserAvatar.module.scss';\n\nconst Sizes = {\n  TINY: 'tiny',\n  SMALL: 'small',\n  MEDIUM: 'medium',\n  LARGE: 'large',\n  MASSIVE: 'massive',\n};\n\nconst COLORS = [\n  'emerald',\n  'peter-river',\n  'wisteria',\n  'carrot',\n  'alizarin',\n  'turquoise',\n  'midnight-blue',\n];\n\nconst getColor = (name) => {\n  let sum = 0;\n  for (let i = 0; i < name.length; i += 1) {\n    sum += name.charCodeAt(i);\n  }\n\n  return COLORS[sum % COLORS.length];\n};\n\nconst UserAvatar = React.memo(\n  ({ id, size, isDisabled, withCreatorIndicator, className, onClick }) => {\n    const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);\n\n    const user = useSelector((state) => selectUserById(state, id));\n\n    const [t] = useTranslation();\n\n    let avatarUrl = null;\n    if (user.avatar) {\n      avatarUrl = user.avatar.thumbnailUrls.cover180;\n    } else if (user.gravatarUrl) {\n      avatarUrl = user.gravatarUrl;\n    }\n\n    const contentNode = (\n      <span\n        title={\n          isUserStatic(user)\n            ? t(`common.${user.name}`, {\n                context: 'title',\n              })\n            : user.name\n        }\n        className={classNames(\n          styles.wrapper,\n          styles[`wrapper${upperFirst(size)}`],\n          onClick && styles.wrapperHoverable,\n          !avatarUrl && styles[`background${upperFirst(camelCase(getColor(user.name)))}`],\n        )}\n        style={{\n          background: avatarUrl && `url(\"${avatarUrl}\") center / cover`,\n        }}\n      >\n        {!avatarUrl && <span className={styles.initials}>{initials(user.name).slice(0, 2)}</span>}\n        {withCreatorIndicator && <span className={styles.creatorIndicator}>+</span>}\n      </span>\n    );\n\n    return onClick ? (\n      <button\n        data-id={id}\n        type=\"button\"\n        disabled={isDisabled}\n        className={classNames(styles.button, className)}\n        onClick={onClick}\n      >\n        {contentNode}\n      </button>\n    ) : (\n      <span className={className}>{contentNode}</span>\n    );\n  },\n);\n\nUserAvatar.propTypes = {\n  id: PropTypes.string,\n  size: PropTypes.oneOf(Object.values(Sizes)),\n  isDisabled: PropTypes.bool,\n  withCreatorIndicator: PropTypes.bool,\n  className: PropTypes.string,\n  onClick: PropTypes.func,\n};\n\nUserAvatar.defaultProps = {\n  id: undefined,\n  size: Sizes.MEDIUM,\n  isDisabled: false,\n  withCreatorIndicator: false,\n  className: undefined,\n  onClick: undefined,\n};\n\nexport default UserAvatar;\n"
  },
  {
    "path": "client/src/components/users/UserAvatar/UserAvatar.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .button {\n    background: transparent;\n    border: none;\n    cursor: pointer;\n    display: inline-block;\n    outline: none;\n    padding: 0;\n  }\n\n  // TODO: provide for different sizes\n  .creatorIndicator {\n    background: #fff;\n    border-radius: 50%;\n    bottom: 0;\n    color: #6a808b;\n    font-size: 12px;\n    height: 10px;\n    line-height: 10px;\n    position: absolute;\n    right: 0;\n    width: 10px;\n  }\n\n  .initials {\n    margin: 0 auto;\n    text-transform: capitalize;\n  }\n\n  .wrapper {\n    border-radius: 50%;\n    color: #fff;\n    display: inline-block;\n    line-height: 1;\n    position: relative;\n    text-align: center;\n    vertical-align: top;\n  }\n\n  .wrapperHoverable:hover {\n    opacity: 0.75;\n  }\n\n  /* Sizes */\n\n  .wrapperTiny {\n    font-size: 10px;\n    height: 24px;\n    line-height: 20px;\n    padding: 2px 0;\n    width: 24px;\n  }\n\n  .wrapperSmall {\n    font-size: 12px;\n    height: 28px;\n    padding: 8px 0;\n    width: 28px;\n  }\n\n  .wrapperMedium {\n    font-size: 14px;\n    height: 32px;\n    padding: 10px 0;\n    width: 32px;\n  }\n\n  .wrapperLarge {\n    font-size: 14px;\n    height: 36px;\n    padding: 12px 0 10px;\n    width: 36px;\n  }\n\n  .wrapperMassive {\n    font-size: 36px;\n    height: 100px;\n    padding: 32px 0 10px;\n    width: 100px;\n  }\n\n  /* Backgrounds */\n\n  .backgroundEmerald {\n    background: #2ecc71;\n  }\n\n  .backgroundPeterRiver {\n    background: #3498db;\n  }\n\n  .backgroundWisteria {\n    background: #8e44ad;\n  }\n\n  .backgroundCarrot {\n    background: #e67e22;\n  }\n\n  .backgroundAlizarin {\n    background: #e74c3c;\n  }\n\n  .backgroundTurquoise {\n    background: #1abc9c;\n  }\n\n  .backgroundMidnightBlue {\n    background: #2c3e50;\n  }\n}\n"
  },
  {
    "path": "client/src/components/users/UserAvatar/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport UserAvatar from './UserAvatar';\n\nexport default UserAvatar;\n"
  },
  {
    "path": "client/src/components/users/UserSettingsModal/AccountPane/AccountPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button, Divider, Dropdown, Header, Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\nimport { usePopupInClosableContext } from '../../../../hooks';\nimport locales from '../../../../locales';\nimport EditAvatarStep from './EditAvatarStep';\nimport EditUserInformation from '../../EditUserInformation';\nimport EditUserUsernameStep from '../../EditUserUsernameStep';\nimport EditUserEmailStep from '../../EditUserEmailStep';\nimport EditUserPasswordStep from '../../EditUserPasswordStep';\nimport UserAvatar from '../../UserAvatar';\n\nimport styles from './AccountPane.module.scss';\n\nconst AccountPane = React.memo(() => {\n  const user = useSelector(selectors.selectCurrentUser);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleLanguageChange = useCallback(\n    (_, { value }) => {\n      dispatch(entryActions.updateCurrentUserLanguage(value));\n    },\n    [dispatch],\n  );\n\n  const EditAvatarPopup = usePopupInClosableContext(EditAvatarStep);\n  const EditUserUsernamePopup = usePopupInClosableContext(EditUserUsernameStep);\n  const EditUserEmailPopup = usePopupInClosableContext(EditUserEmailStep);\n  const EditUserPasswordPopup = usePopupInClosableContext(EditUserPasswordStep);\n\n  const isUsernameEditable = !user.lockedFieldNames.includes('username');\n  const isEmailEditable = !user.lockedFieldNames.includes('email');\n  const isPasswordEditable = !user.lockedFieldNames.includes('password');\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <EditAvatarPopup>\n        <UserAvatar id={user.id} size=\"massive\" isDisabled={user.isAvatarUpdating} />\n      </EditAvatarPopup>\n      <br />\n      <br />\n      <EditUserInformation id={user.id} />\n      <Divider horizontal section>\n        <Header as=\"h4\">\n          {t('common.language', {\n            context: 'title',\n          })}\n        </Header>\n      </Divider>\n      <Dropdown\n        fluid\n        selection\n        options={locales.map((locale) => ({\n          value: locale.language,\n          flag: locale.country,\n          text: locale.name,\n        }))}\n        value={user.language}\n        onChange={handleLanguageChange}\n      />\n      {(isUsernameEditable || isEmailEditable || isPasswordEditable) && (\n        <>\n          <Divider horizontal section>\n            <Header as=\"h4\">\n              {t('common.authentication', {\n                context: 'title',\n              })}\n            </Header>\n          </Divider>\n          {isUsernameEditable && (\n            <div className={styles.action}>\n              <EditUserUsernamePopup id={user.id}>\n                <Button className={styles.actionButton}>\n                  {t('action.editUsername', {\n                    context: 'title',\n                  })}\n                </Button>\n              </EditUserUsernamePopup>\n            </div>\n          )}\n          {isEmailEditable && (\n            <div className={styles.action}>\n              <EditUserEmailPopup id={user.id}>\n                <Button className={styles.actionButton}>\n                  {t('action.editEmail', {\n                    context: 'title',\n                  })}\n                </Button>\n              </EditUserEmailPopup>\n            </div>\n          )}\n          {isPasswordEditable && (\n            <div className={styles.action}>\n              <EditUserPasswordPopup id={user.id}>\n                <Button className={styles.actionButton}>\n                  {t('action.editPassword', {\n                    context: 'title',\n                  })}\n                </Button>\n              </EditUserPasswordPopup>\n            </div>\n          )}\n        </>\n      )}\n    </Tab.Pane>\n  );\n});\n\nexport default AccountPane;\n"
  },
  {
    "path": "client/src/components/users/UserSettingsModal/AccountPane/AccountPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .action {\n    border: none;\n    border-radius: 0.28571429rem;\n    display: inline-block;\n    height: 36px;\n    overflow: hidden;\n    position: relative;\n    transition: background 0.3s ease;\n    width: 100%;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .actionButton {\n    background: transparent;\n    color: #6b808c;\n    font-weight: normal;\n    height: 36px;\n    line-height: 24px;\n    padding: 6px 12px;\n    text-align: left;\n    text-decoration: underline;\n    width: 100%;\n  }\n\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/users/UserSettingsModal/AccountPane/EditAvatarStep.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Button } from 'semantic-ui-react';\nimport { FilePicker, Popup } from '../../../../lib/custom-ui';\n\nimport selectors from '../../../../selectors';\nimport entryActions from '../../../../entry-actions';\n\nimport styles from './EditAvatarStep.module.scss';\n\nconst EditAvatarStep = React.memo(({ onClose }) => {\n  const defaultValue = useSelector((state) => selectors.selectCurrentUser(state).avatar);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const fieldRef = useRef(null);\n\n  const handleFileSelect = useCallback(\n    (file) => {\n      dispatch(\n        entryActions.updateCurrentUserAvatar({\n          file,\n        }),\n      );\n\n      onClose();\n    },\n    [onClose, dispatch],\n  );\n\n  const handleDeleteClick = useCallback(() => {\n    dispatch(\n      entryActions.updateCurrentUser({\n        avatar: null,\n      }),\n    );\n\n    onClose();\n  }, [onClose, dispatch]);\n\n  useEffect(() => {\n    fieldRef.current.focus();\n  }, []);\n\n  return (\n    <>\n      <Popup.Header>\n        {t('common.editAvatar', {\n          context: 'title',\n        })}\n      </Popup.Header>\n      <Popup.Content>\n        <div className={styles.action}>\n          <FilePicker accept=\"image/*\" onSelect={handleFileSelect}>\n            <Button\n              ref={fieldRef}\n              content={t('action.uploadNewAvatar')}\n              className={styles.actionButton}\n            />\n          </FilePicker>\n        </div>\n        {defaultValue && (\n          <Button negative content={t('action.deleteAvatar')} onClick={handleDeleteClick} />\n        )}\n      </Popup.Content>\n    </>\n  );\n});\n\nEditAvatarStep.propTypes = {\n  onClose: PropTypes.func.isRequired,\n};\n\nexport default EditAvatarStep;\n"
  },
  {
    "path": "client/src/components/users/UserSettingsModal/AccountPane/EditAvatarStep.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .action {\n    border: none;\n    border-radius: 0.28571429rem;\n    display: inline-block;\n    height: 36px;\n    overflow: hidden;\n    position: relative;\n    transition: background 0.3s ease;\n    width: 100%;\n\n    &:hover {\n      background: #e9e9e9;\n    }\n  }\n\n  .actionButton {\n    background: transparent;\n    color: #6b808c;\n    font-weight: normal;\n    height: 36px;\n    line-height: 24px;\n    padding: 6px 12px;\n    text-align: left;\n    text-decoration: underline;\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "client/src/components/users/UserSettingsModal/AccountPane/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport AccountPane from './AccountPane';\n\nexport default AccountPane;\n"
  },
  {
    "path": "client/src/components/users/UserSettingsModal/NotificationsPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport NotificationServices from '../../notification-services/NotificationServices';\n\nimport styles from './NotificationsPane.module.scss';\n\nconst NotificationsPane = React.memo(() => {\n  const notificationServiceIds = useSelector(selectors.selectNotificationServiceIdsForCurrentUser);\n\n  const dispatch = useDispatch();\n\n  const handleCreate = useCallback(\n    (data) => {\n      dispatch(entryActions.createNotificationServiceInCurrentUser(data));\n    },\n    [dispatch],\n  );\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <NotificationServices ids={notificationServiceIds} onCreate={handleCreate} />\n    </Tab.Pane>\n  );\n});\n\nexport default NotificationsPane;\n"
  },
  {
    "path": "client/src/components/users/UserSettingsModal/NotificationsPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/users/UserSettingsModal/PreferencesPane.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Radio, Tab } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\n\nimport styles from './PreferencesPane.module.scss';\n\nconst PreferencesPane = React.memo(() => {\n  const user = useSelector(selectors.selectCurrentUser);\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleChange = useCallback(\n    (_, { name: fieldName, checked }) => {\n      dispatch(\n        entryActions.updateCurrentUser({\n          [fieldName]: checked,\n        }),\n      );\n    },\n    [dispatch],\n  );\n\n  return (\n    <Tab.Pane attached={false} className={styles.wrapper}>\n      <Radio\n        toggle\n        name=\"subscribeToOwnCards\"\n        checked={user.subscribeToOwnCards}\n        label={t('common.subscribeToMyOwnCardsByDefault')}\n        className={styles.radio}\n        onChange={handleChange}\n      />\n      <Radio\n        toggle\n        name=\"subscribeToCardWhenCommenting\"\n        checked={user.subscribeToCardWhenCommenting}\n        label={t('common.subscribeToCardWhenCommenting')}\n        className={styles.radio}\n        onChange={handleChange}\n      />\n      <Radio\n        toggle\n        name=\"turnOffRecentCardHighlighting\"\n        checked={user.turnOffRecentCardHighlighting}\n        label={t('common.turnOffRecentCardHighlighting')}\n        className={styles.radio}\n        onChange={handleChange}\n      />\n    </Tab.Pane>\n  );\n});\n\nexport default PreferencesPane;\n"
  },
  {
    "path": "client/src/components/users/UserSettingsModal/PreferencesPane.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .radio {\n    margin-bottom: 16px;\n    width: 100%;\n\n    &:last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  .wrapper {\n    border: none;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "client/src/components/users/UserSettingsModal/UserSettingsModal.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback } from 'react';\nimport { useDispatch } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Tab } from 'semantic-ui-react';\n\nimport entryActions from '../../../entry-actions';\nimport { useClosableModal } from '../../../hooks';\nimport AccountPane from './AccountPane';\nimport PreferencesPane from './PreferencesPane';\nimport NotificationsPane from './NotificationsPane';\n\nconst UserSettingsModal = React.memo(() => {\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n\n  const handleClose = useCallback(() => {\n    dispatch(entryActions.closeModal());\n  }, [dispatch]);\n\n  const [ClosableModal] = useClosableModal();\n\n  const panes = [\n    {\n      menuItem: t('common.account', {\n        context: 'title',\n      }),\n      render: () => <AccountPane />,\n    },\n    {\n      menuItem: t('common.preferences', {\n        context: 'title',\n      }),\n      render: () => <PreferencesPane />,\n    },\n    {\n      menuItem: t('common.notifications', {\n        context: 'title',\n      }),\n      render: () => <NotificationsPane />,\n    },\n  ];\n\n  return (\n    <ClosableModal open closeIcon size=\"small\" centered={false} onClose={handleClose}>\n      <ClosableModal.Content>\n        <Tab\n          menu={{\n            secondary: true,\n            pointing: true,\n          }}\n          panes={panes}\n        />\n      </ClosableModal.Content>\n    </ClosableModal>\n  );\n});\n\nexport default UserSettingsModal;\n"
  },
  {
    "path": "client/src/components/users/UserSettingsModal/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport UserSettingsModal from './UserSettingsModal';\n\nexport default UserSettingsModal;\n"
  },
  {
    "path": "client/src/components/webhooks/Webhooks/Editor.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useImperativeHandle } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Dropdown, Input } from 'semantic-ui-react';\n\nimport { useNestedRef } from '../../../hooks';\nimport WEBHOOK_EVENTS from '../../../constants/WebhookEvents';\n\nimport styles from './Editor.module.scss';\n\nconst Editor = React.forwardRef(({ data, isReadOnly, onFieldChange }, ref) => {\n  const [t] = useTranslation();\n\n  const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');\n  const [urlFieldRef, handleUrlFieldRef] = useNestedRef('inputRef');\n\n  const focusNameField = useCallback(() => {\n    nameFieldRef.current.focus({\n      preventScroll: true,\n    });\n  }, [nameFieldRef]);\n\n  const selectNameField = useCallback(() => {\n    nameFieldRef.current.select();\n  }, [nameFieldRef]);\n\n  const selectUrlField = useCallback(() => {\n    urlFieldRef.current.select();\n  }, [urlFieldRef]);\n\n  useImperativeHandle(\n    ref,\n    () => ({\n      focusNameField,\n      selectNameField,\n      selectUrlField,\n    }),\n    [focusNameField, selectNameField, selectUrlField],\n  );\n\n  return (\n    <>\n      <div className={styles.text}>{t('common.title')}</div>\n      <Input\n        fluid\n        ref={handleNameFieldRef}\n        name=\"name\"\n        value={data.name}\n        maxLength={128}\n        readOnly={isReadOnly}\n        className={styles.field}\n        onChange={onFieldChange}\n      />\n      <div className={styles.text}>{t('common.url')}</div>\n      <Input\n        fluid\n        ref={handleUrlFieldRef}\n        name=\"url\"\n        value={data.url}\n        maxLength={2048}\n        readOnly={isReadOnly}\n        className={styles.field}\n        onChange={onFieldChange}\n      />\n      <div className={styles.text}>\n        {t('common.accessToken')} (\n        {t('common.optional', {\n          context: 'inline',\n        })}\n        )\n      </div>\n      <Input\n        fluid\n        name=\"accessToken\"\n        value={data.accessToken}\n        maxLength={512}\n        readOnly={isReadOnly}\n        className={styles.field}\n        onChange={onFieldChange}\n      />\n      {data.excludedEvents.length === 0 && (\n        <>\n          <div className={styles.text}>\n            {t('common.events')} (\n            {t('common.optional', {\n              context: 'inline',\n            })}\n            )\n          </div>\n          <Dropdown\n            selection\n            multiple\n            fluid\n            name=\"events\"\n            options={WEBHOOK_EVENTS.map((event) => ({\n              text: event,\n              value: event,\n            }))}\n            value={data.events}\n            placeholder=\"All\"\n            readOnly={isReadOnly}\n            className={styles.field}\n            onChange={onFieldChange}\n          />\n        </>\n      )}\n      {data.events.length === 0 && (\n        <>\n          <div className={styles.text}>\n            {t('common.excludedEvents')} (\n            {t('common.optional', {\n              context: 'inline',\n            })}\n            )\n          </div>\n          <Dropdown\n            selection\n            multiple\n            fluid\n            name=\"excludedEvents\"\n            options={WEBHOOK_EVENTS.map((event) => ({\n              text: event,\n              value: event,\n            }))}\n            value={data.excludedEvents}\n            placeholder=\"None\"\n            readOnly={isReadOnly}\n            className={styles.field}\n            onChange={onFieldChange}\n          />\n        </>\n      )}\n    </>\n  );\n});\n\nEditor.propTypes = {\n  data: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  isReadOnly: PropTypes.bool,\n  onFieldChange: PropTypes.func.isRequired,\n};\n\nEditor.defaultProps = {\n  isReadOnly: false,\n};\n\nexport default React.memo(Editor);\n"
  },
  {
    "path": "client/src/components/webhooks/Webhooks/Editor.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .field {\n    margin-bottom: 8px;\n  }\n\n  .text {\n    color: #444444;\n    font-size: 12px;\n    font-weight: bold;\n    padding-bottom: 6px;\n  }\n}\n"
  },
  {
    "path": "client/src/components/webhooks/Webhooks/Item.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { dequal } from 'dequal';\nimport React, { useCallback, useMemo, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useTranslation } from 'react-i18next';\nimport { Accordion, Button, Form, Icon } from 'semantic-ui-react';\n\nimport selectors from '../../../selectors';\nimport entryActions from '../../../entry-actions';\nimport { useForm, usePopupInClosableContext } from '../../../hooks';\nimport { isUrl } from '../../../utils/validator';\nimport Editor from './Editor';\nimport ConfirmationStep from '../../common/ConfirmationStep';\n\nimport styles from './Item.module.scss';\nimport { useToggle } from '../../../lib/hooks';\n\nconst Item = React.memo(({ id }) => {\n  const selectWebhookById = useMemo(() => selectors.makeSelectWebhookById(), []);\n\n  const webhook = useSelector((state) => selectWebhookById(state, id));\n\n  const dispatch = useDispatch();\n  const [t] = useTranslation();\n  const [isOpened, toggleOpened] = useToggle();\n\n  const defaultData = useMemo(\n    () => ({\n      name: webhook.name,\n      url: webhook.url,\n      accessToken: webhook.accessToken,\n      events: webhook.events,\n      excludedEvents: webhook.excludedEvents,\n    }),\n    [webhook],\n  );\n\n  const [data, handleFieldChange] = useForm(() => ({\n    name: '',\n    url: '',\n    ...defaultData,\n    accessToken: defaultData.accessToken || '',\n    events: defaultData.events || [],\n    excludedEvents: defaultData.excludedEvents || [],\n  }));\n\n  const cleanData = useMemo(\n    () => ({\n      ...data,\n      name: data.name.trim(),\n      url: data.url.trim(),\n      accessToken: data.accessToken.trim() || null,\n      events: data.events.length === 0 ? null : data.events,\n      excludedEvents: data.excludedEvents.length === 0 ? null : data.excludedEvents,\n    }),\n    [data],\n  );\n\n  const editorRef = useRef(null);\n\n  const handleDeleteConfirm = useCallback(() => {\n    dispatch(entryActions.deleteWebhook(id));\n  }, [id, dispatch]);\n\n  const handleSubmit = useCallback(() => {\n    if (!cleanData.name) {\n      editorRef.current.selectNameField();\n      return;\n    }\n\n    if (!cleanData.url || !isUrl(cleanData.url)) {\n      editorRef.current.selectUrlField();\n      return;\n    }\n\n    dispatch(entryActions.updateWebhook(id, cleanData));\n  }, [id, dispatch, cleanData]);\n\n  const handleOpenClick = useCallback(() => {\n    toggleOpened();\n  }, [toggleOpened]);\n\n  const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);\n\n  return (\n    <>\n      <Accordion.Title active={isOpened} className={styles.title} onClick={handleOpenClick}>\n        <Icon name=\"dropdown\" />\n        {defaultData.name}\n      </Accordion.Title>\n      <Accordion.Content active={isOpened}>\n        <div>\n          <Form onSubmit={handleSubmit}>\n            <Editor\n              ref={editorRef}\n              data={data}\n              isReadOnly={!webhook.isPersisted}\n              onFieldChange={handleFieldChange}\n            />\n            <div className={styles.controls}>\n              <Button\n                positive\n                disabled={dequal(cleanData, defaultData)}\n                content={t('action.save')}\n              />\n              <ConfirmationPopup\n                title=\"common.deleteWebhook\"\n                content=\"common.areYouSureYouWantToDeleteThisWebhook\"\n                buttonContent=\"action.deleteWebhook\"\n                onConfirm={handleDeleteConfirm}\n              >\n                <Button\n                  type=\"button\"\n                  disabled={!webhook.isPersisted}\n                  className={styles.deleteButton}\n                >\n                  {t('action.delete')}\n                </Button>\n              </ConfirmationPopup>\n            </div>\n          </Form>\n        </div>\n      </Accordion.Content>\n    </>\n  );\n});\n\nItem.propTypes = {\n  id: PropTypes.string.isRequired,\n};\n\nexport default Item;\n"
  },
  {
    "path": "client/src/components/webhooks/Webhooks/Item.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global(#app) {\n  .controls {\n    display: flex;\n    justify-content: space-between;\n  }\n\n  .deleteButton {\n    box-shadow: 0 1px 0 #cbcccc;\n    margin-right: 0;\n  }\n\n  .title {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n}\n"
  },
  {
    "path": "client/src/components/webhooks/Webhooks/Webhooks.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useEffect, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { useTranslation } from 'react-i18next';\nimport { Accordion, Button, Form, Segment } from 'semantic-ui-react';\nimport { useDidUpdate, useToggle } from '../../../lib/hooks';\n\nimport { useForm } from '../../../hooks';\nimport { isUrl } from '../../../utils/validator';\nimport Item from './Item';\nimport Editor from './Editor';\n\nconst DEFAULT_DATA = {\n  name: '',\n  url: '',\n  accessToken: '',\n  events: [],\n  excludedEvents: [],\n};\n\nconst Webhooks = React.memo(({ ids, onCreate }) => {\n  const [t] = useTranslation();\n\n  const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);\n  const [focusNameFieldState, focusNameField] = useToggle();\n\n  const editorRef = useRef(null);\n\n  const handleCreateSubmit = useCallback(() => {\n    const cleanData = {\n      ...data,\n      name: data.name.trim(),\n      url: data.url.trim(),\n      accessToken: data.accessToken.trim() || null,\n      events: data.events.length === 0 ? null : data.events,\n      excludedEvents: data.excludedEvents.length === 0 ? null : data.excludedEvents,\n    };\n\n    if (!cleanData.name) {\n      editorRef.current.selectNameField();\n      return;\n    }\n\n    if (!cleanData.url || !isUrl(cleanData.url)) {\n      editorRef.current.selectUrlField();\n      return;\n    }\n\n    onCreate(cleanData);\n    setData(DEFAULT_DATA);\n    focusNameField();\n  }, [onCreate, data, setData, focusNameField]);\n\n  useEffect(() => {\n    if (editorRef.current) {\n      editorRef.current.focusNameField();\n    }\n  }, []);\n\n  useDidUpdate(() => {\n    if (editorRef.current) {\n      editorRef.current.focusNameField();\n    }\n  }, [focusNameFieldState]);\n\n  return (\n    <>\n      {ids.length > 0 && (\n        <Accordion styled fluid>\n          {ids.map((id) => (\n            <Item key={id} id={id} />\n          ))}\n        </Accordion>\n      )}\n      {ids.length < 10 && (\n        <Segment>\n          <Form onSubmit={handleCreateSubmit}>\n            <Editor ref={editorRef} data={data} onFieldChange={handleFieldChange} />\n            <Button positive>{t('action.addWebhook')}</Button>\n          </Form>\n        </Segment>\n      )}\n    </>\n  );\n});\n\nWebhooks.propTypes = {\n  ids: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types\n  onCreate: PropTypes.func.isRequired,\n};\n\nexport default Webhooks;\n"
  },
  {
    "path": "client/src/components/webhooks/Webhooks/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Webhooks from './Webhooks';\n\nexport default Webhooks;\n"
  },
  {
    "path": "client/src/configs/markdown-plugins/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport sup from '@diplodoc/transform/lib/plugins/sup';\nimport monospace from '@diplodoc/transform/lib/plugins/monospace';\nimport code from '@diplodoc/transform/lib/plugins/code';\nimport imsize from '@diplodoc/transform/lib/plugins/imsize';\n// import video from '@diplodoc/transform/lib/plugins/video';\nimport table from '@diplodoc/transform/lib/plugins/table';\nimport note from '@diplodoc/transform/lib/plugins/notes';\nimport cut from '@diplodoc/transform/lib/plugins/cut';\n// import meta from '@diplodoc/transform/lib/plugins/meta';\nimport deflist from '@diplodoc/transform/lib/plugins/deflist';\n/* eslint-disable import/no-unresolved */\nimport ins from '@gravity-ui/markdown-editor/markdown-it/ins';\nimport mark from '@gravity-ui/markdown-editor/markdown-it/mark';\nimport sub from '@gravity-ui/markdown-editor/markdown-it/sub';\nimport emoji from '@gravity-ui/markdown-editor/markdown-it/emoji';\nimport color from '@gravity-ui/markdown-editor/markdown-it/color';\nimport { emojiDefs } from '@gravity-ui/markdown-editor/_/bundle/emoji';\n/* eslint-enable import/no-unresolved */\n\nimport link from './link';\nimport mention from './mention';\n\nexport default [\n  ins,\n  mark,\n  sub,\n  (md) => md.use(emoji, { defs: emojiDefs }),\n  color,\n  sup,\n  monospace,\n  code,\n  (md) => md.use(imsize, { enableInlineStyling: true }),\n  // video,\n  table,\n  (md) => md.use(note, { notesAutotitle: false, log: console }),\n  cut,\n  // meta,\n  deflist,\n  link,\n  mention,\n];\n"
  },
  {
    "path": "client/src/configs/markdown-plugins/link.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport history from '../../history';\nimport Config from '../../constants/Config';\n\nconst SAME_SITE_CLASS = 'same-site';\n\ndocument.addEventListener('click', (event) => {\n  const element = event.target.closest(`a.${SAME_SITE_CLASS}`);\n\n  if (element) {\n    event.preventDefault();\n    history.push(element.href);\n  }\n});\n\nfunction process(token, nextToken) {\n  const href = token.attrGet('href');\n\n  if (!href) {\n    return;\n  }\n\n  let url;\n  try {\n    url = new URL(href, window.location);\n  } catch {\n    return;\n  }\n\n  const isSameSite =\n    url.origin === window.location.origin && url.pathname.startsWith(Config.BASE_PATH);\n\n  const trimOrigin = isSameSite && nextToken.type === 'text' && nextToken.content === href;\n\n  if (isSameSite) {\n    token.attrSet('class', SAME_SITE_CLASS);\n  } else {\n    token.attrSet('target', '_blank');\n    token.attrSet('rel', 'noreferrer');\n  }\n\n  if (trimOrigin) {\n    nextToken.content = url.pathname; // eslint-disable-line no-param-reassign\n  }\n}\n\nexport default (md) => {\n  const plugin = ({ tokens }) => {\n    tokens.forEach((token) => {\n      if (!token.children) {\n        return;\n      }\n\n      token.children.forEach((currentToken, index) => {\n        if (currentToken.type === 'link_open') {\n          process(currentToken, token.children[index + 1]);\n        }\n      });\n    });\n  };\n\n  try {\n    md.core.ruler.before('includes', 'link', plugin);\n  } catch (error) {\n    md.core.ruler.push('link', plugin);\n  }\n};\n"
  },
  {
    "path": "client/src/configs/markdown-plugins/mention.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport default (md) => {\n  md.core.ruler.push('mention', ({ tokens }) => {\n    tokens.forEach((token) => {\n      if (!token.children) {\n        return;\n      }\n\n      for (let i = 0; i < token.children.length - 3; i += 1) {\n        const currentToken = token.children[i];\n        const linkOpenToken = token.children[i + 1];\n        const textToken = token.children[i + 2];\n        const linkCloseToken = token.children[i + 3];\n\n        if (\n          currentToken.type === 'text' &&\n          currentToken.content.endsWith('@') &&\n          linkOpenToken.type === 'link_open' &&\n          textToken.type === 'text' &&\n          linkCloseToken.type === 'link_close'\n        ) {\n          const userId = linkOpenToken.attrGet('href');\n          const { content: name } = textToken;\n\n          if (currentToken.content.length === 1) {\n            token.children.splice(i, 1);\n            i -= 1;\n          } else {\n            currentToken.content = currentToken.content.slice(0, -1);\n          }\n\n          const mentionToken = {\n            ...currentToken,\n            type: 'mention',\n            meta: {\n              userId,\n              name,\n            },\n          };\n\n          token.children.splice(i + 1, 3, mentionToken);\n          i += 1;\n        }\n      }\n    });\n  });\n\n  // eslint-disable-next-line no-param-reassign\n  md.renderer.rules.mention = (tokens, index) => {\n    const { userId, name } = tokens[index].meta;\n    return `<span class=\"mention\" data-user-id=\"${userId}\">@${name}</span>`;\n  };\n};\n"
  },
  {
    "path": "client/src/constants/AccessTokenSteps.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport default {\n  ACCEPT_TERMS: 'accept-terms',\n};\n"
  },
  {
    "path": "client/src/constants/ActionTypes.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport default {\n  /* Router */\n\n  LOCATION_CHANGE_HANDLE: 'LOCATION_CHANGE_HANDLE',\n  LOCATION_CHANGE_HANDLE__CONTENT_FETCH: 'LOCATION_CHANGE_HANDLE__CONTENT_FETCH',\n  LOCATION_CHANGE_HANDLE__BOARD_FETCH: 'LOCATION_CHANGE_HANDLE__BOARD_FETCH',\n\n  /* Socket */\n\n  SOCKET_DISCONNECT_HANDLE: 'SOCKET_DISCONNECT_HANDLE',\n  SOCKET_RECONNECT_HANDLE: 'SOCKET_RECONNECT_HANDLE',\n  SOCKET_RECONNECT_HANDLE__CORE_FETCH: 'SOCKET_RECONNECT_HANDLE__CORE_FETCH',\n\n  /* Bootstrap */\n\n  BOOTSTRAP_UPDATE_HANDLE: 'BOOTSTRAP_UPDATE_HANDLE',\n\n  /* Login */\n\n  LOGIN_INITIALIZE: 'LOGIN_INITIALIZE',\n  AUTHENTICATE: 'AUTHENTICATE',\n  AUTHENTICATE__SUCCESS: 'AUTHENTICATE__SUCCESS',\n  AUTHENTICATE__FAILURE: 'AUTHENTICATE__FAILURE',\n  WITH_OIDC_AUTHENTICATE: 'WITH_OIDC_AUTHENTICATE',\n  WITH_OIDC_AUTHENTICATE__SUCCESS: 'WITH_OIDC_AUTHENTICATE__SUCCESS',\n  WITH_OIDC_AUTHENTICATE__FAILURE: 'WITH_OIDC_AUTHENTICATE__FAILURE',\n  WITH_OIDC_AUTHENTICATE__DEBUG: 'WITH_OIDC_AUTHENTICATE__DEBUG',\n  AUTHENTICATE_ERROR_CLEAR: 'AUTHENTICATE_ERROR_CLEAR',\n  TERMS_ACCEPT: 'TERMS_ACCEPT',\n  TERMS_ACCEPT__SUCCESS: 'TERMS_ACCEPT__SUCCESS',\n  TERMS_ACCEPT__FAILURE: 'TERMS_ACCEPT__FAILURE',\n  TERMS_CANCEL: 'TERMS_CANCEL',\n  TERMS_CANCEL__SUCCESS: 'TERMS_CANCEL__SUCCESS',\n  TERMS_CANCEL__FAILURE: 'TERMS_CANCEL__FAILURE',\n  TERMS_LANGUAGE_UPDATE: 'TERMS_LANGUAGE_UPDATE',\n  TERMS_LANGUAGE_UPDATE__SUCCESS: 'TERMS_LANGUAGE_UPDATE__SUCCESS',\n  TERMS_LANGUAGE_UPDATE__FAILURE: 'TERMS_LANGUAGE_UPDATE__FAILURE',\n\n  /* Core */\n\n  CORE_INITIALIZE: 'CORE_INITIALIZE',\n  CORE_INITIALIZE__BOOTSTRAP_FETCH: 'CORE_INITIALIZE__BOOTSTRAP_FETCH',\n  FAVORITES_TOGGLE: 'FAVORITES_TOGGLE',\n  EDIT_MODE_TOGGLE: 'EDIT_MODE_TOGGLE',\n  HOME_VIEW_UPDATE: 'HOME_VIEW_UPDATE',\n  LOGOUT: 'LOGOUT',\n  LOGOUT__ACCESS_TOKEN_REVOKE: 'LOGOUT__ACCESS_TOKEN_REVOKE',\n\n  /* Modals */\n\n  MODAL_OPEN: 'MODAL_OPEN',\n  MODAL_CLOSE: 'MODAL_CLOSE',\n\n  /* Config */\n\n  CONFIG_UPDATE: 'CONFIG_UPDATE',\n  CONFIG_UPDATE__SUCCESS: 'CONFIG_UPDATE__SUCCESS',\n  CONFIG_UPDATE__FAILURE: 'CONFIG_UPDATE__FAILURE',\n  CONFIG_UPDATE_HANDLE: 'CONFIG_UPDATE_HANDLE',\n  SMTP_CONFIG_TEST: 'SMTP_CONFIG_TEST',\n  SMTP_CONFIG_TEST__SUCCESS: 'SMTP_CONFIG_TEST__SUCCESS',\n  SMTP_CONFIG_TEST__FAILURE: 'SMTP_CONFIG_TEST__FAILURE',\n\n  /* Webhooks */\n\n  WEBHOOK_CREATE: 'WEBHOOK_CREATE',\n  WEBHOOK_CREATE__SUCCESS: 'WEBHOOK_CREATE__SUCCESS',\n  WEBHOOK_CREATE__FAILURE: 'WEBHOOK_CREATE__FAILURE',\n  WEBHOOK_CREATE_HANDLE: 'WEBHOOK_CREATE_HANDLE',\n  WEBHOOK_UPDATE: 'WEBHOOK_UPDATE',\n  WEBHOOK_UPDATE__SUCCESS: 'WEBHOOK_UPDATE__SUCCESS',\n  WEBHOOK_UPDATE__FAILURE: 'WEBHOOK_UPDATE__FAILURE',\n  WEBHOOK_UPDATE_HANDLE: 'WEBHOOK_UPDATE_HANDLE',\n  WEBHOOK_DELETE: 'WEBHOOK_DELETE',\n  WEBHOOK_DELETE__SUCCESS: 'WEBHOOK_DELETE__SUCCESS',\n  WEBHOOK_DELETE__FAILURE: 'WEBHOOK_DELETE__FAILURE',\n  WEBHOOK_DELETE_HANDLE: 'WEBHOOK_DELETE_HANDLE',\n\n  /* Users */\n\n  USERS_RESET_HANDLE: 'USERS_RESET_HANDLE',\n  USER_CREATE: 'USER_CREATE',\n  USER_CREATE__SUCCESS: 'USER_CREATE__SUCCESS',\n  USER_CREATE__FAILURE: 'USER_CREATE__FAILURE',\n  USER_CREATE_HANDLE: 'USER_CREATE_HANDLE',\n  USER_CREATE_ERROR_CLEAR: 'USER_CREATE_ERROR_CLEAR',\n  USER_UPDATE: 'USER_UPDATE',\n  USER_UPDATE__SUCCESS: 'USER_UPDATE__SUCCESS',\n  USER_UPDATE__FAILURE: 'USER_UPDATE__FAILURE',\n  USER_UPDATE_HANDLE: 'USER_UPDATE_HANDLE',\n  USER_EMAIL_UPDATE: 'USER_EMAIL_UPDATE',\n  USER_EMAIL_UPDATE__SUCCESS: 'USER_EMAIL_UPDATE__SUCCESS',\n  USER_EMAIL_UPDATE__FAILURE: 'USER_EMAIL_UPDATE__FAILURE',\n  USER_EMAIL_UPDATE_ERROR_CLEAR: 'USER_EMAIL_UPDATE_ERROR_CLEAR',\n  USER_PASSWORD_UPDATE: 'USER_PASSWORD_UPDATE',\n  USER_PASSWORD_UPDATE__SUCCESS: 'USER_PASSWORD_UPDATE__SUCCESS',\n  USER_PASSWORD_UPDATE__FAILURE: 'USER_PASSWORD_UPDATE__FAILURE',\n  USER_PASSWORD_UPDATE_ERROR_CLEAR: 'USER_PASSWORD_UPDATE_ERROR_CLEAR',\n  USER_USERNAME_UPDATE: 'USER_USERNAME_UPDATE',\n  USER_USERNAME_UPDATE__SUCCESS: 'USER_USERNAME_UPDATE__SUCCESS',\n  USER_USERNAME_UPDATE__FAILURE: 'USER_USERNAME_UPDATE__FAILURE',\n  USER_USERNAME_UPDATE_ERROR_CLEAR: 'USER_USERNAME_UPDATE_ERROR_CLEAR',\n  USER_AVATAR_UPDATE: 'USER_AVATAR_UPDATE',\n  USER_AVATAR_UPDATE__SUCCESS: 'USER_AVATAR_UPDATE__SUCCESS',\n  USER_AVATAR_UPDATE__FAILURE: 'USER_AVATAR_UPDATE__FAILURE',\n  USER_API_KEY_CREATE: 'USER_API_KEY_CREATE',\n  USER_API_KEY_CREATE__SUCCESS: 'USER_API_KEY_CREATE__SUCCESS',\n  USER_API_KEY_CREATE__FAILURE: 'USER_API_KEY_CREATE__FAILURE',\n  USER_API_KEY_DELETE: 'USER_API_KEY_DELETE',\n  USER_API_KEY_DELETE__SUCCESS: 'USER_API_KEY_DELETE__SUCCESS',\n  USER_API_KEY_DELETE__FAILURE: 'USER_API_KEY_DELETE__FAILURE',\n  USER_API_KEY_VALUE_CLEAR: 'USER_API_KEY_VALUE_CLEAR',\n  USER_DELETE: 'USER_DELETE',\n  USER_DELETE__SUCCESS: 'USER_DELETE__SUCCESS',\n  USER_DELETE__FAILURE: 'USER_DELETE__FAILURE',\n  USER_DELETE_HANDLE: 'USER_DELETE_HANDLE',\n  USER_TO_CARD_ADD: 'USER_TO_CARD_ADD',\n  USER_TO_CARD_ADD__SUCCESS: 'USER_TO_CARD_ADD__SUCCESS',\n  USER_TO_CARD_ADD__FAILURE: 'USER_TO_CARD_ADD__FAILURE',\n  USER_TO_CARD_ADD_HANDLE: 'USER_TO_CARD_ADD_HANDLE',\n  USER_FROM_CARD_REMOVE: 'USER_FROM_CARD_REMOVE',\n  USER_FROM_CARD_REMOVE__SUCCESS: 'USER_FROM_CARD_REMOVE__SUCCESS',\n  USER_FROM_CARD_REMOVE__FAILURE: 'USER_FROM_CARD_REMOVE__FAILURE',\n  USER_FROM_CARD_REMOVE_HANDLE: 'USER_FROM_CARD_REMOVE_HANDLE',\n  USER_TO_BOARD_FILTER_ADD: 'USER_TO_BOARD_FILTER_ADD',\n  USER_FROM_BOARD_FILTER_REMOVE: 'USER_FROM_BOARD_FILTER_REMOVE',\n\n  /* Projects */\n\n  PROJECTS_SEARCH: 'PROJECTS_SEARCH',\n  PROJECTS_ORDER_UPDATE: 'PROJECTS_ORDER_UPDATE',\n  HIDDEN_PROJECTS_TOGGLE: 'HIDDEN_PROJECTS_TOGGLE',\n  PROJECT_CREATE: 'PROJECT_CREATE',\n  PROJECT_CREATE__SUCCESS: 'PROJECT_CREATE__SUCCESS',\n  PROJECT_CREATE__FAILURE: 'PROJECT_CREATE__FAILURE',\n  PROJECT_CREATE_HANDLE: 'PROJECT_CREATE_HANDLE',\n  PROJECT_UPDATE: 'PROJECT_UPDATE',\n  PROJECT_UPDATE__SUCCESS: 'PROJECT_UPDATE__SUCCESS',\n  PROJECT_UPDATE__FAILURE: 'PROJECT_UPDATE__FAILURE',\n  PROJECT_UPDATE_HANDLE: 'PROJECT_UPDATE_HANDLE',\n  PROJECT_DELETE: 'PROJECT_DELETE',\n  PROJECT_DELETE__SUCCESS: 'PROJECT_DELETE__SUCCESS',\n  PROJECT_DELETE__FAILURE: 'PROJECT_DELETE__FAILURE',\n  PROJECT_DELETE_HANDLE: 'PROJECT_DELETE_HANDLE',\n\n  /* Project managers */\n\n  PROJECT_MANAGER_CREATE: 'PROJECT_MANAGER_CREATE',\n  PROJECT_MANAGER_CREATE__SUCCESS: 'PROJECT_MANAGER_CREATE__SUCCESS',\n  PROJECT_MANAGER_CREATE__FAILURE: 'PROJECT_MANAGER_CREATE__FAILURE',\n  PROJECT_MANAGER_CREATE_HANDLE: 'PROJECT_MANAGER_CREATE_HANDLE',\n  PROJECT_MANAGER_DELETE: 'PROJECT_MANAGER_DELETE',\n  PROJECT_MANAGER_DELETE__SUCCESS: 'PROJECT_MANAGER_DELETE__SUCCESS',\n  PROJECT_MANAGER_DELETE__FAILURE: 'PROJECT_MANAGER_DELETE__FAILURE',\n  PROJECT_MANAGER_DELETE_HANDLE: 'PROJECT_MANAGER_DELETE_HANDLE',\n\n  /* Background images */\n\n  BACKGROUND_IMAGE_CREATE: 'BACKGROUND_IMAGE_CREATE',\n  BACKGROUND_IMAGE_CREATE__SUCCESS: 'BACKGROUND_IMAGE_CREATE__SUCCESS',\n  BACKGROUND_IMAGE_CREATE__FAILURE: 'BACKGROUND_IMAGE_CREATE__FAILURE',\n  BACKGROUND_IMAGE_CREATE_HANDLE: 'BACKGROUND_IMAGE_CREATE_HANDLE',\n  BACKGROUND_IMAGE_DELETE: 'BACKGROUND_IMAGE_DELETE',\n  BACKGROUND_IMAGE_DELETE__SUCCESS: 'BACKGROUND_IMAGE_DELETE__SUCCESS',\n  BACKGROUND_IMAGE_DELETE__FAILURE: 'BACKGROUND_IMAGE_DELETE__FAILURE',\n  BACKGROUND_IMAGE_DELETE_HANDLE: 'BACKGROUND_IMAGE_DELETE_HANDLE',\n\n  /* Base custom field groups */\n\n  BASE_CUSTOM_FIELD_GROUP_CREATE: 'BASE_CUSTOM_FIELD_GROUP_CREATE',\n  BASE_CUSTOM_FIELD_GROUP_CREATE__SUCCESS: 'BASE_CUSTOM_FIELD_GROUP_CREATE__SUCCESS',\n  BASE_CUSTOM_FIELD_GROUP_CREATE__FAILURE: 'BASE_CUSTOM_FIELD_GROUP_CREATE__FAILURE',\n  BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE: 'BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE',\n  BASE_CUSTOM_FIELD_GROUP_UPDATE: 'BASE_CUSTOM_FIELD_GROUP_UPDATE',\n  BASE_CUSTOM_FIELD_GROUP_UPDATE__SUCCESS: 'BASE_CUSTOM_FIELD_GROUP_UPDATE__SUCCESS',\n  BASE_CUSTOM_FIELD_GROUP_UPDATE__FAILURE: 'BASE_CUSTOM_FIELD_GROUP_UPDATE__FAILURE',\n  BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE: 'BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE',\n  BASE_CUSTOM_FIELD_GROUP_DELETE: 'BASE_CUSTOM_FIELD_GROUP_DELETE',\n  BASE_CUSTOM_FIELD_GROUP_DELETE__SUCCESS: 'BASE_CUSTOM_FIELD_GROUP_DELETE__SUCCESS',\n  BASE_CUSTOM_FIELD_GROUP_DELETE__FAILURE: 'BASE_CUSTOM_FIELD_GROUP_DELETE__FAILURE',\n  BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE: 'BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE',\n\n  /* Boards */\n\n  BOARD_CREATE: 'BOARD_CREATE',\n  BOARD_CREATE__SUCCESS: 'BOARD_CREATE__SUCCESS',\n  BOARD_CREATE__FAILURE: 'BOARD_CREATE__FAILURE',\n  BOARD_CREATE_HANDLE: 'BOARD_CREATE_HANDLE',\n  BOARD_FETCH: 'BOARD_FETCH',\n  BOARD_FETCH__SUCCESS: 'BOARD_FETCH__SUCCESS',\n  BOARD_FETCH__FAILURE: 'BOARD_FETCH__FAILURE',\n  BOARD_UPDATE: 'BOARD_UPDATE',\n  BOARD_UPDATE__SUCCESS: 'BOARD_UPDATE__SUCCESS',\n  BOARD_UPDATE__FAILURE: 'BOARD_UPDATE__FAILURE',\n  BOARD_UPDATE_HANDLE: 'BOARD_UPDATE_HANDLE',\n  BOARD_CONTEXT_UPDATE: 'BOARD_CONTEXT_UPDATE',\n  IN_BOARD_SEARCH: 'IN_BOARD_SEARCH', // TODO: rename?\n  BOARD_DELETE: 'BOARD_DELETE',\n  BOARD_DELETE__SUCCESS: 'BOARD_DELETE__SUCCESS',\n  BOARD_DELETE__FAILURE: 'BOARD_DELETE__FAILURE',\n  BOARD_DELETE_HANDLE: 'BOARD_DELETE_HANDLE',\n\n  /* Board memberships */\n\n  BOARD_MEMBERSHIP_CREATE: 'BOARD_MEMBERSHIP_CREATE',\n  BOARD_MEMBERSHIP_CREATE__SUCCESS: 'BOARD_MEMBERSHIP_CREATE__SUCCESS',\n  BOARD_MEMBERSHIP_CREATE__FAILURE: 'BOARD_MEMBERSHIP_CREATE__FAILURE',\n  BOARD_MEMBERSHIP_CREATE_HANDLE: 'BOARD_MEMBERSHIP_CREATE_HANDLE',\n  BOARD_MEMBERSHIP_UPDATE: 'BOARD_MEMBERSHIP_UPDATE',\n  BOARD_MEMBERSHIP_UPDATE__SUCCESS: 'BOARD_MEMBERSHIP_UPDATE__SUCCESS',\n  BOARD_MEMBERSHIP_UPDATE__FAILURE: 'BOARD_MEMBERSHIP_UPDATE__FAILURE',\n  BOARD_MEMBERSHIP_UPDATE_HANDLE: 'BOARD_MEMBERSHIP_UPDATE_HANDLE',\n  BOARD_MEMBERSHIP_DELETE: 'BOARD_MEMBERSHIP_DELETE',\n  BOARD_MEMBERSHIP_DELETE__SUCCESS: 'BOARD_MEMBERSHIP_DELETE__SUCCESS',\n  BOARD_MEMBERSHIP_DELETE__FAILURE: 'BOARD_MEMBERSHIP_DELETE__FAILURE',\n  BOARD_MEMBERSHIP_DELETE_HANDLE: 'BOARD_MEMBERSHIP_DELETE_HANDLE',\n\n  /* Labels */\n\n  LABEL_CREATE: 'LABEL_CREATE',\n  LABEL_CREATE__SUCCESS: 'LABEL_CREATE__SUCCESS',\n  LABEL_CREATE__FAILURE: 'LABEL_CREATE__FAILURE',\n  LABEL_FROM_CARD_CREATE: 'LABEL_FROM_CARD_CREATE',\n  LABEL_FROM_CARD_CREATE__SUCCESS: 'LABEL_FROM_CARD_CREATE__SUCCESS',\n  LABEL_FROM_CARD_CREATE__FAILURE: 'LABEL_FROM_CARD_CREATE__FAILURE',\n  LABEL_CREATE_HANDLE: 'LABEL_CREATE_HANDLE',\n  LABEL_UPDATE: 'LABEL_UPDATE',\n  LABEL_UPDATE__SUCCESS: 'LABEL_UPDATE__SUCCESS',\n  LABEL_UPDATE__FAILURE: 'LABEL_UPDATE__FAILURE',\n  LABEL_UPDATE_HANDLE: 'LABEL_UPDATE_HANDLE',\n  LABEL_DELETE: 'LABEL_DELETE',\n  LABEL_DELETE__SUCCESS: 'LABEL_DELETE__SUCCESS',\n  LABEL_DELETE__FAILURE: 'LABEL_DELETE__FAILURE',\n  LABEL_DELETE_HANDLE: 'LABEL_DELETE_HANDLE',\n  LABEL_TO_CARD_ADD: 'LABEL_TO_CARD_ADD',\n  LABEL_TO_CARD_ADD__SUCCESS: 'LABEL_TO_CARD_ADD__SUCCESS',\n  LABEL_TO_CARD_ADD__FAILURE: 'LABEL_TO_CARD_ADD__FAILURE',\n  LABEL_TO_CARD_ADD_HANDLE: 'LABEL_TO_CARD_ADD_HANDLE',\n  LABEL_FROM_CARD_REMOVE: 'LABEL_FROM_CARD_REMOVE',\n  LABEL_FROM_CARD_REMOVE__SUCCESS: 'LABEL_FROM_CARD_REMOVE__SUCCESS',\n  LABEL_FROM_CARD_REMOVE__FAILURE: 'LABEL_FROM_CARD_REMOVE__FAILURE',\n  LABEL_FROM_CARD_REMOVE_HANDLE: 'LABEL_FROM_CARD_REMOVE_HANDLE',\n  LABEL_TO_BOARD_FILTER_ADD: 'LABEL_TO_BOARD_FILTER_ADD',\n  LABEL_FROM_BOARD_FILTER_REMOVE: 'LABEL_FROM_BOARD_FILTER_REMOVE',\n\n  /* Lists */\n\n  LIST_CREATE: 'LIST_CREATE',\n  LIST_CREATE__SUCCESS: 'LIST_CREATE__SUCCESS',\n  LIST_CREATE__FAILURE: 'LIST_CREATE__FAILURE',\n  LIST_CREATE_HANDLE: 'LIST_CREATE_HANDLE',\n  LIST_UPDATE: 'LIST_UPDATE',\n  LIST_UPDATE__SUCCESS: 'LIST_UPDATE__SUCCESS',\n  LIST_UPDATE__FAILURE: 'LIST_UPDATE__FAILURE',\n  LIST_UPDATE_HANDLE: 'LIST_UPDATE_HANDLE',\n  LIST_SORT: 'LIST_SORT',\n  LIST_SORT__SUCCESS: 'LIST_SORT__SUCCESS',\n  LIST_SORT__FAILURE: 'LIST_SORT__FAILURE',\n  LIST_CARDS_MOVE: 'LIST_CARDS_MOVE',\n  LIST_CARDS_MOVE__SUCCESS: 'LIST_CARDS_MOVE__SUCCESS',\n  LIST_CARDS_MOVE__FAILURE: 'LIST_CARDS_MOVE__FAILURE',\n  LIST_CLEAR: 'LIST_CLEAR',\n  LIST_CLEAR__SUCCESS: 'LIST_CLEAR__SUCCESS',\n  LIST_CLEAR__FAILURE: 'LIST_CLEAR__FAILURE',\n  LIST_CLEAR_HANDLE: 'LIST_CLEAR_HANDLE',\n  LIST_DELETE: 'LIST_DELETE',\n  LIST_DELETE__SUCCESS: 'LIST_DELETE__SUCCESS',\n  LIST_DELETE__FAILURE: 'LIST_DELETE__FAILURE',\n  LIST_DELETE_HANDLE: 'LIST_DELETE_HANDLE',\n\n  /* Cards */\n\n  CARDS_FETCH: 'CARDS_FETCH',\n  CARDS_FETCH__SUCCESS: 'CARDS_FETCH__SUCCESS',\n  CARDS_FETCH__FAILURE: 'CARDS_FETCH__FAILURE',\n  CARDS_UPDATE_HANDLE: 'CARDS_UPDATE_HANDLE',\n  CARD_CREATE: 'CARD_CREATE',\n  CARD_CREATE__SUCCESS: 'CARD_CREATE__SUCCESS',\n  CARD_CREATE__FAILURE: 'CARD_CREATE__FAILURE',\n  CARD_CREATE_HANDLE: 'CARD_CREATE_HANDLE',\n  CARD_FETCH: 'CARD_FETCH',\n  CARD_FETCH__SUCCESS: 'CARD_FETCH__SUCCESS',\n  CARD_FETCH__FAILURE: 'CARD_FETCH__FAILURE',\n  CARD_UPDATE: 'CARD_UPDATE',\n  CARD_UPDATE__SUCCESS: 'CARD_UPDATE__SUCCESS',\n  CARD_UPDATE__FAILURE: 'CARD_UPDATE__FAILURE',\n  CARD_UPDATE_HANDLE: 'CARD_UPDATE_HANDLE',\n  CARD_TRANSFER: 'CARD_TRANSFER',\n  CARD_TRANSFER__SUCCESS: 'CARD_TRANSFER__SUCCESS',\n  CARD_TRANSFER__FAILURE: 'CARD_TRANSFER__FAILURE',\n  CARD_DUPLICATE: 'CARD_DUPLICATE',\n  CARD_DUPLICATE__SUCCESS: 'CARD_DUPLICATE__SUCCESS',\n  CARD_DUPLICATE__FAILURE: 'CARD_DUPLICATE__FAILURE',\n  CARD_COPY: 'CARD_COPY',\n  CARD_CUT: 'CARD_CUT',\n  CARD_PASTE: 'CARD_PASTE',\n  CARD_DELETE: 'CARD_DELETE',\n  CARD_DELETE__SUCCESS: 'CARD_DELETE__SUCCESS',\n  CARD_DELETE__FAILURE: 'CARD_DELETE__FAILURE',\n  CARD_DELETE_HANDLE: 'CARD_DELETE_HANDLE',\n\n  /* Task lists */\n\n  TASK_LIST_CREATE: 'TASK_LIST_CREATE',\n  TASK_LIST_CREATE__SUCCESS: 'TASK_LIST_CREATE__SUCCESS',\n  TASK_LIST_CREATE__FAILURE: 'TASK_LIST_CREATE__FAILURE',\n  TASK_LIST_CREATE_HANDLE: 'TASK_LIST_CREATE_HANDLE',\n  TASK_LIST_UPDATE: 'TASK_LIST_UPDATE',\n  TASK_LIST_UPDATE__SUCCESS: 'TASK_LIST_UPDATE__SUCCESS',\n  TASK_LIST_UPDATE__FAILURE: 'TASK_LIST_UPDATE__FAILURE',\n  TASK_LIST_UPDATE_HANDLE: 'TASK_LIST_UPDATE_HANDLE',\n  TASK_LIST_DELETE: 'TASK_LIST_DELETE',\n  TASK_LIST_DELETE__SUCCESS: 'TASK_LIST_DELETE__SUCCESS',\n  TASK_LIST_DELETE__FAILURE: 'TASK_LIST_DELETE__FAILURE',\n  TASK_LIST_DELETE_HANDLE: 'TASK_LIST_DELETE_HANDLE',\n\n  /* Tasks */\n\n  TASK_CREATE: 'TASK_CREATE',\n  TASK_CREATE__SUCCESS: 'TASK_CREATE__SUCCESS',\n  TASK_CREATE__FAILURE: 'TASK_CREATE__FAILURE',\n  TASK_CREATE_HANDLE: 'TASK_CREATE_HANDLE',\n  TASK_UPDATE: 'TASK_UPDATE',\n  TASK_UPDATE__SUCCESS: 'TASK_UPDATE__SUCCESS',\n  TASK_UPDATE__FAILURE: 'TASK_UPDATE__FAILURE',\n  TASK_UPDATE_HANDLE: 'TASK_UPDATE_HANDLE',\n  TASK_DELETE: 'TASK_DELETE',\n  TASK_DELETE__SUCCESS: 'TASK_DELETE__SUCCESS',\n  TASK_DELETE__FAILURE: 'TASK_DELETE__FAILURE',\n  TASK_DELETE_HANDLE: 'TASK_DELETE_HANDLE',\n\n  /* Attachments */\n\n  ATTACHMENT_CREATE: 'ATTACHMENT_CREATE',\n  ATTACHMENT_CREATE__SUCCESS: 'ATTACHMENT_CREATE__SUCCESS',\n  ATTACHMENT_CREATE__FAILURE: 'ATTACHMENT_CREATE__FAILURE',\n  ATTACHMENT_CREATE_HANDLE: 'ATTACHMENT_CREATE_HANDLE',\n  ATTACHMENT_UPDATE: 'ATTACHMENT_UPDATE',\n  ATTACHMENT_UPDATE__SUCCESS: 'ATTACHMENT_UPDATE__SUCCESS',\n  ATTACHMENT_UPDATE__FAILURE: 'ATTACHMENT_UPDATE__FAILURE',\n  ATTACHMENT_UPDATE_HANDLE: 'ATTACHMENT_UPDATE_HANDLE',\n  ATTACHMENT_DELETE: 'ATTACHMENT_DELETE',\n  ATTACHMENT_DELETE__SUCCESS: 'ATTACHMENT_DELETE__SUCCESS',\n  ATTACHMENT_DELETE__FAILURE: 'ATTACHMENT_DELETE__FAILURE',\n  ATTACHMENT_DELETE_HANDLE: 'ATTACHMENT_DELETE_HANDLE',\n\n  /* Custom field groups */\n\n  CUSTOM_FIELD_GROUP_CREATE: 'CUSTOM_FIELD_GROUP_CREATE',\n  CUSTOM_FIELD_GROUP_CREATE__SUCCESS: 'CUSTOM_FIELD_GROUP_CREATE__SUCCESS',\n  CUSTOM_FIELD_GROUP_CREATE__FAILURE: 'CUSTOM_FIELD_GROUP_CREATE__FAILURE',\n  CUSTOM_FIELD_GROUP_CREATE_HANDLE: 'CUSTOM_FIELD_GROUP_CREATE_HANDLE',\n  CUSTOM_FIELD_GROUP_UPDATE: 'CUSTOM_FIELD_GROUP_UPDATE',\n  CUSTOM_FIELD_GROUP_UPDATE__SUCCESS: 'CUSTOM_FIELD_GROUP_UPDATE__SUCCESS',\n  CUSTOM_FIELD_GROUP_UPDATE__FAILURE: 'CUSTOM_FIELD_GROUP_UPDATE__FAILURE',\n  CUSTOM_FIELD_GROUP_UPDATE_HANDLE: 'CUSTOM_FIELD_GROUP_UPDATE_HANDLE',\n  CUSTOM_FIELD_GROUP_DELETE: 'CUSTOM_FIELD_GROUP_DELETE',\n  CUSTOM_FIELD_GROUP_DELETE__SUCCESS: 'CUSTOM_FIELD_GROUP_DELETE__SUCCESS',\n  CUSTOM_FIELD_GROUP_DELETE__FAILURE: 'CUSTOM_FIELD_GROUP_DELETE__FAILURE',\n  CUSTOM_FIELD_GROUP_DELETE_HANDLE: 'CUSTOM_FIELD_GROUP_DELETE_HANDLE',\n\n  /* Custom fields */\n\n  CUSTOM_FIELD_CREATE: 'CUSTOM_FIELD_CREATE',\n  CUSTOM_FIELD_CREATE__SUCCESS: 'CUSTOM_FIELD_CREATE__SUCCESS',\n  CUSTOM_FIELD_CREATE__FAILURE: 'CUSTOM_FIELD_CREATE__FAILURE',\n  CUSTOM_FIELD_CREATE_HANDLE: 'CUSTOM_FIELD_CREATE_HANDLE',\n  CUSTOM_FIELD_UPDATE: 'CUSTOM_FIELD_UPDATE',\n  CUSTOM_FIELD_UPDATE__SUCCESS: 'CUSTOM_FIELD_UPDATE__SUCCESS',\n  CUSTOM_FIELD_UPDATE__FAILURE: 'CUSTOM_FIELD_UPDATE__FAILURE',\n  CUSTOM_FIELD_UPDATE_HANDLE: 'CUSTOM_FIELD_UPDATE_HANDLE',\n  CUSTOM_FIELD_DELETE: 'CUSTOM_FIELD_DELETE',\n  CUSTOM_FIELD_DELETE__SUCCESS: 'CUSTOM_FIELD_DELETE__SUCCESS',\n  CUSTOM_FIELD_DELETE__FAILURE: 'CUSTOM_FIELD_DELETE__FAILURE',\n  CUSTOM_FIELD_DELETE_HANDLE: 'CUSTOM_FIELD_DELETE_HANDLE',\n\n  /* Custom field values */\n\n  CUSTOM_FIELD_VALUE_UPDATE: 'CUSTOM_FIELD_VALUE_UPDATE',\n  CUSTOM_FIELD_VALUE_UPDATE__SUCCESS: 'CUSTOM_FIELD_VALUE_UPDATE__SUCCESS',\n  CUSTOM_FIELD_VALUE_UPDATE__FAILURE: 'CUSTOM_FIELD_VALUE_UPDATE__FAILURE',\n  CUSTOM_FIELD_VALUE_UPDATE_HANDLE: 'CUSTOM_FIELD_VALUE_UPDATE_HANDLE',\n  CUSTOM_FIELD_VALUE_DELETE: 'CUSTOM_FIELD_VALUE_DELETE',\n  CUSTOM_FIELD_VALUE_DELETE__SUCCESS: 'CUSTOM_FIELD_VALUE_DELETE__SUCCESS',\n  CUSTOM_FIELD_VALUE_DELETE__FAILURE: 'CUSTOM_FIELD_VALUE_DELETE__FAILURE',\n  CUSTOM_FIELD_VALUE_DELETE_HANDLE: 'CUSTOM_FIELD_VALUE_DELETE_HANDLE',\n\n  /* Comments */\n\n  COMMENTS_FETCH: 'COMMENTS_FETCH',\n  COMMENTS_FETCH__SUCCESS: 'COMMENTS_FETCH__SUCCESS',\n  COMMENTS_FETCH__FAILURE: 'COMMENTS_FETCH__FAILURE',\n  COMMENT_CREATE: 'COMMENT_CREATE',\n  COMMENT_CREATE__SUCCESS: 'COMMENT_CREATE__SUCCESS',\n  COMMENT_CREATE__FAILURE: 'COMMENT_CREATE__FAILURE',\n  COMMENT_CREATE_HANDLE: 'COMMENT_CREATE_HANDLE',\n  COMMENT_UPDATE: 'COMMENT_UPDATE',\n  COMMENT_UPDATE__SUCCESS: 'COMMENT_UPDATE__SUCCESS',\n  COMMENT_UPDATE__FAILURE: 'COMMENT_UPDATE__FAILURE',\n  COMMENT_UPDATE_HANDLE: 'COMMENT_UPDATE_HANDLE',\n  COMMENT_DELETE: 'COMMENT_DELETE',\n  COMMENT_DELETE__SUCCESS: 'COMMENT_DELETE__SUCCESS',\n  COMMENT_DELETE__FAILURE: 'COMMENT_DELETE__FAILURE',\n  COMMENT_DELETE_HANDLE: 'COMMENT_DELETE_HANDLE',\n\n  /* Activities */\n\n  ACTIVITIES_IN_BOARD_FETCH: 'ACTIVITIES_IN_BOARD_FETCH',\n  ACTIVITIES_IN_BOARD_FETCH__SUCCESS: 'ACTIVITIES_IN_BOARD_FETCH__SUCCESS',\n  ACTIVITIES_IN_BOARD_FETCH__FAILURE: 'ACTIVITIES_IN_BOARD_FETCH__FAILURE',\n  ACTIVITIES_IN_CARD_FETCH: 'ACTIVITIES_IN_CARD_FETCH',\n  ACTIVITIES_IN_CARD_FETCH__SUCCESS: 'ACTIVITIES_IN_CARD_FETCH__SUCCESS',\n  ACTIVITIES_IN_CARD_FETCH__FAILURE: 'ACTIVITIES_IN_CARD_FETCH__FAILURE',\n  ACTIVITY_CREATE_HANDLE: 'ACTIVITY_CREATE_HANDLE',\n\n  /* Notifications */\n\n  ALL_NOTIFICATIONS_DELETE: 'ALL_NOTIFICATIONS_DELETE',\n  ALL_NOTIFICATIONS_DELETE__SUCCESS: 'ALL_NOTIFICATIONS_DELETE__SUCCESS',\n  ALL_NOTIFICATIONS_DELETE__FAILURE: 'ALL_NOTIFICATIONS_DELETE__FAILURE',\n  NOTIFICATION_CREATE_HANDLE: 'NOTIFICATION_CREATE_HANDLE',\n  NOTIFICATION_DELETE: 'NOTIFICATION_DELETE',\n  NOTIFICATION_DELETE__SUCCESS: 'NOTIFICATION_DELETE__SUCCESS',\n  NOTIFICATION_DELETE__FAILURE: 'NOTIFICATION_DELETE__FAILURE',\n  NOTIFICATION_DELETE_HANDLE: 'NOTIFICATION_DELETE_HANDLE',\n\n  /* Notification Services */\n\n  NOTIFICATION_SERVICE_CREATE: 'NOTIFICATION_SERVICE_CREATE',\n  NOTIFICATION_SERVICE_CREATE__SUCCESS: 'NOTIFICATION_SERVICE_CREATE__SUCCESS',\n  NOTIFICATION_SERVICE_CREATE__FAILURE: 'NOTIFICATION_SERVICE_CREATE__FAILURE',\n  NOTIFICATION_SERVICE_CREATE_HANDLE: 'NOTIFICATION_SERVICE_CREATE_HANDLE',\n  NOTIFICATION_SERVICE_UPDATE: 'NOTIFICATION_SERVICE_UPDATE',\n  NOTIFICATION_SERVICE_UPDATE__SUCCESS: 'NOTIFICATION_SERVICE_UPDATE__SUCCESS',\n  NOTIFICATION_SERVICE_UPDATE__FAILURE: 'NOTIFICATION_SERVICE_UPDATE__FAILURE',\n  NOTIFICATION_SERVICE_UPDATE_HANDLE: 'NOTIFICATION_SERVICE_UPDATE_HANDLE',\n  NOTIFICATION_SERVICE_TEST: 'NOTIFICATION_SERVICE_TEST',\n  NOTIFICATION_SERVICE_TEST__SUCCESS: 'NOTIFICATION_SERVICE_TEST__SUCCESS',\n  NOTIFICATION_SERVICE_TEST__FAILURE: 'NOTIFICATION_SERVICE_TEST__FAILURE',\n  NOTIFICATION_SERVICE_DELETE: 'NOTIFICATION_SERVICE_DELETE',\n  NOTIFICATION_SERVICE_DELETE__SUCCESS: 'NOTIFICATION_SERVICE_DELETE__SUCCESS',\n  NOTIFICATION_SERVICE_DELETE__FAILURE: 'NOTIFICATION_SERVICE_DELETE__FAILURE',\n  NOTIFICATION_SERVICE_DELETE_HANDLE: 'NOTIFICATION_SERVICE_DELETE_HANDLE',\n};\n"
  },
  {
    "path": "client/src/constants/BackgroundGradients.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport default [\n  'old-lime',\n  'ocean-dive',\n  'tzepesch-style',\n  'jungle-mesh',\n  'strawberry-dust',\n  'purple-rose',\n  'sun-scream',\n  'warm-rust',\n  'sky-change',\n  'green-eyes',\n  'blue-xchange',\n  'blood-orange',\n  'sour-peel',\n  'green-ninja',\n  'algae-green',\n  'coral-reef',\n  'steel-grey',\n  'heat-waves',\n  'velvet-lounge',\n  'purple-rain',\n  'blue-steel',\n  'blueish-curve',\n  'prism-light',\n  'green-mist',\n  'red-curtain',\n];\n"
  },
  {
    "path": "client/src/constants/ClipboardTypes.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst COPY = 'COPY';\nconst CUT = 'CUT';\n\nexport default {\n  COPY,\n  CUT,\n};\n"
  },
  {
    "path": "client/src/constants/Config.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst BASE_PATH = window.BASE_PATH || '';\n\nconst ACCESS_TOKEN_KEY = 'accessToken';\nconst ACCESS_TOKEN_VERSION_KEY = 'accessTokenVersion';\nconst ACCESS_TOKEN_VERSION = '1';\n\nconst POSITION_GAP = 65536;\nconst CARDS_LIMIT = 50;\nconst COMMENTS_LIMIT = 50;\nconst ACTIVITIES_LIMIT = 50;\n\nconst MAX_SIZE_TO_DISPLAY_CONTENT = 256 * 1024;\n\nconst IS_MAC = navigator.platform.startsWith('Mac');\n\nexport default {\n  BASE_PATH,\n  ACCESS_TOKEN_KEY,\n  ACCESS_TOKEN_VERSION_KEY,\n  ACCESS_TOKEN_VERSION,\n  POSITION_GAP,\n  CARDS_LIMIT,\n  COMMENTS_LIMIT,\n  ACTIVITIES_LIMIT,\n  MAX_SIZE_TO_DISPLAY_CONTENT,\n  IS_MAC,\n};\n"
  },
  {
    "path": "client/src/constants/DroppableTypes.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst BOARD = 'BOARD';\nconst LABEL = 'LABEL';\nconst LIST = 'LIST';\nconst CARD = 'CARD';\nconst TASK_LIST = 'TASK_LIST';\nconst TASK = 'TASK';\nconst CUSTOM_FIELD_GROUP = 'CUSTOM_FIELD_GROUP';\nconst CUSTOM_FIELD = 'CUSTOM_FIELD';\n\nexport default {\n  BOARD,\n  LABEL,\n  LIST,\n  CARD,\n  TASK_LIST,\n  TASK,\n  CUSTOM_FIELD_GROUP,\n  CUSTOM_FIELD,\n};\n"
  },
  {
    "path": "client/src/constants/Encodings.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst BINARY = 'binary';\nconst UTF8 = 'utf8';\n\nexport default {\n  BINARY,\n  UTF8,\n};\n"
  },
  {
    "path": "client/src/constants/EntryActionTypes.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst PREFIX = '@entry';\n\nexport default {\n  PREFIX,\n\n  /* Socket */\n\n  SOCKET_DISCONNECT_HANDLE: `${PREFIX}/SOCKET_DISCONNECT_HANDLE`,\n  SOCKET_RECONNECT_HANDLE: `${PREFIX}/SOCKET_RECONNECT_HANDLE`,\n\n  /* Bootstrap */\n\n  BOOTSTRAP_UPDATE_HANDLE: `${PREFIX}/BOOTSTRAP_UPDATE_HANDLE`,\n\n  /* Login */\n\n  AUTHENTICATE: `${PREFIX}/AUTHENTICATE`,\n  WITH_OIDC_AUTHENTICATE: `${PREFIX}/WITH_OIDC_AUTHENTICATE`,\n  AUTHENTICATE_ERROR_CLEAR: `${PREFIX}/AUTHENTICATE_ERROR_CLEAR`,\n  TERMS_ACCEPT: `${PREFIX}/TERMS_ACCEPT`,\n  TERMS_CANCEL: `${PREFIX}/TERMS_CANCEL`,\n  TERMS_LANGUAGE_UPDATE: `${PREFIX}/TERMS_LANGUAGE_UPDATE`,\n\n  /* Core */\n\n  FAVORITES_TOGGLE: `${PREFIX}/FAVORITES_TOGGLE`,\n  EDIT_MODE_TOGGLE: `${PREFIX}/EDIT_MODE_TOGGLE`,\n  HOME_VIEW_UPDATE: `${PREFIX}/HOME_VIEW_UPDATE`,\n  LOGOUT: `${PREFIX}/LOGOUT`,\n\n  /* Modals */\n\n  MODAL_OPEN: `${PREFIX}/MODAL_OPEN`,\n  MODAL_CLOSE: `${PREFIX}/MODAL_CLOSE`,\n\n  /* Config */\n\n  CONFIG_UPDATE: `${PREFIX}/CONFIG_UPDATE`,\n  CONFIG_UPDATE_HANDLE: `${PREFIX}/CONFIG_UPDATE_HANDLE`,\n  SMTP_CONFIG_TEST: `${PREFIX}/SMTP_CONFIG_TEST`,\n\n  /* Webhooks */\n\n  WEBHOOK_CREATE: `${PREFIX}/WEBHOOK_CREATE`,\n  WEBHOOK_CREATE_HANDLE: `${PREFIX}/WEBHOOK_CREATE_HANDLE`,\n  WEBHOOK_UPDATE: `${PREFIX}/WEBHOOK_UPDATE`,\n  WEBHOOK_UPDATE_HANDLE: `${PREFIX}/WEBHOOK_UPDATE_HANDLE`,\n  WEBHOOK_DELETE: `${PREFIX}/WEBHOOK_DELETE`,\n  WEBHOOK_DELETE_HANDLE: `${PREFIX}/WEBHOOK_DELETE_HANDLE`,\n\n  /* Users */\n\n  USERS_RESET_HANDLE: `${PREFIX}/USERS_RESET_HANDLE`,\n  USER_CREATE: `${PREFIX}/USER_CREATE`,\n  USER_CREATE_HANDLE: `${PREFIX}/USER_CREATE_HANDLE`,\n  USER_CREATE_ERROR_CLEAR: `${PREFIX}/USER_CREATE_ERROR_CLEAR`,\n  USER_UPDATE: `${PREFIX}/USER_UPDATE`,\n  CURRENT_USER_UPDATE: `${PREFIX}/CURRENT_USER_UPDATE`,\n  USER_UPDATE_HANDLE: `${PREFIX}/USER_UPDATE_HANDLE`,\n  CURRENT_USER_LANGUAGE_UPDATE: `${PREFIX}/CURRENT_USER_LANGUAGE_UPDATE`,\n  USER_EMAIL_UPDATE: `${PREFIX}/USER_EMAIL_UPDATE`,\n  CURRENT_USER_EMAIL_UPDATE: `${PREFIX}/CURRENT_USER_EMAIL_UPDATE`,\n  USER_EMAIL_UPDATE_ERROR_CLEAR: `${PREFIX}/USER_EMAIL_UPDATE_ERROR_CLEAR`,\n  CURRENT_USER_EMAIL_UPDATE_ERROR_CLEAR: `${PREFIX}/CURRENT_USER_EMAIL_UPDATE_ERROR_CLEAR`,\n  USER_PASSWORD_UPDATE: `${PREFIX}/USER_PASSWORD_UPDATE`,\n  CURRENT_USER_PASSWORD_UPDATE: `${PREFIX}/CURRENT_USER_PASSWORD_UPDATE`,\n  USER_PASSWORD_UPDATE_ERROR_CLEAR: `${PREFIX}/USER_PASSWORD_UPDATE_ERROR_CLEAR`,\n  CURRENT_USER_PASSWORD_UPDATE_ERROR_CLEAR: `${PREFIX}/CURRENT_USER_PASSWORD_UPDATE_ERROR_CLEAR`,\n  USER_USERNAME_UPDATE: `${PREFIX}/USER_USERNAME_UPDATE`,\n  CURRENT_USER_USERNAME_UPDATE: `${PREFIX}/CURRENT_USER_USERNAME_UPDATE`,\n  USER_USERNAME_UPDATE_ERROR_CLEAR: `${PREFIX}/USER_USERNAME_UPDATE_ERROR_CLEAR`,\n  CURRENT_USER_USERNAME_UPDATE_ERROR_CLEAR: `${PREFIX}/CURRENT_USER_USERNAME_UPDATE_ERROR_CLEAR`,\n  CURRENT_USER_AVATAR_UPDATE: `${PREFIX}/CURRENT_USER_AVATAR_UPDATE`,\n  USER_API_KEY_CREATE: `${PREFIX}/USER_API_KEY_CREATE`,\n  USER_API_KEY_DELETE: `${PREFIX}/USER_API_KEY_DELETE`,\n  USER_API_KEY_VALUE_CLEAR: `${PREFIX}/USER_API_KEY_VALUE_CLEAR`,\n  USER_DELETE: `${PREFIX}/USER_DELETE`,\n  USER_DELETE_HANDLE: `${PREFIX}/USER_DELETE_HANDLE`,\n  USER_TO_CARD_ADD: `${PREFIX}/USER_TO_CARD_ADD`,\n  USER_TO_CURRENT_CARD_ADD: `${PREFIX}/USER_TO_CURRENT_CARD_ADD`,\n  CURRENT_USER_TO_CURRENT_CARD_ADD: `${PREFIX}/CURRENT_USER_TO_CURRENT_CARD_ADD`,\n  USER_TO_CARD_ADD_HANDLE: `${PREFIX}/USER_TO_CARD_ADD_HANDLE`,\n  USER_FROM_CARD_REMOVE: `${PREFIX}/USER_FROM_CARD_REMOVE`,\n  USER_FROM_CURRENT_CARD_REMOVE: `${PREFIX}/USER_FROM_CURRENT_CARD_REMOVE`,\n  CURRENT_USER_FROM_CURRENT_CARD_REMOVE: `${PREFIX}/CURRENT_USER_FROM_CURRENT_CARD_REMOVE`,\n  USER_FROM_CARD_REMOVE_HANDLE: `${PREFIX}/USER_FROM_CARD_REMOVE_HANDLE`,\n  USER_TO_FILTER_IN_CURRENT_BOARD_ADD: `${PREFIX}/USER_TO_FILTER_IN_CURRENT_BOARD_ADD`,\n  USER_FROM_FILTER_IN_CURRENT_BOARD_REMOVE: `${PREFIX}/USER_FROM_FILTER_IN_CURRENT_BOARD_REMOVE`,\n\n  /* Projects */\n\n  PROJECTS_SEARCH: `${PREFIX}/PROJECTS_SEARCH`,\n  PROJECTS_ORDER_UPDATE: `${PREFIX}/PROJECTS_ORDER_UPDATE`,\n  HIDDEN_PROJECTS_TOGGLE: `${PREFIX}/HIDDEN_PROJECTS_TOGGLE`,\n  PROJECT_CREATE: `${PREFIX}/PROJECT_CREATE`,\n  PROJECT_CREATE_HANDLE: `${PREFIX}/PROJECT_CREATE_HANDLE`,\n  PROJECT_UPDATE: `${PREFIX}/PROJECT_UPDATE`,\n  CURRENT_PROJECT_UPDATE: `${PREFIX}/CURRENT_PROJECT_UPDATE`,\n  PROJECT_UPDATE_HANDLE: `${PREFIX}/PROJECT_UPDATE_HANDLE`,\n  CURRENT_PROJECT_DELETE: `${PREFIX}/CURRENT_PROJECT_DELETE`,\n  PROJECT_DELETE_HANDLE: `${PREFIX}/PROJECT_DELETE_HANDLE`,\n\n  /* Project managers */\n\n  MANAGER_IN_CURRENT_PROJECT_CREATE: `${PREFIX}/MANAGER_IN_CURRENT_PROJECT_CREATE`,\n  PROJECT_MANAGER_CREATE_HANDLE: `${PREFIX}/PROJECT_MANAGER_CREATE_HANDLE`,\n  PROJECT_MANAGER_DELETE: `${PREFIX}/PROJECT_MANAGER_DELETE`,\n  PROJECT_MANAGER_DELETE_HANDLE: `${PREFIX}/PROJECT_MANAGER_DELETE_HANDLE`,\n\n  /* Background images */\n\n  BACKGROUND_IMAGE_IN_CURRENT_PROJECT_CREATE: `${PREFIX}/BACKGROUND_IMAGE_IN_CURRENT_PROJECT_CREATE`,\n  BACKGROUND_IMAGE_CREATE_HANDLE: `${PREFIX}/BACKGROUND_IMAGE_CREATE_HANDLE`,\n  BACKGROUND_IMAGE_DELETE: `${PREFIX}/BACKGROUND_IMAGE_DELETE`,\n  BACKGROUND_IMAGE_DELETE_HANDLE: `${PREFIX}/BACKGROUND_IMAGE_DELETE_HANDLE`,\n\n  /* Base custom field groups */\n\n  BASE_CUSTOM_FIELD_GROUP_IN_CURRENT_PROJECT_CREATE: `${PREFIX}/BASE_CUSTOM_FIELD_GROUP_IN_CURRENT_PROJECT_CREATE`,\n  BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE: `${PREFIX}/BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE`,\n  BASE_CUSTOM_FIELD_GROUP_UPDATE: `${PREFIX}/BASE_CUSTOM_FIELD_GROUP_UPDATE`,\n  BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE: `${PREFIX}/BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE`,\n  BASE_CUSTOM_FIELD_GROUP_DELETE: `${PREFIX}/BASE_CUSTOM_FIELD_GROUP_DELETE`,\n  BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE: `${PREFIX}/BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE`,\n\n  /* Boards */\n\n  BOARD_IN_CURRENT_PROJECT_CREATE: `${PREFIX}/BOARD_IN_CURRENT_PROJECT_CREATE`,\n  BOARD_CREATE_HANDLE: `${PREFIX}/BOARD_CREATE_HANDLE`,\n  BOARD_FETCH: `${PREFIX}/BOARD_FETCH`,\n  BOARD_UPDATE: `${PREFIX}/BOARD_UPDATE`,\n  CURRENT_BOARD_UPDATE: `${PREFIX}/CURRENT_BOARD_UPDATE`,\n  BOARD_UPDATE_HANDLE: `${PREFIX}/BOARD_UPDATE_HANDLE`,\n  BOARD_MOVE: `${PREFIX}/BOARD_MOVE`,\n  CONTEXT_IN_CURRENT_BOARD_UPDATE: `${PREFIX}/CONTEXT_IN_CURRENT_BOARD_UPDATE`,\n  VIEW_IN_CURRENT_BOARD_UPDATE: `${PREFIX}/VIEW_IN_CURRENT_BOARD_UPDATE`,\n  IN_CURRENT_BOARD_SEARCH: `${PREFIX}/IN_CURRENT_BOARD_SEARCH`,\n  BOARD_DELETE: `${PREFIX}/BOARD_DELETE`,\n  BOARD_DELETE_HANDLE: `${PREFIX}/BOARD_DELETE_HANDLE`,\n\n  /* Board memberships */\n\n  MEMBERSHIP_IN_CURRENT_BOARD_CREATE: `${PREFIX}/MEMBERSHIP_IN_CURRENT_BOARD_CREATE`,\n  BOARD_MEMBERSHIP_CREATE_HANDLE: `${PREFIX}/BOARD_MEMBERSHIP_CREATE_HANDLE`,\n  BOARD_MEMBERSHIP_UPDATE: `${PREFIX}/BOARD_MEMBERSHIP_UPDATE`,\n  BOARD_MEMBERSHIP_UPDATE_HANDLE: `${PREFIX}/BOARD_MEMBERSHIP_UPDATE_HANDLE`,\n  BOARD_MEMBERSHIP_DELETE: `${PREFIX}/BOARD_MEMBERSHIP_DELETE`,\n  BOARD_MEMBERSHIP_DELETE_HANDLE: `${PREFIX}/BOARD_MEMBERSHIP_DELETE_HANDLE`,\n\n  /* Labels */\n\n  LABEL_IN_CURRENT_BOARD_CREATE: `${PREFIX}/LABEL_IN_CURRENT_BOARD_CREATE`,\n  LABEL_FROM_CARD_CREATE: `${PREFIX}/LABEL_FROM_CARD_CREATE`,\n  LABEL_CREATE_HANDLE: `${PREFIX}/LABEL_CREATE_HANDLE`,\n  LABEL_UPDATE: `${PREFIX}/LABEL_UPDATE`,\n  LABEL_UPDATE_HANDLE: `${PREFIX}/LABEL_UPDATE_HANDLE`,\n  LABEL_MOVE: `${PREFIX}/LABEL_MOVE`,\n  LABEL_DELETE: `${PREFIX}/LABEL_DELETE`,\n  LABEL_DELETE_HANDLE: `${PREFIX}/LABEL_DELETE_HANDLE`,\n  LABEL_TO_CARD_ADD: `${PREFIX}/LABEL_TO_CARD_ADD`,\n  LABEL_TO_CURRENT_CARD_ADD: `${PREFIX}/LABEL_TO_CURRENT_CARD_ADD`,\n  LABEL_TO_CARD_ADD_HANDLE: `${PREFIX}/LABEL_TO_CARD_ADD_HANDLE`,\n  LABEL_FROM_CARD_REMOVE: `${PREFIX}/LABEL_FROM_CARD_REMOVE`,\n  LABEL_FROM_CURRENT_CARD_REMOVE: `${PREFIX}/LABEL_FROM_CURRENT_CARD_REMOVE`,\n  LABEL_FROM_CARD_REMOVE_HANDLE: `${PREFIX}/LABEL_FROM_CARD_REMOVE_HANDLE`,\n  LABEL_TO_FILTER_IN_CURRENT_BOARD_ADD: `${PREFIX}/LABEL_TO_FILTER_IN_CURRENT_BOARD_ADD`,\n  LABEL_FROM_FILTER_IN_CURRENT_BOARD_REMOVE: `${PREFIX}/LABEL_FROM_FILTER_IN_CURRENT_BOARD_REMOVE`,\n\n  /* Lists */\n\n  LIST_IN_CURRENT_BOARD_CREATE: `${PREFIX}/LIST_IN_CURRENT_BOARD_CREATE`,\n  LIST_CREATE_HANDLE: `${PREFIX}/LIST_CREATE_HANDLE`,\n  LIST_UPDATE: `${PREFIX}/LIST_UPDATE`,\n  LIST_UPDATE_HANDLE: `${PREFIX}/LIST_UPDATE_HANDLE`,\n  LIST_MOVE: `${PREFIX}/LIST_MOVE`,\n  LIST_TRANSFER: `${PREFIX}/LIST_TRANSFER`,\n  LIST_SORT: `${PREFIX}/LIST_SORT`,\n  LIST_CARDS_TO_ARCHIVE_LIST_MOVE: `${PREFIX}/LIST_CARDS_TO_ARCHIVE_LIST_MOVE`,\n  TRASH_LIST_IN_CURRENT_BOARD_CLEAR: `${PREFIX}/TRASH_LIST_IN_CURRENT_BOARD_CLEAR`,\n  LIST_CLEAR_HANDLE: `${PREFIX}/LIST_CLEAR_HANDLE`,\n  LIST_DELETE: `${PREFIX}/LIST_DELETE`,\n  LIST_DELETE_HANDLE: `${PREFIX}/LIST_DELETE_HANDLE`,\n\n  /* Cards */\n\n  CARDS_IN_CURRENT_LIST_FETCH: `${PREFIX}/CARDS_IN_CURRENT_LIST_FETCH`,\n  CARDS_UPDATE_HANDLE: `${PREFIX}/CARDS_UPDATE_HANDLE`,\n  CARD_CREATE: `${PREFIX}/CARD_CREATE`,\n  CARD_IN_CURRENT_CONTEXT_CREATE: `${PREFIX}/CARD_IN_CURRENT_CONTEXT_CREATE`,\n  CARD_IN_CURRENT_LIST_CREATE: `${PREFIX}/CARD_IN_CURRENT_LIST_CREATE`,\n  CARD_CREATE_HANDLE: `${PREFIX}/CARD_CREATE_HANDLE`,\n  CARD_UPDATE: `${PREFIX}/CARD_UPDATE`,\n  CURRENT_CARD_UPDATE: `${PREFIX}/CURRENT_CARD_UPDATE`,\n  CARD_UPDATE_HANDLE: `${PREFIX}/CARD_UPDATE_HANDLE`,\n  CARD_MOVE: `${PREFIX}/CARD_MOVE`,\n  CURRENT_CARD_MOVE: `${PREFIX}/CURRENT_CARD_MOVE`,\n  CARD_TO_ARCHIVE_MOVE: `${PREFIX}/CARD_TO_ARCHIVE_MOVE`,\n  CURRENT_CARD_TO_ARCHIVE_MOVE: `${PREFIX}/CURRENT_CARD_TO_ARCHIVE_MOVE`,\n  CARD_TO_TRASH_MOVE: `${PREFIX}/CARD_TO_TRASH_MOVE`,\n  CURRENT_CARD_TO_TRASH_MOVE: `${PREFIX}/CURRENT_CARD_TO_TRASH_MOVE`,\n  CARD_TRANSFER: `${PREFIX}/CARD_TRANSFER`,\n  CURRENT_CARD_TRANSFER: `${PREFIX}/CURRENT_CARD_TRANSFER`,\n  CARD_DUPLICATE: `${PREFIX}/CARD_DUPLICATE`,\n  CURRENT_CARD_DUPLICATE: `${PREFIX}/CURRENT_CARD_DUPLICATE`,\n  CARD_COPY: `${PREFIX}/CARD_COPY`,\n  CARD_CUT: `${PREFIX}/CARD_CUT`,\n  CARD_PASTE: `${PREFIX}/CARD_PASTE`,\n  CARD_IN_CURRENT_CONTEXT_PASTE: `${PREFIX}/CARD_IN_CURRENT_CONTEXT_PASTE`,\n  CARD_IN_CURRENT_LIST_PASTE: `${PREFIX}/CARD_IN_CURRENT_LIST_PASTE`,\n  TO_ADJACENT_CARD_GO: `${PREFIX}/TO_ADJACENT_CARD_GO`,\n  CARD_DELETE: `${PREFIX}/CARD_DELETE`,\n  CURRENT_CARD_DELETE: `${PREFIX}/CURRENT_CARD_DELETE`,\n  CARD_DELETE_HANDLE: `${PREFIX}/CARD_DELETE_HANDLE`,\n\n  /* Task lists */\n\n  TASK_LIST_IN_CURRENT_CARD_CREATE: `${PREFIX}/TASK_LIST_IN_CURRENT_CARD_CREATE`,\n  TASK_LIST_CREATE_HANDLE: `${PREFIX}/TASK_LIST_CREATE_HANDLE`,\n  TASK_LIST_UPDATE: `${PREFIX}/TASK_LIST_UPDATE`,\n  TASK_LIST_UPDATE_HANDLE: `${PREFIX}/TASK_LIST_UPDATE_HANDLE`,\n  TASK_LIST_MOVE: `${PREFIX}/TASK_LIST_MOVE`,\n  TASK_LIST_DELETE: `${PREFIX}/TASK_LIST_DELETE`,\n  TASK_LIST_DELETE_HANDLE: `${PREFIX}/TASK_LIST_DELETE_HANDLE`,\n\n  /* Tasks */\n\n  TASK_CREATE: `${PREFIX}/TASK_CREATE`,\n  TASK_CREATE_HANDLE: `${PREFIX}/TASK_CREATE_HANDLE`,\n  TASK_UPDATE: `${PREFIX}/TASK_UPDATE`,\n  TASK_UPDATE_HANDLE: `${PREFIX}/TASK_UPDATE_HANDLE`,\n  TASK_MOVE: `${PREFIX}/TASK_MOVE`,\n  TASK_DELETE: `${PREFIX}/TASK_DELETE`,\n  TASK_DELETE_HANDLE: `${PREFIX}/TASK_DELETE_HANDLE`,\n\n  /* Attachments */\n\n  ATTACHMENT_IN_CURRENT_CARD_CREATE: `${PREFIX}/ATTACHMENT_IN_CURRENT_CARD_CREATE`,\n  ATTACHMENT_CREATE_HANDLE: `${PREFIX}/ATTACHMENT_CREATE_HANDLE`,\n  ATTACHMENT_UPDATE: `${PREFIX}/ATTACHMENT_UPDATE`,\n  ATTACHMENT_UPDATE_HANDLE: `${PREFIX}/ATTACHMENT_UPDATE_HANDLE`,\n  ATTACHMENT_DELETE: `${PREFIX}/ATTACHMENT_DELETE`,\n  ATTACHMENT_DELETE_HANDLE: `${PREFIX}/ATTACHMENT_DELETE_HANDLE`,\n\n  /* Custom field groups */\n\n  CUSTOM_FIELD_GROUP_IN_CURRENT_BOARD_CREATE: `${PREFIX}/CUSTOM_FIELD_GROUP_IN_CURRENT_BOARD_CREATE`,\n  CUSTOM_FIELD_GROUP_IN_CURRENT_CARD_CREATE: `${PREFIX}/CUSTOM_FIELD_GROUP_IN_CURRENT_CARD_CREATE`,\n  CUSTOM_FIELD_GROUP_CREATE_HANDLE: `${PREFIX}/CUSTOM_FIELD_GROUP_CREATE_HANDLE`,\n  CUSTOM_FIELD_GROUP_UPDATE: `${PREFIX}/CUSTOM_FIELD_GROUP_UPDATE`,\n  CUSTOM_FIELD_GROUP_UPDATE_HANDLE: `${PREFIX}/CUSTOM_FIELD_GROUP_UPDATE_HANDLE`,\n  CUSTOM_FIELD_GROUP_MOVE: `${PREFIX}/CUSTOM_FIELD_GROUP_MOVE`,\n  CUSTOM_FIELD_GROUP_DELETE: `${PREFIX}/CUSTOM_FIELD_GROUP_DELETE`,\n  CUSTOM_FIELD_GROUP_DELETE_HANDLE: `${PREFIX}/CUSTOM_FIELD_GROUP_DELETE_HANDLE`,\n\n  /* Custom fields */\n\n  CUSTOM_FIELD_IN_BASE_GROUP_CREATE: `${PREFIX}/CUSTOM_FIELD_IN_BASE_GROUP_CREATE`,\n  CUSTOM_FIELD_IN_GROUP_CREATE: `${PREFIX}/CUSTOM_FIELD_IN_GROUP_CREATE`,\n  CUSTOM_FIELD_CREATE_HANDLE: `${PREFIX}/CUSTOM_FIELD_CREATE_HANDLE`,\n  CUSTOM_FIELD_UPDATE: `${PREFIX}/CUSTOM_FIELD_UPDATE`,\n  CUSTOM_FIELD_UPDATE_HANDLE: `${PREFIX}/CUSTOM_FIELD_UPDATE_HANDLE`,\n  CUSTOM_FIELD_MOVE: `${PREFIX}/CUSTOM_FIELD_MOVE`,\n  CUSTOM_FIELD_DELETE: `${PREFIX}/CUSTOM_FIELD_DELETE`,\n  CUSTOM_FIELD_DELETE_HANDLE: `${PREFIX}/CUSTOM_FIELD_DELETE_HANDLE`,\n\n  /* Custom field values */\n\n  CUSTOM_FIELD_VALUE_UPDATE: `${PREFIX}/CUSTOM_FIELD_VALUE_UPDATE`,\n  CUSTOM_FIELD_VALUE_UPDATE_HANDLE: `${PREFIX}/CUSTOM_FIELD_VALUE_UPDATE_HANDLE`,\n  CUSTOM_FIELD_VALUE_DELETE: `${PREFIX}/CUSTOM_FIELD_VALUE_DELETE`,\n  CUSTOM_FIELD_VALUE_DELETE_HANDLE: `${PREFIX}/CUSTOM_FIELD_VALUE_DELETE_HANDLE`,\n\n  /* Comments */\n\n  COMMENTS_IN_CURRENT_CARD_FETCH: `${PREFIX}/COMMENTS_IN_CURRENT_CARD_FETCH`,\n  COMMENT_IN_CURRENT_CARD_CREATE: `${PREFIX}/COMMENT_IN_CURRENT_CARD_CREATE`,\n  COMMENT_CREATE_HANDLE: `${PREFIX}/COMMENT_CREATE_HANDLE`,\n  COMMENT_UPDATE: `${PREFIX}/COMMENT_UPDATE`,\n  COMMENT_UPDATE_HANDLE: `${PREFIX}/COMMENT_UPDATE_HANDLE`,\n  COMMENT_DELETE: `${PREFIX}/COMMENT_DELETE`,\n  COMMENT_DELETE_HANDLE: `${PREFIX}/COMMENT_DELETE_HANDLE`,\n\n  /* Activities */\n\n  ACTIVITIES_IN_CURRENT_BOARD_FETCH: `${PREFIX}/ACTIVITIES_IN_CURRENT_BOARD_FETCH`,\n  ACTIVITIES_IN_CURRENT_CARD_FETCH: `${PREFIX}/ACTIVITIES_IN_CURRENT_CARD_FETCH`,\n  ACTIVITY_CREATE_HANDLE: `${PREFIX}/ACTIVITY_CREATE_HANDLE`,\n\n  /* Notifications */\n\n  ALL_NOTIFICATIONS_DELETE: `${PREFIX}/ALL_NOTIFICATIONS_DELETE`,\n  NOTIFICATION_CREATE_HANDLE: `${PREFIX}/NOTIFICATION_CREATE_HANDLE`,\n  NOTIFICATION_DELETE: `${PREFIX}/NOTIFICATION_DELETE`,\n  NOTIFICATION_DELETE_HANDLE: `${PREFIX}/NOTIFICATION_DELETE_HANDLE`,\n\n  /* Notification Services */\n\n  NOTIFICATION_SERVICE_IN_CURRENT_USER_CREATE: `${PREFIX}/NOTIFICATION_SERVICE_IN_CURRENT_USER_CREATE`,\n  NOTIFICATION_SERVICE_IN_BOARD_CREATE: `${PREFIX}/NOTIFICATION_SERVICE_IN_BOARD_CREATE`,\n  NOTIFICATION_SERVICE_CREATE_HANDLE: `${PREFIX}/NOTIFICATION_SERVICE_CREATE_HANDLE`,\n  NOTIFICATION_SERVICE_UPDATE: `${PREFIX}/NOTIFICATION_SERVICE_UPDATE`,\n  NOTIFICATION_SERVICE_UPDATE_HANDLE: `${PREFIX}/NOTIFICATION_SERVICE_UPDATE_HANDLE`,\n  NOTIFICATION_SERVICE_TEST: `${PREFIX}/NOTIFICATION_SERVICE_TEST`,\n  NOTIFICATION_SERVICE_DELETE: `${PREFIX}/NOTIFICATION_SERVICE_DELETE`,\n  NOTIFICATION_SERVICE_DELETE_HANDLE: `${PREFIX}/NOTIFICATION_SERVICE_DELETE_HANDLE`,\n};\n"
  },
  {
    "path": "client/src/constants/Enums.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport const SortOrders = {\n  ASC: 'asc',\n  DESC: 'desc',\n};\n\nexport const EditorModes = {\n  WYSIWYG: 'wysiwyg',\n  MARKUP: 'markup',\n};\n\nexport const HomeViews = {\n  GRID_PROJECTS: 'gridProjects',\n  GROUPED_PROJECTS: 'groupedProjects',\n};\n\nexport const UserRoles = {\n  ADMIN: 'admin',\n  PROJECT_OWNER: 'projectOwner',\n  BOARD_USER: 'boardUser',\n};\n\nexport const ProjectOrders = {\n  BY_DEFAULT: 'byDefault',\n  ALPHABETICALLY: 'alphabetically',\n  BY_CREATION_TIME: 'byCreationTime',\n};\n\nexport const ProjectGroups = {\n  MY_OWN: 'myOwn',\n  TEAM: 'team',\n  SHARED_WITH_ME: 'sharedWithMe',\n  OTHERS: 'others',\n};\n\nexport const ProjectTypes = {\n  PRIVATE: 'private',\n  SHARED: 'shared',\n};\n\nexport const ProjectBackgroundTypes = {\n  GRADIENT: 'gradient',\n  IMAGE: 'image',\n};\n\nexport const BoardViews = {\n  KANBAN: 'kanban',\n  GRID: 'grid',\n  LIST: 'list',\n};\n\nexport const BoardContexts = {\n  BOARD: 'board',\n  ARCHIVE: 'archive',\n  TRASH: 'trash',\n};\n\nexport const BoardMembershipRoles = {\n  EDITOR: 'editor',\n  VIEWER: 'viewer',\n};\n\nexport const ListTypes = {\n  ACTIVE: 'active',\n  CLOSED: 'closed',\n  ARCHIVE: 'archive',\n  TRASH: 'trash',\n};\n\nexport const ListTypeStates = {\n  OPENED: 'opened',\n  CLOSED: 'closed',\n};\n\nexport const ListSortFieldNames = {\n  NAME: 'name',\n  DUE_DATE: 'dueDate',\n  CREATED_AT: 'createdAt',\n};\n\nexport const CardTypes = {\n  PROJECT: 'project',\n  STORY: 'story',\n};\n\nexport const AttachmentTypes = {\n  FILE: 'file',\n  LINK: 'link',\n};\n\nexport const ActivityTypes = {\n  CREATE_CARD: 'createCard',\n  MOVE_CARD: 'moveCard',\n  ADD_MEMBER_TO_CARD: 'addMemberToCard',\n  REMOVE_MEMBER_FROM_CARD: 'removeMemberFromCard',\n  COMPLETE_TASK: 'completeTask',\n  UNCOMPLETE_TASK: 'uncompleteTask',\n};\n\nexport const NotificationTypes = {\n  MOVE_CARD: 'moveCard',\n  COMMENT_CARD: 'commentCard',\n  ADD_MEMBER_TO_CARD: 'addMemberToCard',\n  MENTION_IN_COMMENT: 'mentionInComment',\n};\n\nexport const NotificationServiceFormats = {\n  TEXT: 'text',\n  MARKDOWN: 'markdown',\n  HTML: 'html',\n};\n"
  },
  {
    "path": "client/src/constants/ErrorCodes.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst UNAUTHORIZED = 'E_UNAUTHORIZED';\nconst NOT_FOUND = 'E_NOT_FOUND';\nconst CONFLICT = 'E_CONFLICT';\n\nexport default {\n  UNAUTHORIZED,\n  NOT_FOUND,\n  CONFLICT,\n};\n"
  },
  {
    "path": "client/src/constants/Icons.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport {\n  BoardContexts,\n  BoardMembershipRoles,\n  BoardViews,\n  CardTypes,\n  HomeViews,\n  ListTypes,\n  ProjectGroups,\n  ProjectOrders,\n  ProjectTypes,\n  UserRoles,\n} from './Enums';\n\nexport const HomeViewIcons = {\n  [HomeViews.GRID_PROJECTS]: 'th',\n  [HomeViews.GROUPED_PROJECTS]: 'th list',\n};\n\nexport const UserRoleIcons = {\n  [UserRoles.ADMIN]: 'user secret',\n  [UserRoles.PROJECT_OWNER]: 'building',\n  [UserRoles.BOARD_USER]: 'columns',\n};\n\nexport const ProjectOrderIcons = {\n  [ProjectOrders.BY_DEFAULT]: 'sort',\n  [ProjectOrders.ALPHABETICALLY]: 'sort alphabet down',\n  [ProjectOrders.BY_CREATION_TIME]: 'clock outline',\n};\n\nexport const ProjectGroupIcons = {\n  [ProjectGroups.MY_OWN]: 'user',\n  [ProjectGroups.TEAM]: 'group',\n  [ProjectGroups.SHARED_WITH_ME]: 'location arrow',\n  [ProjectGroups.OTHERS]: 'user secret',\n};\n\nexport const ProjectTypeIcons = {\n  [ProjectTypes.PRIVATE]: 'privacy',\n  [ProjectTypes.SHARED]: 'group',\n};\n\nexport const BoardViewIcons = {\n  [BoardViews.KANBAN]: 'columns',\n  [BoardViews.GRID]: 'th',\n  [BoardViews.LIST]: 'unordered list',\n};\n\nexport const BoardContextIcons = {\n  [BoardContexts.BOARD]: 'suitcase',\n  [BoardContexts.ARCHIVE]: 'archive',\n  [BoardContexts.TRASH]: 'trash alternate',\n};\n\nexport const BoardMembershipRoleIcons = {\n  [BoardMembershipRoles.EDITOR]: 'star',\n  [BoardMembershipRoles.VIEWER]: 'eye',\n};\n\nexport const ListTypeIcons = {\n  [ListTypes.ACTIVE]: 'lightbulb',\n  [ListTypes.CLOSED]: 'flag checkered',\n  [ListTypes.ARCHIVE]: 'archive',\n  [ListTypes.TRASH]: 'trash alternate',\n};\n\nexport const CardTypeIcons = {\n  [CardTypes.PROJECT]: 'list alternate outline',\n  [CardTypes.STORY]: 'images outline',\n};\n"
  },
  {
    "path": "client/src/constants/LabelColors.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport default [\n  'muddy-grey',\n  'autumn-leafs',\n  'morning-sky',\n  'antique-blue',\n  'egg-yellow',\n  'desert-sand',\n  'dark-granite',\n  'fresh-salad',\n  'lagoon-blue',\n  'midnight-blue',\n  'light-orange',\n  'pumpkin-orange',\n  'light-concrete',\n  'sunny-grass',\n  'navy-blue',\n  'lilac-eyes',\n  'apricot-red',\n  'orange-peel',\n  'silver-glint',\n  'bright-moss',\n  'deep-ocean',\n  'summer-sky',\n  'berry-red',\n  'light-cocoa',\n  'grey-stone',\n  'tank-green',\n  'coral-green',\n  'sugar-plum',\n  'pink-tulip',\n  'shady-rust',\n  'wet-rock',\n  'wet-moss',\n  'turquoise-sea',\n  'lavender-fields',\n  'piggy-red',\n  'light-mud',\n  'gun-metal',\n  'modern-green',\n  'french-coast',\n  'sweet-lilac',\n  'red-burgundy',\n  'pirate-gold',\n];\n"
  },
  {
    "path": "client/src/constants/ListColors.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport default [\n  'berry-red',\n  'pumpkin-orange',\n  'lagoon-blue',\n  'pink-tulip',\n  'light-mud',\n  'orange-peel',\n  'bright-moss',\n  'antique-blue',\n  'dark-granite',\n  'turquoise-sea',\n];\n"
  },
  {
    "path": "client/src/constants/ListTypeStateByType.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { ListTypes, ListTypeStates } from './Enums';\n\nexport default {\n  [ListTypes.ACTIVE]: ListTypeStates.OPENED,\n  [ListTypes.CLOSED]: ListTypeStates.CLOSED,\n};\n"
  },
  {
    "path": "client/src/constants/ModalTypes.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst ADMINISTRATION = 'ADMINISTRATION';\nconst ABOUT = 'ABOUT';\nconst USER_SETTINGS = 'USER_SETTINGS';\nconst ADD_PROJECT = 'ADD_PROJECT';\nconst PROJECT_SETTINGS = 'PROJECT_SETTINGS';\nconst BOARD_SETTINGS = 'BOARD_SETTINGS';\nconst BOARD_ACTIVITIES = 'BOARD_ACTIVITIES';\n\nexport default {\n  ADMINISTRATION,\n  ABOUT,\n  USER_SETTINGS,\n  ADD_PROJECT,\n  PROJECT_SETTINGS,\n  BOARD_SETTINGS,\n  BOARD_ACTIVITIES,\n};\n"
  },
  {
    "path": "client/src/constants/Paths.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Config from './Config';\n\nconst ROOT = `${Config.BASE_PATH}/`;\nconst LOGIN = `${Config.BASE_PATH}/login`;\nconst OIDC_CALLBACK = `${Config.BASE_PATH}/oidc-callback`;\nconst PROJECTS = `${Config.BASE_PATH}/projects/:id`;\nconst BOARDS = `${Config.BASE_PATH}/boards/:id`;\nconst CARDS = `${Config.BASE_PATH}/cards/:id`;\n\nexport default {\n  ROOT,\n  LOGIN,\n  OIDC_CALLBACK,\n  PROJECTS,\n  BOARDS,\n  CARDS,\n};\n"
  },
  {
    "path": "client/src/constants/StaticUsers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport deletedUserAvatar from '../assets/images/deleted-user.png';\n\nexport const StaticUserIds = {\n  DELETED: null,\n};\n\nconst DELETED = {\n  id: StaticUserIds.DELETED,\n  name: 'deletedUser',\n  avatar: {\n    thumbnailUrls: {\n      cover180: deletedUserAvatar,\n    },\n  },\n};\n\nexport const STATIC_USER_BY_ID = {\n  [StaticUserIds.DELETED]: DELETED,\n};\n\nexport default {\n  DELETED,\n};\n"
  },
  {
    "path": "client/src/constants/ToastTypes.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst FILE_IS_TOO_BIG = 'FILE_IS_TOO_BIG';\nconst NOT_ENOUGH_STORAGE = 'NOT_ENOUGH_STORAGE';\nconst EMPTY_TRASH = 'EMPTY_TRASH';\nconst SOURCE_CARD_NOT_COPYABLE = 'SOURCE_CARD_NOT_COPYABLE';\nconst SOURCE_CARD_NOT_MOVABLE = 'SOURCE_CARD_NOT_MOVABLE';\n\nexport default {\n  FILE_IS_TOO_BIG,\n  NOT_ENOUGH_STORAGE,\n  EMPTY_TRASH,\n  SOURCE_CARD_NOT_COPYABLE,\n  SOURCE_CARD_NOT_MOVABLE,\n};\n"
  },
  {
    "path": "client/src/constants/WebhookEvents.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport default [\n  'actionCreate',\n\n  'attachmentCreate',\n  'attachmentUpdate',\n  'attachmentDelete',\n\n  'backgroundImageCreate',\n  'backgroundImageDelete',\n\n  'baseCustomFieldGroupCreate',\n  'baseCustomFieldGroupUpdate',\n  'baseCustomFieldGroupDelete',\n\n  'boardCreate',\n  'boardUpdate',\n  'boardDelete',\n\n  'boardMembershipCreate',\n  'boardMembershipUpdate',\n  'boardMembershipDelete',\n\n  'cardCreate',\n  'cardUpdate',\n  'cardDelete',\n\n  'cardLabelCreate',\n  'cardLabelDelete',\n\n  'cardMembershipCreate',\n  'cardMembershipDelete',\n\n  'commentCreate',\n  'commentUpdate',\n  'commentDelete',\n\n  'configUpdate',\n\n  'customFieldCreate',\n  'customFieldUpdate',\n  'customFieldDelete',\n\n  'customFieldGroupCreate',\n  'customFieldGroupUpdate',\n  'customFieldGroupDelete',\n\n  'customFieldValueUpdate',\n  'customFieldValueDelete',\n\n  'labelCreate',\n  'labelUpdate',\n  'labelDelete',\n\n  'listCreate',\n  'listUpdate',\n  'listClear',\n  'listDelete',\n\n  'notificationCreate',\n  'notificationUpdate',\n\n  'notificationServiceCreate',\n  'notificationServiceUpdate',\n  'notificationServiceDelete',\n\n  'projectCreate',\n  'projectUpdate',\n  'projectDelete',\n\n  'projectManagerCreate',\n  'projectManagerDelete',\n\n  'taskCreate',\n  'taskUpdate',\n  'taskDelete',\n\n  'taskListCreate',\n  'taskListUpdate',\n  'taskListDelete',\n\n  'userCreate',\n  'userUpdate',\n  'userDelete',\n\n  'webhookCreate',\n  'webhookUpdate',\n  'webhookDelete',\n];\n"
  },
  {
    "path": "client/src/contexts/BoardShortcutsContext.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createContext } from 'react';\n\nexport default createContext([null, null, null, null]);\n"
  },
  {
    "path": "client/src/contexts/ClosableContext.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createContext } from 'react';\n\nexport default createContext([null, null, null]);\n"
  },
  {
    "path": "client/src/contexts/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ClosableContext from './ClosableContext';\nimport BoardShortcutsContext from './BoardShortcutsContext';\n\nexport { ClosableContext, BoardShortcutsContext };\n"
  },
  {
    "path": "client/src/entry-actions/activities.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst fetchActivitiesInCurrentBoard = () => ({\n  type: EntryActionTypes.ACTIVITIES_IN_CURRENT_BOARD_FETCH,\n  payload: {},\n});\n\nconst fetchActivitiesInCurrentCard = () => ({\n  type: EntryActionTypes.ACTIVITIES_IN_CURRENT_CARD_FETCH,\n  payload: {},\n});\n\nconst handleActivityCreate = (activity) => ({\n  type: EntryActionTypes.ACTIVITY_CREATE_HANDLE,\n  payload: {\n    activity,\n  },\n});\n\nexport default {\n  fetchActivitiesInCurrentBoard,\n  fetchActivitiesInCurrentCard,\n  handleActivityCreate,\n};\n"
  },
  {
    "path": "client/src/entry-actions/attachments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createAttachmentInCurrentCard = (data) => ({\n  type: EntryActionTypes.ATTACHMENT_IN_CURRENT_CARD_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleAttachmentCreate = (attachment, requestId) => ({\n  type: EntryActionTypes.ATTACHMENT_CREATE_HANDLE,\n  payload: {\n    attachment,\n    requestId,\n  },\n});\n\nconst updateAttachment = (id, data) => ({\n  type: EntryActionTypes.ATTACHMENT_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleAttachmentUpdate = (attachment) => ({\n  type: EntryActionTypes.ATTACHMENT_UPDATE_HANDLE,\n  payload: {\n    attachment,\n  },\n});\n\nconst deleteAttachment = (id) => ({\n  type: EntryActionTypes.ATTACHMENT_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleAttachmentDelete = (attachment) => ({\n  type: EntryActionTypes.ATTACHMENT_DELETE_HANDLE,\n  payload: {\n    attachment,\n  },\n});\n\nexport default {\n  createAttachmentInCurrentCard,\n  handleAttachmentCreate,\n  updateAttachment,\n  handleAttachmentUpdate,\n  deleteAttachment,\n  handleAttachmentDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/background-images.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createBackgroundImageInCurrentProject = (data) => ({\n  type: EntryActionTypes.BACKGROUND_IMAGE_IN_CURRENT_PROJECT_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleBackgroundImageCreate = (backgroundImage, requestId) => ({\n  type: EntryActionTypes.BACKGROUND_IMAGE_CREATE_HANDLE,\n  payload: {\n    backgroundImage,\n    requestId,\n  },\n});\n\nconst deleteBackgroundImage = (id) => ({\n  type: EntryActionTypes.BACKGROUND_IMAGE_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleBackgroundImageDelete = (backgroundImage) => ({\n  type: EntryActionTypes.BACKGROUND_IMAGE_DELETE_HANDLE,\n  payload: {\n    backgroundImage,\n  },\n});\n\nexport default {\n  createBackgroundImageInCurrentProject,\n  handleBackgroundImageCreate,\n  deleteBackgroundImage,\n  handleBackgroundImageDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/base-custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createBaseCustomFieldGroupInCurrentProject = (data) => ({\n  type: EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_IN_CURRENT_PROJECT_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleBaseCustomFieldGroupCreate = (baseCustomFieldGroup) => ({\n  type: EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE,\n  payload: {\n    baseCustomFieldGroup,\n  },\n});\n\nconst updateBaseCustomFieldGroup = (id, data) => ({\n  type: EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleBaseCustomFieldGroupUpdate = (baseCustomFieldGroup) => ({\n  type: EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE,\n  payload: {\n    baseCustomFieldGroup,\n  },\n});\n\nconst deleteBaseCustomFieldGroup = (id) => ({\n  type: EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleBaseCustomFieldGroupDelete = (baseCustomFieldGroup) => ({\n  type: EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE,\n  payload: {\n    baseCustomFieldGroup,\n  },\n});\n\nexport default {\n  createBaseCustomFieldGroupInCurrentProject,\n  handleBaseCustomFieldGroupCreate,\n  updateBaseCustomFieldGroup,\n  handleBaseCustomFieldGroupUpdate,\n  deleteBaseCustomFieldGroup,\n  handleBaseCustomFieldGroupDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/board-memberships.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createMembershipInCurrentBoard = (data) => ({\n  type: EntryActionTypes.MEMBERSHIP_IN_CURRENT_BOARD_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleBoardMembershipCreate = (boardMembership, users) => ({\n  type: EntryActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE,\n  payload: {\n    boardMembership,\n    users,\n  },\n});\n\nconst updateBoardMembership = (id, data) => ({\n  type: EntryActionTypes.BOARD_MEMBERSHIP_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleBoardMembershipUpdate = (boardMembership) => ({\n  type: EntryActionTypes.BOARD_MEMBERSHIP_UPDATE_HANDLE,\n  payload: {\n    boardMembership,\n  },\n});\n\nconst deleteBoardMembership = (id) => ({\n  type: EntryActionTypes.BOARD_MEMBERSHIP_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleBoardMembershipDelete = (boardMembership) => ({\n  type: EntryActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE,\n  payload: {\n    boardMembership,\n  },\n});\n\nexport default {\n  createMembershipInCurrentBoard,\n  handleBoardMembershipCreate,\n  updateBoardMembership,\n  handleBoardMembershipUpdate,\n  deleteBoardMembership,\n  handleBoardMembershipDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/boards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createBoardInCurrentProject = (data) => ({\n  type: EntryActionTypes.BOARD_IN_CURRENT_PROJECT_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleBoardCreate = (board, boardMemberships, requestId) => ({\n  type: EntryActionTypes.BOARD_CREATE_HANDLE,\n  payload: {\n    board,\n    boardMemberships,\n    requestId,\n  },\n});\n\nconst fetchBoard = (id) => ({\n  type: EntryActionTypes.BOARD_FETCH,\n  payload: {\n    id,\n  },\n});\n\nconst updateBoard = (id, data) => ({\n  type: EntryActionTypes.BOARD_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst updateCurrentBoard = (data) => ({\n  type: EntryActionTypes.CURRENT_BOARD_UPDATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleBoardUpdate = (board) => ({\n  type: EntryActionTypes.BOARD_UPDATE_HANDLE,\n  payload: {\n    board,\n  },\n});\n\nconst moveBoard = (id, index) => ({\n  type: EntryActionTypes.BOARD_MOVE,\n  payload: {\n    id,\n    index,\n  },\n});\n\nconst updateContextInCurrentBoard = (value) => ({\n  type: EntryActionTypes.CONTEXT_IN_CURRENT_BOARD_UPDATE,\n  payload: {\n    value,\n  },\n});\n\nconst updateViewInCurrentBoard = (value) => ({\n  type: EntryActionTypes.VIEW_IN_CURRENT_BOARD_UPDATE,\n  payload: {\n    value,\n  },\n});\n\nconst searchInCurrentBoard = (value) => ({\n  type: EntryActionTypes.IN_CURRENT_BOARD_SEARCH,\n  payload: {\n    value,\n  },\n});\n\nconst deleteBoard = (id) => ({\n  type: EntryActionTypes.BOARD_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleBoardDelete = (board) => ({\n  type: EntryActionTypes.BOARD_DELETE_HANDLE,\n  payload: {\n    board,\n  },\n});\n\nexport default {\n  createBoardInCurrentProject,\n  handleBoardCreate,\n  fetchBoard,\n  updateBoard,\n  updateCurrentBoard,\n  handleBoardUpdate,\n  moveBoard,\n  updateContextInCurrentBoard,\n  updateViewInCurrentBoard,\n  searchInCurrentBoard,\n  deleteBoard,\n  handleBoardDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/bootstrap.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst handleBootstrapUpdate = (bootstrap) => ({\n  type: EntryActionTypes.BOOTSTRAP_UPDATE_HANDLE,\n  payload: {\n    bootstrap,\n  },\n});\n\nexport default {\n  handleBootstrapUpdate,\n};\n"
  },
  {
    "path": "client/src/entry-actions/cards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst fetchCardsInCurrentList = () => ({\n  type: EntryActionTypes.CARDS_IN_CURRENT_LIST_FETCH,\n  payload: {},\n});\n\nconst handleCardsUpdate = (cards, activities) => ({\n  type: EntryActionTypes.CARDS_UPDATE_HANDLE,\n  payload: {\n    cards,\n    activities,\n  },\n});\n\nconst createCard = (listId, data, index, autoOpen = false) => ({\n  type: EntryActionTypes.CARD_CREATE,\n  payload: {\n    listId,\n    data,\n    index,\n    autoOpen,\n  },\n});\n\nconst createCardInCurrentContext = (data, index = 0, autoOpen = false) => ({\n  type: EntryActionTypes.CARD_IN_CURRENT_CONTEXT_CREATE,\n  payload: {\n    data,\n    index,\n    autoOpen,\n  },\n});\n\nconst createCardInCurrentList = (data, autoOpen = false) => ({\n  type: EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE,\n  payload: {\n    data,\n    autoOpen,\n  },\n});\n\nconst handleCardCreate = (card) => ({\n  type: EntryActionTypes.CARD_CREATE_HANDLE,\n  payload: {\n    card,\n  },\n});\n\nconst updateCard = (id, data) => ({\n  type: EntryActionTypes.CARD_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst updateCurrentCard = (data) => ({\n  type: EntryActionTypes.CURRENT_CARD_UPDATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleCardUpdate = (card) => ({\n  type: EntryActionTypes.CARD_UPDATE_HANDLE,\n  payload: {\n    card,\n  },\n});\n\nconst moveCard = (id, listId, index = 0) => ({\n  type: EntryActionTypes.CARD_MOVE,\n  payload: {\n    id,\n    listId,\n    index,\n  },\n});\n\nconst moveCurrentCard = (listId, index = 0, autoClose = false) => ({\n  type: EntryActionTypes.CURRENT_CARD_MOVE,\n  payload: {\n    listId,\n    index,\n    autoClose,\n  },\n});\n\nconst moveCardToArchive = (id) => ({\n  type: EntryActionTypes.CARD_TO_ARCHIVE_MOVE,\n  payload: {\n    id,\n  },\n});\n\nconst moveCurrentCardToArchive = () => ({\n  type: EntryActionTypes.CURRENT_CARD_TO_ARCHIVE_MOVE,\n  payload: {},\n});\n\nconst moveCardToTrash = (id) => ({\n  type: EntryActionTypes.CARD_TO_TRASH_MOVE,\n  payload: {\n    id,\n  },\n});\n\nconst moveCurrentCardToTrash = () => ({\n  type: EntryActionTypes.CURRENT_CARD_TO_TRASH_MOVE,\n  payload: {},\n});\n\nconst transferCard = (id, boardId, listId, index = 0) => ({\n  type: EntryActionTypes.CARD_TRANSFER,\n  payload: {\n    id,\n    boardId,\n    listId,\n    index,\n  },\n});\n\nconst transferCurrentCard = (boardId, listId, index = 0) => ({\n  type: EntryActionTypes.CURRENT_CARD_TRANSFER,\n  payload: {\n    boardId,\n    listId,\n    index,\n  },\n});\n\nconst duplicateCard = (id, data = {}) => ({\n  type: EntryActionTypes.CARD_DUPLICATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst duplicateCurrentCard = (data = {}) => ({\n  type: EntryActionTypes.CURRENT_CARD_DUPLICATE,\n  payload: {\n    data,\n  },\n});\n\nconst copyCard = (id) => ({\n  type: EntryActionTypes.CARD_COPY,\n  payload: {\n    id,\n  },\n});\n\nconst cutCard = (id) => ({\n  type: EntryActionTypes.CARD_CUT,\n  payload: {\n    id,\n  },\n});\n\nconst pasteCard = (listId) => ({\n  type: EntryActionTypes.CARD_PASTE,\n  payload: {\n    listId,\n  },\n});\n\nconst pasteCardInCurrentContext = () => ({\n  type: EntryActionTypes.CARD_IN_CURRENT_CONTEXT_PASTE,\n  payload: {},\n});\n\nconst pasteCardInCurrentList = () => ({\n  type: EntryActionTypes.CARD_IN_CURRENT_LIST_PASTE,\n  payload: {},\n});\n\nconst goToAdjacentCard = (direction) => ({\n  type: EntryActionTypes.TO_ADJACENT_CARD_GO,\n  payload: {\n    direction,\n  },\n});\n\nconst deleteCard = (id) => ({\n  type: EntryActionTypes.CARD_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst deleteCurrentCard = () => ({\n  type: EntryActionTypes.CURRENT_CARD_DELETE,\n  payload: {},\n});\n\nconst handleCardDelete = (card) => ({\n  type: EntryActionTypes.CARD_DELETE_HANDLE,\n  payload: {\n    card,\n  },\n});\n\nexport default {\n  fetchCardsInCurrentList,\n  handleCardsUpdate,\n  createCard,\n  createCardInCurrentContext,\n  createCardInCurrentList,\n  handleCardCreate,\n  updateCard,\n  updateCurrentCard,\n  handleCardUpdate,\n  moveCard,\n  moveCurrentCard,\n  moveCardToArchive,\n  moveCurrentCardToArchive,\n  moveCardToTrash,\n  moveCurrentCardToTrash,\n  transferCard,\n  transferCurrentCard,\n  duplicateCard,\n  duplicateCurrentCard,\n  copyCard,\n  cutCard,\n  pasteCard,\n  pasteCardInCurrentContext,\n  pasteCardInCurrentList,\n  goToAdjacentCard,\n  deleteCard,\n  deleteCurrentCard,\n  handleCardDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/comments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst fetchCommentsInCurrentCard = () => ({\n  type: EntryActionTypes.COMMENTS_IN_CURRENT_CARD_FETCH,\n  payload: {},\n});\n\nconst createCommentInCurrentCard = (data) => ({\n  type: EntryActionTypes.COMMENT_IN_CURRENT_CARD_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleCommentCreate = (comment, users) => ({\n  type: EntryActionTypes.COMMENT_CREATE_HANDLE,\n  payload: {\n    comment,\n    users,\n  },\n});\n\nconst updateComment = (id, data) => ({\n  type: EntryActionTypes.COMMENT_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleCommentUpdate = (comment) => ({\n  type: EntryActionTypes.COMMENT_UPDATE_HANDLE,\n  payload: {\n    comment,\n  },\n});\n\nconst deleteComment = (id) => ({\n  type: EntryActionTypes.COMMENT_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleCommentDelete = (comment) => ({\n  type: EntryActionTypes.COMMENT_DELETE_HANDLE,\n  payload: {\n    comment,\n  },\n});\n\nexport default {\n  fetchCommentsInCurrentCard,\n  createCommentInCurrentCard,\n  handleCommentCreate,\n  updateComment,\n  handleCommentUpdate,\n  deleteComment,\n  handleCommentDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/config.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst updateConfig = (data) => ({\n  type: EntryActionTypes.CONFIG_UPDATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleConfigUpdate = (config) => ({\n  type: EntryActionTypes.CONFIG_UPDATE_HANDLE,\n  payload: {\n    config,\n  },\n});\n\nconst testSmtpConfig = () => ({\n  type: EntryActionTypes.SMTP_CONFIG_TEST,\n  payload: {},\n});\n\nexport default {\n  updateConfig,\n  handleConfigUpdate,\n  testSmtpConfig,\n};\n"
  },
  {
    "path": "client/src/entry-actions/core.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst toggleFavorites = (isEnabled) => ({\n  type: EntryActionTypes.FAVORITES_TOGGLE,\n  payload: {\n    isEnabled,\n  },\n});\n\nconst toggleEditMode = (isEnabled) => ({\n  type: EntryActionTypes.EDIT_MODE_TOGGLE,\n  payload: {\n    isEnabled,\n  },\n});\n\nconst updateHomeView = (value) => ({\n  type: EntryActionTypes.HOME_VIEW_UPDATE,\n  payload: {\n    value,\n  },\n});\n\nconst logout = (revokeAccessToken = true) => ({\n  type: EntryActionTypes.LOGOUT,\n  payload: {\n    revokeAccessToken,\n  },\n});\n\nexport default {\n  toggleFavorites,\n  toggleEditMode,\n  updateHomeView,\n  logout,\n};\n"
  },
  {
    "path": "client/src/entry-actions/custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createCustomFieldGroupInCurrentBoard = (data) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_GROUP_IN_CURRENT_BOARD_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst createCustomFieldGroupInCurrentCard = (data) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_GROUP_IN_CURRENT_CARD_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleCustomFieldGroupCreate = (customFieldGroup) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_GROUP_CREATE_HANDLE,\n  payload: {\n    customFieldGroup,\n  },\n});\n\nconst updateCustomFieldGroup = (id, data) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_GROUP_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleCustomFieldGroupUpdate = (customFieldGroup) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_GROUP_UPDATE_HANDLE,\n  payload: {\n    customFieldGroup,\n  },\n});\n\nconst moveCustomFieldGroup = (id, index) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_GROUP_MOVE,\n  payload: {\n    id,\n    index,\n  },\n});\n\nconst deleteCustomFieldGroup = (id) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_GROUP_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleCustomFieldGroupDelete = (customFieldGroup) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_GROUP_DELETE_HANDLE,\n  payload: {\n    customFieldGroup,\n  },\n});\n\nexport default {\n  createCustomFieldGroupInCurrentBoard,\n  createCustomFieldGroupInCurrentCard,\n  handleCustomFieldGroupCreate,\n  updateCustomFieldGroup,\n  handleCustomFieldGroupUpdate,\n  moveCustomFieldGroup,\n  deleteCustomFieldGroup,\n  handleCustomFieldGroupDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/custom-field-values.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst updateCustomFieldValue = (cardId, customFieldGroupId, customFieldId, data) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_VALUE_UPDATE,\n  payload: {\n    cardId,\n    customFieldGroupId,\n    customFieldId,\n    data,\n  },\n});\n\nconst handleCustomFieldValueUpdate = (customFieldValue) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_VALUE_UPDATE_HANDLE,\n  payload: {\n    customFieldValue,\n  },\n});\n\nconst deleteCustomFieldValue = (cardId, customFieldGroupId, customFieldId) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_VALUE_DELETE,\n  payload: {\n    cardId,\n    customFieldGroupId,\n    customFieldId,\n  },\n});\n\nconst handleCustomFieldValueDelete = (customFieldValue) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_VALUE_DELETE_HANDLE,\n  payload: {\n    customFieldValue,\n  },\n});\n\nexport default {\n  updateCustomFieldValue,\n  handleCustomFieldValueUpdate,\n  deleteCustomFieldValue,\n  handleCustomFieldValueDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/custom-fields.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createCustomFieldInBaseGroup = (baseCustomFieldGroupId, data) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_IN_BASE_GROUP_CREATE,\n  payload: {\n    baseCustomFieldGroupId,\n    data,\n  },\n});\n\nconst createCustomFieldInGroup = (customFieldGroupId, data) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_IN_GROUP_CREATE,\n  payload: {\n    customFieldGroupId,\n    data,\n  },\n});\n\nconst handleCustomFieldCreate = (customField) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_CREATE_HANDLE,\n  payload: {\n    customField,\n  },\n});\n\nconst updateCustomField = (id, data) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleCustomFieldUpdate = (customField) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_UPDATE_HANDLE,\n  payload: {\n    customField,\n  },\n});\n\nconst moveCustomField = (id, index) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_MOVE,\n  payload: {\n    id,\n    index,\n  },\n});\n\nconst deleteCustomField = (id) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleCustomFieldDelete = (customField) => ({\n  type: EntryActionTypes.CUSTOM_FIELD_DELETE_HANDLE,\n  payload: {\n    customField,\n  },\n});\n\nexport default {\n  createCustomFieldInBaseGroup,\n  createCustomFieldInGroup,\n  handleCustomFieldCreate,\n  updateCustomField,\n  handleCustomFieldUpdate,\n  moveCustomField,\n  deleteCustomField,\n  handleCustomFieldDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport socket from './socket';\nimport bootstrap from './bootstrap';\nimport login from './login';\nimport core from './core';\nimport modals from './modals';\nimport config from './config';\nimport webhooks from './webhooks';\nimport users from './users';\nimport projects from './projects';\nimport projectManagers from './project-managers';\nimport backgroundImages from './background-images';\nimport baseCustomFieldGroups from './base-custom-field-groups';\nimport boards from './boards';\nimport boardMemberships from './board-memberships';\nimport labels from './labels';\nimport lists from './lists';\nimport cards from './cards';\nimport taskLists from './task-lists';\nimport tasks from './tasks';\nimport attachments from './attachments';\nimport customFieldGroups from './custom-field-groups';\nimport customFields from './custom-fields';\nimport customFieldValues from './custom-field-values';\nimport comments from './comments';\nimport activities from './activities';\nimport notifications from './notifications';\nimport notificationServices from './notification-services';\n\nexport default {\n  ...socket,\n  ...bootstrap,\n  ...login,\n  ...core,\n  ...modals,\n  ...config,\n  ...webhooks,\n  ...users,\n  ...projects,\n  ...projectManagers,\n  ...backgroundImages,\n  ...baseCustomFieldGroups,\n  ...boards,\n  ...boardMemberships,\n  ...labels,\n  ...lists,\n  ...cards,\n  ...taskLists,\n  ...tasks,\n  ...attachments,\n  ...customFieldGroups,\n  ...customFields,\n  ...customFieldValues,\n  ...comments,\n  ...activities,\n  ...notifications,\n  ...notificationServices,\n};\n"
  },
  {
    "path": "client/src/entry-actions/labels.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createLabelInCurrentBoard = (data) => ({\n  type: EntryActionTypes.LABEL_IN_CURRENT_BOARD_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst createLabelFromCard = (cardId, data) => ({\n  type: EntryActionTypes.LABEL_FROM_CARD_CREATE,\n  payload: {\n    cardId,\n    data,\n  },\n});\n\nconst handleLabelCreate = (label) => ({\n  type: EntryActionTypes.LABEL_CREATE_HANDLE,\n  payload: {\n    label,\n  },\n});\n\nconst updateLabel = (id, data) => ({\n  type: EntryActionTypes.LABEL_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleLabelUpdate = (label) => ({\n  type: EntryActionTypes.LABEL_UPDATE_HANDLE,\n  payload: {\n    label,\n  },\n});\n\nconst moveLabel = (id, index) => ({\n  type: EntryActionTypes.LABEL_MOVE,\n  payload: {\n    id,\n    index,\n  },\n});\n\nconst deleteLabel = (id) => ({\n  type: EntryActionTypes.LABEL_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleLabelDelete = (label) => ({\n  type: EntryActionTypes.LABEL_DELETE_HANDLE,\n  payload: {\n    label,\n  },\n});\n\nconst addLabelToCard = (id, cardId) => ({\n  type: EntryActionTypes.LABEL_TO_CARD_ADD,\n  payload: {\n    id,\n    cardId,\n  },\n});\n\nconst addLabelToCurrentCard = (id) => ({\n  type: EntryActionTypes.LABEL_TO_CURRENT_CARD_ADD,\n  payload: {\n    id,\n  },\n});\n\nconst handleLabelToCardAdd = (cardLabel) => ({\n  type: EntryActionTypes.LABEL_TO_CARD_ADD_HANDLE,\n  payload: {\n    cardLabel,\n  },\n});\n\nconst removeLabelFromCard = (id, cardId) => ({\n  type: EntryActionTypes.LABEL_FROM_CARD_REMOVE,\n  payload: {\n    id,\n    cardId,\n  },\n});\n\nconst removeLabelFromCurrentCard = (id) => ({\n  type: EntryActionTypes.LABEL_FROM_CURRENT_CARD_REMOVE,\n  payload: {\n    id,\n  },\n});\n\nconst handleLabelFromCardRemove = (cardLabel) => ({\n  type: EntryActionTypes.LABEL_FROM_CARD_REMOVE_HANDLE,\n  payload: {\n    cardLabel,\n  },\n});\n\nconst addLabelToFilterInCurrentBoard = (id) => ({\n  type: EntryActionTypes.LABEL_TO_FILTER_IN_CURRENT_BOARD_ADD,\n  payload: {\n    id,\n  },\n});\n\nconst removeLabelFromFilterInCurrentBoard = (id) => ({\n  type: EntryActionTypes.LABEL_FROM_FILTER_IN_CURRENT_BOARD_REMOVE,\n  payload: {\n    id,\n  },\n});\n\nexport default {\n  createLabelInCurrentBoard,\n  createLabelFromCard,\n  handleLabelCreate,\n  updateLabel,\n  handleLabelUpdate,\n  moveLabel,\n  deleteLabel,\n  handleLabelDelete,\n  addLabelToCard,\n  addLabelToCurrentCard,\n  handleLabelToCardAdd,\n  removeLabelFromCard,\n  removeLabelFromCurrentCard,\n  handleLabelFromCardRemove,\n  addLabelToFilterInCurrentBoard,\n  removeLabelFromFilterInCurrentBoard,\n};\n"
  },
  {
    "path": "client/src/entry-actions/lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createListInCurrentBoard = (data) => ({\n  type: EntryActionTypes.LIST_IN_CURRENT_BOARD_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleListCreate = (list) => ({\n  type: EntryActionTypes.LIST_CREATE_HANDLE,\n  payload: {\n    list,\n  },\n});\n\nconst updateList = (id, data) => ({\n  type: EntryActionTypes.LIST_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleListUpdate = (list) => ({\n  type: EntryActionTypes.LIST_UPDATE_HANDLE,\n  payload: {\n    list,\n  },\n});\n\nconst moveList = (id, index) => ({\n  type: EntryActionTypes.LIST_MOVE,\n  payload: {\n    id,\n    index,\n  },\n});\n\nconst transferList = (id, boardId, index = 0) => ({\n  type: EntryActionTypes.LIST_TRANSFER,\n  payload: {\n    id,\n    boardId,\n    index,\n  },\n});\n\nconst sortList = (id, data) => {\n  return {\n    type: EntryActionTypes.LIST_SORT,\n    payload: {\n      id,\n      data,\n    },\n  };\n};\n\nconst moveListCardsToArchiveList = (id) => ({\n  type: EntryActionTypes.LIST_CARDS_TO_ARCHIVE_LIST_MOVE,\n  payload: {\n    id,\n  },\n});\n\nconst clearTrashListInCurrentBoard = () => ({\n  type: EntryActionTypes.TRASH_LIST_IN_CURRENT_BOARD_CLEAR,\n  payload: {},\n});\n\nconst handleListClear = (list) => ({\n  type: EntryActionTypes.LIST_CLEAR_HANDLE,\n  payload: {\n    list,\n  },\n});\n\nconst deleteList = (id) => ({\n  type: EntryActionTypes.LIST_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleListDelete = (list, cards) => ({\n  type: EntryActionTypes.LIST_DELETE_HANDLE,\n  payload: {\n    list,\n    cards,\n  },\n});\n\nexport default {\n  createListInCurrentBoard,\n  handleListCreate,\n  updateList,\n  handleListUpdate,\n  moveList,\n  transferList,\n  sortList,\n  moveListCardsToArchiveList,\n  clearTrashListInCurrentBoard,\n  handleListClear,\n  deleteList,\n  handleListDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/login.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst authenticate = (data) => ({\n  type: EntryActionTypes.AUTHENTICATE,\n  payload: {\n    data,\n  },\n});\n\nconst authenticateWithOidc = () => ({\n  type: EntryActionTypes.WITH_OIDC_AUTHENTICATE,\n  payload: {},\n});\n\nconst clearAuthenticateError = () => ({\n  type: EntryActionTypes.AUTHENTICATE_ERROR_CLEAR,\n  payload: {},\n});\n\nconst acceptTerms = (signature) => ({\n  type: EntryActionTypes.TERMS_ACCEPT,\n  payload: {\n    signature,\n  },\n});\n\nconst cancelTerms = () => ({\n  type: EntryActionTypes.TERMS_CANCEL,\n  payload: {},\n});\n\nconst updateTermsLanguage = (value) => ({\n  type: EntryActionTypes.TERMS_LANGUAGE_UPDATE,\n  payload: {\n    value,\n  },\n});\n\nexport default {\n  authenticate,\n  authenticateWithOidc,\n  clearAuthenticateError,\n  acceptTerms,\n  cancelTerms,\n  updateTermsLanguage,\n};\n"
  },
  {
    "path": "client/src/entry-actions/modals.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\nimport ModalTypes from '../constants/ModalTypes';\n\nconst openAdministrationModal = () => ({\n  type: EntryActionTypes.MODAL_OPEN,\n  payload: {\n    type: ModalTypes.ADMINISTRATION,\n  },\n});\n\nconst openAboutModal = () => ({\n  type: EntryActionTypes.MODAL_OPEN,\n  payload: {\n    type: ModalTypes.ABOUT,\n  },\n});\n\nconst openUserSettingsModal = () => ({\n  type: EntryActionTypes.MODAL_OPEN,\n  payload: {\n    type: ModalTypes.USER_SETTINGS,\n  },\n});\n\nconst openAddProjectModal = (defaultProjectType) => ({\n  type: EntryActionTypes.MODAL_OPEN,\n  payload: {\n    type: ModalTypes.ADD_PROJECT,\n    params: {\n      defaultType: defaultProjectType,\n    },\n  },\n});\n\nconst openProjectSettingsModal = () => ({\n  type: EntryActionTypes.MODAL_OPEN,\n  payload: {\n    type: ModalTypes.PROJECT_SETTINGS,\n  },\n});\n\nconst openBoardSettingsModal = (boardId) => ({\n  type: EntryActionTypes.MODAL_OPEN,\n  payload: {\n    type: ModalTypes.BOARD_SETTINGS,\n    params: {\n      id: boardId,\n    },\n  },\n});\n\nconst openBoardActivitiesModal = () => ({\n  type: EntryActionTypes.MODAL_OPEN,\n  payload: {\n    type: ModalTypes.BOARD_ACTIVITIES,\n  },\n});\n\nconst closeModal = () => ({\n  type: EntryActionTypes.MODAL_CLOSE,\n  payload: {},\n});\n\nexport default {\n  openAdministrationModal,\n  openAboutModal,\n  openUserSettingsModal,\n  openAddProjectModal,\n  openProjectSettingsModal,\n  openBoardSettingsModal,\n  openBoardActivitiesModal,\n  closeModal,\n};\n"
  },
  {
    "path": "client/src/entry-actions/notification-services.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createNotificationServiceInCurrentUser = (data) => ({\n  type: EntryActionTypes.NOTIFICATION_SERVICE_IN_CURRENT_USER_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst createNotificationServiceInBoard = (boardId, data) => ({\n  type: EntryActionTypes.NOTIFICATION_SERVICE_IN_BOARD_CREATE,\n  payload: {\n    boardId,\n    data,\n  },\n});\n\nconst handleNotificationServiceCreate = (notificationService) => ({\n  type: EntryActionTypes.NOTIFICATION_SERVICE_CREATE_HANDLE,\n  payload: {\n    notificationService,\n  },\n});\n\nconst updateNotificationService = (id, data) => ({\n  type: EntryActionTypes.NOTIFICATION_SERVICE_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleNotificationServiceUpdate = (notificationService) => ({\n  type: EntryActionTypes.NOTIFICATION_SERVICE_UPDATE_HANDLE,\n  payload: {\n    notificationService,\n  },\n});\n\nconst testNotificationService = (id) => ({\n  type: EntryActionTypes.NOTIFICATION_SERVICE_TEST,\n  payload: {\n    id,\n  },\n});\n\nconst deleteNotificationService = (id) => ({\n  type: EntryActionTypes.NOTIFICATION_SERVICE_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleNotificationServiceDelete = (notificationService) => ({\n  type: EntryActionTypes.NOTIFICATION_SERVICE_DELETE_HANDLE,\n  payload: {\n    notificationService,\n  },\n});\n\nexport default {\n  createNotificationServiceInCurrentUser,\n  createNotificationServiceInBoard,\n  handleNotificationServiceCreate,\n  updateNotificationService,\n  handleNotificationServiceUpdate,\n  testNotificationService,\n  deleteNotificationService,\n  handleNotificationServiceDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/notifications.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst deleteAllNotifications = () => ({\n  type: EntryActionTypes.ALL_NOTIFICATIONS_DELETE,\n  payload: {},\n});\n\nconst handleNotificationCreate = (notification, users) => ({\n  type: EntryActionTypes.NOTIFICATION_CREATE_HANDLE,\n  payload: {\n    notification,\n    users,\n  },\n});\n\nconst deleteNotification = (id) => ({\n  type: EntryActionTypes.NOTIFICATION_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleNotificationDelete = (notification) => ({\n  type: EntryActionTypes.NOTIFICATION_DELETE_HANDLE,\n  payload: {\n    notification,\n  },\n});\n\nexport default {\n  deleteAllNotifications,\n  handleNotificationCreate,\n  deleteNotification,\n  handleNotificationDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/project-managers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createManagerInCurrentProject = (data) => ({\n  type: EntryActionTypes.MANAGER_IN_CURRENT_PROJECT_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleProjectManagerCreate = (projectManager, users) => ({\n  type: EntryActionTypes.PROJECT_MANAGER_CREATE_HANDLE,\n  payload: {\n    projectManager,\n    users,\n  },\n});\n\nconst deleteProjectManager = (id) => ({\n  type: EntryActionTypes.PROJECT_MANAGER_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleProjectManagerDelete = (projectManager) => ({\n  type: EntryActionTypes.PROJECT_MANAGER_DELETE_HANDLE,\n  payload: {\n    projectManager,\n  },\n});\n\nexport default {\n  createManagerInCurrentProject,\n  handleProjectManagerCreate,\n  deleteProjectManager,\n  handleProjectManagerDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/projects.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst searchProjects = (value) => ({\n  type: EntryActionTypes.PROJECTS_SEARCH,\n  payload: {\n    value,\n  },\n});\n\nconst updateProjectsOrder = (value) => ({\n  type: EntryActionTypes.PROJECTS_ORDER_UPDATE,\n  payload: {\n    value,\n  },\n});\n\nconst toggleHiddenProjects = (isVisible) => ({\n  type: EntryActionTypes.HIDDEN_PROJECTS_TOGGLE,\n  payload: {\n    isVisible,\n  },\n});\n\nconst createProject = (data) => ({\n  type: EntryActionTypes.PROJECT_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleProjectCreate = (project) => ({\n  type: EntryActionTypes.PROJECT_CREATE_HANDLE,\n  payload: {\n    project,\n  },\n});\n\nconst updateProject = (id, data) => ({\n  type: EntryActionTypes.PROJECT_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst updateCurrentProject = (data) => ({\n  type: EntryActionTypes.CURRENT_PROJECT_UPDATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleProjectUpdate = (project) => ({\n  type: EntryActionTypes.PROJECT_UPDATE_HANDLE,\n  payload: {\n    project,\n  },\n});\n\nconst deleteCurrentProject = () => ({\n  type: EntryActionTypes.CURRENT_PROJECT_DELETE,\n  payload: {},\n});\n\nconst handleProjectDelete = (project) => ({\n  type: EntryActionTypes.PROJECT_DELETE_HANDLE,\n  payload: {\n    project,\n  },\n});\n\nexport default {\n  searchProjects,\n  updateProjectsOrder,\n  toggleHiddenProjects,\n  createProject,\n  handleProjectCreate,\n  updateProject,\n  updateCurrentProject,\n  handleProjectUpdate,\n  deleteCurrentProject,\n  handleProjectDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/socket.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst handleSocketDisconnect = () => ({\n  type: EntryActionTypes.SOCKET_DISCONNECT_HANDLE,\n  payload: {},\n});\n\nconst handleSocketReconnect = () => ({\n  type: EntryActionTypes.SOCKET_RECONNECT_HANDLE,\n  payload: {},\n});\n\nexport default {\n  handleSocketDisconnect,\n  handleSocketReconnect,\n};\n"
  },
  {
    "path": "client/src/entry-actions/task-lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createTaskListInCurrentCard = (data) => ({\n  type: EntryActionTypes.TASK_LIST_IN_CURRENT_CARD_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleTaskListCreate = (taskList) => ({\n  type: EntryActionTypes.TASK_LIST_CREATE_HANDLE,\n  payload: {\n    taskList,\n  },\n});\n\nconst updateTaskList = (id, data) => ({\n  type: EntryActionTypes.TASK_LIST_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleTaskListUpdate = (taskList) => ({\n  type: EntryActionTypes.TASK_LIST_UPDATE_HANDLE,\n  payload: {\n    taskList,\n  },\n});\n\nconst moveTaskList = (id, index) => ({\n  type: EntryActionTypes.TASK_LIST_MOVE,\n  payload: {\n    id,\n    index,\n  },\n});\n\nconst deleteTaskList = (id) => ({\n  type: EntryActionTypes.TASK_LIST_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleTaskListDelete = (taskList) => ({\n  type: EntryActionTypes.TASK_LIST_DELETE_HANDLE,\n  payload: {\n    taskList,\n  },\n});\n\nexport default {\n  createTaskListInCurrentCard,\n  handleTaskListCreate,\n  updateTaskList,\n  handleTaskListUpdate,\n  moveTaskList,\n  deleteTaskList,\n  handleTaskListDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/tasks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createTask = (taskListId, data) => ({\n  type: EntryActionTypes.TASK_CREATE,\n  payload: {\n    taskListId,\n    data,\n  },\n});\n\nconst handleTaskCreate = (task) => ({\n  type: EntryActionTypes.TASK_CREATE_HANDLE,\n  payload: {\n    task,\n  },\n});\n\nconst updateTask = (id, data) => ({\n  type: EntryActionTypes.TASK_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleTaskUpdate = (task) => ({\n  type: EntryActionTypes.TASK_UPDATE_HANDLE,\n  payload: {\n    task,\n  },\n});\n\nconst moveTask = (id, taskListId, index) => ({\n  type: EntryActionTypes.TASK_MOVE,\n  payload: {\n    id,\n    taskListId,\n    index,\n  },\n});\n\nconst deleteTask = (id) => ({\n  type: EntryActionTypes.TASK_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleTaskDelete = (task) => ({\n  type: EntryActionTypes.TASK_DELETE_HANDLE,\n  payload: {\n    task,\n  },\n});\n\nexport default {\n  createTask,\n  handleTaskCreate,\n  updateTask,\n  handleTaskUpdate,\n  moveTask,\n  deleteTask,\n  handleTaskDelete,\n};\n"
  },
  {
    "path": "client/src/entry-actions/users.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst handleUsersReset = () => ({\n  type: EntryActionTypes.USERS_RESET_HANDLE,\n  payload: {},\n});\n\nconst createUser = (data) => ({\n  type: EntryActionTypes.USER_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleUserCreate = (user) => ({\n  type: EntryActionTypes.USER_CREATE_HANDLE,\n  payload: {\n    user,\n  },\n});\n\nconst clearUserCreateError = () => ({\n  type: EntryActionTypes.USER_CREATE_ERROR_CLEAR,\n  payload: {},\n});\n\nconst updateUser = (id, data) => ({\n  type: EntryActionTypes.USER_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst updateCurrentUser = (data) => ({\n  type: EntryActionTypes.CURRENT_USER_UPDATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleUserUpdate = (user) => ({\n  type: EntryActionTypes.USER_UPDATE_HANDLE,\n  payload: {\n    user,\n  },\n});\n\nconst updateCurrentUserLanguage = (language) => ({\n  type: EntryActionTypes.CURRENT_USER_LANGUAGE_UPDATE,\n  payload: {\n    language,\n  },\n});\n\nconst updateUserEmail = (id, data) => ({\n  type: EntryActionTypes.USER_EMAIL_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst updateCurrentUserEmail = (data) => ({\n  type: EntryActionTypes.CURRENT_USER_EMAIL_UPDATE,\n  payload: {\n    data,\n  },\n});\n\nconst clearUserEmailUpdateError = (id) => ({\n  type: EntryActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR,\n  payload: {\n    id,\n  },\n});\n\nconst clearCurrentUserEmailUpdateError = () => ({\n  type: EntryActionTypes.CURRENT_USER_EMAIL_UPDATE_ERROR_CLEAR,\n  payload: {},\n});\n\nconst updateUserPassword = (id, data) => ({\n  type: EntryActionTypes.USER_PASSWORD_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst updateCurrentUserPassword = (data) => ({\n  type: EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE,\n  payload: {\n    data,\n  },\n});\n\nconst clearUserPasswordUpdateError = (id) => ({\n  type: EntryActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR,\n  payload: {\n    id,\n  },\n});\n\nconst clearCurrentUserPasswordUpdateError = () => ({\n  type: EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE_ERROR_CLEAR,\n  payload: {},\n});\n\nconst updateUserUsername = (id, data) => ({\n  type: EntryActionTypes.USER_USERNAME_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst updateCurrentUserUsername = (data) => ({\n  type: EntryActionTypes.CURRENT_USER_USERNAME_UPDATE,\n  payload: {\n    data,\n  },\n});\n\nconst clearUserUsernameUpdateError = (id) => ({\n  type: EntryActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR,\n  payload: {\n    id,\n  },\n});\n\nconst clearCurrentUserUsernameUpdateError = () => ({\n  type: EntryActionTypes.CURRENT_USER_USERNAME_UPDATE_ERROR_CLEAR,\n  payload: {},\n});\n\nconst updateCurrentUserAvatar = (data) => ({\n  type: EntryActionTypes.CURRENT_USER_AVATAR_UPDATE,\n  payload: {\n    data,\n  },\n});\n\nconst createUserApiKey = (id) => ({\n  type: EntryActionTypes.USER_API_KEY_CREATE,\n  payload: {\n    id,\n  },\n});\n\nconst deleteUserApiKey = (id) => ({\n  type: EntryActionTypes.USER_API_KEY_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst clearUserApiKeyValue = (id) => ({\n  type: EntryActionTypes.USER_API_KEY_VALUE_CLEAR,\n  payload: {\n    id,\n  },\n});\n\nconst deleteUser = (id) => ({\n  type: EntryActionTypes.USER_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleUserDelete = (user) => ({\n  type: EntryActionTypes.USER_DELETE_HANDLE,\n  payload: {\n    user,\n  },\n});\n\nconst addUserToCard = (id, cardId) => ({\n  type: EntryActionTypes.USER_TO_CARD_ADD,\n  payload: {\n    id,\n    cardId,\n  },\n});\n\nconst addUserToCurrentCard = (id) => ({\n  type: EntryActionTypes.USER_TO_CURRENT_CARD_ADD,\n  payload: {\n    id,\n  },\n});\n\nconst addCurrentUserToCurrentCard = () => ({\n  type: EntryActionTypes.CURRENT_USER_TO_CURRENT_CARD_ADD,\n  payload: {},\n});\n\nconst handleUserToCardAdd = (cardMembership) => ({\n  type: EntryActionTypes.USER_TO_CARD_ADD_HANDLE,\n  payload: {\n    cardMembership,\n  },\n});\n\nconst removeUserFromCard = (id, cardId) => ({\n  type: EntryActionTypes.USER_FROM_CARD_REMOVE,\n  payload: {\n    id,\n    cardId,\n  },\n});\n\nconst removeUserFromCurrentCard = (id) => ({\n  type: EntryActionTypes.USER_FROM_CURRENT_CARD_REMOVE,\n  payload: {\n    id,\n  },\n});\n\nconst removeCurrentUserFromCurrentCard = () => ({\n  type: EntryActionTypes.CURRENT_USER_FROM_CURRENT_CARD_REMOVE,\n  payload: {},\n});\n\nconst handleUserFromCardRemove = (cardMembership) => ({\n  type: EntryActionTypes.USER_FROM_CARD_REMOVE_HANDLE,\n  payload: {\n    cardMembership,\n  },\n});\n\nconst addUserToFilterInCurrentBoard = (id, replace = false) => ({\n  type: EntryActionTypes.USER_TO_FILTER_IN_CURRENT_BOARD_ADD,\n  payload: {\n    id,\n    replace,\n  },\n});\n\nconst removeUserFromFilterInCurrentBoard = (id) => ({\n  type: EntryActionTypes.USER_FROM_FILTER_IN_CURRENT_BOARD_REMOVE,\n  payload: {\n    id,\n  },\n});\n\nexport default {\n  handleUsersReset,\n  createUser,\n  handleUserCreate,\n  clearUserCreateError,\n  updateUser,\n  updateCurrentUser,\n  handleUserUpdate,\n  updateCurrentUserLanguage,\n  updateUserEmail,\n  updateCurrentUserEmail,\n  clearUserEmailUpdateError,\n  clearCurrentUserEmailUpdateError,\n  updateUserPassword,\n  updateCurrentUserPassword,\n  clearUserPasswordUpdateError,\n  clearCurrentUserPasswordUpdateError,\n  updateUserUsername,\n  updateCurrentUserUsername,\n  clearUserUsernameUpdateError,\n  clearCurrentUserUsernameUpdateError,\n  updateCurrentUserAvatar,\n  createUserApiKey,\n  deleteUserApiKey,\n  clearUserApiKeyValue,\n  deleteUser,\n  handleUserDelete,\n  addUserToCard,\n  addUserToCurrentCard,\n  addCurrentUserToCurrentCard,\n  handleUserToCardAdd,\n  removeUserFromCard,\n  removeUserFromCurrentCard,\n  removeCurrentUserFromCurrentCard,\n  handleUserFromCardRemove,\n  addUserToFilterInCurrentBoard,\n  removeUserFromFilterInCurrentBoard,\n};\n"
  },
  {
    "path": "client/src/entry-actions/webhooks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport EntryActionTypes from '../constants/EntryActionTypes';\n\nconst createWebhook = (data) => ({\n  type: EntryActionTypes.WEBHOOK_CREATE,\n  payload: {\n    data,\n  },\n});\n\nconst handleWebhookCreate = (webhook) => ({\n  type: EntryActionTypes.WEBHOOK_CREATE_HANDLE,\n  payload: {\n    webhook,\n  },\n});\n\nconst updateWebhook = (id, data) => ({\n  type: EntryActionTypes.WEBHOOK_UPDATE,\n  payload: {\n    id,\n    data,\n  },\n});\n\nconst handleWebhookUpdate = (webhook) => ({\n  type: EntryActionTypes.WEBHOOK_UPDATE_HANDLE,\n  payload: {\n    webhook,\n  },\n});\n\nconst deleteWebhook = (id) => ({\n  type: EntryActionTypes.WEBHOOK_DELETE,\n  payload: {\n    id,\n  },\n});\n\nconst handleWebhookDelete = (webhook) => ({\n  type: EntryActionTypes.WEBHOOK_DELETE_HANDLE,\n  payload: {\n    webhook,\n  },\n});\n\nexport default {\n  createWebhook,\n  handleWebhookCreate,\n  updateWebhook,\n  handleWebhookUpdate,\n  deleteWebhook,\n  handleWebhookDelete,\n};\n"
  },
  {
    "path": "client/src/history.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createBrowserHistory } from 'history';\n\nexport default createBrowserHistory();\n"
  },
  {
    "path": "client/src/hooks/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport useNestedRef from './use-nested-ref';\nimport useField from './use-field';\nimport useForm from './use-form';\nimport useClosable from './use-closable';\nimport useEscapeInterceptor from './use-escape-interceptor';\nimport useSteps from './use-steps';\nimport useModal from './use-modal';\nimport useClosableModal from './use-closable-modal';\nimport usePopupInClosableContext from './use-popup-in-closable-context';\n\nexport {\n  useNestedRef,\n  useField,\n  useForm,\n  useClosable,\n  useEscapeInterceptor,\n  useSteps,\n  useModal,\n  useClosableModal,\n  usePopupInClosableContext,\n};\n"
  },
  {
    "path": "client/src/hooks/use-closable-modal.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { Modal } from 'semantic-ui-react';\n\nimport useClosable from './use-closable';\nimport { ClosableContext } from '../contexts';\n\nexport default (initialClosableValue) => {\n  const [isClosableActiveRef, activateClosable, deactivateClosable, setIsClosableActive] =\n    useClosable(initialClosableValue);\n\n  const closableContextValue = useMemo(\n    () => [activateClosable, deactivateClosable, setIsClosableActive],\n    [activateClosable, deactivateClosable, setIsClosableActive],\n  );\n\n  const ClosableModal = useMemo(() => {\n    // eslint-disable-next-line no-shadow\n    const ClosableModal = React.memo(({ closeIcon, onClose, ...props }) => {\n      const handleClose = useCallback(\n        (event) => {\n          if (isClosableActiveRef.current) {\n            if (closeIcon && event.type === 'click') {\n              if (!event.currentTarget.classList.contains('close')) {\n                return;\n              }\n            } else {\n              return;\n            }\n          }\n\n          if (onClose) {\n            onClose();\n          }\n        },\n        [closeIcon, onClose],\n      );\n\n      return (\n        <ClosableContext.Provider value={closableContextValue}>\n          {/* eslint-disable-next-line react/jsx-props-no-spreading */}\n          <Modal open {...props} closeIcon={closeIcon} onClose={handleClose} />\n        </ClosableContext.Provider>\n      );\n    });\n\n    ClosableModal.propTypes = {\n      closeIcon: PropTypes.bool,\n      onClose: PropTypes.func,\n    };\n\n    ClosableModal.defaultProps = {\n      closeIcon: undefined,\n      onClose: undefined,\n    };\n\n    ClosableModal.Header = Modal.Header;\n    ClosableModal.Content = Modal.Content;\n    ClosableModal.Actions = Modal.Actions;\n\n    return ClosableModal;\n  }, [isClosableActiveRef, closableContextValue]);\n\n  return [\n    ClosableModal,\n    isClosableActiveRef,\n    activateClosable,\n    deactivateClosable,\n    setIsClosableActive,\n  ];\n};\n"
  },
  {
    "path": "client/src/hooks/use-closable.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback, useRef } from 'react';\n\nexport default (initialValue = false) => {\n  const isActiveRef = useRef(initialValue);\n\n  const setIsActive = useCallback((isActive) => {\n    setTimeout(() => {\n      isActiveRef.current = isActive;\n    });\n  }, []);\n\n  const activate = useCallback(() => {\n    setIsActive(true);\n  }, [setIsActive]);\n\n  const deactivate = useCallback(() => {\n    setIsActive(false);\n  }, [setIsActive]);\n\n  return [isActiveRef, activate, deactivate, setIsActive];\n};\n"
  },
  {
    "path": "client/src/hooks/use-escape-interceptor.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback } from 'react';\nimport { useEventCallback } from '../lib/hooks';\n\nexport default (onEscape) => {\n  const handleWindowKeydown = useEventCallback(\n    (event) => {\n      if (event.key === 'Escape') {\n        event.stopPropagation();\n\n        if (onEscape) {\n          onEscape();\n        }\n      }\n    },\n    [onEscape],\n  );\n\n  const activate = useCallback(() => {\n    window.addEventListener('keydown', handleWindowKeydown, true);\n  }, [handleWindowKeydown]);\n\n  const deactivate = useCallback(() => {\n    window.removeEventListener('keydown', handleWindowKeydown, true);\n  }, [handleWindowKeydown]);\n\n  return [activate, deactivate];\n};\n"
  },
  {
    "path": "client/src/hooks/use-field.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback, useState } from 'react';\n\nexport default (initialValue) => {\n  const [value, setValue] = useState(initialValue);\n\n  const handleChange = useCallback((_, { value: nextValue }) => {\n    setValue(nextValue);\n  }, []);\n\n  return [value, handleChange, setValue];\n};\n"
  },
  {
    "path": "client/src/hooks/use-form.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback, useState } from 'react';\n\nconst CHECKED_TYPES_SET = new Set(['checkbox', 'radio']);\n\nexport default (initialData) => {\n  const [data, setData] = useState(initialData);\n\n  const handleFieldChange = useCallback((_, { type, name: fieldName, value, checked }) => {\n    setData((prevData) => ({\n      ...prevData,\n      [fieldName]: CHECKED_TYPES_SET.has(type) ? checked : value,\n    }));\n  }, []);\n\n  return [data, handleFieldChange, setData];\n};\n"
  },
  {
    "path": "client/src/hooks/use-modal.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback, useState } from 'react';\n\n// TODO: rename?\nexport default (initialParams) => {\n  const [modal, setModal] = useState(() => initialParams);\n\n  const open = useCallback((params) => {\n    setModal(params);\n  }, []);\n\n  const handleClose = useCallback(() => {\n    setModal(null);\n  }, []);\n\n  return [modal, open, handleClose];\n};\n"
  },
  {
    "path": "client/src/hooks/use-nested-ref.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback, useRef } from 'react';\n\nexport default (nestedRefName = 'ref') => {\n  const ref = useRef(null);\n\n  const handleRef = useCallback(\n    (element) => {\n      ref.current = element?.[nestedRefName].current;\n    },\n    [nestedRefName],\n  );\n\n  return [ref, handleRef];\n};\n"
  },
  {
    "path": "client/src/hooks/use-popup-in-closable-context.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback, useContext } from 'react';\nimport { usePopup } from '../lib/popup';\n\nimport { ClosableContext } from '../contexts';\n\nexport default (Step, { onOpen, onClose, ...props } = {}) => {\n  const [activateClosable, deactivateClosable] = useContext(ClosableContext);\n\n  const handleOpen = useCallback(() => {\n    activateClosable();\n\n    if (onOpen) {\n      onOpen();\n    }\n  }, [onOpen, activateClosable]);\n\n  const handleClose = useCallback(() => {\n    deactivateClosable();\n\n    if (onClose) {\n      onClose();\n    }\n  }, [onClose, deactivateClosable]);\n\n  return usePopup(Step, {\n    ...props,\n    onOpen: handleOpen,\n    onClose: handleClose,\n  });\n};\n"
  },
  {
    "path": "client/src/hooks/use-steps.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback, useState } from 'react';\n\nconst createStep = (type, params = {}) => {\n  if (!type) {\n    return null;\n  }\n\n  return {\n    type,\n    params,\n  };\n};\n\nexport default (initialType, initialParams) => {\n  const [step, setStep] = useState(() => createStep(initialType, initialParams));\n\n  const open = useCallback((type, params) => {\n    setStep(createStep(type, params));\n  }, []);\n\n  const handleBack = useCallback(() => {\n    setStep(null);\n  }, []);\n\n  return [step, open, handleBack];\n};\n"
  },
  {
    "path": "client/src/i18n.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport i18n from 'i18next';\nimport LanguageDetector from 'i18next-browser-languagedetector';\nimport { initReactI18next } from 'react-i18next';\nimport formatDate from 'date-fns/format';\nimport parseDate from 'date-fns/parse';\nimport {\n  registerLocale as registerDatepickerLocale,\n  setDefaultLocale as setDefaultDatepickerLocale,\n} from 'react-datepicker';\nimport timeAgoDefaultLocale from 'javascript-time-ago/locale/en';\nimport TimeAgo from 'javascript-time-ago';\nimport { configure as configureMarkdownEditor } from '@gravity-ui/markdown-editor';\n// eslint-disable-next-line import/no-unresolved\nimport { i18n as markdownEditorI18n } from '@gravity-ui/markdown-editor/_/i18n/i18n';\n\nimport { embeddedLocales, languages } from './locales';\n\nconst FALLBACK_LANGUAGE = 'en-US';\n\ni18n.dateFns = {\n  locales: {},\n  init() {},\n  addLocale(language, locale) {\n    this.locales[language] = locale;\n    registerDatepickerLocale(language, locale);\n  },\n  setLanguage(language) {\n    setDefaultDatepickerLocale(language);\n  },\n  getLocale(language = i18n.resolvedLanguage) {\n    return this.locales[language];\n  },\n  format(date, format, { language, ...options } = {}) {\n    return formatDate(date, format, {\n      locale: this.getLocale(language),\n      ...options,\n    });\n  },\n  parse(dateString, format, backupDate, { language, ...options } = {}) {\n    return parseDate(dateString, format, backupDate, {\n      locale: this.getLocale(language),\n      ...options,\n    });\n  },\n};\n\ni18n.timeAgo = {\n  init() {\n    TimeAgo.addDefaultLocale(timeAgoDefaultLocale);\n  },\n  addLocale(_, locale) {\n    TimeAgo.addLocale(locale);\n  },\n  setLanguage() {},\n};\n\ni18n.markdownEditor = {\n  init() {\n    markdownEditorI18n.setFallbackLang(FALLBACK_LANGUAGE);\n    this.addLocale(FALLBACK_LANGUAGE, embeddedLocales[FALLBACK_LANGUAGE].markdownEditor);\n  },\n  addLocale(language, locale) {\n    Object.entries(locale).forEach(([keyset, data]) => {\n      markdownEditorI18n.registerKeyset(language, keyset, data);\n    });\n  },\n  setLanguage(language) {\n    configureMarkdownEditor({\n      lang: language,\n    });\n  },\n};\n\ni18n.dateFns.init();\ni18n.timeAgo.init();\ni18n.markdownEditor.init();\n\ni18n.on('languageChanged', () => {\n  i18n.dateFns.setLanguage(i18n.resolvedLanguage);\n  i18n.timeAgo.setLanguage(i18n.resolvedLanguage);\n  i18n.markdownEditor.setLanguage(i18n.resolvedLanguage);\n});\n\nconst formatDatePostProcessor = {\n  type: 'postProcessor',\n  name: 'formatDate',\n  process(value, _, options) {\n    return i18n.dateFns.format(options.value, value);\n  },\n};\n\nconst parseDatePostProcessor = {\n  type: 'postProcessor',\n  name: 'parseDate',\n  process(value, _, options) {\n    return i18n.dateFns.parse(options.value, value, new Date());\n  },\n};\n\ni18n\n  .use(LanguageDetector)\n  .use(formatDatePostProcessor)\n  .use(parseDatePostProcessor)\n  .use(initReactI18next)\n  .init({\n    resources: embeddedLocales,\n    fallbackLng: FALLBACK_LANGUAGE,\n    supportedLngs: languages,\n    load: 'currentOnly',\n    interpolation: {\n      escapeValue: false,\n      format(value, format, language) {\n        if (value instanceof Date) {\n          return i18n.dateFns.format(value, format, {\n            language,\n          });\n        }\n\n        return value;\n      },\n    },\n    react: {\n      useSuspense: true,\n    },\n    debug: import.meta.env.DEV,\n  });\n\ni18n.loadCoreLocale = async (language = i18n.resolvedLanguage) => {\n  if (language === FALLBACK_LANGUAGE) {\n    return;\n  }\n\n  const { default: locale } = await import(`./locales/${language}/core.js`);\n\n  Object.keys(locale).forEach((namespace) => {\n    switch (namespace) {\n      case 'dateFns':\n      case 'timeAgo':\n      case 'markdownEditor':\n        i18n[namespace].addLocale(language, locale[namespace]);\n\n        break;\n      default:\n        i18n.addResourceBundle(language, namespace, locale[namespace], true, true);\n    }\n  });\n};\n\n/* i18n.detectLanguage = () => {\n  const {\n    services: { languageDetector, languageUtils },\n  } = i18n;\n\n  localStorage.removeItem(languageDetector.options.lookupLocalStorage);\n  const detectedLanguages = languageDetector.detect();\n\n  i18n.language = languageUtils.getBestMatchFromCodes(detectedLanguages);\n  i18n.languages = languageUtils.toResolveHierarchy(i18n.language);\n\n  i18n.resolvedLanguage = undefined;\n  i18n.setResolvedLanguage(i18n.language);\n}; */\n\nexport default i18n;\n"
  },
  {
    "path": "client/src/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\n\nimport store from './store';\nimport history from './history';\nimport Root from './components/common/Root';\n\nimport './i18n';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(React.createElement(Root, { store, history }));\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/FilePicker/FilePicker.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useRef } from 'react';\nimport PropTypes from 'prop-types';\n\nimport styles from './FilePicker.module.css';\n\nconst FilePicker = React.memo(({ children, accept, multiple, onSelect }) => {\n  const fieldRef = useRef(null);\n\n  const handleTriggerClick = useCallback(() => {\n    fieldRef.current.click();\n  }, []);\n\n  const handleFieldChange = useCallback(\n    ({ target }) => {\n      onSelect(multiple ? [...target.files] : target.files[0]);\n      target.value = null; // eslint-disable-line no-param-reassign\n    },\n    [multiple, onSelect],\n  );\n\n  const tigger = React.cloneElement(children, {\n    onClick: handleTriggerClick,\n  });\n\n  return (\n    <>\n      {tigger}\n      <input\n        ref={fieldRef}\n        type=\"file\"\n        accept={accept}\n        multiple={multiple}\n        className={styles.field}\n        onChange={handleFieldChange}\n      />\n    </>\n  );\n});\n\nFilePicker.propTypes = {\n  children: PropTypes.element.isRequired,\n  accept: PropTypes.string,\n  multiple: PropTypes.bool,\n  onSelect: PropTypes.func.isRequired,\n};\n\nFilePicker.defaultProps = {\n  accept: undefined,\n  multiple: undefined,\n};\n\nexport default FilePicker;\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/FilePicker/FilePicker.module.css",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n.field {\n  display: none;\n}\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/FilePicker/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport FilePicker from './FilePicker';\n\nexport default FilePicker;\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Input/Input.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { Input as SemanticUIInput } from 'semantic-ui-react';\n\nimport InputPassword from './InputPassword';\nimport InputMask from './InputMask';\n\nexport default class Input extends SemanticUIInput {\n  static Password = InputPassword;\n\n  static Mask = InputMask;\n}\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Input/InputMask.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport { Input } from 'semantic-ui-react';\n\nimport MaskedInput from './MaskedInput';\n\nconst InputMask = React.forwardRef(({ mask, maskChar, ...props }, ref) => (\n  // eslint-disable-next-line react/jsx-props-no-spreading\n  <Input {...props} ref={ref} input={<MaskedInput mask={mask} maskChar={maskChar} />} />\n));\n\nInputMask.propTypes = {\n  mask: PropTypes.string.isRequired,\n  maskChar: PropTypes.string,\n};\n\nInputMask.defaultProps = {\n  maskChar: undefined,\n};\n\nexport default React.memo(InputMask);\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Input/InputPassword.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport zxcvbn from 'zxcvbn';\nimport React, { useCallback, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { Icon, Input, Progress } from 'semantic-ui-react';\nimport { useToggle } from '../../../hooks';\n\nimport styles from './InputPassword.module.css';\n\nconst STRENGTH_SCORE_COLORS = ['red', 'orange', 'yellow', 'olive', 'green'];\n\nconst InputPassword = React.forwardRef(\n  ({ value, withStrengthBar, minStrengthScore, className, onClear, ...props }, ref) => {\n    const [isVisible, toggleVisible] = useToggle();\n\n    const strengthScore = useMemo(() => {\n      if (!withStrengthBar) {\n        return undefined;\n      }\n\n      return zxcvbn(value).score;\n    }, [value, withStrengthBar]);\n\n    const handleToggleClick = useCallback(() => {\n      toggleVisible();\n    }, [toggleVisible]);\n\n    const inputProps = {\n      ...props,\n      ref,\n      value,\n      type: isVisible ? 'text' : 'password',\n      icon: onClear ? (\n        <Icon link name=\"cancel\" onClick={onClear} />\n      ) : (\n        <Icon link name={isVisible ? 'eye' : 'eye slash'} onClick={handleToggleClick} />\n      ),\n    };\n\n    if (!withStrengthBar) {\n      return (\n        <Input\n          {...inputProps} // eslint-disable-line react/jsx-props-no-spreading\n          className={className}\n        />\n      );\n    }\n\n    return (\n      <div className={className}>\n        <Input\n          {...inputProps} // eslint-disable-line react/jsx-props-no-spreading\n          error={!!value && strengthScore < minStrengthScore}\n        />\n        <Progress\n          value={value ? strengthScore + 1 : 0}\n          total={5}\n          color={STRENGTH_SCORE_COLORS[strengthScore]}\n          size=\"tiny\"\n          className={styles.strengthBar}\n        />\n      </div>\n    );\n  },\n);\n\nInputPassword.propTypes = {\n  value: PropTypes.string.isRequired,\n  withStrengthBar: PropTypes.bool,\n  minStrengthScore: PropTypes.number,\n  className: PropTypes.string,\n  onClear: PropTypes.func,\n};\n\nInputPassword.defaultProps = {\n  withStrengthBar: false,\n  minStrengthScore: 2,\n  className: undefined,\n  onClear: undefined,\n};\n\nexport default React.memo(InputPassword);\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Input/InputPassword.module.css",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n.strengthBar {\n  margin: 4px 0 0 !important;\n  opacity: 0.5;\n}\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Input/MaskedInput.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport InputMask from 'react-input-mask';\n\nexport default class MaskedInput extends InputMask {\n  focus(options) {\n    this.getInputDOMNode().focus(options);\n  }\n\n  select() {\n    this.getInputDOMNode().select();\n  }\n}\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Input/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Input from './Input';\n\nexport default Input;\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Masonry/Masonry.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useCallback, useRef, useState } from 'react';\nimport ReactDOM from 'react-dom';\nimport PropTypes from 'prop-types';\n\nimport styles from './Masonry.module.scss';\n\nconst pixelsToNumber = (pixels) => {\n  return Number(pixels.replace('px', ''));\n};\n\nconst Masonry = React.memo(({ children, columns, spacing }) => {\n  const [maxColumnHeight, setMaxColumnHeight] = useState(0);\n\n  const wrapperRef = useRef(null);\n  const resizeObserverRef = useRef(null);\n\n  const handleChildResize = useCallback(() => {\n    const { current: wrapperElement } = wrapperRef;\n    const columnHeights = new Array(columns).fill(0);\n\n    wrapperElement.childNodes.forEach((childElement) => {\n      if (childElement.nodeType !== Node.ELEMENT_NODE || childElement.dataset.lineBreak) {\n        return;\n      }\n\n      const childComputedStyle = window.getComputedStyle(childElement);\n\n      const childHeight =\n        Math.ceil(pixelsToNumber(childComputedStyle.height)) +\n        pixelsToNumber(childComputedStyle.marginTop) +\n        pixelsToNumber(childComputedStyle.marginBottom);\n\n      const index = columnHeights.indexOf(Math.min(...columnHeights));\n\n      columnHeights[index] += childHeight;\n      childElement.style.order = index + 1; // eslint-disable-line no-param-reassign\n    });\n\n    ReactDOM.flushSync(() => {\n      setMaxColumnHeight(Math.max(...columnHeights));\n    });\n  }, [columns]);\n\n  const handleWrapperRef = useCallback(\n    (element) => {\n      wrapperRef.current = element;\n\n      if (resizeObserverRef.current) {\n        resizeObserverRef.current.disconnect();\n      }\n\n      if (!element) {\n        resizeObserverRef.current = null;\n        return;\n      }\n\n      resizeObserverRef.current = new ResizeObserver(handleChildResize);\n\n      element.childNodes.forEach((childElement) => {\n        if (!childElement.dataset.lineBreak) {\n          resizeObserverRef.current.observe(childElement);\n        }\n      });\n    },\n    [children, handleChildResize], // eslint-disable-line react-hooks/exhaustive-deps\n  );\n\n  const styledChildren = React.Children.map(\n    children,\n    (child) =>\n      child &&\n      React.cloneElement(child, {\n        style: {\n          margin: `${spacing / 2}px`,\n          width: `calc(${(100 / columns).toFixed(2)}% - ${spacing}px)`,\n        },\n      }),\n  );\n\n  const lineBreaks = [...Array(columns > 0 ? columns - 1 : 0)].map((_, index) => (\n    <span\n      data-line-break\n      key={index} // eslint-disable-line react/no-array-index-key\n      className={styles.lineBreak}\n      style={{\n        order: index + 1,\n      }}\n    />\n  ));\n\n  return (\n    <div\n      ref={handleWrapperRef}\n      className={styles.wrapper}\n      style={{\n        height: `${maxColumnHeight}px`,\n        margin: `-${spacing / 2}px`,\n      }}\n    >\n      {styledChildren}\n      {lineBreaks}\n    </div>\n  );\n});\n\nMasonry.propTypes = {\n  children: PropTypes.node.isRequired,\n  columns: PropTypes.number.isRequired,\n  spacing: PropTypes.number.isRequired,\n};\n\nexport default Masonry;\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Masonry/Masonry.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n.lineBreak {\n  flex-basis: 100%;\n  margin: 0 !important;\n  padding: 0 !important;\n  width: 0 !important;\n}\n\n.wrapper {\n  align-content: flex-start;\n  box-sizing: border-box;\n  display: flex;\n  flex-flow: column wrap;\n  width: 100%;\n\n  & > * {\n    box-sizing: border-box;\n  }\n}\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Masonry/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Masonry from './Masonry';\n\nexport default Masonry;\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Popup/Popup.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { Popup as SemanticUIPopup } from 'semantic-ui-react';\n\nimport PopupHeader from './PopupHeader';\n\nexport default class Popup extends SemanticUIPopup {\n  static Header = PopupHeader;\n}\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Popup/PopupHeader.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport { Button, Popup as SemanticUIPopup } from 'semantic-ui-react';\n\nimport styles from './PopupHeader.module.css';\n\nconst PopupHeader = React.memo(({ children, onBack }) => (\n  <SemanticUIPopup.Header className={styles.wrapper}>\n    {onBack && <Button icon=\"angle left\" onClick={onBack} className={styles.backButton} />}\n    <div className={styles.content}>{children}</div>\n  </SemanticUIPopup.Header>\n));\n\nPopupHeader.propTypes = {\n  children: PropTypes.node.isRequired,\n  onBack: PropTypes.func,\n};\n\nPopupHeader.defaultProps = {\n  onBack: undefined,\n};\n\nexport default PopupHeader;\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Popup/PopupHeader.module.css",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n.backButton {\n  background: transparent !important;\n  box-shadow: none !important;\n  left: 0;\n  margin: 0 !important;\n  padding: 10px 8px 10px 12px !important;\n  position: absolute;\n  top: 0;\n  width: 40px;\n  z-index: 2000;\n}\n\n.content {\n  border-bottom: 1px solid #eee;\n  font-size: 14px;\n  font-weight: normal;\n  left: 0;\n  line-height: 20px;\n  margin: 0 12px;\n  overflow: hidden;\n  padding: 12px 28px 8px;\n  position: absolute;\n  right: 0;\n  text-overflow: ellipsis;\n  top: 0;\n  white-space: nowrap;\n}\n\n.wrapper {\n  height: 40px;\n  margin: 0 -12px 8px !important;\n  position: relative;\n  text-align: center;\n}\n"
  },
  {
    "path": "client/src/lib/custom-ui/components/Popup/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Popup from './Popup';\n\nexport default Popup;\n"
  },
  {
    "path": "client/src/lib/custom-ui/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Input from './components/Input';\nimport Popup from './components/Popup';\nimport Masonry from './components/Masonry';\nimport FilePicker from './components/FilePicker';\n\nexport { Input, Popup, Masonry, FilePicker };\n"
  },
  {
    "path": "client/src/lib/custom-ui/styles.css",
    "content": "/*\n* # Semantic UI - 2.4.0\n* https://github.com/Semantic-Org/Semantic-UI\n* http://www.semantic-ui.com/\n*\n* Copyright 2014 Contributors\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n@font-face {\n  font-family: \"Nunitoga\";\n  src: url(\"./assets/fonts/Nunitoga-Light.woff2\") format(\"woff2\");\n  font-weight: lighter;\n  font-style: normal;\n  unicode-range: U+0000-036F, U+0400-1EFF, U+2000-10FFFF;\n}\n\n@font-face {\n  font-family: \"Nunitoga\";\n  src: url(\"./assets/fonts/Nunitoga-LightItalic.woff2\") format(\"woff2\");\n  font-weight: lighter;\n  font-style: italic;\n  unicode-range: U+0000-036F, U+0400-1EFF, U+2000-10FFFF;\n}\n\n@font-face {\n  font-family: \"Nunitoga\";\n  src: url(\"./assets/fonts/Nunitoga-Medium.woff2\") format(\"woff2\");\n  font-weight: normal;\n  font-style: normal;\n  unicode-range: U+0000-036F, U+0400-1EFF, U+2000-10FFFF;\n}\n\n@font-face {\n  font-family: \"Nunitoga\";\n  src: url(\"./assets/fonts/Nunitoga-MediumItalic.woff2\") format(\"woff2\");\n  font-weight: normal;\n  font-style: italic;\n  unicode-range: U+0000-036F, U+0400-1EFF, U+2000-10FFFF;\n}\n\n@font-face {\n  font-family: \"Nunitoga\";\n  src: url(\"./assets/fonts/Nunitoga-Bold.woff2\") format(\"woff2\");\n  font-weight: bold;\n  font-style: normal;\n  unicode-range: U+0000-036F, U+0400-1EFF, U+2000-10FFFF;\n}\n\n@font-face {\n  font-family: \"Nunitoga\";\n  src: url(\"./assets/fonts/Nunitoga-BoldItalic.woff2\") format(\"woff2\");\n  font-weight: bold;\n  font-style: italic;\n  unicode-range: U+0000-036F, U+0400-1EFF, U+2000-10FFFF;\n}\n\n/*!\n* # Semantic UI 2.4.0 - Reset\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Reset\n*******************************/\n\n/* Border-Box */\n\n*,\n*:before,\n*:after {\n  -webkit-box-sizing: inherit;\n  box-sizing: inherit;\n}\n\nhtml {\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n}\n\n/* iPad Input Shadows */\n\ninput[type=\"text\"],\ninput[type=\"email\"],\ninput[type=\"search\"],\ninput[type=\"password\"] {\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  /* mobile firefox too! */\n}\n\n/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n  ========================================================================== */\n\n/**\n* 1. Correct the line height in all browsers.\n* 2. Prevent adjustments of font size after orientation changes in\n*    IE on Windows Phone and in iOS.\n*/\n\nhtml {\n  line-height: 1.15;\n  /* 1 */\n  -ms-text-size-adjust: 100%;\n  /* 2 */\n  -webkit-text-size-adjust: 100%;\n  /* 2 */\n}\n\n/* Sections\n  ========================================================================== */\n\n/**\n* Remove the margin in all browsers (opinionated).\n*/\n\nbody {\n  margin: 0;\n}\n\n/**\n* Add the correct display in IE 9-.\n*/\n\narticle,\naside,\nfooter,\nheader,\nnav,\nsection {\n  display: block;\n}\n\n/**\n* Correct the font size and margin on `h1` elements within `section` and\n* `article` contexts in Chrome, Firefox, and Safari.\n*/\n\nh1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n\n/* Grouping content\n  ========================================================================== */\n\n/**\n* Add the correct display in IE 9-.\n* 1. Add the correct display in IE.\n*/\n\nfigcaption,\nfigure,\nmain {\n  /* 1 */\n  display: block;\n}\n\n/**\n* Add the correct margin in IE 8.\n*/\n\nfigure {\n  margin: 1em 40px;\n}\n\n/**\n* 1. Add the correct box sizing in Firefox.\n* 2. Show the overflow in Edge and IE.\n*/\n\nhr {\n  -webkit-box-sizing: content-box;\n  box-sizing: content-box;\n  /* 1 */\n  height: 0;\n  /* 1 */\n  overflow: visible;\n  /* 2 */\n}\n\n/**\n* 1. Correct the inheritance and scaling of font size in all browsers.\n* 2. Correct the odd `em` font sizing in all browsers.\n*/\n\npre {\n  font-family: monospace, monospace;\n  /* 1 */\n  font-size: 1em;\n  /* 2 */\n}\n\n/* Text-level semantics\n  ========================================================================== */\n\n/**\n* 1. Remove the gray background on active links in IE 10.\n* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.\n*/\n\na {\n  background-color: transparent;\n  /* 1 */\n  -webkit-text-decoration-skip: objects;\n  /* 2 */\n}\n\n/**\n* 1. Remove the bottom border in Chrome 57- and Firefox 39-.\n* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n*/\n\nabbr[title] {\n  border-bottom: none;\n  /* 1 */\n  text-decoration: underline;\n  /* 2 */\n  -webkit-text-decoration: underline dotted;\n  text-decoration: underline dotted;\n  /* 2 */\n}\n\n/**\n* Prevent the duplicate application of `bolder` by the next rule in Safari 6.\n*/\n\nb,\nstrong {\n  font-weight: inherit;\n}\n\n/**\n* Add the correct font weight in Chrome, Edge, and Safari.\n*/\n\nb,\nstrong {\n  font-weight: bolder;\n}\n\n/**\n* 1. Correct the inheritance and scaling of font size in all browsers.\n* 2. Correct the odd `em` font sizing in all browsers.\n*/\n\ncode,\nkbd,\nsamp {\n  font-family: monospace, monospace;\n  /* 1 */\n  font-size: 1em;\n  /* 2 */\n}\n\n/**\n* Add the correct font style in Android 4.3-.\n*/\n\ndfn {\n  font-style: italic;\n}\n\n/**\n* Add the correct background and color in IE 9-.\n*/\n\nmark {\n  background-color: #ff0;\n  color: #000;\n}\n\n/**\n* Add the correct font size in all browsers.\n*/\n\nsmall {\n  font-size: 80%;\n}\n\n/**\n* Prevent `sub` and `sup` elements from affecting the line height in\n* all browsers.\n*/\n\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nsup {\n  top: -0.5em;\n}\n\n/* Embedded content\n  ========================================================================== */\n\n/**\n* Add the correct display in IE 9-.\n*/\n\naudio,\nvideo {\n  display: inline-block;\n}\n\n/**\n* Add the correct display in iOS 4-7.\n*/\n\naudio:not([controls]) {\n  display: none;\n  height: 0;\n}\n\n/**\n* Remove the border on images inside links in IE 10-.\n*/\n\nimg {\n  border-style: none;\n}\n\n/**\n* Hide the overflow in IE.\n*/\n\nsvg:not(:root) {\n  overflow: hidden;\n}\n\n/* Forms\n  ========================================================================== */\n\n/**\n* 1. Change the font styles in all browsers (opinionated).\n* 2. Remove the margin in Firefox and Safari.\n*/\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  font-family: sans-serif;\n  /* 1 */\n  font-size: 100%;\n  /* 1 */\n  line-height: 1.15;\n  /* 1 */\n  margin: 0;\n  /* 2 */\n}\n\n/**\n* Show the overflow in IE.\n* 1. Show the overflow in Edge.\n*/\n\nbutton,\ninput {\n  /* 1 */\n  overflow: visible;\n}\n\n/**\n* Remove the inheritance of text transform in Edge, Firefox, and IE.\n* 1. Remove the inheritance of text transform in Firefox.\n*/\n\nbutton,\nselect {\n  /* 1 */\n  text-transform: none;\n}\n\n/**\n* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`\n*    controls in Android 4.\n* 2. Correct the inability to style clickable types in iOS and Safari.\n*/\n\nbutton,\nhtml [type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n  -webkit-appearance: button;\n  /* 2 */\n}\n\n/**\n* Remove the inner border and padding in Firefox.\n*/\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n  border-style: none;\n  padding: 0;\n}\n\n/**\n* Restore the focus styles unset by the previous rule.\n*/\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n  outline: 1px dotted ButtonText;\n}\n\n/**\n* Correct the padding in Firefox.\n*/\n\nfieldset {\n  padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n* 1. Correct the text wrapping in Edge and IE.\n* 2. Correct the color inheritance from `fieldset` elements in IE.\n* 3. Remove the padding so developers are not caught out when they zero out\n*    `fieldset` elements in all browsers.\n*/\n\nlegend {\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n  /* 1 */\n  color: inherit;\n  /* 2 */\n  display: table;\n  /* 1 */\n  max-width: 100%;\n  /* 1 */\n  padding: 0;\n  /* 3 */\n  white-space: normal;\n  /* 1 */\n}\n\n/**\n* 1. Add the correct display in IE 9-.\n* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.\n*/\n\nprogress {\n  display: inline-block;\n  /* 1 */\n  vertical-align: baseline;\n  /* 2 */\n}\n\n/**\n* Remove the default vertical scrollbar in IE.\n*/\n\ntextarea {\n  overflow: auto;\n}\n\n/**\n* 1. Add the correct box sizing in IE 10-.\n* 2. Remove the padding in IE 10-.\n*/\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n  /* 1 */\n  padding: 0;\n  /* 2 */\n}\n\n/**\n* Correct the cursor style of increment and decrement buttons in Chrome.\n*/\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\n\n/**\n* 1. Correct the odd appearance in Chrome and Safari.\n* 2. Correct the outline style in Safari.\n*/\n\n[type=\"search\"] {\n  -webkit-appearance: textfield;\n  /* 1 */\n  outline-offset: -2px;\n  /* 2 */\n}\n\n/**\n* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.\n*/\n\n[type=\"search\"]::-webkit-search-cancel-button,\n[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n/**\n* 1. Correct the inability to style clickable types in iOS and Safari.\n* 2. Change font properties to `inherit` in Safari.\n*/\n\n::-webkit-file-upload-button {\n  -webkit-appearance: button;\n  /* 1 */\n  font: inherit;\n  /* 2 */\n}\n\n/* Interactive\n  ========================================================================== */\n\n/*\n* Add the correct display in IE 9-.\n* 1. Add the correct display in Edge, IE, and Firefox.\n*/\n\ndetails,\nmenu {\n  display: block;\n}\n\n/*\n* Add the correct display in all browsers.\n*/\n\nsummary {\n  display: list-item;\n}\n\n/* Scripting\n  ========================================================================== */\n\n/**\n* Add the correct display in IE 9-.\n*/\n\ncanvas {\n  display: inline-block;\n}\n\n/**\n* Add the correct display in IE.\n*/\n\ntemplate {\n  display: none;\n}\n\n/* Hidden\n  ========================================================================== */\n\n/**\n* Add the correct display in IE 10-.\n*/\n\n[hidden] {\n  display: none;\n}\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Site\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Page\n*******************************/\n\nhtml,\nbody {\n  height: 100%;\n}\n\nhtml {\n  font-size: 14px;\n}\n\nbody {\n  margin: 0px;\n  padding: 0px;\n  overflow-x: hidden;\n  min-width: 320px;\n  background: #ffffff;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-size: 14px;\n  line-height: 1.4285em;\n  color: rgba(0, 0, 0, 0.87);\n  font-smoothing: antialiased;\n}\n\n/*******************************\n            Headers\n*******************************/\n\nh1,\nh2,\nh3,\nh4,\nh5 {\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  line-height: 1.28571429em;\n  margin: calc(2rem - 0.14285714em) 0em 1rem;\n  font-weight: bold;\n  padding: 0em;\n}\n\nh1 {\n  min-height: 1rem;\n  font-size: 2rem;\n}\n\nh2 {\n  font-size: 1.71428571rem;\n}\n\nh3 {\n  font-size: 1.28571429rem;\n}\n\nh4 {\n  font-size: 1.07142857rem;\n}\n\nh5 {\n  font-size: 1rem;\n}\n\nh1:first-child,\nh2:first-child,\nh3:first-child,\nh4:first-child,\nh5:first-child {\n  margin-top: 0em;\n}\n\nh1:last-child,\nh2:last-child,\nh3:last-child,\nh4:last-child,\nh5:last-child {\n  margin-bottom: 0em;\n}\n\n/*******************************\n            Text\n*******************************/\n\np {\n  margin: 0em 0em 1em;\n  line-height: 1.4285em;\n}\n\np:first-child {\n  margin-top: 0em;\n}\n\np:last-child {\n  margin-bottom: 0em;\n}\n\n/*-------------------\n        Links\n--------------------*/\n\na {\n  color: #4183c4;\n  text-decoration: none;\n}\n\na:hover {\n  color: #1e70bf;\n  text-decoration: none;\n}\n\n/*******************************\n        Scrollbars\n*******************************/\n\n/*******************************\n          Highlighting\n*******************************/\n\n/* Site */\n\n::-webkit-selection {\n  background-color: #cce2ff;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n::-moz-selection {\n  background-color: #cce2ff;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n::selection {\n  background-color: #cce2ff;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* Form */\n\ntextarea::-webkit-selection,\ninput::-webkit-selection {\n  background-color: rgba(100, 100, 100, 0.4);\n  color: rgba(0, 0, 0, 0.87);\n}\n\ntextarea::-moz-selection,\ninput::-moz-selection {\n  background-color: rgba(100, 100, 100, 0.4);\n  color: rgba(0, 0, 0, 0.87);\n}\n\ntextarea::selection,\ninput::selection {\n  background-color: rgba(100, 100, 100, 0.4);\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* Force Simple Scrollbars */\n\n/* body ::-webkit-scrollbar {\n  -webkit-appearance: none;\n  width: 10px;\n  height: 10px;\n}\n\nbody ::-webkit-scrollbar-track {\n  background: rgba(0, 0, 0, 0.1);\n  border-radius: 0px;\n}\n\nbody ::-webkit-scrollbar-thumb {\n  cursor: pointer;\n  border-radius: 5px;\n  background: rgba(0, 0, 0, 0.25);\n  -webkit-transition: color 0.2s ease;\n  transition: color 0.2s ease;\n}\n\nbody ::-webkit-scrollbar-thumb:window-inactive {\n  background: rgba(0, 0, 0, 0.15);\n}\n\nbody ::-webkit-scrollbar-thumb:hover {\n  background: rgba(128, 135, 139, 0.8);\n} */\n\n/* Inverted UI */\n\n/* body .ui.inverted::-webkit-scrollbar-track {\n  background: rgba(255, 255, 255, 0.1);\n}\n\nbody .ui.inverted::-webkit-scrollbar-thumb {\n  background: rgba(255, 255, 255, 0.25);\n}\n\nbody .ui.inverted::-webkit-scrollbar-thumb:window-inactive {\n  background: rgba(255, 255, 255, 0.15);\n}\n\nbody .ui.inverted::-webkit-scrollbar-thumb:hover {\n  background: rgba(255, 255, 255, 0.35);\n} */\n\n/*******************************\n        Global Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Button\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Button\n*******************************/\n\n.ui.button {\n  cursor: pointer;\n  display: inline-block;\n  min-height: 1em;\n  outline: none;\n  border: none;\n  vertical-align: baseline;\n  background: #e0e1e2 none;\n  color: rgba(0, 0, 0, 0.6);\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  margin: 0em 0.25em 0em 0em;\n  padding: 0.78571429em 1.5em 0.78571429em;\n  text-transform: none;\n  text-shadow: none;\n  font-weight: bold;\n  line-height: 1em;\n  font-style: normal;\n  text-align: center;\n  text-decoration: none;\n  border-radius: 0.28571429rem;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent inset,\n    0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0px 0px 1px transparent inset,\n    0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  -webkit-transition: opacity 0.1s ease, background-color 0.1s ease,\n    color 0.1s ease, background 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: opacity 0.1s ease, background-color 0.1s ease, color 0.1s ease,\n    background 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: opacity 0.1s ease, background-color 0.1s ease, color 0.1s ease,\n    box-shadow 0.1s ease, background 0.1s ease;\n  transition: opacity 0.1s ease, background-color 0.1s ease, color 0.1s ease,\n    box-shadow 0.1s ease, background 0.1s ease, -webkit-box-shadow 0.1s ease;\n  will-change: \"\";\n  -webkit-tap-highlight-color: transparent;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------\n      Hover\n---------------*/\n\n.ui.button:hover {\n  background-color: #cacbcd;\n  background-image: none;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent inset,\n    0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0px 0px 1px transparent inset,\n    0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  color: rgba(0, 0, 0, 0.8);\n}\n\n.ui.button:hover .icon {\n  opacity: 0.85;\n}\n\n/*--------------\n      Focus\n---------------*/\n\n.ui.button:focus {\n  background-color: #cacbcd;\n  color: rgba(0, 0, 0, 0.8);\n  background-image: \"\" !important;\n  -webkit-box-shadow: \"\" !important;\n  box-shadow: \"\" !important;\n}\n\n.ui.button:focus .icon {\n  opacity: 0.85;\n}\n\n/*--------------\n      Down\n---------------*/\n\n.ui.button:active,\n.ui.active.button:active {\n  background-color: #babbbc;\n  background-image: \"\";\n  color: rgba(0, 0, 0, 0.9);\n  -webkit-box-shadow: 0px 0px 0px 1px transparent inset, none;\n  box-shadow: 0px 0px 0px 1px transparent inset, none;\n}\n\n/*--------------\n    Active\n---------------*/\n\n.ui.active.button {\n  background-color: #c0c1c2;\n  background-image: none;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent inset;\n  box-shadow: 0px 0px 0px 1px transparent inset;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n.ui.active.button:hover {\n  background-color: #c0c1c2;\n  background-image: none;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n.ui.active.button:active {\n  background-color: #c0c1c2;\n  background-image: none;\n}\n\n/*--------------\n    Loading\n---------------*/\n\n/* Specificity hack */\n\n.ui.loading.loading.loading.loading.loading.loading.button {\n  position: relative;\n  cursor: default;\n  text-shadow: none !important;\n  color: transparent !important;\n  opacity: 1;\n  pointer-events: auto;\n  -webkit-transition: all 0s linear, opacity 0.1s ease;\n  transition: all 0s linear, opacity 0.1s ease;\n}\n\n.ui.loading.button:before {\n  position: absolute;\n  content: \"\";\n  top: 50%;\n  left: 50%;\n  margin: -0.64285714em 0em 0em -0.64285714em;\n  width: 1.28571429em;\n  height: 1.28571429em;\n  border-radius: 500rem;\n  border: 0.2em solid rgba(0, 0, 0, 0.15);\n}\n\n.ui.loading.button:after {\n  position: absolute;\n  content: \"\";\n  top: 50%;\n  left: 50%;\n  margin: -0.64285714em 0em 0em -0.64285714em;\n  width: 1.28571429em;\n  height: 1.28571429em;\n  -webkit-animation: button-spin 0.6s linear;\n  animation: button-spin 0.6s linear;\n  -webkit-animation-iteration-count: infinite;\n  animation-iteration-count: infinite;\n  border-radius: 500rem;\n  border-color: #ffffff transparent transparent;\n  border-style: solid;\n  border-width: 0.2em;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent;\n  box-shadow: 0px 0px 0px 1px transparent;\n}\n\n.ui.labeled.icon.loading.button .icon {\n  background-color: transparent;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n@-webkit-keyframes button-spin {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes button-spin {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n.ui.basic.loading.button:not(.inverted):before {\n  border-color: rgba(0, 0, 0, 0.1);\n}\n\n.ui.basic.loading.button:not(.inverted):after {\n  border-top-color: #767676;\n}\n\n/*-------------------\n      Disabled\n--------------------*/\n\n.ui.buttons .disabled.button,\n.ui.disabled.button,\n.ui.button:disabled,\n.ui.disabled.button:hover,\n.ui.disabled.active.button {\n  cursor: default;\n  opacity: 0.45 !important;\n  background-image: none !important;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  pointer-events: none !important;\n}\n\n/* Basic Group With Disabled */\n\n.ui.basic.buttons .ui.disabled.button {\n  border-color: rgba(34, 36, 38, 0.5);\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*-------------------\n      Animated\n--------------------*/\n\n.ui.animated.button {\n  position: relative;\n  overflow: hidden;\n  padding-right: 0em !important;\n  vertical-align: middle;\n  z-index: 1;\n}\n\n.ui.animated.button .content {\n  will-change: transform, opacity;\n}\n\n.ui.animated.button .visible.content {\n  position: relative;\n  margin-right: 1.5em;\n}\n\n.ui.animated.button .hidden.content {\n  position: absolute;\n  width: 100%;\n}\n\n/* Horizontal */\n\n.ui.animated.button .visible.content,\n.ui.animated.button .hidden.content {\n  -webkit-transition: right 0.3s ease 0s;\n  transition: right 0.3s ease 0s;\n}\n\n.ui.animated.button .visible.content {\n  left: auto;\n  right: 0%;\n}\n\n.ui.animated.button .hidden.content {\n  top: 50%;\n  left: auto;\n  right: -100%;\n  margin-top: -0.5em;\n}\n\n.ui.animated.button:focus .visible.content,\n.ui.animated.button:hover .visible.content {\n  left: auto;\n  right: 200%;\n}\n\n.ui.animated.button:focus .hidden.content,\n.ui.animated.button:hover .hidden.content {\n  left: auto;\n  right: 0%;\n}\n\n/* Vertical */\n\n.ui.vertical.animated.button .visible.content,\n.ui.vertical.animated.button .hidden.content {\n  -webkit-transition: top 0.3s ease, -webkit-transform 0.3s ease;\n  transition: top 0.3s ease, -webkit-transform 0.3s ease;\n  transition: top 0.3s ease, transform 0.3s ease;\n  transition: top 0.3s ease, transform 0.3s ease, -webkit-transform 0.3s ease;\n}\n\n.ui.vertical.animated.button .visible.content {\n  -webkit-transform: translateY(0%);\n  transform: translateY(0%);\n  right: auto;\n}\n\n.ui.vertical.animated.button .hidden.content {\n  top: -50%;\n  left: 0%;\n  right: auto;\n}\n\n.ui.vertical.animated.button:focus .visible.content,\n.ui.vertical.animated.button:hover .visible.content {\n  -webkit-transform: translateY(200%);\n  transform: translateY(200%);\n  right: auto;\n}\n\n.ui.vertical.animated.button:focus .hidden.content,\n.ui.vertical.animated.button:hover .hidden.content {\n  top: 50%;\n  right: auto;\n}\n\n/* Fade */\n\n.ui.fade.animated.button .visible.content,\n.ui.fade.animated.button .hidden.content {\n  -webkit-transition: opacity 0.3s ease, -webkit-transform 0.3s ease;\n  transition: opacity 0.3s ease, -webkit-transform 0.3s ease;\n  transition: opacity 0.3s ease, transform 0.3s ease;\n  transition: opacity 0.3s ease, transform 0.3s ease,\n    -webkit-transform 0.3s ease;\n}\n\n.ui.fade.animated.button .visible.content {\n  left: auto;\n  right: auto;\n  opacity: 1;\n  -webkit-transform: scale(1);\n  transform: scale(1);\n}\n\n.ui.fade.animated.button .hidden.content {\n  opacity: 0;\n  left: 0%;\n  right: auto;\n  -webkit-transform: scale(1.5);\n  transform: scale(1.5);\n}\n\n.ui.fade.animated.button:focus .visible.content,\n.ui.fade.animated.button:hover .visible.content {\n  left: auto;\n  right: auto;\n  opacity: 0;\n  -webkit-transform: scale(0.75);\n  transform: scale(0.75);\n}\n\n.ui.fade.animated.button:focus .hidden.content,\n.ui.fade.animated.button:hover .hidden.content {\n  left: 0%;\n  right: auto;\n  opacity: 1;\n  -webkit-transform: scale(1);\n  transform: scale(1);\n}\n\n/*-------------------\n      Inverted\n--------------------*/\n\n.ui.inverted.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n  box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n  background: transparent none;\n  color: #ffffff;\n  text-shadow: none !important;\n}\n\n/* Group */\n\n.ui.inverted.buttons .button {\n  margin: 0px 0px 0px -2px;\n}\n\n.ui.inverted.buttons .button:first-child {\n  margin-left: 0em;\n}\n\n.ui.inverted.vertical.buttons .button {\n  margin: 0px 0px -2px 0px;\n}\n\n.ui.inverted.vertical.buttons .button:first-child {\n  margin-top: 0em;\n}\n\n/* States */\n\n/* Hover */\n\n.ui.inverted.button:hover {\n  background: #ffffff;\n  -webkit-box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n  box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n  color: rgba(0, 0, 0, 0.8);\n}\n\n/* Active / Focus */\n\n.ui.inverted.button:focus,\n.ui.inverted.button.active {\n  background: #ffffff;\n  -webkit-box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n  box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n  color: rgba(0, 0, 0, 0.8);\n}\n\n/* Active Focus */\n\n.ui.inverted.button.active:focus {\n  background: #dcddde;\n  -webkit-box-shadow: 0px 0px 0px 2px #dcddde inset !important;\n  box-shadow: 0px 0px 0px 2px #dcddde inset !important;\n  color: rgba(0, 0, 0, 0.8);\n}\n\n/*-------------------\n    Labeled Button\n--------------------*/\n\n.ui.labeled.button:not(.icon) {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n  background: none !important;\n  padding: 0px !important;\n  border: none !important;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n}\n\n.ui.labeled.button > .button {\n  margin: 0px;\n}\n\n.ui.labeled.button > .label {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n  margin: 0px 0px 0px -1px !important;\n  padding: \"\";\n  font-size: 1em;\n  border-color: rgba(34, 36, 38, 0.15);\n}\n\n/* Tag */\n\n.ui.labeled.button > .tag.label:before {\n  width: 1.85em;\n  height: 1.85em;\n}\n\n/* Right */\n\n.ui.labeled.button:not([class*=\"left labeled\"]) > .button {\n  border-top-right-radius: 0px;\n  border-bottom-right-radius: 0px;\n}\n\n.ui.labeled.button:not([class*=\"left labeled\"]) > .label {\n  border-top-left-radius: 0px;\n  border-bottom-left-radius: 0px;\n}\n\n/* Left Side */\n\n.ui[class*=\"left labeled\"].button > .button {\n  border-top-left-radius: 0px;\n  border-bottom-left-radius: 0px;\n}\n\n.ui[class*=\"left labeled\"].button > .label {\n  border-top-right-radius: 0px;\n  border-bottom-right-radius: 0px;\n}\n\n/*-------------------\n      Social\n--------------------*/\n\n/* Facebook */\n\n.ui.facebook.button {\n  background-color: #3b5998;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.facebook.button:hover {\n  background-color: #304d8a;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.facebook.button:active {\n  background-color: #2d4373;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Twitter */\n\n.ui.twitter.button {\n  background-color: #55acee;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.twitter.button:hover {\n  background-color: #35a2f4;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.twitter.button:active {\n  background-color: #2795e9;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Google Plus */\n\n.ui.google.plus.button {\n  background-color: #dd4b39;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.google.plus.button:hover {\n  background-color: #e0321c;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.google.plus.button:active {\n  background-color: #c23321;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Linked In */\n\n.ui.linkedin.button {\n  background-color: #1f88be;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.linkedin.button:hover {\n  background-color: #147baf;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.linkedin.button:active {\n  background-color: #186992;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* YouTube */\n\n.ui.youtube.button {\n  background-color: #ff0000;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.youtube.button:hover {\n  background-color: #e60000;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.youtube.button:active {\n  background-color: #cc0000;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Instagram */\n\n.ui.instagram.button {\n  background-color: #49769c;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.instagram.button:hover {\n  background-color: #3d698e;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.instagram.button:active {\n  background-color: #395c79;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Pinterest */\n\n.ui.pinterest.button {\n  background-color: #bd081c;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.pinterest.button:hover {\n  background-color: #ac0013;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.pinterest.button:active {\n  background-color: #8c0615;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* VK */\n\n.ui.vk.button {\n  background-color: #4d7198;\n  color: #ffffff;\n  background-image: none;\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.vk.button:hover {\n  background-color: #41648a;\n  color: #ffffff;\n}\n\n.ui.vk.button:active {\n  background-color: #3c5876;\n  color: #ffffff;\n}\n\n/*--------------\n    Icon\n---------------*/\n\n.ui.button > .icon:not(.button) {\n  height: 0.85714286em;\n  opacity: 0.8;\n  margin: 0em 0.42857143em 0em -0.21428571em;\n  -webkit-transition: opacity 0.1s ease;\n  transition: opacity 0.1s ease;\n  vertical-align: \"\";\n  color: \"\";\n}\n\n.ui.button:not(.icon) > .icon:not(.button):not(.dropdown) {\n  margin: 0em 0.42857143em 0em -0.21428571em;\n}\n\n.ui.button:not(.icon) > .right.icon:not(.button):not(.dropdown) {\n  margin: 0em -0.21428571em 0em 0.42857143em;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*-------------------\n      Floated\n--------------------*/\n\n.ui[class*=\"left floated\"].buttons,\n.ui[class*=\"left floated\"].button {\n  float: left;\n  margin-left: 0em;\n  margin-right: 0.25em;\n}\n\n.ui[class*=\"right floated\"].buttons,\n.ui[class*=\"right floated\"].button {\n  float: right;\n  margin-right: 0em;\n  margin-left: 0.25em;\n}\n\n/*-------------------\n      Compact\n--------------------*/\n\n.ui.compact.buttons .button,\n.ui.compact.button {\n  padding: 0.58928571em 1.125em 0.58928571em;\n}\n\n.ui.compact.icon.buttons .button,\n.ui.compact.icon.button {\n  padding: 0.58928571em 0.58928571em 0.58928571em;\n}\n\n.ui.compact.labeled.icon.buttons .button,\n.ui.compact.labeled.icon.button {\n  padding: 0.58928571em 3.69642857em 0.58928571em;\n}\n\n/*-------------------\n        Sizes\n--------------------*/\n\n.ui.mini.buttons .button,\n.ui.mini.buttons .or,\n.ui.mini.button {\n  font-size: 0.78571429rem;\n}\n\n.ui.tiny.buttons .button,\n.ui.tiny.buttons .or,\n.ui.tiny.button {\n  font-size: 0.85714286rem;\n}\n\n.ui.small.buttons .button,\n.ui.small.buttons .or,\n.ui.small.button {\n  font-size: 0.92857143rem;\n}\n\n.ui.buttons .button,\n.ui.buttons .or,\n.ui.button {\n  font-size: 1rem;\n}\n\n.ui.large.buttons .button,\n.ui.large.buttons .or,\n.ui.large.button {\n  font-size: 1.14285714rem;\n}\n\n.ui.big.buttons .button,\n.ui.big.buttons .or,\n.ui.big.button {\n  font-size: 1.28571429rem;\n}\n\n.ui.huge.buttons .button,\n.ui.huge.buttons .or,\n.ui.huge.button {\n  font-size: 1.42857143rem;\n}\n\n.ui.massive.buttons .button,\n.ui.massive.buttons .or,\n.ui.massive.button {\n  font-size: 1.71428571rem;\n}\n\n/*--------------\n    Icon Only\n---------------*/\n\n.ui.icon.buttons .button,\n.ui.icon.button {\n  padding: 0.78571429em 0.78571429em 0.78571429em;\n}\n\n.ui.icon.buttons .button > .icon,\n.ui.icon.button > .icon {\n  opacity: 0.9;\n  margin: 0em !important;\n  vertical-align: top;\n}\n\n/*-------------------\n        Basic\n--------------------*/\n\n.ui.basic.buttons .button,\n.ui.basic.button {\n  background: transparent none !important;\n  color: rgba(0, 0, 0, 0.6) !important;\n  font-weight: normal;\n  border-radius: 0.28571429rem;\n  text-transform: none;\n  text-shadow: none !important;\n  -webkit-box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.basic.buttons {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  border-radius: 0.28571429rem;\n}\n\n.ui.basic.buttons .button {\n  border-radius: 0em;\n}\n\n.ui.basic.buttons .button:hover,\n.ui.basic.button:hover {\n  background: #ffffff !important;\n  color: rgba(0, 0, 0, 0.8) !important;\n  -webkit-box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.35) inset,\n    0px 0px 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.35) inset,\n    0px 0px 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.basic.buttons .button:focus,\n.ui.basic.button:focus {\n  background: #ffffff !important;\n  color: rgba(0, 0, 0, 0.8) !important;\n  -webkit-box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.35) inset,\n    0px 0px 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.35) inset,\n    0px 0px 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.basic.buttons .button:active,\n.ui.basic.button:active {\n  background: #f8f8f8 !important;\n  color: rgba(0, 0, 0, 0.9) !important;\n  -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.15) inset,\n    0px 1px 4px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.15) inset,\n    0px 1px 4px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.basic.buttons .active.button,\n.ui.basic.active.button {\n  background: rgba(0, 0, 0, 0.05) !important;\n  -webkit-box-shadow: \"\" !important;\n  box-shadow: \"\" !important;\n  color: rgba(0, 0, 0, 0.95) !important;\n}\n\n.ui.basic.buttons .active.button:hover,\n.ui.basic.active.button:hover {\n  background-color: rgba(0, 0, 0, 0.05);\n}\n\n/* Vertical */\n\n.ui.basic.buttons .button:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.35) inset,\n    0px 0px 0px 0px rgba(34, 36, 38, 0.15) inset inset;\n  box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.35) inset,\n    0px 0px 0px 0px rgba(34, 36, 38, 0.15) inset inset;\n}\n\n.ui.basic.buttons .button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.15) inset,\n    0px 1px 4px 0px rgba(34, 36, 38, 0.15) inset inset;\n  box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.15) inset,\n    0px 1px 4px 0px rgba(34, 36, 38, 0.15) inset inset;\n}\n\n.ui.basic.buttons .active.button {\n  -webkit-box-shadow: \"\" !important;\n  box-shadow: \"\" !important;\n}\n\n/* Standard Basic Inverted */\n\n.ui.basic.inverted.buttons .button,\n.ui.basic.inverted.button {\n  background-color: transparent !important;\n  color: #f9fafb !important;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n}\n\n.ui.basic.inverted.buttons .button:hover,\n.ui.basic.inverted.button:hover {\n  color: #ffffff !important;\n  -webkit-box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n  box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n}\n\n.ui.basic.inverted.buttons .button:focus,\n.ui.basic.inverted.button:focus {\n  color: #ffffff !important;\n  -webkit-box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n  box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n}\n\n.ui.basic.inverted.buttons .button:active,\n.ui.basic.inverted.button:active {\n  background-color: rgba(255, 255, 255, 0.08) !important;\n  color: #ffffff !important;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.9) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.9) inset !important;\n}\n\n.ui.basic.inverted.buttons .active.button,\n.ui.basic.inverted.active.button {\n  background-color: rgba(255, 255, 255, 0.08);\n  color: #ffffff;\n  text-shadow: none;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.7) inset;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.7) inset;\n}\n\n.ui.basic.inverted.buttons .active.button:hover,\n.ui.basic.inverted.active.button:hover {\n  background-color: rgba(255, 255, 255, 0.15);\n  -webkit-box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n  box-shadow: 0px 0px 0px 2px #ffffff inset !important;\n}\n\n/* Basic Group */\n\n.ui.basic.buttons .button {\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.basic.vertical.buttons .button {\n  border-left: none;\n}\n\n.ui.basic.vertical.buttons .button {\n  border-left-width: 0px;\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.basic.vertical.buttons .button:first-child {\n  border-top-width: 0px;\n}\n\n/*--------------\n  Labeled Icon\n---------------*/\n\n.ui.labeled.icon.buttons .button,\n.ui.labeled.icon.button {\n  position: relative;\n  padding-left: 4.07142857em !important;\n  padding-right: 1.5em !important;\n}\n\n/* Left Labeled */\n\n.ui.labeled.icon.buttons > .button > .icon,\n.ui.labeled.icon.button > .icon {\n  position: absolute;\n  height: 100%;\n  line-height: 1;\n  border-radius: 0px;\n  border-top-left-radius: inherit;\n  border-bottom-left-radius: inherit;\n  text-align: center;\n  margin: 0em;\n  width: 2.57142857em;\n  background-color: rgba(0, 0, 0, 0.05);\n  color: \"\";\n  -webkit-box-shadow: -1px 0px 0px 0px transparent inset;\n  box-shadow: -1px 0px 0px 0px transparent inset;\n}\n\n/* Left Labeled */\n\n.ui.labeled.icon.buttons > .button > .icon,\n.ui.labeled.icon.button > .icon {\n  top: 0em;\n  left: 0em;\n}\n\n/* Right Labeled */\n\n.ui[class*=\"right labeled\"].icon.button {\n  padding-right: 4.07142857em !important;\n  padding-left: 1.5em !important;\n}\n\n.ui[class*=\"right labeled\"].icon.button > .icon {\n  left: auto;\n  right: 0em;\n  border-radius: 0px;\n  border-top-right-radius: inherit;\n  border-bottom-right-radius: inherit;\n  -webkit-box-shadow: 1px 0px 0px 0px transparent inset;\n  box-shadow: 1px 0px 0px 0px transparent inset;\n}\n\n.ui.labeled.icon.buttons > .button > .icon:before,\n.ui.labeled.icon.button > .icon:before,\n.ui.labeled.icon.buttons > .button > .icon:after,\n.ui.labeled.icon.button > .icon:after {\n  display: block;\n  position: absolute;\n  width: 100%;\n  top: 50%;\n  text-align: center;\n  -webkit-transform: translateY(-50%);\n  transform: translateY(-50%);\n}\n\n.ui.labeled.icon.buttons .button > .icon {\n  border-radius: 0em;\n}\n\n.ui.labeled.icon.buttons .button:first-child > .icon {\n  border-top-left-radius: 0.28571429rem;\n  border-bottom-left-radius: 0.28571429rem;\n}\n\n.ui.labeled.icon.buttons .button:last-child > .icon {\n  border-top-right-radius: 0.28571429rem;\n  border-bottom-right-radius: 0.28571429rem;\n}\n\n.ui.vertical.labeled.icon.buttons .button:first-child > .icon {\n  border-radius: 0em;\n  border-top-left-radius: 0.28571429rem;\n}\n\n.ui.vertical.labeled.icon.buttons .button:last-child > .icon {\n  border-radius: 0em;\n  border-bottom-left-radius: 0.28571429rem;\n}\n\n/* Fluid Labeled */\n\n.ui.fluid[class*=\"left labeled\"].icon.button,\n.ui.fluid[class*=\"right labeled\"].icon.button {\n  padding-left: 1.5em !important;\n  padding-right: 1.5em !important;\n}\n\n/*--------------\n    Toggle\n---------------*/\n\n/* Toggle (Modifies active state to give affordances) */\n\n.ui.toggle.buttons .active.button,\n.ui.buttons .button.toggle.active,\n.ui.button.toggle.active {\n  background-color: #21ba45 !important;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  text-shadow: none;\n  color: #ffffff !important;\n}\n\n.ui.button.toggle.active:hover {\n  background-color: #16ab39 !important;\n  text-shadow: none;\n  color: #ffffff !important;\n}\n\n/*--------------\n    Circular\n---------------*/\n\n.ui.circular.button {\n  border-radius: 10em;\n}\n\n.ui.circular.button > .icon {\n  width: 1em;\n  vertical-align: baseline;\n}\n\n/*-------------------\n      Or Buttons\n--------------------*/\n\n.ui.buttons .or {\n  position: relative;\n  width: 0.3em;\n  height: 2.57142857em;\n  z-index: 3;\n}\n\n.ui.buttons .or:before {\n  position: absolute;\n  text-align: center;\n  border-radius: 500rem;\n  content: \"or\";\n  top: 50%;\n  left: 50%;\n  background-color: #ffffff;\n  text-shadow: none;\n  margin-top: -0.89285714em;\n  margin-left: -0.89285714em;\n  width: 1.78571429em;\n  height: 1.78571429em;\n  line-height: 1.78571429em;\n  color: rgba(0, 0, 0, 0.4);\n  font-style: normal;\n  font-weight: bold;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent inset;\n  box-shadow: 0px 0px 0px 1px transparent inset;\n}\n\n.ui.buttons .or[data-text]:before {\n  content: attr(data-text);\n}\n\n/* Fluid Or */\n\n.ui.fluid.buttons .or {\n  width: 0em !important;\n}\n\n.ui.fluid.buttons .or:after {\n  display: none;\n}\n\n/*-------------------\n      Attached\n--------------------*/\n\n/* Singular */\n\n.ui.attached.button {\n  position: relative;\n  display: block;\n  margin: 0em;\n  border-radius: 0em;\n  -webkit-box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.15) !important;\n  box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.15) !important;\n}\n\n/* Top / Bottom */\n\n.ui.attached.top.button {\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n.ui.attached.bottom.button {\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n/* Left / Right */\n\n.ui.left.attached.button {\n  display: inline-block;\n  border-left: none;\n  text-align: right;\n  padding-right: 0.75em;\n  border-radius: 0.28571429rem 0em 0em 0.28571429rem;\n}\n\n.ui.right.attached.button {\n  display: inline-block;\n  text-align: left;\n  padding-left: 0.75em;\n  border-radius: 0em 0.28571429rem 0.28571429rem 0em;\n}\n\n/* Plural */\n\n.ui.attached.buttons {\n  position: relative;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  border-radius: 0em;\n  width: auto !important;\n  z-index: 2;\n  margin-left: -1px;\n  margin-right: -1px;\n}\n\n.ui.attached.buttons .button {\n  margin: 0em;\n}\n\n.ui.attached.buttons .button:first-child {\n  border-radius: 0em;\n}\n\n.ui.attached.buttons .button:last-child {\n  border-radius: 0em;\n}\n\n/* Top / Bottom */\n\n.ui[class*=\"top attached\"].buttons {\n  margin-bottom: -1px;\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n.ui[class*=\"top attached\"].buttons .button:first-child {\n  border-radius: 0.28571429rem 0em 0em 0em;\n}\n\n.ui[class*=\"top attached\"].buttons .button:last-child {\n  border-radius: 0em 0.28571429rem 0em 0em;\n}\n\n.ui[class*=\"bottom attached\"].buttons {\n  margin-top: -1px;\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n.ui[class*=\"bottom attached\"].buttons .button:first-child {\n  border-radius: 0em 0em 0em 0.28571429rem;\n}\n\n.ui[class*=\"bottom attached\"].buttons .button:last-child {\n  border-radius: 0em 0em 0.28571429rem 0em;\n}\n\n/* Left / Right */\n\n.ui[class*=\"left attached\"].buttons {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  margin-right: 0em;\n  margin-left: -1px;\n  border-radius: 0em 0.28571429rem 0.28571429rem 0em;\n}\n\n.ui[class*=\"left attached\"].buttons .button:first-child {\n  margin-left: -1px;\n  border-radius: 0em 0.28571429rem 0em 0em;\n}\n\n.ui[class*=\"left attached\"].buttons .button:last-child {\n  margin-left: -1px;\n  border-radius: 0em 0em 0.28571429rem 0em;\n}\n\n.ui[class*=\"right attached\"].buttons {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  margin-left: 0em;\n  margin-right: -1px;\n  border-radius: 0.28571429rem 0em 0em 0.28571429rem;\n}\n\n.ui[class*=\"right attached\"].buttons .button:first-child {\n  margin-left: -1px;\n  border-radius: 0.28571429rem 0em 0em 0em;\n}\n\n.ui[class*=\"right attached\"].buttons .button:last-child {\n  margin-left: -1px;\n  border-radius: 0em 0em 0em 0.28571429rem;\n}\n\n/*-------------------\n        Fluid\n--------------------*/\n\n.ui.fluid.buttons,\n.ui.fluid.button {\n  width: 100%;\n}\n\n.ui.fluid.button {\n  display: block;\n}\n\n.ui.two.buttons {\n  width: 100%;\n}\n\n.ui.two.buttons > .button {\n  width: 50%;\n}\n\n.ui.three.buttons {\n  width: 100%;\n}\n\n.ui.three.buttons > .button {\n  width: 33.333%;\n}\n\n.ui.four.buttons {\n  width: 100%;\n}\n\n.ui.four.buttons > .button {\n  width: 25%;\n}\n\n.ui.five.buttons {\n  width: 100%;\n}\n\n.ui.five.buttons > .button {\n  width: 20%;\n}\n\n.ui.six.buttons {\n  width: 100%;\n}\n\n.ui.six.buttons > .button {\n  width: 16.666%;\n}\n\n.ui.seven.buttons {\n  width: 100%;\n}\n\n.ui.seven.buttons > .button {\n  width: 14.285%;\n}\n\n.ui.eight.buttons {\n  width: 100%;\n}\n\n.ui.eight.buttons > .button {\n  width: 12.5%;\n}\n\n.ui.nine.buttons {\n  width: 100%;\n}\n\n.ui.nine.buttons > .button {\n  width: 11.11%;\n}\n\n.ui.ten.buttons {\n  width: 100%;\n}\n\n.ui.ten.buttons > .button {\n  width: 10%;\n}\n\n.ui.eleven.buttons {\n  width: 100%;\n}\n\n.ui.eleven.buttons > .button {\n  width: 9.09%;\n}\n\n.ui.twelve.buttons {\n  width: 100%;\n}\n\n.ui.twelve.buttons > .button {\n  width: 8.3333%;\n}\n\n/* Fluid Vertical Buttons */\n\n.ui.fluid.vertical.buttons,\n.ui.fluid.vertical.buttons > .button {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  width: auto;\n}\n\n.ui.two.vertical.buttons > .button {\n  height: 50%;\n}\n\n.ui.three.vertical.buttons > .button {\n  height: 33.333%;\n}\n\n.ui.four.vertical.buttons > .button {\n  height: 25%;\n}\n\n.ui.five.vertical.buttons > .button {\n  height: 20%;\n}\n\n.ui.six.vertical.buttons > .button {\n  height: 16.666%;\n}\n\n.ui.seven.vertical.buttons > .button {\n  height: 14.285%;\n}\n\n.ui.eight.vertical.buttons > .button {\n  height: 12.5%;\n}\n\n.ui.nine.vertical.buttons > .button {\n  height: 11.11%;\n}\n\n.ui.ten.vertical.buttons > .button {\n  height: 10%;\n}\n\n.ui.eleven.vertical.buttons > .button {\n  height: 9.09%;\n}\n\n.ui.twelve.vertical.buttons > .button {\n  height: 8.3333%;\n}\n\n/*-------------------\n      Colors\n--------------------*/\n\n/*--- Black ---*/\n\n.ui.black.buttons .button,\n.ui.black.button {\n  background-color: #1b1c1d;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.black.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.black.buttons .button:hover,\n.ui.black.button:hover {\n  background-color: #27292a;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.black.buttons .button:focus,\n.ui.black.button:focus {\n  background-color: #2f3032;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.black.buttons .button:active,\n.ui.black.button:active {\n  background-color: #343637;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.black.buttons .active.button,\n.ui.black.buttons .active.button:active,\n.ui.black.active.button,\n.ui.black.button .active.button:active {\n  background-color: #0f0f10;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.black.buttons .button,\n.ui.basic.black.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #1b1c1d inset !important;\n  box-shadow: 0px 0px 0px 1px #1b1c1d inset !important;\n  color: #1b1c1d !important;\n}\n\n.ui.basic.black.buttons .button:hover,\n.ui.basic.black.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #27292a inset !important;\n  box-shadow: 0px 0px 0px 1px #27292a inset !important;\n  color: #27292a !important;\n}\n\n.ui.basic.black.buttons .button:focus,\n.ui.basic.black.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #2f3032 inset !important;\n  box-shadow: 0px 0px 0px 1px #2f3032 inset !important;\n  color: #27292a !important;\n}\n\n.ui.basic.black.buttons .active.button,\n.ui.basic.black.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #0f0f10 inset !important;\n  box-shadow: 0px 0px 0px 1px #0f0f10 inset !important;\n  color: #343637 !important;\n}\n\n.ui.basic.black.buttons .button:active,\n.ui.basic.black.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #343637 inset !important;\n  box-shadow: 0px 0px 0px 1px #343637 inset !important;\n  color: #343637 !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.black.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.black.buttons .button,\n.ui.inverted.black.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #d4d4d5 inset !important;\n  box-shadow: 0px 0px 0px 2px #d4d4d5 inset !important;\n  color: #ffffff;\n}\n\n.ui.inverted.black.buttons .button:hover,\n.ui.inverted.black.button:hover,\n.ui.inverted.black.buttons .button:focus,\n.ui.inverted.black.button:focus,\n.ui.inverted.black.buttons .button.active,\n.ui.inverted.black.button.active,\n.ui.inverted.black.buttons .button:active,\n.ui.inverted.black.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.inverted.black.buttons .button:hover,\n.ui.inverted.black.button:hover {\n  background-color: #000000;\n}\n\n.ui.inverted.black.buttons .button:focus,\n.ui.inverted.black.button:focus {\n  background-color: #000000;\n}\n\n.ui.inverted.black.buttons .active.button,\n.ui.inverted.black.active.button {\n  background-color: #000000;\n}\n\n.ui.inverted.black.buttons .button:active,\n.ui.inverted.black.button:active {\n  background-color: #000000;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.black.basic.buttons .button,\n.ui.inverted.black.buttons .basic.button,\n.ui.inverted.black.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.black.basic.buttons .button:hover,\n.ui.inverted.black.buttons .basic.button:hover,\n.ui.inverted.black.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #000000 inset !important;\n  box-shadow: 0px 0px 0px 2px #000000 inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.black.basic.buttons .button:focus,\n.ui.inverted.black.basic.buttons .button:focus,\n.ui.inverted.black.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #000000 inset !important;\n  box-shadow: 0px 0px 0px 2px #000000 inset !important;\n  color: #545454 !important;\n}\n\n.ui.inverted.black.basic.buttons .active.button,\n.ui.inverted.black.buttons .basic.active.button,\n.ui.inverted.black.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #000000 inset !important;\n  box-shadow: 0px 0px 0px 2px #000000 inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.black.basic.buttons .button:active,\n.ui.inverted.black.buttons .basic.button:active,\n.ui.inverted.black.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #000000 inset !important;\n  box-shadow: 0px 0px 0px 2px #000000 inset !important;\n  color: #ffffff !important;\n}\n\n/*--- Grey ---*/\n\n.ui.grey.buttons .button,\n.ui.grey.button {\n  background-color: #767676;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.grey.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.grey.buttons .button:hover,\n.ui.grey.button:hover {\n  background-color: #838383;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.grey.buttons .button:focus,\n.ui.grey.button:focus {\n  background-color: #8a8a8a;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.grey.buttons .button:active,\n.ui.grey.button:active {\n  background-color: #909090;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.grey.buttons .active.button,\n.ui.grey.buttons .active.button:active,\n.ui.grey.active.button,\n.ui.grey.button .active.button:active {\n  background-color: #696969;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.grey.buttons .button,\n.ui.basic.grey.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #767676 inset !important;\n  box-shadow: 0px 0px 0px 1px #767676 inset !important;\n  color: #767676 !important;\n}\n\n.ui.basic.grey.buttons .button:hover,\n.ui.basic.grey.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #838383 inset !important;\n  box-shadow: 0px 0px 0px 1px #838383 inset !important;\n  color: #838383 !important;\n}\n\n.ui.basic.grey.buttons .button:focus,\n.ui.basic.grey.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #8a8a8a inset !important;\n  box-shadow: 0px 0px 0px 1px #8a8a8a inset !important;\n  color: #838383 !important;\n}\n\n.ui.basic.grey.buttons .active.button,\n.ui.basic.grey.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #696969 inset !important;\n  box-shadow: 0px 0px 0px 1px #696969 inset !important;\n  color: #909090 !important;\n}\n\n.ui.basic.grey.buttons .button:active,\n.ui.basic.grey.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #909090 inset !important;\n  box-shadow: 0px 0px 0px 1px #909090 inset !important;\n  color: #909090 !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.grey.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.grey.buttons .button,\n.ui.inverted.grey.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #d4d4d5 inset !important;\n  box-shadow: 0px 0px 0px 2px #d4d4d5 inset !important;\n  color: #ffffff;\n}\n\n.ui.inverted.grey.buttons .button:hover,\n.ui.inverted.grey.button:hover,\n.ui.inverted.grey.buttons .button:focus,\n.ui.inverted.grey.button:focus,\n.ui.inverted.grey.buttons .button.active,\n.ui.inverted.grey.button.active,\n.ui.inverted.grey.buttons .button:active,\n.ui.inverted.grey.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: rgba(0, 0, 0, 0.6);\n}\n\n.ui.inverted.grey.buttons .button:hover,\n.ui.inverted.grey.button:hover {\n  background-color: #cfd0d2;\n}\n\n.ui.inverted.grey.buttons .button:focus,\n.ui.inverted.grey.button:focus {\n  background-color: #c7c9cb;\n}\n\n.ui.inverted.grey.buttons .active.button,\n.ui.inverted.grey.active.button {\n  background-color: #cfd0d2;\n}\n\n.ui.inverted.grey.buttons .button:active,\n.ui.inverted.grey.button:active {\n  background-color: #c2c4c5;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.grey.basic.buttons .button,\n.ui.inverted.grey.buttons .basic.button,\n.ui.inverted.grey.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.grey.basic.buttons .button:hover,\n.ui.inverted.grey.buttons .basic.button:hover,\n.ui.inverted.grey.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #cfd0d2 inset !important;\n  box-shadow: 0px 0px 0px 2px #cfd0d2 inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.grey.basic.buttons .button:focus,\n.ui.inverted.grey.basic.buttons .button:focus,\n.ui.inverted.grey.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #c7c9cb inset !important;\n  box-shadow: 0px 0px 0px 2px #c7c9cb inset !important;\n  color: #dcddde !important;\n}\n\n.ui.inverted.grey.basic.buttons .active.button,\n.ui.inverted.grey.buttons .basic.active.button,\n.ui.inverted.grey.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #cfd0d2 inset !important;\n  box-shadow: 0px 0px 0px 2px #cfd0d2 inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.grey.basic.buttons .button:active,\n.ui.inverted.grey.buttons .basic.button:active,\n.ui.inverted.grey.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #c2c4c5 inset !important;\n  box-shadow: 0px 0px 0px 2px #c2c4c5 inset !important;\n  color: #ffffff !important;\n}\n\n/*--- Brown ---*/\n\n.ui.brown.buttons .button,\n.ui.brown.button {\n  background-color: #a5673f;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.brown.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.brown.buttons .button:hover,\n.ui.brown.button:hover {\n  background-color: #975b33;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.brown.buttons .button:focus,\n.ui.brown.button:focus {\n  background-color: #90532b;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.brown.buttons .button:active,\n.ui.brown.button:active {\n  background-color: #805031;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.brown.buttons .active.button,\n.ui.brown.buttons .active.button:active,\n.ui.brown.active.button,\n.ui.brown.button .active.button:active {\n  background-color: #995a31;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.brown.buttons .button,\n.ui.basic.brown.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #a5673f inset !important;\n  box-shadow: 0px 0px 0px 1px #a5673f inset !important;\n  color: #a5673f !important;\n}\n\n.ui.basic.brown.buttons .button:hover,\n.ui.basic.brown.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #975b33 inset !important;\n  box-shadow: 0px 0px 0px 1px #975b33 inset !important;\n  color: #975b33 !important;\n}\n\n.ui.basic.brown.buttons .button:focus,\n.ui.basic.brown.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #90532b inset !important;\n  box-shadow: 0px 0px 0px 1px #90532b inset !important;\n  color: #975b33 !important;\n}\n\n.ui.basic.brown.buttons .active.button,\n.ui.basic.brown.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #995a31 inset !important;\n  box-shadow: 0px 0px 0px 1px #995a31 inset !important;\n  color: #805031 !important;\n}\n\n.ui.basic.brown.buttons .button:active,\n.ui.basic.brown.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #805031 inset !important;\n  box-shadow: 0px 0px 0px 1px #805031 inset !important;\n  color: #805031 !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.brown.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.brown.buttons .button,\n.ui.inverted.brown.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #d67c1c inset !important;\n  box-shadow: 0px 0px 0px 2px #d67c1c inset !important;\n  color: #d67c1c;\n}\n\n.ui.inverted.brown.buttons .button:hover,\n.ui.inverted.brown.button:hover,\n.ui.inverted.brown.buttons .button:focus,\n.ui.inverted.brown.button:focus,\n.ui.inverted.brown.buttons .button.active,\n.ui.inverted.brown.button.active,\n.ui.inverted.brown.buttons .button:active,\n.ui.inverted.brown.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.inverted.brown.buttons .button:hover,\n.ui.inverted.brown.button:hover {\n  background-color: #c86f11;\n}\n\n.ui.inverted.brown.buttons .button:focus,\n.ui.inverted.brown.button:focus {\n  background-color: #c16808;\n}\n\n.ui.inverted.brown.buttons .active.button,\n.ui.inverted.brown.active.button {\n  background-color: #cc6f0d;\n}\n\n.ui.inverted.brown.buttons .button:active,\n.ui.inverted.brown.button:active {\n  background-color: #a96216;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.brown.basic.buttons .button,\n.ui.inverted.brown.buttons .basic.button,\n.ui.inverted.brown.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.brown.basic.buttons .button:hover,\n.ui.inverted.brown.buttons .basic.button:hover,\n.ui.inverted.brown.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #c86f11 inset !important;\n  box-shadow: 0px 0px 0px 2px #c86f11 inset !important;\n  color: #d67c1c !important;\n}\n\n.ui.inverted.brown.basic.buttons .button:focus,\n.ui.inverted.brown.basic.buttons .button:focus,\n.ui.inverted.brown.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #c16808 inset !important;\n  box-shadow: 0px 0px 0px 2px #c16808 inset !important;\n  color: #d67c1c !important;\n}\n\n.ui.inverted.brown.basic.buttons .active.button,\n.ui.inverted.brown.buttons .basic.active.button,\n.ui.inverted.brown.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #cc6f0d inset !important;\n  box-shadow: 0px 0px 0px 2px #cc6f0d inset !important;\n  color: #d67c1c !important;\n}\n\n.ui.inverted.brown.basic.buttons .button:active,\n.ui.inverted.brown.buttons .basic.button:active,\n.ui.inverted.brown.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #a96216 inset !important;\n  box-shadow: 0px 0px 0px 2px #a96216 inset !important;\n  color: #d67c1c !important;\n}\n\n/*--- Blue ---*/\n\n.ui.blue.buttons .button,\n.ui.blue.button {\n  background-color: #2185d0;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.blue.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.blue.buttons .button:hover,\n.ui.blue.button:hover {\n  background-color: #1678c2;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.blue.buttons .button:focus,\n.ui.blue.button:focus {\n  background-color: #0d71bb;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.blue.buttons .button:active,\n.ui.blue.button:active {\n  background-color: #1a69a4;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.blue.buttons .active.button,\n.ui.blue.buttons .active.button:active,\n.ui.blue.active.button,\n.ui.blue.button .active.button:active {\n  background-color: #1279c6;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.blue.buttons .button,\n.ui.basic.blue.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #2185d0 inset !important;\n  box-shadow: 0px 0px 0px 1px #2185d0 inset !important;\n  color: #2185d0 !important;\n}\n\n.ui.basic.blue.buttons .button:hover,\n.ui.basic.blue.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #1678c2 inset !important;\n  box-shadow: 0px 0px 0px 1px #1678c2 inset !important;\n  color: #1678c2 !important;\n}\n\n.ui.basic.blue.buttons .button:focus,\n.ui.basic.blue.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #0d71bb inset !important;\n  box-shadow: 0px 0px 0px 1px #0d71bb inset !important;\n  color: #1678c2 !important;\n}\n\n.ui.basic.blue.buttons .active.button,\n.ui.basic.blue.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #1279c6 inset !important;\n  box-shadow: 0px 0px 0px 1px #1279c6 inset !important;\n  color: #1a69a4 !important;\n}\n\n.ui.basic.blue.buttons .button:active,\n.ui.basic.blue.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #1a69a4 inset !important;\n  box-shadow: 0px 0px 0px 1px #1a69a4 inset !important;\n  color: #1a69a4 !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.blue.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.blue.buttons .button,\n.ui.inverted.blue.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #54c8ff inset !important;\n  box-shadow: 0px 0px 0px 2px #54c8ff inset !important;\n  color: #54c8ff;\n}\n\n.ui.inverted.blue.buttons .button:hover,\n.ui.inverted.blue.button:hover,\n.ui.inverted.blue.buttons .button:focus,\n.ui.inverted.blue.button:focus,\n.ui.inverted.blue.buttons .button.active,\n.ui.inverted.blue.button.active,\n.ui.inverted.blue.buttons .button:active,\n.ui.inverted.blue.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.inverted.blue.buttons .button:hover,\n.ui.inverted.blue.button:hover {\n  background-color: #3ac0ff;\n}\n\n.ui.inverted.blue.buttons .button:focus,\n.ui.inverted.blue.button:focus {\n  background-color: #2bbbff;\n}\n\n.ui.inverted.blue.buttons .active.button,\n.ui.inverted.blue.active.button {\n  background-color: #3ac0ff;\n}\n\n.ui.inverted.blue.buttons .button:active,\n.ui.inverted.blue.button:active {\n  background-color: #21b8ff;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.blue.basic.buttons .button,\n.ui.inverted.blue.buttons .basic.button,\n.ui.inverted.blue.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.blue.basic.buttons .button:hover,\n.ui.inverted.blue.buttons .basic.button:hover,\n.ui.inverted.blue.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #3ac0ff inset !important;\n  box-shadow: 0px 0px 0px 2px #3ac0ff inset !important;\n  color: #54c8ff !important;\n}\n\n.ui.inverted.blue.basic.buttons .button:focus,\n.ui.inverted.blue.basic.buttons .button:focus,\n.ui.inverted.blue.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #2bbbff inset !important;\n  box-shadow: 0px 0px 0px 2px #2bbbff inset !important;\n  color: #54c8ff !important;\n}\n\n.ui.inverted.blue.basic.buttons .active.button,\n.ui.inverted.blue.buttons .basic.active.button,\n.ui.inverted.blue.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #3ac0ff inset !important;\n  box-shadow: 0px 0px 0px 2px #3ac0ff inset !important;\n  color: #54c8ff !important;\n}\n\n.ui.inverted.blue.basic.buttons .button:active,\n.ui.inverted.blue.buttons .basic.button:active,\n.ui.inverted.blue.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #21b8ff inset !important;\n  box-shadow: 0px 0px 0px 2px #21b8ff inset !important;\n  color: #54c8ff !important;\n}\n\n/*--- Green ---*/\n\n.ui.green.buttons .button,\n.ui.green.button {\n  background-color: #21ba45;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.green.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.green.buttons .button:hover,\n.ui.green.button:hover {\n  background-color: #16ab39;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.green.buttons .button:focus,\n.ui.green.button:focus {\n  background-color: #0ea432;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.green.buttons .button:active,\n.ui.green.button:active {\n  background-color: #198f35;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.green.buttons .active.button,\n.ui.green.buttons .active.button:active,\n.ui.green.active.button,\n.ui.green.button .active.button:active {\n  background-color: #13ae38;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.green.buttons .button,\n.ui.basic.green.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #21ba45 inset !important;\n  box-shadow: 0px 0px 0px 1px #21ba45 inset !important;\n  color: #21ba45 !important;\n}\n\n.ui.basic.green.buttons .button:hover,\n.ui.basic.green.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #16ab39 inset !important;\n  box-shadow: 0px 0px 0px 1px #16ab39 inset !important;\n  color: #16ab39 !important;\n}\n\n.ui.basic.green.buttons .button:focus,\n.ui.basic.green.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #0ea432 inset !important;\n  box-shadow: 0px 0px 0px 1px #0ea432 inset !important;\n  color: #16ab39 !important;\n}\n\n.ui.basic.green.buttons .active.button,\n.ui.basic.green.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #13ae38 inset !important;\n  box-shadow: 0px 0px 0px 1px #13ae38 inset !important;\n  color: #198f35 !important;\n}\n\n.ui.basic.green.buttons .button:active,\n.ui.basic.green.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #198f35 inset !important;\n  box-shadow: 0px 0px 0px 1px #198f35 inset !important;\n  color: #198f35 !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.green.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.green.buttons .button,\n.ui.inverted.green.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #2ecc40 inset !important;\n  box-shadow: 0px 0px 0px 2px #2ecc40 inset !important;\n  color: #2ecc40;\n}\n\n.ui.inverted.green.buttons .button:hover,\n.ui.inverted.green.button:hover,\n.ui.inverted.green.buttons .button:focus,\n.ui.inverted.green.button:focus,\n.ui.inverted.green.buttons .button.active,\n.ui.inverted.green.button.active,\n.ui.inverted.green.buttons .button:active,\n.ui.inverted.green.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.inverted.green.buttons .button:hover,\n.ui.inverted.green.button:hover {\n  background-color: #22be34;\n}\n\n.ui.inverted.green.buttons .button:focus,\n.ui.inverted.green.button:focus {\n  background-color: #19b82b;\n}\n\n.ui.inverted.green.buttons .active.button,\n.ui.inverted.green.active.button {\n  background-color: #1fc231;\n}\n\n.ui.inverted.green.buttons .button:active,\n.ui.inverted.green.button:active {\n  background-color: #25a233;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.green.basic.buttons .button,\n.ui.inverted.green.buttons .basic.button,\n.ui.inverted.green.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.green.basic.buttons .button:hover,\n.ui.inverted.green.buttons .basic.button:hover,\n.ui.inverted.green.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #22be34 inset !important;\n  box-shadow: 0px 0px 0px 2px #22be34 inset !important;\n  color: #2ecc40 !important;\n}\n\n.ui.inverted.green.basic.buttons .button:focus,\n.ui.inverted.green.basic.buttons .button:focus,\n.ui.inverted.green.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #19b82b inset !important;\n  box-shadow: 0px 0px 0px 2px #19b82b inset !important;\n  color: #2ecc40 !important;\n}\n\n.ui.inverted.green.basic.buttons .active.button,\n.ui.inverted.green.buttons .basic.active.button,\n.ui.inverted.green.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #1fc231 inset !important;\n  box-shadow: 0px 0px 0px 2px #1fc231 inset !important;\n  color: #2ecc40 !important;\n}\n\n.ui.inverted.green.basic.buttons .button:active,\n.ui.inverted.green.buttons .basic.button:active,\n.ui.inverted.green.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #25a233 inset !important;\n  box-shadow: 0px 0px 0px 2px #25a233 inset !important;\n  color: #2ecc40 !important;\n}\n\n/*--- Orange ---*/\n\n.ui.orange.buttons .button,\n.ui.orange.button {\n  background-color: #f2711c;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.orange.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.orange.buttons .button:hover,\n.ui.orange.button:hover {\n  background-color: #f26202;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.orange.buttons .button:focus,\n.ui.orange.button:focus {\n  background-color: #e55b00;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.orange.buttons .button:active,\n.ui.orange.button:active {\n  background-color: #cf590c;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.orange.buttons .active.button,\n.ui.orange.buttons .active.button:active,\n.ui.orange.active.button,\n.ui.orange.button .active.button:active {\n  background-color: #f56100;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.orange.buttons .button,\n.ui.basic.orange.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #f2711c inset !important;\n  box-shadow: 0px 0px 0px 1px #f2711c inset !important;\n  color: #f2711c !important;\n}\n\n.ui.basic.orange.buttons .button:hover,\n.ui.basic.orange.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #f26202 inset !important;\n  box-shadow: 0px 0px 0px 1px #f26202 inset !important;\n  color: #f26202 !important;\n}\n\n.ui.basic.orange.buttons .button:focus,\n.ui.basic.orange.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #e55b00 inset !important;\n  box-shadow: 0px 0px 0px 1px #e55b00 inset !important;\n  color: #f26202 !important;\n}\n\n.ui.basic.orange.buttons .active.button,\n.ui.basic.orange.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #f56100 inset !important;\n  box-shadow: 0px 0px 0px 1px #f56100 inset !important;\n  color: #cf590c !important;\n}\n\n.ui.basic.orange.buttons .button:active,\n.ui.basic.orange.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #cf590c inset !important;\n  box-shadow: 0px 0px 0px 1px #cf590c inset !important;\n  color: #cf590c !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.orange.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.orange.buttons .button,\n.ui.inverted.orange.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #ff851b inset !important;\n  box-shadow: 0px 0px 0px 2px #ff851b inset !important;\n  color: #ff851b;\n}\n\n.ui.inverted.orange.buttons .button:hover,\n.ui.inverted.orange.button:hover,\n.ui.inverted.orange.buttons .button:focus,\n.ui.inverted.orange.button:focus,\n.ui.inverted.orange.buttons .button.active,\n.ui.inverted.orange.button.active,\n.ui.inverted.orange.buttons .button:active,\n.ui.inverted.orange.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.inverted.orange.buttons .button:hover,\n.ui.inverted.orange.button:hover {\n  background-color: #ff7701;\n}\n\n.ui.inverted.orange.buttons .button:focus,\n.ui.inverted.orange.button:focus {\n  background-color: #f17000;\n}\n\n.ui.inverted.orange.buttons .active.button,\n.ui.inverted.orange.active.button {\n  background-color: #ff7701;\n}\n\n.ui.inverted.orange.buttons .button:active,\n.ui.inverted.orange.button:active {\n  background-color: #e76b00;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.orange.basic.buttons .button,\n.ui.inverted.orange.buttons .basic.button,\n.ui.inverted.orange.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.orange.basic.buttons .button:hover,\n.ui.inverted.orange.buttons .basic.button:hover,\n.ui.inverted.orange.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #ff7701 inset !important;\n  box-shadow: 0px 0px 0px 2px #ff7701 inset !important;\n  color: #ff851b !important;\n}\n\n.ui.inverted.orange.basic.buttons .button:focus,\n.ui.inverted.orange.basic.buttons .button:focus,\n.ui.inverted.orange.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #f17000 inset !important;\n  box-shadow: 0px 0px 0px 2px #f17000 inset !important;\n  color: #ff851b !important;\n}\n\n.ui.inverted.orange.basic.buttons .active.button,\n.ui.inverted.orange.buttons .basic.active.button,\n.ui.inverted.orange.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #ff7701 inset !important;\n  box-shadow: 0px 0px 0px 2px #ff7701 inset !important;\n  color: #ff851b !important;\n}\n\n.ui.inverted.orange.basic.buttons .button:active,\n.ui.inverted.orange.buttons .basic.button:active,\n.ui.inverted.orange.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #e76b00 inset !important;\n  box-shadow: 0px 0px 0px 2px #e76b00 inset !important;\n  color: #ff851b !important;\n}\n\n/*--- Pink ---*/\n\n.ui.pink.buttons .button,\n.ui.pink.button {\n  background-color: #e03997;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.pink.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.pink.buttons .button:hover,\n.ui.pink.button:hover {\n  background-color: #e61a8d;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.pink.buttons .button:focus,\n.ui.pink.button:focus {\n  background-color: #e10f85;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.pink.buttons .button:active,\n.ui.pink.button:active {\n  background-color: #c71f7e;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.pink.buttons .active.button,\n.ui.pink.buttons .active.button:active,\n.ui.pink.active.button,\n.ui.pink.button .active.button:active {\n  background-color: #ea158d;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.pink.buttons .button,\n.ui.basic.pink.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #e03997 inset !important;\n  box-shadow: 0px 0px 0px 1px #e03997 inset !important;\n  color: #e03997 !important;\n}\n\n.ui.basic.pink.buttons .button:hover,\n.ui.basic.pink.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #e61a8d inset !important;\n  box-shadow: 0px 0px 0px 1px #e61a8d inset !important;\n  color: #e61a8d !important;\n}\n\n.ui.basic.pink.buttons .button:focus,\n.ui.basic.pink.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #e10f85 inset !important;\n  box-shadow: 0px 0px 0px 1px #e10f85 inset !important;\n  color: #e61a8d !important;\n}\n\n.ui.basic.pink.buttons .active.button,\n.ui.basic.pink.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #ea158d inset !important;\n  box-shadow: 0px 0px 0px 1px #ea158d inset !important;\n  color: #c71f7e !important;\n}\n\n.ui.basic.pink.buttons .button:active,\n.ui.basic.pink.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #c71f7e inset !important;\n  box-shadow: 0px 0px 0px 1px #c71f7e inset !important;\n  color: #c71f7e !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.pink.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.pink.buttons .button,\n.ui.inverted.pink.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #ff8edf inset !important;\n  box-shadow: 0px 0px 0px 2px #ff8edf inset !important;\n  color: #ff8edf;\n}\n\n.ui.inverted.pink.buttons .button:hover,\n.ui.inverted.pink.button:hover,\n.ui.inverted.pink.buttons .button:focus,\n.ui.inverted.pink.button:focus,\n.ui.inverted.pink.buttons .button.active,\n.ui.inverted.pink.button.active,\n.ui.inverted.pink.buttons .button:active,\n.ui.inverted.pink.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.inverted.pink.buttons .button:hover,\n.ui.inverted.pink.button:hover {\n  background-color: #ff74d8;\n}\n\n.ui.inverted.pink.buttons .button:focus,\n.ui.inverted.pink.button:focus {\n  background-color: #ff65d3;\n}\n\n.ui.inverted.pink.buttons .active.button,\n.ui.inverted.pink.active.button {\n  background-color: #ff74d8;\n}\n\n.ui.inverted.pink.buttons .button:active,\n.ui.inverted.pink.button:active {\n  background-color: #ff5bd1;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.pink.basic.buttons .button,\n.ui.inverted.pink.buttons .basic.button,\n.ui.inverted.pink.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.pink.basic.buttons .button:hover,\n.ui.inverted.pink.buttons .basic.button:hover,\n.ui.inverted.pink.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #ff74d8 inset !important;\n  box-shadow: 0px 0px 0px 2px #ff74d8 inset !important;\n  color: #ff8edf !important;\n}\n\n.ui.inverted.pink.basic.buttons .button:focus,\n.ui.inverted.pink.basic.buttons .button:focus,\n.ui.inverted.pink.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #ff65d3 inset !important;\n  box-shadow: 0px 0px 0px 2px #ff65d3 inset !important;\n  color: #ff8edf !important;\n}\n\n.ui.inverted.pink.basic.buttons .active.button,\n.ui.inverted.pink.buttons .basic.active.button,\n.ui.inverted.pink.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #ff74d8 inset !important;\n  box-shadow: 0px 0px 0px 2px #ff74d8 inset !important;\n  color: #ff8edf !important;\n}\n\n.ui.inverted.pink.basic.buttons .button:active,\n.ui.inverted.pink.buttons .basic.button:active,\n.ui.inverted.pink.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #ff5bd1 inset !important;\n  box-shadow: 0px 0px 0px 2px #ff5bd1 inset !important;\n  color: #ff8edf !important;\n}\n\n/*--- Violet ---*/\n\n.ui.violet.buttons .button,\n.ui.violet.button {\n  background-color: #6435c9;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.violet.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.violet.buttons .button:hover,\n.ui.violet.button:hover {\n  background-color: #5829bb;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.violet.buttons .button:focus,\n.ui.violet.button:focus {\n  background-color: #4f20b5;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.violet.buttons .button:active,\n.ui.violet.button:active {\n  background-color: #502aa1;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.violet.buttons .active.button,\n.ui.violet.buttons .active.button:active,\n.ui.violet.active.button,\n.ui.violet.button .active.button:active {\n  background-color: #5626bf;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.violet.buttons .button,\n.ui.basic.violet.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #6435c9 inset !important;\n  box-shadow: 0px 0px 0px 1px #6435c9 inset !important;\n  color: #6435c9 !important;\n}\n\n.ui.basic.violet.buttons .button:hover,\n.ui.basic.violet.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #5829bb inset !important;\n  box-shadow: 0px 0px 0px 1px #5829bb inset !important;\n  color: #5829bb !important;\n}\n\n.ui.basic.violet.buttons .button:focus,\n.ui.basic.violet.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #4f20b5 inset !important;\n  box-shadow: 0px 0px 0px 1px #4f20b5 inset !important;\n  color: #5829bb !important;\n}\n\n.ui.basic.violet.buttons .active.button,\n.ui.basic.violet.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #5626bf inset !important;\n  box-shadow: 0px 0px 0px 1px #5626bf inset !important;\n  color: #502aa1 !important;\n}\n\n.ui.basic.violet.buttons .button:active,\n.ui.basic.violet.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #502aa1 inset !important;\n  box-shadow: 0px 0px 0px 1px #502aa1 inset !important;\n  color: #502aa1 !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.violet.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.violet.buttons .button,\n.ui.inverted.violet.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #a291fb inset !important;\n  box-shadow: 0px 0px 0px 2px #a291fb inset !important;\n  color: #a291fb;\n}\n\n.ui.inverted.violet.buttons .button:hover,\n.ui.inverted.violet.button:hover,\n.ui.inverted.violet.buttons .button:focus,\n.ui.inverted.violet.button:focus,\n.ui.inverted.violet.buttons .button.active,\n.ui.inverted.violet.button.active,\n.ui.inverted.violet.buttons .button:active,\n.ui.inverted.violet.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.inverted.violet.buttons .button:hover,\n.ui.inverted.violet.button:hover {\n  background-color: #8a73ff;\n}\n\n.ui.inverted.violet.buttons .button:focus,\n.ui.inverted.violet.button:focus {\n  background-color: #7d64ff;\n}\n\n.ui.inverted.violet.buttons .active.button,\n.ui.inverted.violet.active.button {\n  background-color: #8a73ff;\n}\n\n.ui.inverted.violet.buttons .button:active,\n.ui.inverted.violet.button:active {\n  background-color: #7860f9;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.violet.basic.buttons .button,\n.ui.inverted.violet.buttons .basic.button,\n.ui.inverted.violet.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.violet.basic.buttons .button:hover,\n.ui.inverted.violet.buttons .basic.button:hover,\n.ui.inverted.violet.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #8a73ff inset !important;\n  box-shadow: 0px 0px 0px 2px #8a73ff inset !important;\n  color: #a291fb !important;\n}\n\n.ui.inverted.violet.basic.buttons .button:focus,\n.ui.inverted.violet.basic.buttons .button:focus,\n.ui.inverted.violet.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #7d64ff inset !important;\n  box-shadow: 0px 0px 0px 2px #7d64ff inset !important;\n  color: #a291fb !important;\n}\n\n.ui.inverted.violet.basic.buttons .active.button,\n.ui.inverted.violet.buttons .basic.active.button,\n.ui.inverted.violet.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #8a73ff inset !important;\n  box-shadow: 0px 0px 0px 2px #8a73ff inset !important;\n  color: #a291fb !important;\n}\n\n.ui.inverted.violet.basic.buttons .button:active,\n.ui.inverted.violet.buttons .basic.button:active,\n.ui.inverted.violet.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #7860f9 inset !important;\n  box-shadow: 0px 0px 0px 2px #7860f9 inset !important;\n  color: #a291fb !important;\n}\n\n/*--- Purple ---*/\n\n.ui.purple.buttons .button,\n.ui.purple.button {\n  background-color: #a333c8;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.purple.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.purple.buttons .button:hover,\n.ui.purple.button:hover {\n  background-color: #9627ba;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.purple.buttons .button:focus,\n.ui.purple.button:focus {\n  background-color: #8f1eb4;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.purple.buttons .button:active,\n.ui.purple.button:active {\n  background-color: #82299f;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.purple.buttons .active.button,\n.ui.purple.buttons .active.button:active,\n.ui.purple.active.button,\n.ui.purple.button .active.button:active {\n  background-color: #9724be;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.purple.buttons .button,\n.ui.basic.purple.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #a333c8 inset !important;\n  box-shadow: 0px 0px 0px 1px #a333c8 inset !important;\n  color: #a333c8 !important;\n}\n\n.ui.basic.purple.buttons .button:hover,\n.ui.basic.purple.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #9627ba inset !important;\n  box-shadow: 0px 0px 0px 1px #9627ba inset !important;\n  color: #9627ba !important;\n}\n\n.ui.basic.purple.buttons .button:focus,\n.ui.basic.purple.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #8f1eb4 inset !important;\n  box-shadow: 0px 0px 0px 1px #8f1eb4 inset !important;\n  color: #9627ba !important;\n}\n\n.ui.basic.purple.buttons .active.button,\n.ui.basic.purple.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #9724be inset !important;\n  box-shadow: 0px 0px 0px 1px #9724be inset !important;\n  color: #82299f !important;\n}\n\n.ui.basic.purple.buttons .button:active,\n.ui.basic.purple.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #82299f inset !important;\n  box-shadow: 0px 0px 0px 1px #82299f inset !important;\n  color: #82299f !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.purple.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.purple.buttons .button,\n.ui.inverted.purple.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #dc73ff inset !important;\n  box-shadow: 0px 0px 0px 2px #dc73ff inset !important;\n  color: #dc73ff;\n}\n\n.ui.inverted.purple.buttons .button:hover,\n.ui.inverted.purple.button:hover,\n.ui.inverted.purple.buttons .button:focus,\n.ui.inverted.purple.button:focus,\n.ui.inverted.purple.buttons .button.active,\n.ui.inverted.purple.button.active,\n.ui.inverted.purple.buttons .button:active,\n.ui.inverted.purple.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.inverted.purple.buttons .button:hover,\n.ui.inverted.purple.button:hover {\n  background-color: #d65aff;\n}\n\n.ui.inverted.purple.buttons .button:focus,\n.ui.inverted.purple.button:focus {\n  background-color: #d24aff;\n}\n\n.ui.inverted.purple.buttons .active.button,\n.ui.inverted.purple.active.button {\n  background-color: #d65aff;\n}\n\n.ui.inverted.purple.buttons .button:active,\n.ui.inverted.purple.button:active {\n  background-color: #cf40ff;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.purple.basic.buttons .button,\n.ui.inverted.purple.buttons .basic.button,\n.ui.inverted.purple.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.purple.basic.buttons .button:hover,\n.ui.inverted.purple.buttons .basic.button:hover,\n.ui.inverted.purple.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #d65aff inset !important;\n  box-shadow: 0px 0px 0px 2px #d65aff inset !important;\n  color: #dc73ff !important;\n}\n\n.ui.inverted.purple.basic.buttons .button:focus,\n.ui.inverted.purple.basic.buttons .button:focus,\n.ui.inverted.purple.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #d24aff inset !important;\n  box-shadow: 0px 0px 0px 2px #d24aff inset !important;\n  color: #dc73ff !important;\n}\n\n.ui.inverted.purple.basic.buttons .active.button,\n.ui.inverted.purple.buttons .basic.active.button,\n.ui.inverted.purple.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #d65aff inset !important;\n  box-shadow: 0px 0px 0px 2px #d65aff inset !important;\n  color: #dc73ff !important;\n}\n\n.ui.inverted.purple.basic.buttons .button:active,\n.ui.inverted.purple.buttons .basic.button:active,\n.ui.inverted.purple.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #cf40ff inset !important;\n  box-shadow: 0px 0px 0px 2px #cf40ff inset !important;\n  color: #dc73ff !important;\n}\n\n/*--- Red ---*/\n\n.ui.red.buttons .button,\n.ui.red.button {\n  background-color: #db2828;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.red.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.red.buttons .button:hover,\n.ui.red.button:hover {\n  background-color: #d01919;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.red.buttons .button:focus,\n.ui.red.button:focus {\n  background-color: #ca1010;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.red.buttons .button:active,\n.ui.red.button:active {\n  background-color: #b21e1e;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.red.buttons .active.button,\n.ui.red.buttons .active.button:active,\n.ui.red.active.button,\n.ui.red.button .active.button:active {\n  background-color: #d41515;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.red.buttons .button,\n.ui.basic.red.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #db2828 inset !important;\n  box-shadow: 0px 0px 0px 1px #db2828 inset !important;\n  color: #db2828 !important;\n}\n\n.ui.basic.red.buttons .button:hover,\n.ui.basic.red.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #d01919 inset !important;\n  box-shadow: 0px 0px 0px 1px #d01919 inset !important;\n  color: #d01919 !important;\n}\n\n.ui.basic.red.buttons .button:focus,\n.ui.basic.red.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #ca1010 inset !important;\n  box-shadow: 0px 0px 0px 1px #ca1010 inset !important;\n  color: #d01919 !important;\n}\n\n.ui.basic.red.buttons .active.button,\n.ui.basic.red.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #d41515 inset !important;\n  box-shadow: 0px 0px 0px 1px #d41515 inset !important;\n  color: #b21e1e !important;\n}\n\n.ui.basic.red.buttons .button:active,\n.ui.basic.red.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #b21e1e inset !important;\n  box-shadow: 0px 0px 0px 1px #b21e1e inset !important;\n  color: #b21e1e !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.red.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.red.buttons .button,\n.ui.inverted.red.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #ff695e inset !important;\n  box-shadow: 0px 0px 0px 2px #ff695e inset !important;\n  color: #ff695e;\n}\n\n.ui.inverted.red.buttons .button:hover,\n.ui.inverted.red.button:hover,\n.ui.inverted.red.buttons .button:focus,\n.ui.inverted.red.button:focus,\n.ui.inverted.red.buttons .button.active,\n.ui.inverted.red.button.active,\n.ui.inverted.red.buttons .button:active,\n.ui.inverted.red.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.inverted.red.buttons .button:hover,\n.ui.inverted.red.button:hover {\n  background-color: #ff5144;\n}\n\n.ui.inverted.red.buttons .button:focus,\n.ui.inverted.red.button:focus {\n  background-color: #ff4335;\n}\n\n.ui.inverted.red.buttons .active.button,\n.ui.inverted.red.active.button {\n  background-color: #ff5144;\n}\n\n.ui.inverted.red.buttons .button:active,\n.ui.inverted.red.button:active {\n  background-color: #ff392b;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.red.basic.buttons .button,\n.ui.inverted.red.buttons .basic.button,\n.ui.inverted.red.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.red.basic.buttons .button:hover,\n.ui.inverted.red.buttons .basic.button:hover,\n.ui.inverted.red.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #ff5144 inset !important;\n  box-shadow: 0px 0px 0px 2px #ff5144 inset !important;\n  color: #ff695e !important;\n}\n\n.ui.inverted.red.basic.buttons .button:focus,\n.ui.inverted.red.basic.buttons .button:focus,\n.ui.inverted.red.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #ff4335 inset !important;\n  box-shadow: 0px 0px 0px 2px #ff4335 inset !important;\n  color: #ff695e !important;\n}\n\n.ui.inverted.red.basic.buttons .active.button,\n.ui.inverted.red.buttons .basic.active.button,\n.ui.inverted.red.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #ff5144 inset !important;\n  box-shadow: 0px 0px 0px 2px #ff5144 inset !important;\n  color: #ff695e !important;\n}\n\n.ui.inverted.red.basic.buttons .button:active,\n.ui.inverted.red.buttons .basic.button:active,\n.ui.inverted.red.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #ff392b inset !important;\n  box-shadow: 0px 0px 0px 2px #ff392b inset !important;\n  color: #ff695e !important;\n}\n\n/*--- Teal ---*/\n\n.ui.teal.buttons .button,\n.ui.teal.button {\n  background-color: #00b5ad;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.teal.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.teal.buttons .button:hover,\n.ui.teal.button:hover {\n  background-color: #009c95;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.teal.buttons .button:focus,\n.ui.teal.button:focus {\n  background-color: #008c86;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.teal.buttons .button:active,\n.ui.teal.button:active {\n  background-color: #00827c;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.teal.buttons .active.button,\n.ui.teal.buttons .active.button:active,\n.ui.teal.active.button,\n.ui.teal.button .active.button:active {\n  background-color: #009c95;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.teal.buttons .button,\n.ui.basic.teal.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #00b5ad inset !important;\n  box-shadow: 0px 0px 0px 1px #00b5ad inset !important;\n  color: #00b5ad !important;\n}\n\n.ui.basic.teal.buttons .button:hover,\n.ui.basic.teal.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #009c95 inset !important;\n  box-shadow: 0px 0px 0px 1px #009c95 inset !important;\n  color: #009c95 !important;\n}\n\n.ui.basic.teal.buttons .button:focus,\n.ui.basic.teal.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #008c86 inset !important;\n  box-shadow: 0px 0px 0px 1px #008c86 inset !important;\n  color: #009c95 !important;\n}\n\n.ui.basic.teal.buttons .active.button,\n.ui.basic.teal.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #009c95 inset !important;\n  box-shadow: 0px 0px 0px 1px #009c95 inset !important;\n  color: #00827c !important;\n}\n\n.ui.basic.teal.buttons .button:active,\n.ui.basic.teal.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #00827c inset !important;\n  box-shadow: 0px 0px 0px 1px #00827c inset !important;\n  color: #00827c !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.teal.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.teal.buttons .button,\n.ui.inverted.teal.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #6dffff inset !important;\n  box-shadow: 0px 0px 0px 2px #6dffff inset !important;\n  color: #6dffff;\n}\n\n.ui.inverted.teal.buttons .button:hover,\n.ui.inverted.teal.button:hover,\n.ui.inverted.teal.buttons .button:focus,\n.ui.inverted.teal.button:focus,\n.ui.inverted.teal.buttons .button.active,\n.ui.inverted.teal.button.active,\n.ui.inverted.teal.buttons .button:active,\n.ui.inverted.teal.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: rgba(0, 0, 0, 0.6);\n}\n\n.ui.inverted.teal.buttons .button:hover,\n.ui.inverted.teal.button:hover {\n  background-color: #54ffff;\n}\n\n.ui.inverted.teal.buttons .button:focus,\n.ui.inverted.teal.button:focus {\n  background-color: #44ffff;\n}\n\n.ui.inverted.teal.buttons .active.button,\n.ui.inverted.teal.active.button {\n  background-color: #54ffff;\n}\n\n.ui.inverted.teal.buttons .button:active,\n.ui.inverted.teal.button:active {\n  background-color: #3affff;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.teal.basic.buttons .button,\n.ui.inverted.teal.buttons .basic.button,\n.ui.inverted.teal.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.teal.basic.buttons .button:hover,\n.ui.inverted.teal.buttons .basic.button:hover,\n.ui.inverted.teal.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #54ffff inset !important;\n  box-shadow: 0px 0px 0px 2px #54ffff inset !important;\n  color: #6dffff !important;\n}\n\n.ui.inverted.teal.basic.buttons .button:focus,\n.ui.inverted.teal.basic.buttons .button:focus,\n.ui.inverted.teal.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #44ffff inset !important;\n  box-shadow: 0px 0px 0px 2px #44ffff inset !important;\n  color: #6dffff !important;\n}\n\n.ui.inverted.teal.basic.buttons .active.button,\n.ui.inverted.teal.buttons .basic.active.button,\n.ui.inverted.teal.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #54ffff inset !important;\n  box-shadow: 0px 0px 0px 2px #54ffff inset !important;\n  color: #6dffff !important;\n}\n\n.ui.inverted.teal.basic.buttons .button:active,\n.ui.inverted.teal.buttons .basic.button:active,\n.ui.inverted.teal.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #3affff inset !important;\n  box-shadow: 0px 0px 0px 2px #3affff inset !important;\n  color: #6dffff !important;\n}\n\n/*--- Olive ---*/\n\n.ui.olive.buttons .button,\n.ui.olive.button {\n  background-color: #b5cc18;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.olive.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.olive.buttons .button:hover,\n.ui.olive.button:hover {\n  background-color: #a7bd0d;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.olive.buttons .button:focus,\n.ui.olive.button:focus {\n  background-color: #a0b605;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.olive.buttons .button:active,\n.ui.olive.button:active {\n  background-color: #8d9e13;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.olive.buttons .active.button,\n.ui.olive.buttons .active.button:active,\n.ui.olive.active.button,\n.ui.olive.button .active.button:active {\n  background-color: #aac109;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.olive.buttons .button,\n.ui.basic.olive.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #b5cc18 inset !important;\n  box-shadow: 0px 0px 0px 1px #b5cc18 inset !important;\n  color: #b5cc18 !important;\n}\n\n.ui.basic.olive.buttons .button:hover,\n.ui.basic.olive.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #a7bd0d inset !important;\n  box-shadow: 0px 0px 0px 1px #a7bd0d inset !important;\n  color: #a7bd0d !important;\n}\n\n.ui.basic.olive.buttons .button:focus,\n.ui.basic.olive.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #a0b605 inset !important;\n  box-shadow: 0px 0px 0px 1px #a0b605 inset !important;\n  color: #a7bd0d !important;\n}\n\n.ui.basic.olive.buttons .active.button,\n.ui.basic.olive.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #aac109 inset !important;\n  box-shadow: 0px 0px 0px 1px #aac109 inset !important;\n  color: #8d9e13 !important;\n}\n\n.ui.basic.olive.buttons .button:active,\n.ui.basic.olive.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #8d9e13 inset !important;\n  box-shadow: 0px 0px 0px 1px #8d9e13 inset !important;\n  color: #8d9e13 !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.olive.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.olive.buttons .button,\n.ui.inverted.olive.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #d9e778 inset !important;\n  box-shadow: 0px 0px 0px 2px #d9e778 inset !important;\n  color: #d9e778;\n}\n\n.ui.inverted.olive.buttons .button:hover,\n.ui.inverted.olive.button:hover,\n.ui.inverted.olive.buttons .button:focus,\n.ui.inverted.olive.button:focus,\n.ui.inverted.olive.buttons .button.active,\n.ui.inverted.olive.button.active,\n.ui.inverted.olive.buttons .button:active,\n.ui.inverted.olive.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: rgba(0, 0, 0, 0.6);\n}\n\n.ui.inverted.olive.buttons .button:hover,\n.ui.inverted.olive.button:hover {\n  background-color: #d8ea5c;\n}\n\n.ui.inverted.olive.buttons .button:focus,\n.ui.inverted.olive.button:focus {\n  background-color: #daef47;\n}\n\n.ui.inverted.olive.buttons .active.button,\n.ui.inverted.olive.active.button {\n  background-color: #daed59;\n}\n\n.ui.inverted.olive.buttons .button:active,\n.ui.inverted.olive.button:active {\n  background-color: #cddf4d;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.olive.basic.buttons .button,\n.ui.inverted.olive.buttons .basic.button,\n.ui.inverted.olive.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.olive.basic.buttons .button:hover,\n.ui.inverted.olive.buttons .basic.button:hover,\n.ui.inverted.olive.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #d8ea5c inset !important;\n  box-shadow: 0px 0px 0px 2px #d8ea5c inset !important;\n  color: #d9e778 !important;\n}\n\n.ui.inverted.olive.basic.buttons .button:focus,\n.ui.inverted.olive.basic.buttons .button:focus,\n.ui.inverted.olive.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #daef47 inset !important;\n  box-shadow: 0px 0px 0px 2px #daef47 inset !important;\n  color: #d9e778 !important;\n}\n\n.ui.inverted.olive.basic.buttons .active.button,\n.ui.inverted.olive.buttons .basic.active.button,\n.ui.inverted.olive.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #daed59 inset !important;\n  box-shadow: 0px 0px 0px 2px #daed59 inset !important;\n  color: #d9e778 !important;\n}\n\n.ui.inverted.olive.basic.buttons .button:active,\n.ui.inverted.olive.buttons .basic.button:active,\n.ui.inverted.olive.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #cddf4d inset !important;\n  box-shadow: 0px 0px 0px 2px #cddf4d inset !important;\n  color: #d9e778 !important;\n}\n\n/*--- Yellow ---*/\n\n.ui.yellow.buttons .button,\n.ui.yellow.button {\n  background-color: #fbbd08;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.yellow.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.yellow.buttons .button:hover,\n.ui.yellow.button:hover {\n  background-color: #eaae00;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.yellow.buttons .button:focus,\n.ui.yellow.button:focus {\n  background-color: #daa300;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.yellow.buttons .button:active,\n.ui.yellow.button:active {\n  background-color: #cd9903;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.yellow.buttons .active.button,\n.ui.yellow.buttons .active.button:active,\n.ui.yellow.active.button,\n.ui.yellow.button .active.button:active {\n  background-color: #eaae00;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.yellow.buttons .button,\n.ui.basic.yellow.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #fbbd08 inset !important;\n  box-shadow: 0px 0px 0px 1px #fbbd08 inset !important;\n  color: #fbbd08 !important;\n}\n\n.ui.basic.yellow.buttons .button:hover,\n.ui.basic.yellow.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #eaae00 inset !important;\n  box-shadow: 0px 0px 0px 1px #eaae00 inset !important;\n  color: #eaae00 !important;\n}\n\n.ui.basic.yellow.buttons .button:focus,\n.ui.basic.yellow.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #daa300 inset !important;\n  box-shadow: 0px 0px 0px 1px #daa300 inset !important;\n  color: #eaae00 !important;\n}\n\n.ui.basic.yellow.buttons .active.button,\n.ui.basic.yellow.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #eaae00 inset !important;\n  box-shadow: 0px 0px 0px 1px #eaae00 inset !important;\n  color: #cd9903 !important;\n}\n\n.ui.basic.yellow.buttons .button:active,\n.ui.basic.yellow.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #cd9903 inset !important;\n  box-shadow: 0px 0px 0px 1px #cd9903 inset !important;\n  color: #cd9903 !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.yellow.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.yellow.buttons .button,\n.ui.inverted.yellow.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #ffe21f inset !important;\n  box-shadow: 0px 0px 0px 2px #ffe21f inset !important;\n  color: #ffe21f;\n}\n\n.ui.inverted.yellow.buttons .button:hover,\n.ui.inverted.yellow.button:hover,\n.ui.inverted.yellow.buttons .button:focus,\n.ui.inverted.yellow.button:focus,\n.ui.inverted.yellow.buttons .button.active,\n.ui.inverted.yellow.button.active,\n.ui.inverted.yellow.buttons .button:active,\n.ui.inverted.yellow.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: rgba(0, 0, 0, 0.6);\n}\n\n.ui.inverted.yellow.buttons .button:hover,\n.ui.inverted.yellow.button:hover {\n  background-color: #ffdf05;\n}\n\n.ui.inverted.yellow.buttons .button:focus,\n.ui.inverted.yellow.button:focus {\n  background-color: #f5d500;\n}\n\n.ui.inverted.yellow.buttons .active.button,\n.ui.inverted.yellow.active.button {\n  background-color: #ffdf05;\n}\n\n.ui.inverted.yellow.buttons .button:active,\n.ui.inverted.yellow.button:active {\n  background-color: #ebcd00;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.yellow.basic.buttons .button,\n.ui.inverted.yellow.buttons .basic.button,\n.ui.inverted.yellow.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.yellow.basic.buttons .button:hover,\n.ui.inverted.yellow.buttons .basic.button:hover,\n.ui.inverted.yellow.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #ffdf05 inset !important;\n  box-shadow: 0px 0px 0px 2px #ffdf05 inset !important;\n  color: #ffe21f !important;\n}\n\n.ui.inverted.yellow.basic.buttons .button:focus,\n.ui.inverted.yellow.basic.buttons .button:focus,\n.ui.inverted.yellow.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #f5d500 inset !important;\n  box-shadow: 0px 0px 0px 2px #f5d500 inset !important;\n  color: #ffe21f !important;\n}\n\n.ui.inverted.yellow.basic.buttons .active.button,\n.ui.inverted.yellow.buttons .basic.active.button,\n.ui.inverted.yellow.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #ffdf05 inset !important;\n  box-shadow: 0px 0px 0px 2px #ffdf05 inset !important;\n  color: #ffe21f !important;\n}\n\n.ui.inverted.yellow.basic.buttons .button:active,\n.ui.inverted.yellow.buttons .basic.button:active,\n.ui.inverted.yellow.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #ebcd00 inset !important;\n  box-shadow: 0px 0px 0px 2px #ebcd00 inset !important;\n  color: #ffe21f !important;\n}\n\n/*-------------------\n      Primary\n--------------------*/\n\n/*--- Standard ---*/\n\n.ui.primary.buttons .button,\n.ui.primary.button {\n  background-color: #2185d0;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.primary.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.primary.buttons .button:hover,\n.ui.primary.button:hover {\n  background-color: #1678c2;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.primary.buttons .button:focus,\n.ui.primary.button:focus {\n  background-color: #0d71bb;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.primary.buttons .button:active,\n.ui.primary.button:active {\n  background-color: #1a69a4;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.primary.buttons .active.button,\n.ui.primary.buttons .active.button:active,\n.ui.primary.active.button,\n.ui.primary.button .active.button:active {\n  background-color: #1279c6;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.primary.buttons .button,\n.ui.basic.primary.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #2185d0 inset !important;\n  box-shadow: 0px 0px 0px 1px #2185d0 inset !important;\n  color: #2185d0 !important;\n}\n\n.ui.basic.primary.buttons .button:hover,\n.ui.basic.primary.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #1678c2 inset !important;\n  box-shadow: 0px 0px 0px 1px #1678c2 inset !important;\n  color: #1678c2 !important;\n}\n\n.ui.basic.primary.buttons .button:focus,\n.ui.basic.primary.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #0d71bb inset !important;\n  box-shadow: 0px 0px 0px 1px #0d71bb inset !important;\n  color: #1678c2 !important;\n}\n\n.ui.basic.primary.buttons .active.button,\n.ui.basic.primary.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #1279c6 inset !important;\n  box-shadow: 0px 0px 0px 1px #1279c6 inset !important;\n  color: #1a69a4 !important;\n}\n\n.ui.basic.primary.buttons .button:active,\n.ui.basic.primary.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #1a69a4 inset !important;\n  box-shadow: 0px 0px 0px 1px #1a69a4 inset !important;\n  color: #1a69a4 !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.primary.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.primary.buttons .button,\n.ui.inverted.primary.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #54c8ff inset !important;\n  box-shadow: 0px 0px 0px 2px #54c8ff inset !important;\n  color: #54c8ff;\n}\n\n.ui.inverted.primary.buttons .button:hover,\n.ui.inverted.primary.button:hover,\n.ui.inverted.primary.buttons .button:focus,\n.ui.inverted.primary.button:focus,\n.ui.inverted.primary.buttons .button.active,\n.ui.inverted.primary.button.active,\n.ui.inverted.primary.buttons .button:active,\n.ui.inverted.primary.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.inverted.primary.buttons .button:hover,\n.ui.inverted.primary.button:hover {\n  background-color: #3ac0ff;\n}\n\n.ui.inverted.primary.buttons .button:focus,\n.ui.inverted.primary.button:focus {\n  background-color: #2bbbff;\n}\n\n.ui.inverted.primary.buttons .active.button,\n.ui.inverted.primary.active.button {\n  background-color: #3ac0ff;\n}\n\n.ui.inverted.primary.buttons .button:active,\n.ui.inverted.primary.button:active {\n  background-color: #21b8ff;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.primary.basic.buttons .button,\n.ui.inverted.primary.buttons .basic.button,\n.ui.inverted.primary.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.primary.basic.buttons .button:hover,\n.ui.inverted.primary.buttons .basic.button:hover,\n.ui.inverted.primary.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #3ac0ff inset !important;\n  box-shadow: 0px 0px 0px 2px #3ac0ff inset !important;\n  color: #54c8ff !important;\n}\n\n.ui.inverted.primary.basic.buttons .button:focus,\n.ui.inverted.primary.basic.buttons .button:focus,\n.ui.inverted.primary.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #2bbbff inset !important;\n  box-shadow: 0px 0px 0px 2px #2bbbff inset !important;\n  color: #54c8ff !important;\n}\n\n.ui.inverted.primary.basic.buttons .active.button,\n.ui.inverted.primary.buttons .basic.active.button,\n.ui.inverted.primary.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #3ac0ff inset !important;\n  box-shadow: 0px 0px 0px 2px #3ac0ff inset !important;\n  color: #54c8ff !important;\n}\n\n.ui.inverted.primary.basic.buttons .button:active,\n.ui.inverted.primary.buttons .basic.button:active,\n.ui.inverted.primary.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #21b8ff inset !important;\n  box-shadow: 0px 0px 0px 2px #21b8ff inset !important;\n  color: #54c8ff !important;\n}\n\n/*-------------------\n      Secondary\n--------------------*/\n\n/* Standard */\n\n.ui.secondary.buttons .button,\n.ui.secondary.button {\n  background-color: #1b1c1d;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.secondary.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.secondary.buttons .button:hover,\n.ui.secondary.button:hover {\n  background-color: #27292a;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.secondary.buttons .button:focus,\n.ui.secondary.button:focus {\n  background-color: #2e3032;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.secondary.buttons .button:active,\n.ui.secondary.button:active {\n  background-color: #343637;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.secondary.buttons .active.button,\n.ui.secondary.buttons .active.button:active,\n.ui.secondary.active.button,\n.ui.secondary.button .active.button:active {\n  background-color: #27292a;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.secondary.buttons .button,\n.ui.basic.secondary.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #1b1c1d inset !important;\n  box-shadow: 0px 0px 0px 1px #1b1c1d inset !important;\n  color: #1b1c1d !important;\n}\n\n.ui.basic.secondary.buttons .button:hover,\n.ui.basic.secondary.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #27292a inset !important;\n  box-shadow: 0px 0px 0px 1px #27292a inset !important;\n  color: #27292a !important;\n}\n\n.ui.basic.secondary.buttons .button:focus,\n.ui.basic.secondary.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #2e3032 inset !important;\n  box-shadow: 0px 0px 0px 1px #2e3032 inset !important;\n  color: #27292a !important;\n}\n\n.ui.basic.secondary.buttons .active.button,\n.ui.basic.secondary.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #27292a inset !important;\n  box-shadow: 0px 0px 0px 1px #27292a inset !important;\n  color: #343637 !important;\n}\n\n.ui.basic.secondary.buttons .button:active,\n.ui.basic.secondary.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #343637 inset !important;\n  box-shadow: 0px 0px 0px 1px #343637 inset !important;\n  color: #343637 !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.primary.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/* Inverted */\n\n.ui.inverted.secondary.buttons .button,\n.ui.inverted.secondary.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px #545454 inset !important;\n  box-shadow: 0px 0px 0px 2px #545454 inset !important;\n  color: #545454;\n}\n\n.ui.inverted.secondary.buttons .button:hover,\n.ui.inverted.secondary.button:hover,\n.ui.inverted.secondary.buttons .button:focus,\n.ui.inverted.secondary.button:focus,\n.ui.inverted.secondary.buttons .button.active,\n.ui.inverted.secondary.button.active,\n.ui.inverted.secondary.buttons .button:active,\n.ui.inverted.secondary.button:active {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.inverted.secondary.buttons .button:hover,\n.ui.inverted.secondary.button:hover {\n  background-color: #616161;\n}\n\n.ui.inverted.secondary.buttons .button:focus,\n.ui.inverted.secondary.button:focus {\n  background-color: #686868;\n}\n\n.ui.inverted.secondary.buttons .active.button,\n.ui.inverted.secondary.active.button {\n  background-color: #616161;\n}\n\n.ui.inverted.secondary.buttons .button:active,\n.ui.inverted.secondary.button:active {\n  background-color: #6e6e6e;\n}\n\n/* Inverted Basic */\n\n.ui.inverted.secondary.basic.buttons .button,\n.ui.inverted.secondary.buttons .basic.button,\n.ui.inverted.secondary.basic.button {\n  background-color: transparent;\n  -webkit-box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 0.5) inset !important;\n  color: #ffffff !important;\n}\n\n.ui.inverted.secondary.basic.buttons .button:hover,\n.ui.inverted.secondary.buttons .basic.button:hover,\n.ui.inverted.secondary.basic.button:hover {\n  -webkit-box-shadow: 0px 0px 0px 2px #616161 inset !important;\n  box-shadow: 0px 0px 0px 2px #616161 inset !important;\n  color: #545454 !important;\n}\n\n.ui.inverted.secondary.basic.buttons .button:focus,\n.ui.inverted.secondary.basic.buttons .button:focus,\n.ui.inverted.secondary.basic.button:focus {\n  -webkit-box-shadow: 0px 0px 0px 2px #686868 inset !important;\n  box-shadow: 0px 0px 0px 2px #686868 inset !important;\n  color: #545454 !important;\n}\n\n.ui.inverted.secondary.basic.buttons .active.button,\n.ui.inverted.secondary.buttons .basic.active.button,\n.ui.inverted.secondary.basic.active.button {\n  -webkit-box-shadow: 0px 0px 0px 2px #616161 inset !important;\n  box-shadow: 0px 0px 0px 2px #616161 inset !important;\n  color: #545454 !important;\n}\n\n.ui.inverted.secondary.basic.buttons .button:active,\n.ui.inverted.secondary.buttons .basic.button:active,\n.ui.inverted.secondary.basic.button:active {\n  -webkit-box-shadow: 0px 0px 0px 2px #6e6e6e inset !important;\n  box-shadow: 0px 0px 0px 2px #6e6e6e inset !important;\n  color: #545454 !important;\n}\n\n/*---------------\n    Positive\n----------------*/\n\n/* Standard */\n\n.ui.positive.buttons .button,\n.ui.positive.button {\n  background-color: #21ba45;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.positive.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.positive.buttons .button:hover,\n.ui.positive.button:hover {\n  background-color: #16ab39;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.positive.buttons .button:focus,\n.ui.positive.button:focus {\n  background-color: #0ea432;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.positive.buttons .button:active,\n.ui.positive.button:active {\n  background-color: #198f35;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.positive.buttons .active.button,\n.ui.positive.buttons .active.button:active,\n.ui.positive.active.button,\n.ui.positive.button .active.button:active {\n  background-color: #13ae38;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.positive.buttons .button,\n.ui.basic.positive.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #21ba45 inset !important;\n  box-shadow: 0px 0px 0px 1px #21ba45 inset !important;\n  color: #21ba45 !important;\n}\n\n.ui.basic.positive.buttons .button:hover,\n.ui.basic.positive.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #16ab39 inset !important;\n  box-shadow: 0px 0px 0px 1px #16ab39 inset !important;\n  color: #16ab39 !important;\n}\n\n.ui.basic.positive.buttons .button:focus,\n.ui.basic.positive.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #0ea432 inset !important;\n  box-shadow: 0px 0px 0px 1px #0ea432 inset !important;\n  color: #16ab39 !important;\n}\n\n.ui.basic.positive.buttons .active.button,\n.ui.basic.positive.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #13ae38 inset !important;\n  box-shadow: 0px 0px 0px 1px #13ae38 inset !important;\n  color: #198f35 !important;\n}\n\n.ui.basic.positive.buttons .button:active,\n.ui.basic.positive.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #198f35 inset !important;\n  box-shadow: 0px 0px 0px 1px #198f35 inset !important;\n  color: #198f35 !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.primary.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/*---------------\n    Negative\n----------------*/\n\n/* Standard */\n\n.ui.negative.buttons .button,\n.ui.negative.button {\n  background-color: #db2828;\n  color: #ffffff;\n  text-shadow: none;\n  background-image: none;\n}\n\n.ui.negative.button {\n  -webkit-box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.negative.buttons .button:hover,\n.ui.negative.button:hover {\n  background-color: #d01919;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.negative.buttons .button:focus,\n.ui.negative.button:focus {\n  background-color: #ca1010;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.negative.buttons .button:active,\n.ui.negative.button:active {\n  background-color: #b21e1e;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n.ui.negative.buttons .active.button,\n.ui.negative.buttons .active.button:active,\n.ui.negative.active.button,\n.ui.negative.button .active.button:active {\n  background-color: #d41515;\n  color: #ffffff;\n  text-shadow: none;\n}\n\n/* Basic */\n\n.ui.basic.negative.buttons .button,\n.ui.basic.negative.button {\n  -webkit-box-shadow: 0px 0px 0px 1px #db2828 inset !important;\n  box-shadow: 0px 0px 0px 1px #db2828 inset !important;\n  color: #db2828 !important;\n}\n\n.ui.basic.negative.buttons .button:hover,\n.ui.basic.negative.button:hover {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #d01919 inset !important;\n  box-shadow: 0px 0px 0px 1px #d01919 inset !important;\n  color: #d01919 !important;\n}\n\n.ui.basic.negative.buttons .button:focus,\n.ui.basic.negative.button:focus {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #ca1010 inset !important;\n  box-shadow: 0px 0px 0px 1px #ca1010 inset !important;\n  color: #d01919 !important;\n}\n\n.ui.basic.negative.buttons .active.button,\n.ui.basic.negative.active.button {\n  background: transparent !important;\n  -webkit-box-shadow: 0px 0px 0px 1px #d41515 inset !important;\n  box-shadow: 0px 0px 0px 1px #d41515 inset !important;\n  color: #b21e1e !important;\n}\n\n.ui.basic.negative.buttons .button:active,\n.ui.basic.negative.button:active {\n  -webkit-box-shadow: 0px 0px 0px 1px #b21e1e inset !important;\n  box-shadow: 0px 0px 0px 1px #b21e1e inset !important;\n  color: #b21e1e !important;\n}\n\n.ui.buttons:not(.vertical) > .basic.primary.button:not(:first-child) {\n  margin-left: -1px;\n}\n\n/*******************************\n            Groups\n*******************************/\n\n.ui.buttons {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n  font-size: 0em;\n  vertical-align: baseline;\n  margin: 0em 0.25em 0em 0em;\n}\n\n.ui.buttons:not(.basic):not(.inverted) {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Clearfix */\n\n.ui.buttons:after {\n  content: \".\";\n  display: block;\n  height: 0;\n  clear: both;\n  visibility: hidden;\n}\n\n/* Standard Group */\n\n.ui.buttons .button {\n  -webkit-box-flex: 1;\n  -ms-flex: 1 0 auto;\n  flex: 1 0 auto;\n  margin: 0em;\n  border-radius: 0em;\n  margin: 0px 0px 0px 0px;\n}\n\n.ui.buttons > .ui.button:not(.basic):not(.inverted),\n.ui.buttons:not(.basic):not(.inverted) > .button {\n  -webkit-box-shadow: 0px 0px 0px 1px transparent inset,\n    0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0px 0px 1px transparent inset,\n    0px 0em 0px 0px rgba(34, 36, 38, 0.15) inset;\n}\n\n.ui.buttons .button:first-child {\n  border-left: none;\n  margin-left: 0em;\n  border-top-left-radius: 0.28571429rem;\n  border-bottom-left-radius: 0.28571429rem;\n}\n\n.ui.buttons .button:last-child {\n  border-top-right-radius: 0.28571429rem;\n  border-bottom-right-radius: 0.28571429rem;\n}\n\n/* Vertical  Style */\n\n.ui.vertical.buttons {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n}\n\n.ui.vertical.buttons .button {\n  display: block;\n  float: none;\n  width: 100%;\n  margin: 0px 0px 0px 0px;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border-radius: 0em;\n}\n\n.ui.vertical.buttons .button:first-child {\n  border-top-left-radius: 0.28571429rem;\n  border-top-right-radius: 0.28571429rem;\n}\n\n.ui.vertical.buttons .button:last-child {\n  margin-bottom: 0px;\n  border-bottom-left-radius: 0.28571429rem;\n  border-bottom-right-radius: 0.28571429rem;\n}\n\n.ui.vertical.buttons .button:only-child {\n  border-radius: 0.28571429rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Container\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Container\n*******************************/\n\n/* All Sizes */\n\n.ui.container {\n  display: block;\n  max-width: 100% !important;\n}\n\n/* Mobile */\n\n@media only screen and (width < 768px) {\n  .ui.container {\n    width: auto !important;\n    margin-left: 1em !important;\n    margin-right: 1em !important;\n  }\n\n  .ui.grid.container {\n    width: auto !important;\n  }\n\n  .ui.relaxed.grid.container {\n    width: auto !important;\n  }\n\n  .ui.very.relaxed.grid.container {\n    width: auto !important;\n  }\n}\n\n/* Tablet */\n\n@media only screen and (768px <= width < 992px) {\n  .ui.container {\n    width: 723px;\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n\n  .ui.grid.container {\n    width: calc(723px + 2rem) !important;\n  }\n\n  .ui.relaxed.grid.container {\n    width: calc(723px + 3rem) !important;\n  }\n\n  .ui.very.relaxed.grid.container {\n    width: calc(723px + 5rem) !important;\n  }\n}\n\n/* Small Monitor */\n\n@media only screen and (992px <= width < 1200px) {\n  .ui.container {\n    width: 933px;\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n\n  .ui.grid.container {\n    width: calc(933px + 2rem) !important;\n  }\n\n  .ui.relaxed.grid.container {\n    width: calc(933px + 3rem) !important;\n  }\n\n  .ui.very.relaxed.grid.container {\n    width: calc(933px + 5rem) !important;\n  }\n}\n\n/* Large Monitor */\n\n@media only screen and (width >= 1200px) {\n  .ui.container {\n    width: 1127px;\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n\n  .ui.grid.container {\n    width: calc(1127px + 2rem) !important;\n  }\n\n  .ui.relaxed.grid.container {\n    width: calc(1127px + 3rem) !important;\n  }\n\n  .ui.very.relaxed.grid.container {\n    width: calc(1127px + 5rem) !important;\n  }\n}\n\n/*******************************\n            Types\n*******************************/\n\n/* Text Container */\n\n.ui.text.container {\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  max-width: 700px !important;\n  line-height: 1.5;\n}\n\n.ui.text.container {\n  font-size: 1.14285714rem;\n}\n\n/* Fluid */\n\n.ui.fluid.container {\n  width: 100%;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n.ui[class*=\"left aligned\"].container {\n  text-align: left;\n}\n\n.ui[class*=\"center aligned\"].container {\n  text-align: center;\n}\n\n.ui[class*=\"right aligned\"].container {\n  text-align: right;\n}\n\n.ui.justified.container {\n  text-align: justify;\n  -webkit-hyphens: auto;\n  -ms-hyphens: auto;\n  hyphens: auto;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Divider\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Divider\n*******************************/\n\n.ui.divider {\n  margin: 1rem 0rem;\n  line-height: 1;\n  height: 0em;\n  font-weight: bold;\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  color: rgba(0, 0, 0, 0.85);\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\n/*--------------\n      Basic\n---------------*/\n\n.ui.divider:not(.vertical):not(.horizontal) {\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n  border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n/*--------------\n    Coupling\n---------------*/\n\n/* Allow divider between each column row */\n\n.ui.grid > .column + .divider,\n.ui.grid > .row > .column + .divider {\n  left: auto;\n}\n\n/*--------------\n  Horizontal\n---------------*/\n\n.ui.horizontal.divider {\n  display: table;\n  white-space: nowrap;\n  height: auto;\n  margin: \"\";\n  line-height: 1;\n  text-align: center;\n}\n\n.ui.horizontal.divider:before,\n.ui.horizontal.divider:after {\n  content: \"\";\n  display: table-cell;\n  position: relative;\n  top: 50%;\n  width: 50%;\n  background-repeat: no-repeat;\n}\n\n.ui.horizontal.divider:before {\n  background-position: right 1em top 50%;\n}\n\n.ui.horizontal.divider:after {\n  background-position: left 1em top 50%;\n}\n\n/*--------------\n    Vertical\n---------------*/\n\n.ui.vertical.divider {\n  position: absolute;\n  z-index: 2;\n  top: 50%;\n  left: 50%;\n  margin: 0rem;\n  padding: 0em;\n  width: auto;\n  height: 50%;\n  line-height: 0em;\n  text-align: center;\n  -webkit-transform: translateX(-50%);\n  transform: translateX(-50%);\n}\n\n.ui.vertical.divider:before,\n.ui.vertical.divider:after {\n  position: absolute;\n  left: 50%;\n  content: \"\";\n  z-index: 3;\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n  border-right: 1px solid rgba(255, 255, 255, 0.1);\n  width: 0%;\n  height: calc(100% - 1rem);\n}\n\n.ui.vertical.divider:before {\n  top: -100%;\n}\n\n.ui.vertical.divider:after {\n  top: auto;\n  bottom: 0px;\n}\n\n/* Inside grid */\n\n@media only screen and (width < 768px) {\n\n  .ui.stackable.grid .ui.vertical.divider,\n  .ui.grid .stackable.row .ui.vertical.divider {\n    display: table;\n    white-space: nowrap;\n    height: auto;\n    margin: \"\";\n    overflow: hidden;\n    line-height: 1;\n    text-align: center;\n    position: static;\n    top: 0;\n    left: 0;\n    -webkit-transform: none;\n    transform: none;\n  }\n\n  .ui.stackable.grid .ui.vertical.divider:before,\n  .ui.grid .stackable.row .ui.vertical.divider:before,\n  .ui.stackable.grid .ui.vertical.divider:after,\n  .ui.grid .stackable.row .ui.vertical.divider:after {\n    position: static;\n    left: 0;\n    border-left: none;\n    border-right: none;\n    content: \"\";\n    display: table-cell;\n    position: relative;\n    top: 50%;\n    width: 50%;\n    background-repeat: no-repeat;\n  }\n\n  .ui.stackable.grid .ui.vertical.divider:before,\n  .ui.grid .stackable.row .ui.vertical.divider:before {\n    background-position: right 1em top 50%;\n  }\n\n  .ui.stackable.grid .ui.vertical.divider:after,\n  .ui.grid .stackable.row .ui.vertical.divider:after {\n    background-position: left 1em top 50%;\n  }\n}\n\n/*--------------\n      Icon\n---------------*/\n\n.ui.divider > .icon {\n  margin: 0rem;\n  font-size: 1rem;\n  height: 1em;\n  vertical-align: middle;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Hidden\n---------------*/\n\n.ui.hidden.divider {\n  border-color: transparent !important;\n}\n\n.ui.hidden.divider:before,\n.ui.hidden.divider:after {\n  display: none;\n}\n\n/*--------------\n    Inverted\n---------------*/\n\n.ui.divider.inverted,\n.ui.vertical.inverted.divider,\n.ui.horizontal.inverted.divider {\n  color: #ffffff;\n}\n\n.ui.divider.inverted,\n.ui.divider.inverted:after,\n.ui.divider.inverted:before {\n  border-top-color: rgba(34, 36, 38, 0.15) !important;\n  border-left-color: rgba(34, 36, 38, 0.15) !important;\n  border-bottom-color: rgba(255, 255, 255, 0.15) !important;\n  border-right-color: rgba(255, 255, 255, 0.15) !important;\n}\n\n/*--------------\n    Fitted\n---------------*/\n\n.ui.fitted.divider {\n  margin: 0em;\n}\n\n/*--------------\n    Clearing\n---------------*/\n\n.ui.clearing.divider {\n  clear: both;\n}\n\n/*--------------\n    Section\n---------------*/\n\n.ui.section.divider {\n  margin-top: 2rem;\n  margin-bottom: 2rem;\n}\n\n/*--------------\n    Sizes\n---------------*/\n\n.ui.divider {\n  font-size: 1rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n.ui.horizontal.divider:before,\n.ui.horizontal.divider:after {\n  background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaAAAAACCAYAAACuTHuKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1OThBRDY4OUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1OThBRDY4QUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjU5OEFENjg3Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjU5OEFENjg4Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+VU513gAAADVJREFUeNrs0DENACAQBDBIWLGBJQby/mUcJn5sJXQmOQMAAAAAAJqt+2prAAAAAACg2xdgANk6BEVuJgyMAAAAAElFTkSuQmCC\");\n}\n\n@media only screen and (width < 768px) {\n\n  .ui.stackable.grid .ui.vertical.divider:before,\n  .ui.grid .stackable.row .ui.vertical.divider:before,\n  .ui.stackable.grid .ui.vertical.divider:after,\n  .ui.grid .stackable.row .ui.vertical.divider:after {\n    background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaAAAAACCAYAAACuTHuKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1OThBRDY4OUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1OThBRDY4QUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjU5OEFENjg3Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjU5OEFENjg4Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+VU513gAAADVJREFUeNrs0DENACAQBDBIWLGBJQby/mUcJn5sJXQmOQMAAAAAAJqt+2prAAAAAACg2xdgANk6BEVuJgyMAAAAAElFTkSuQmCC\");\n  }\n}\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Flag\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Flag\n*******************************/\n\ni.flag:not(.icon) {\n  display: inline-block;\n  width: 16px;\n  height: 11px;\n  line-height: 11px;\n  vertical-align: baseline;\n  margin: 0em 0.5em 0em 0em;\n  text-decoration: inherit;\n  speak: none;\n  font-smoothing: antialiased;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n}\n\n/* Sprite */\n\ni.flag:not(.icon):before {\n  display: inline-block;\n  content: \"\";\n  background: url(\"./assets/images/flags.png\") no-repeat -108px -1976px;\n  width: 16px;\n  height: 11px;\n}\n\n/* Flag Sprite Based On http://www.famfamfam.com/lab/icons/flags/ */\n\n/*******************************\n        Theme Overrides\n*******************************/\n\ni.flag.ad:before,\ni.flag.andorra:before {\n  background-position: 0px 0px;\n}\n\ni.flag.ae:before,\ni.flag.united.arab.emirates:before,\ni.flag.uae:before {\n  background-position: 0px -26px;\n}\n\ni.flag.af:before,\ni.flag.afghanistan:before {\n  background-position: 0px -52px;\n}\n\ni.flag.ag:before,\ni.flag.antigua:before {\n  background-position: 0px -78px;\n}\n\ni.flag.ai:before,\ni.flag.anguilla:before {\n  background-position: 0px -104px;\n}\n\ni.flag.al:before,\ni.flag.albania:before {\n  background-position: 0px -130px;\n}\n\ni.flag.am:before,\ni.flag.armenia:before {\n  background-position: 0px -156px;\n}\n\ni.flag.an:before,\ni.flag.netherlands.antilles:before {\n  background-position: 0px -182px;\n}\n\ni.flag.ao:before,\ni.flag.angola:before {\n  background-position: 0px -208px;\n}\n\ni.flag.ar:before,\ni.flag.argentina:before {\n  background-position: 0px -234px;\n}\n\ni.flag.as:before,\ni.flag.american.samoa:before {\n  background-position: 0px -260px;\n}\n\ni.flag.at:before,\ni.flag.austria:before {\n  background-position: 0px -286px;\n}\n\ni.flag.au:before,\ni.flag.australia:before {\n  background-position: 0px -312px;\n}\n\ni.flag.aw:before,\ni.flag.aruba:before {\n  background-position: 0px -338px;\n}\n\ni.flag.ax:before,\ni.flag.aland.islands:before {\n  background-position: 0px -364px;\n}\n\ni.flag.az:before,\ni.flag.azerbaijan:before {\n  background-position: 0px -390px;\n}\n\ni.flag.ba:before,\ni.flag.bosnia:before {\n  background-position: 0px -416px;\n}\n\ni.flag.bb:before,\ni.flag.barbados:before {\n  background-position: 0px -442px;\n}\n\ni.flag.bd:before,\ni.flag.bangladesh:before {\n  background-position: 0px -468px;\n}\n\ni.flag.be:before,\ni.flag.belgium:before {\n  background-position: 0px -494px;\n}\n\ni.flag.bf:before,\ni.flag.burkina.faso:before {\n  background-position: 0px -520px;\n}\n\ni.flag.bg:before,\ni.flag.bulgaria:before {\n  background-position: 0px -546px;\n}\n\ni.flag.bh:before,\ni.flag.bahrain:before {\n  background-position: 0px -572px;\n}\n\ni.flag.bi:before,\ni.flag.burundi:before {\n  background-position: 0px -598px;\n}\n\ni.flag.bj:before,\ni.flag.benin:before {\n  background-position: 0px -624px;\n}\n\ni.flag.bm:before,\ni.flag.bermuda:before {\n  background-position: 0px -650px;\n}\n\ni.flag.bn:before,\ni.flag.brunei:before {\n  background-position: 0px -676px;\n}\n\ni.flag.bo:before,\ni.flag.bolivia:before {\n  background-position: 0px -702px;\n}\n\ni.flag.br:before,\ni.flag.brazil:before {\n  background-position: 0px -728px;\n}\n\ni.flag.bs:before,\ni.flag.bahamas:before {\n  background-position: 0px -754px;\n}\n\ni.flag.bt:before,\ni.flag.bhutan:before {\n  background-position: 0px -780px;\n}\n\ni.flag.bv:before,\ni.flag.bouvet.island:before {\n  background-position: 0px -806px;\n}\n\ni.flag.bw:before,\ni.flag.botswana:before {\n  background-position: 0px -832px;\n}\n\ni.flag.by:before,\ni.flag.belarus:before {\n  background-position: 0px -858px;\n}\n\ni.flag.bz:before,\ni.flag.belize:before {\n  background-position: 0px -884px;\n}\n\ni.flag.ca:before,\ni.flag.canada:before {\n  background-position: 0px -910px;\n}\n\ni.flag.cc:before,\ni.flag.cocos.islands:before {\n  background-position: 0px -962px;\n}\n\ni.flag.cd:before,\ni.flag.congo:before {\n  background-position: 0px -988px;\n}\n\ni.flag.cf:before,\ni.flag.central.african.republic:before {\n  background-position: 0px -1014px;\n}\n\ni.flag.cg:before,\ni.flag.congo.brazzaville:before {\n  background-position: 0px -1040px;\n}\n\ni.flag.ch:before,\ni.flag.switzerland:before {\n  background-position: 0px -1066px;\n}\n\ni.flag.ci:before,\ni.flag.cote.divoire:before {\n  background-position: 0px -1092px;\n}\n\ni.flag.ck:before,\ni.flag.cook.islands:before {\n  background-position: 0px -1118px;\n}\n\ni.flag.cl:before,\ni.flag.chile:before {\n  background-position: 0px -1144px;\n}\n\ni.flag.cm:before,\ni.flag.cameroon:before {\n  background-position: 0px -1170px;\n}\n\ni.flag.cn:before,\ni.flag.china:before {\n  background-position: 0px -1196px;\n}\n\ni.flag.co:before,\ni.flag.colombia:before {\n  background-position: 0px -1222px;\n}\n\ni.flag.cr:before,\ni.flag.costa.rica:before {\n  background-position: 0px -1248px;\n}\n\ni.flag.cs:before,\ni.flag.serbia:before {\n  background-position: 0px -1274px;\n}\n\ni.flag.cu:before,\ni.flag.cuba:before {\n  background-position: 0px -1300px;\n}\n\ni.flag.cv:before,\ni.flag.cape.verde:before {\n  background-position: 0px -1326px;\n}\n\ni.flag.cx:before,\ni.flag.christmas.island:before {\n  background-position: 0px -1352px;\n}\n\ni.flag.cy:before,\ni.flag.cyprus:before {\n  background-position: 0px -1378px;\n}\n\ni.flag.cz:before,\ni.flag.czech.republic:before {\n  background-position: 0px -1404px;\n}\n\ni.flag.de:before,\ni.flag.germany:before {\n  background-position: 0px -1430px;\n}\n\ni.flag.dj:before,\ni.flag.djibouti:before {\n  background-position: 0px -1456px;\n}\n\ni.flag.dk:before,\ni.flag.denmark:before {\n  background-position: 0px -1482px;\n}\n\ni.flag.dm:before,\ni.flag.dominica:before {\n  background-position: 0px -1508px;\n}\n\ni.flag.do:before,\ni.flag.dominican.republic:before {\n  background-position: 0px -1534px;\n}\n\ni.flag.dz:before,\ni.flag.algeria:before {\n  background-position: 0px -1560px;\n}\n\ni.flag.ec:before,\ni.flag.ecuador:before {\n  background-position: 0px -1586px;\n}\n\ni.flag.ee:before,\ni.flag.estonia:before {\n  background-position: 0px -1612px;\n}\n\ni.flag.eg:before,\ni.flag.egypt:before {\n  background-position: 0px -1638px;\n}\n\ni.flag.eh:before,\ni.flag.western.sahara:before {\n  background-position: 0px -1664px;\n}\n\ni.flag.gb.eng:before,\ni.flag.england:before {\n  background-position: 0px -1690px;\n}\n\ni.flag.er:before,\ni.flag.eritrea:before {\n  background-position: 0px -1716px;\n}\n\ni.flag.es:before,\ni.flag.spain:before {\n  background-position: 0px -1742px;\n}\n\ni.flag.et:before,\ni.flag.ethiopia:before {\n  background-position: 0px -1768px;\n}\n\ni.flag.eu:before,\ni.flag.european.union:before {\n  background-position: 0px -1794px;\n}\n\ni.flag.fi:before,\ni.flag.finland:before {\n  background-position: 0px -1846px;\n}\n\ni.flag.fj:before,\ni.flag.fiji:before {\n  background-position: 0px -1872px;\n}\n\ni.flag.fk:before,\ni.flag.falkland.islands:before {\n  background-position: 0px -1898px;\n}\n\ni.flag.fm:before,\ni.flag.micronesia:before {\n  background-position: 0px -1924px;\n}\n\ni.flag.fo:before,\ni.flag.faroe.islands:before {\n  background-position: 0px -1950px;\n}\n\ni.flag.fr:before,\ni.flag.france:before {\n  background-position: 0px -1976px;\n}\n\ni.flag.ga:before,\ni.flag.gabon:before {\n  background-position: -36px 0px;\n}\n\ni.flag.gb:before,\ni.flag.uk:before,\ni.flag.united.kingdom:before {\n  background-position: -36px -26px;\n}\n\ni.flag.gd:before,\ni.flag.grenada:before {\n  background-position: -36px -52px;\n}\n\ni.flag.ge:before,\ni.flag.georgia:before {\n  background-position: -36px -78px;\n}\n\ni.flag.gf:before,\ni.flag.french.guiana:before {\n  background-position: -36px -104px;\n}\n\ni.flag.gh:before,\ni.flag.ghana:before {\n  background-position: -36px -130px;\n}\n\ni.flag.gi:before,\ni.flag.gibraltar:before {\n  background-position: -36px -156px;\n}\n\ni.flag.gl:before,\ni.flag.greenland:before {\n  background-position: -36px -182px;\n}\n\ni.flag.gm:before,\ni.flag.gambia:before {\n  background-position: -36px -208px;\n}\n\ni.flag.gn:before,\ni.flag.guinea:before {\n  background-position: -36px -234px;\n}\n\ni.flag.gp:before,\ni.flag.guadeloupe:before {\n  background-position: -36px -260px;\n}\n\ni.flag.gq:before,\ni.flag.equatorial.guinea:before {\n  background-position: -36px -286px;\n}\n\ni.flag.gr:before,\ni.flag.greece:before {\n  background-position: -36px -312px;\n}\n\ni.flag.gs:before,\ni.flag.sandwich.islands:before {\n  background-position: -36px -338px;\n}\n\ni.flag.gt:before,\ni.flag.guatemala:before {\n  background-position: -36px -364px;\n}\n\ni.flag.gu:before,\ni.flag.guam:before {\n  background-position: -36px -390px;\n}\n\ni.flag.gw:before,\ni.flag.guinea-bissau:before {\n  background-position: -36px -416px;\n}\n\ni.flag.gy:before,\ni.flag.guyana:before {\n  background-position: -36px -442px;\n}\n\ni.flag.hk:before,\ni.flag.hong.kong:before {\n  background-position: -36px -468px;\n}\n\ni.flag.hm:before,\ni.flag.heard.island:before {\n  background-position: -36px -494px;\n}\n\ni.flag.hn:before,\ni.flag.honduras:before {\n  background-position: -36px -520px;\n}\n\ni.flag.hr:before,\ni.flag.croatia:before {\n  background-position: -36px -546px;\n}\n\ni.flag.ht:before,\ni.flag.haiti:before {\n  background-position: -36px -572px;\n}\n\ni.flag.hu:before,\ni.flag.hungary:before {\n  background-position: -36px -598px;\n}\n\ni.flag.id:before,\ni.flag.indonesia:before {\n  background-position: -36px -624px;\n}\n\ni.flag.ie:before,\ni.flag.ireland:before {\n  background-position: -36px -650px;\n}\n\ni.flag.il:before,\ni.flag.israel:before {\n  background-position: -36px -676px;\n}\n\ni.flag.in:before,\ni.flag.india:before {\n  background-position: -36px -702px;\n}\n\ni.flag.io:before,\ni.flag.indian.ocean.territory:before {\n  background-position: -36px -728px;\n}\n\ni.flag.iq:before,\ni.flag.iraq:before {\n  background-position: -36px -754px;\n}\n\ni.flag.ir:before,\ni.flag.iran:before {\n  background-position: -36px -780px;\n}\n\ni.flag.is:before,\ni.flag.iceland:before {\n  background-position: -36px -806px;\n}\n\ni.flag.it:before,\ni.flag.italy:before {\n  background-position: -36px -832px;\n}\n\ni.flag.jm:before,\ni.flag.jamaica:before {\n  background-position: -36px -858px;\n}\n\ni.flag.jo:before,\ni.flag.jordan:before {\n  background-position: -36px -884px;\n}\n\ni.flag.jp:before,\ni.flag.japan:before {\n  background-position: -36px -910px;\n}\n\ni.flag.ke:before,\ni.flag.kenya:before {\n  background-position: -36px -936px;\n}\n\ni.flag.kg:before,\ni.flag.kyrgyzstan:before {\n  background-position: -36px -962px;\n}\n\ni.flag.kh:before,\ni.flag.cambodia:before {\n  background-position: -36px -988px;\n}\n\ni.flag.ki:before,\ni.flag.kiribati:before {\n  background-position: -36px -1014px;\n}\n\ni.flag.km:before,\ni.flag.comoros:before {\n  background-position: -36px -1040px;\n}\n\ni.flag.kn:before,\ni.flag.saint.kitts.and.nevis:before {\n  background-position: -36px -1066px;\n}\n\ni.flag.kp:before,\ni.flag.north.korea:before {\n  background-position: -36px -1092px;\n}\n\ni.flag.kr:before,\ni.flag.south.korea:before {\n  background-position: -36px -1118px;\n}\n\ni.flag.kw:before,\ni.flag.kuwait:before {\n  background-position: -36px -1144px;\n}\n\ni.flag.ky:before,\ni.flag.cayman.islands:before {\n  background-position: -36px -1170px;\n}\n\ni.flag.kz:before,\ni.flag.kazakhstan:before {\n  background-position: -36px -1196px;\n}\n\ni.flag.la:before,\ni.flag.laos:before {\n  background-position: -36px -1222px;\n}\n\ni.flag.lb:before,\ni.flag.lebanon:before {\n  background-position: -36px -1248px;\n}\n\ni.flag.lc:before,\ni.flag.saint.lucia:before {\n  background-position: -36px -1274px;\n}\n\ni.flag.li:before,\ni.flag.liechtenstein:before {\n  background-position: -36px -1300px;\n}\n\ni.flag.lk:before,\ni.flag.sri.lanka:before {\n  background-position: -36px -1326px;\n}\n\ni.flag.lr:before,\ni.flag.liberia:before {\n  background-position: -36px -1352px;\n}\n\ni.flag.ls:before,\ni.flag.lesotho:before {\n  background-position: -36px -1378px;\n}\n\ni.flag.lt:before,\ni.flag.lithuania:before {\n  background-position: -36px -1404px;\n}\n\ni.flag.lu:before,\ni.flag.luxembourg:before {\n  background-position: -36px -1430px;\n}\n\ni.flag.lv:before,\ni.flag.latvia:before {\n  background-position: -36px -1456px;\n}\n\ni.flag.ly:before,\ni.flag.libya:before {\n  background-position: -36px -1482px;\n}\n\ni.flag.ma:before,\ni.flag.morocco:before {\n  background-position: -36px -1508px;\n}\n\ni.flag.mc:before,\ni.flag.monaco:before {\n  background-position: -36px -1534px;\n}\n\ni.flag.md:before,\ni.flag.moldova:before {\n  background-position: -36px -1560px;\n}\n\ni.flag.me:before,\ni.flag.montenegro:before {\n  background-position: -36px -1586px;\n}\n\ni.flag.mg:before,\ni.flag.madagascar:before {\n  background-position: -36px -1613px;\n}\n\ni.flag.mh:before,\ni.flag.marshall.islands:before {\n  background-position: -36px -1639px;\n}\n\ni.flag.mk:before,\ni.flag.macedonia:before {\n  background-position: -36px -1665px;\n}\n\ni.flag.ml:before,\ni.flag.mali:before {\n  background-position: -36px -1691px;\n}\n\ni.flag.mm:before,\ni.flag.myanmar:before,\ni.flag.burma:before {\n  background-position: -73px -1821px;\n}\n\ni.flag.mn:before,\ni.flag.mongolia:before {\n  background-position: -36px -1743px;\n}\n\ni.flag.mo:before,\ni.flag.macau:before {\n  background-position: -36px -1769px;\n}\n\ni.flag.mp:before,\ni.flag.northern.mariana.islands:before {\n  background-position: -36px -1795px;\n}\n\ni.flag.mq:before,\ni.flag.martinique:before {\n  background-position: -36px -1821px;\n}\n\ni.flag.mr:before,\ni.flag.mauritania:before {\n  background-position: -36px -1847px;\n}\n\ni.flag.ms:before,\ni.flag.montserrat:before {\n  background-position: -36px -1873px;\n}\n\ni.flag.mt:before,\ni.flag.malta:before {\n  background-position: -36px -1899px;\n}\n\ni.flag.mu:before,\ni.flag.mauritius:before {\n  background-position: -36px -1925px;\n}\n\ni.flag.mv:before,\ni.flag.maldives:before {\n  background-position: -36px -1951px;\n}\n\ni.flag.mw:before,\ni.flag.malawi:before {\n  background-position: -36px -1977px;\n}\n\ni.flag.mx:before,\ni.flag.mexico:before {\n  background-position: -72px 0px;\n}\n\ni.flag.my:before,\ni.flag.malaysia:before {\n  background-position: -72px -26px;\n}\n\ni.flag.mz:before,\ni.flag.mozambique:before {\n  background-position: -72px -52px;\n}\n\ni.flag.na:before,\ni.flag.namibia:before {\n  background-position: -72px -78px;\n}\n\ni.flag.nc:before,\ni.flag.new.caledonia:before {\n  background-position: -72px -104px;\n}\n\ni.flag.ne:before,\ni.flag.niger:before {\n  background-position: -72px -130px;\n}\n\ni.flag.nf:before,\ni.flag.norfolk.island:before {\n  background-position: -72px -156px;\n}\n\ni.flag.ng:before,\ni.flag.nigeria:before {\n  background-position: -72px -182px;\n}\n\ni.flag.ni:before,\ni.flag.nicaragua:before {\n  background-position: -72px -208px;\n}\n\ni.flag.nl:before,\ni.flag.netherlands:before {\n  background-position: -72px -234px;\n}\n\ni.flag.no:before,\ni.flag.norway:before {\n  background-position: -72px -260px;\n}\n\ni.flag.np:before,\ni.flag.nepal:before {\n  background-position: -72px -286px;\n}\n\ni.flag.nr:before,\ni.flag.nauru:before {\n  background-position: -72px -312px;\n}\n\ni.flag.nu:before,\ni.flag.niue:before {\n  background-position: -72px -338px;\n}\n\ni.flag.nz:before,\ni.flag.new.zealand:before {\n  background-position: -72px -364px;\n}\n\ni.flag.om:before,\ni.flag.oman:before {\n  background-position: -72px -390px;\n}\n\ni.flag.pa:before,\ni.flag.panama:before {\n  background-position: -72px -416px;\n}\n\ni.flag.pe:before,\ni.flag.peru:before {\n  background-position: -72px -442px;\n}\n\ni.flag.pf:before,\ni.flag.french.polynesia:before {\n  background-position: -72px -468px;\n}\n\ni.flag.pg:before,\ni.flag.new.guinea:before {\n  background-position: -72px -494px;\n}\n\ni.flag.ph:before,\ni.flag.philippines:before {\n  background-position: -72px -520px;\n}\n\ni.flag.pk:before,\ni.flag.pakistan:before {\n  background-position: -72px -546px;\n}\n\ni.flag.pl:before,\ni.flag.poland:before {\n  background-position: -72px -572px;\n}\n\ni.flag.pm:before,\ni.flag.saint.pierre:before {\n  background-position: -72px -598px;\n}\n\ni.flag.pn:before,\ni.flag.pitcairn.islands:before {\n  background-position: -72px -624px;\n}\n\ni.flag.pr:before,\ni.flag.puerto.rico:before {\n  background-position: -72px -650px;\n}\n\ni.flag.ps:before,\ni.flag.palestine:before {\n  background-position: -72px -676px;\n}\n\ni.flag.pt:before,\ni.flag.portugal:before {\n  background-position: -72px -702px;\n}\n\ni.flag.pw:before,\ni.flag.palau:before {\n  background-position: -72px -728px;\n}\n\ni.flag.py:before,\ni.flag.paraguay:before {\n  background-position: -72px -754px;\n}\n\ni.flag.qa:before,\ni.flag.qatar:before {\n  background-position: -72px -780px;\n}\n\ni.flag.re:before,\ni.flag.reunion:before {\n  background-position: -72px -806px;\n}\n\ni.flag.ro:before,\ni.flag.romania:before {\n  background-position: -72px -832px;\n}\n\ni.flag.rs:before,\ni.flag.serbia:before {\n  background-position: -72px -858px;\n}\n\ni.flag.ru:before,\ni.flag.russia:before {\n  background-position: -72px -884px;\n}\n\ni.flag.rw:before,\ni.flag.rwanda:before {\n  background-position: -72px -910px;\n}\n\ni.flag.sa:before,\ni.flag.saudi.arabia:before {\n  background-position: -72px -936px;\n}\n\ni.flag.sb:before,\ni.flag.solomon.islands:before {\n  background-position: -72px -962px;\n}\n\ni.flag.sc:before,\ni.flag.seychelles:before {\n  background-position: -72px -988px;\n}\n\ni.flag.gb.sct:before,\ni.flag.scotland:before {\n  background-position: -72px -1014px;\n}\n\ni.flag.sd:before,\ni.flag.sudan:before {\n  background-position: -72px -1040px;\n}\n\ni.flag.se:before,\ni.flag.sweden:before {\n  background-position: -72px -1066px;\n}\n\ni.flag.sg:before,\ni.flag.singapore:before {\n  background-position: -72px -1092px;\n}\n\ni.flag.sh:before,\ni.flag.saint.helena:before {\n  background-position: -72px -1118px;\n}\n\ni.flag.si:before,\ni.flag.slovenia:before {\n  background-position: -72px -1144px;\n}\n\ni.flag.sj:before,\ni.flag.svalbard:before,\ni.flag.jan.mayen:before {\n  background-position: -72px -1170px;\n}\n\ni.flag.sk:before,\ni.flag.slovakia:before {\n  background-position: -72px -1196px;\n}\n\ni.flag.sl:before,\ni.flag.sierra.leone:before {\n  background-position: -72px -1222px;\n}\n\ni.flag.sm:before,\ni.flag.san.marino:before {\n  background-position: -72px -1248px;\n}\n\ni.flag.sn:before,\ni.flag.senegal:before {\n  background-position: -72px -1274px;\n}\n\ni.flag.so:before,\ni.flag.somalia:before {\n  background-position: -72px -1300px;\n}\n\ni.flag.sr:before,\ni.flag.suriname:before {\n  background-position: -72px -1326px;\n}\n\ni.flag.st:before,\ni.flag.sao.tome:before {\n  background-position: -72px -1352px;\n}\n\ni.flag.sv:before,\ni.flag.el.salvador:before {\n  background-position: -72px -1378px;\n}\n\ni.flag.sy:before,\ni.flag.syria:before {\n  background-position: -72px -1404px;\n}\n\ni.flag.sz:before,\ni.flag.swaziland:before {\n  background-position: -72px -1430px;\n}\n\ni.flag.tc:before,\ni.flag.caicos.islands:before {\n  background-position: -72px -1456px;\n}\n\ni.flag.td:before,\ni.flag.chad:before {\n  background-position: -72px -1482px;\n}\n\ni.flag.tf:before,\ni.flag.french.territories:before {\n  background-position: -72px -1508px;\n}\n\ni.flag.tg:before,\ni.flag.togo:before {\n  background-position: -72px -1534px;\n}\n\ni.flag.th:before,\ni.flag.thailand:before {\n  background-position: -72px -1560px;\n}\n\ni.flag.tj:before,\ni.flag.tajikistan:before {\n  background-position: -72px -1586px;\n}\n\ni.flag.tk:before,\ni.flag.tokelau:before {\n  background-position: -72px -1612px;\n}\n\ni.flag.tl:before,\ni.flag.timorleste:before {\n  background-position: -72px -1638px;\n}\n\ni.flag.tm:before,\ni.flag.turkmenistan:before {\n  background-position: -72px -1664px;\n}\n\ni.flag.tn:before,\ni.flag.tunisia:before {\n  background-position: -72px -1690px;\n}\n\ni.flag.to:before,\ni.flag.tonga:before {\n  background-position: -72px -1716px;\n}\n\ni.flag.tr:before,\ni.flag.turkey:before {\n  background-position: -72px -1742px;\n}\n\ni.flag.tt:before,\ni.flag.trinidad:before {\n  background-position: -72px -1768px;\n}\n\ni.flag.tv:before,\ni.flag.tuvalu:before {\n  background-position: -72px -1794px;\n}\n\ni.flag.tw:before,\ni.flag.taiwan:before {\n  background-position: -72px -1820px;\n}\n\ni.flag.tz:before,\ni.flag.tanzania:before {\n  background-position: -72px -1846px;\n}\n\ni.flag.ua:before,\ni.flag.ukraine:before {\n  background-position: -72px -1872px;\n}\n\ni.flag.ug:before,\ni.flag.uganda:before {\n  background-position: -72px -1898px;\n}\n\ni.flag.um:before,\ni.flag.us.minor.islands:before {\n  background-position: -72px -1924px;\n}\n\ni.flag.us:before,\ni.flag.america:before,\ni.flag.united.states:before {\n  background-position: -72px -1950px;\n}\n\ni.flag.uy:before,\ni.flag.uruguay:before {\n  background-position: -72px -1976px;\n}\n\ni.flag.uz:before,\ni.flag.uzbekistan:before {\n  background-position: -108px 0px;\n}\n\ni.flag.va:before,\ni.flag.vatican.city:before {\n  background-position: -108px -26px;\n}\n\ni.flag.vc:before,\ni.flag.saint.vincent:before {\n  background-position: -108px -52px;\n}\n\ni.flag.ve:before,\ni.flag.venezuela:before {\n  background-position: -108px -78px;\n}\n\ni.flag.vg:before,\ni.flag.british.virgin.islands:before {\n  background-position: -108px -104px;\n}\n\ni.flag.vi:before,\ni.flag.us.virgin.islands:before {\n  background-position: -108px -130px;\n}\n\ni.flag.vn:before,\ni.flag.vietnam:before {\n  background-position: -108px -156px;\n}\n\ni.flag.vu:before,\ni.flag.vanuatu:before {\n  background-position: -108px -182px;\n}\n\ni.flag.gb.wls:before,\ni.flag.wales:before {\n  background-position: -108px -208px;\n}\n\ni.flag.wf:before,\ni.flag.wallis.and.futuna:before {\n  background-position: -108px -234px;\n}\n\ni.flag.ws:before,\ni.flag.samoa:before {\n  background-position: -108px -260px;\n}\n\ni.flag.ye:before,\ni.flag.yemen:before {\n  background-position: -108px -286px;\n}\n\ni.flag.yt:before,\ni.flag.mayotte:before {\n  background-position: -108px -312px;\n}\n\ni.flag.za:before,\ni.flag.south.africa:before {\n  background-position: -108px -338px;\n}\n\ni.flag.zm:before,\ni.flag.zambia:before {\n  background-position: -108px -364px;\n}\n\ni.flag.zw:before,\ni.flag.zimbabwe:before {\n  background-position: -108px -390px;\n}\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Header\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Header\n*******************************/\n\n/* Standard */\n\n.ui.header {\n  border: none;\n  margin: calc(2rem - 0.14285714em) 0em 1rem;\n  padding: 0em 0em;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-weight: bold;\n  line-height: 1.28571429em;\n  text-transform: none;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.header:first-child {\n  margin-top: -0.14285714em;\n}\n\n.ui.header:last-child {\n  margin-bottom: 0em;\n}\n\n/*--------------\n  Sub Header\n---------------*/\n\n.ui.header .sub.header {\n  display: block;\n  font-weight: normal;\n  padding: 0em;\n  margin: 0em;\n  font-size: 1rem;\n  line-height: 1.2em;\n  color: rgba(0, 0, 0, 0.6);\n}\n\n/*--------------\n      Icon\n---------------*/\n\n.ui.header > .icon {\n  display: table-cell;\n  opacity: 1;\n  font-size: 1.5em;\n  padding-top: 0em;\n  vertical-align: middle;\n}\n\n/* With Text Node */\n\n.ui.header .icon:only-child {\n  display: inline-block;\n  padding: 0em;\n  margin-right: 0.75rem;\n}\n\n/*-------------------\n        Image\n--------------------*/\n\n.ui.header > .image:not(.icon),\n.ui.header > img {\n  display: inline-block;\n  margin-top: 0.14285714em;\n  width: 2.5em;\n  height: auto;\n  vertical-align: middle;\n}\n\n.ui.header > .image:not(.icon):only-child,\n.ui.header > img:only-child {\n  margin-right: 0.75rem;\n}\n\n/*--------------\n    Content\n---------------*/\n\n.ui.header .content {\n  display: inline-block;\n  vertical-align: top;\n}\n\n/* After Image */\n\n.ui.header > img + .content,\n.ui.header > .image + .content {\n  padding-left: 0.75rem;\n  vertical-align: middle;\n}\n\n/* After Icon */\n\n.ui.header > .icon + .content {\n  padding-left: 0.75rem;\n  display: table-cell;\n  vertical-align: middle;\n}\n\n/*--------------\nLoose Coupling\n---------------*/\n\n.ui.header .ui.label {\n  font-size: \"\";\n  margin-left: 0.5rem;\n  vertical-align: middle;\n}\n\n/* Positioning */\n\n.ui.header + p {\n  margin-top: 0em;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*--------------\n    Page\n---------------*/\n\nh1.ui.header {\n  font-size: 2rem;\n}\n\nh2.ui.header {\n  font-size: 1.71428571rem;\n}\n\nh3.ui.header {\n  font-size: 1.28571429rem;\n}\n\nh4.ui.header {\n  font-size: 1.07142857rem;\n}\n\nh5.ui.header {\n  font-size: 1rem;\n}\n\n/* Sub Header */\n\nh1.ui.header .sub.header {\n  font-size: 1.14285714rem;\n}\n\nh2.ui.header .sub.header {\n  font-size: 1.14285714rem;\n}\n\nh3.ui.header .sub.header {\n  font-size: 1rem;\n}\n\nh4.ui.header .sub.header {\n  font-size: 1rem;\n}\n\nh5.ui.header .sub.header {\n  font-size: 0.92857143rem;\n}\n\n/*--------------\nContent Heading\n---------------*/\n\n.ui.huge.header {\n  min-height: 1em;\n  font-size: 2em;\n}\n\n.ui.large.header {\n  font-size: 1.71428571em;\n}\n\n.ui.medium.header {\n  font-size: 1.28571429em;\n}\n\n.ui.small.header {\n  font-size: 1.07142857em;\n}\n\n.ui.tiny.header {\n  font-size: 1em;\n}\n\n/* Sub Header */\n\n.ui.huge.header .sub.header {\n  font-size: 1.14285714rem;\n}\n\n.ui.large.header .sub.header {\n  font-size: 1.14285714rem;\n}\n\n.ui.header .sub.header {\n  font-size: 1rem;\n}\n\n.ui.small.header .sub.header {\n  font-size: 1rem;\n}\n\n.ui.tiny.header .sub.header {\n  font-size: 0.92857143rem;\n}\n\n/*--------------\n  Sub Heading\n---------------*/\n\n.ui.sub.header {\n  padding: 0em;\n  margin-bottom: 0.14285714rem;\n  font-weight: bold;\n  font-size: 0.85714286em;\n  text-transform: uppercase;\n  color: \"\";\n}\n\n.ui.small.sub.header {\n  font-size: 0.78571429em;\n}\n\n.ui.sub.header {\n  font-size: 0.85714286em;\n}\n\n.ui.large.sub.header {\n  font-size: 0.92857143em;\n}\n\n.ui.huge.sub.header {\n  font-size: 1em;\n}\n\n/*-------------------\n        Icon\n--------------------*/\n\n.ui.icon.header {\n  display: inline-block;\n  text-align: center;\n  margin: 2rem 0em 1rem;\n}\n\n.ui.icon.header:after {\n  content: \"\";\n  display: block;\n  height: 0px;\n  clear: both;\n  visibility: hidden;\n}\n\n.ui.icon.header:first-child {\n  margin-top: 0em;\n}\n\n.ui.icon.header .icon {\n  float: none;\n  display: block;\n  width: auto;\n  height: auto;\n  line-height: 1;\n  padding: 0em;\n  font-size: 3em;\n  margin: 0em auto 0.5rem;\n  opacity: 1;\n}\n\n.ui.icon.header .content {\n  display: block;\n  padding: 0em;\n}\n\n.ui.icon.header .circular.icon {\n  font-size: 2em;\n}\n\n.ui.icon.header .square.icon {\n  font-size: 2em;\n}\n\n.ui.block.icon.header .icon {\n  margin-bottom: 0em;\n}\n\n.ui.icon.header.aligned {\n  margin-left: auto;\n  margin-right: auto;\n  display: block;\n}\n\n/*******************************\n            States\n*******************************/\n\n.ui.disabled.header {\n  opacity: 0.45;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*-------------------\n      Inverted\n--------------------*/\n\n.ui.inverted.header {\n  color: #ffffff;\n}\n\n.ui.inverted.header .sub.header {\n  color: rgba(255, 255, 255, 0.8);\n}\n\n.ui.inverted.attached.header {\n  background: #545454 -webkit-gradient(linear, left top, left bottom, from(transparent), to(rgba(0, 0, 0, 0.05)));\n  background: #545454 -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  background: #545454 linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border-color: transparent;\n}\n\n.ui.inverted.block.header {\n  background: #545454 -webkit-gradient(linear, left top, left bottom, from(transparent), to(rgba(0, 0, 0, 0.05)));\n  background: #545454 -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  background: #545454 linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.inverted.block.header {\n  border-bottom: none;\n}\n\n/*-------------------\n      Colors\n--------------------*/\n\n/*--- Red ---*/\n\n.ui.red.header {\n  color: #db2828 !important;\n}\n\na.ui.red.header:hover {\n  color: #d01919 !important;\n}\n\n.ui.red.dividing.header {\n  border-bottom: 2px solid #db2828;\n}\n\n/* Inverted */\n\n.ui.inverted.red.header {\n  color: #ff695e !important;\n}\n\na.ui.inverted.red.header:hover {\n  color: #ff5144 !important;\n}\n\n/*--- Orange ---*/\n\n.ui.orange.header {\n  color: #f2711c !important;\n}\n\na.ui.orange.header:hover {\n  color: #f26202 !important;\n}\n\n.ui.orange.dividing.header {\n  border-bottom: 2px solid #f2711c;\n}\n\n/* Inverted */\n\n.ui.inverted.orange.header {\n  color: #ff851b !important;\n}\n\na.ui.inverted.orange.header:hover {\n  color: #ff7701 !important;\n}\n\n/*--- Olive ---*/\n\n.ui.olive.header {\n  color: #b5cc18 !important;\n}\n\na.ui.olive.header:hover {\n  color: #a7bd0d !important;\n}\n\n.ui.olive.dividing.header {\n  border-bottom: 2px solid #b5cc18;\n}\n\n/* Inverted */\n\n.ui.inverted.olive.header {\n  color: #d9e778 !important;\n}\n\na.ui.inverted.olive.header:hover {\n  color: #d8ea5c !important;\n}\n\n/*--- Yellow ---*/\n\n.ui.yellow.header {\n  color: #fbbd08 !important;\n}\n\na.ui.yellow.header:hover {\n  color: #eaae00 !important;\n}\n\n.ui.yellow.dividing.header {\n  border-bottom: 2px solid #fbbd08;\n}\n\n/* Inverted */\n\n.ui.inverted.yellow.header {\n  color: #ffe21f !important;\n}\n\na.ui.inverted.yellow.header:hover {\n  color: #ffdf05 !important;\n}\n\n/*--- Green ---*/\n\n.ui.green.header {\n  color: #21ba45 !important;\n}\n\na.ui.green.header:hover {\n  color: #16ab39 !important;\n}\n\n.ui.green.dividing.header {\n  border-bottom: 2px solid #21ba45;\n}\n\n/* Inverted */\n\n.ui.inverted.green.header {\n  color: #2ecc40 !important;\n}\n\na.ui.inverted.green.header:hover {\n  color: #22be34 !important;\n}\n\n/*--- Teal ---*/\n\n.ui.teal.header {\n  color: #00b5ad !important;\n}\n\na.ui.teal.header:hover {\n  color: #009c95 !important;\n}\n\n.ui.teal.dividing.header {\n  border-bottom: 2px solid #00b5ad;\n}\n\n/* Inverted */\n\n.ui.inverted.teal.header {\n  color: #6dffff !important;\n}\n\na.ui.inverted.teal.header:hover {\n  color: #54ffff !important;\n}\n\n/*--- Blue ---*/\n\n.ui.blue.header {\n  color: #2185d0 !important;\n}\n\na.ui.blue.header:hover {\n  color: #1678c2 !important;\n}\n\n.ui.blue.dividing.header {\n  border-bottom: 2px solid #2185d0;\n}\n\n/* Inverted */\n\n.ui.inverted.blue.header {\n  color: #54c8ff !important;\n}\n\na.ui.inverted.blue.header:hover {\n  color: #3ac0ff !important;\n}\n\n/*--- Violet ---*/\n\n.ui.violet.header {\n  color: #6435c9 !important;\n}\n\na.ui.violet.header:hover {\n  color: #5829bb !important;\n}\n\n.ui.violet.dividing.header {\n  border-bottom: 2px solid #6435c9;\n}\n\n/* Inverted */\n\n.ui.inverted.violet.header {\n  color: #a291fb !important;\n}\n\na.ui.inverted.violet.header:hover {\n  color: #8a73ff !important;\n}\n\n/*--- Purple ---*/\n\n.ui.purple.header {\n  color: #a333c8 !important;\n}\n\na.ui.purple.header:hover {\n  color: #9627ba !important;\n}\n\n.ui.purple.dividing.header {\n  border-bottom: 2px solid #a333c8;\n}\n\n/* Inverted */\n\n.ui.inverted.purple.header {\n  color: #dc73ff !important;\n}\n\na.ui.inverted.purple.header:hover {\n  color: #d65aff !important;\n}\n\n/*--- Pink ---*/\n\n.ui.pink.header {\n  color: #e03997 !important;\n}\n\na.ui.pink.header:hover {\n  color: #e61a8d !important;\n}\n\n.ui.pink.dividing.header {\n  border-bottom: 2px solid #e03997;\n}\n\n/* Inverted */\n\n.ui.inverted.pink.header {\n  color: #ff8edf !important;\n}\n\na.ui.inverted.pink.header:hover {\n  color: #ff74d8 !important;\n}\n\n/*--- Brown ---*/\n\n.ui.brown.header {\n  color: #a5673f !important;\n}\n\na.ui.brown.header:hover {\n  color: #975b33 !important;\n}\n\n.ui.brown.dividing.header {\n  border-bottom: 2px solid #a5673f;\n}\n\n/* Inverted */\n\n.ui.inverted.brown.header {\n  color: #d67c1c !important;\n}\n\na.ui.inverted.brown.header:hover {\n  color: #c86f11 !important;\n}\n\n/*--- Grey ---*/\n\n.ui.grey.header {\n  color: #767676 !important;\n}\n\na.ui.grey.header:hover {\n  color: #838383 !important;\n}\n\n.ui.grey.dividing.header {\n  border-bottom: 2px solid #767676;\n}\n\n/* Inverted */\n\n.ui.inverted.grey.header {\n  color: #dcddde !important;\n}\n\na.ui.inverted.grey.header:hover {\n  color: #cfd0d2 !important;\n}\n\n/*-------------------\n      Aligned\n--------------------*/\n\n.ui.left.aligned.header {\n  text-align: left;\n}\n\n.ui.right.aligned.header {\n  text-align: right;\n}\n\n.ui.centered.header,\n.ui.center.aligned.header {\n  text-align: center;\n}\n\n.ui.justified.header {\n  text-align: justify;\n}\n\n.ui.justified.header:after {\n  display: inline-block;\n  content: \"\";\n  width: 100%;\n}\n\n/*-------------------\n      Floated\n--------------------*/\n\n.ui.floated.header,\n.ui[class*=\"left floated\"].header {\n  float: left;\n  margin-top: 0em;\n  margin-right: 0.5em;\n}\n\n.ui[class*=\"right floated\"].header {\n  float: right;\n  margin-top: 0em;\n  margin-left: 0.5em;\n}\n\n/*-------------------\n      Fitted\n--------------------*/\n\n.ui.fitted.header {\n  padding: 0em;\n}\n\n/*-------------------\n      Dividing\n--------------------*/\n\n.ui.dividing.header {\n  padding-bottom: 0.21428571rem;\n  border-bottom: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.dividing.header .sub.header {\n  padding-bottom: 0.21428571rem;\n}\n\n.ui.dividing.header .icon {\n  margin-bottom: 0em;\n}\n\n.ui.inverted.dividing.header {\n  border-bottom-color: rgba(255, 255, 255, 0.1);\n}\n\n/*-------------------\n        Block\n--------------------*/\n\n.ui.block.header {\n  background: #f3f4f5;\n  padding: 0.78571429rem 1rem;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border: 1px solid #d4d4d5;\n  border-radius: 0.28571429rem;\n}\n\n.ui.tiny.block.header {\n  font-size: 0.85714286rem;\n}\n\n.ui.small.block.header {\n  font-size: 0.92857143rem;\n}\n\n.ui.block.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) {\n  font-size: 1rem;\n}\n\n.ui.large.block.header {\n  font-size: 1.14285714rem;\n}\n\n.ui.huge.block.header {\n  font-size: 1.42857143rem;\n}\n\n/*-------------------\n      Attached\n--------------------*/\n\n.ui.attached.header {\n  background: #ffffff;\n  padding: 0.78571429rem 1rem;\n  margin-left: -1px;\n  margin-right: -1px;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border: 1px solid #d4d4d5;\n}\n\n.ui.attached.block.header {\n  background: #f3f4f5;\n}\n\n.ui.attached:not(.top):not(.bottom).header {\n  margin-top: 0em;\n  margin-bottom: 0em;\n  border-top: none;\n  border-radius: 0em;\n}\n\n.ui.top.attached.header {\n  margin-bottom: 0em;\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n.ui.bottom.attached.header {\n  margin-top: 0em;\n  border-top: none;\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n/* Attached Sizes */\n\n.ui.tiny.attached.header {\n  font-size: 0.85714286em;\n}\n\n.ui.small.attached.header {\n  font-size: 0.92857143em;\n}\n\n.ui.attached.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) {\n  font-size: 1em;\n}\n\n.ui.large.attached.header {\n  font-size: 1.14285714em;\n}\n\n.ui.huge.attached.header {\n  font-size: 1.42857143em;\n}\n\n/*-------------------\n        Sizing\n--------------------*/\n\n.ui.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) {\n  font-size: 1.28571429em;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Icon\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Icon\n*******************************/\n\n@font-face {\n  font-family: \"Icons\";\n  src: url(\"./assets/fonts/icons.eot\");\n  src: url(\"./assets/fonts/icons.eot?#iefix\") format(\"embedded-opentype\"),\n    url(\"./assets/fonts/icons.woff2\") format(\"woff2\"),\n    url(\"./assets/fonts/icons.woff\") format(\"woff\"),\n    url(\"./assets/fonts/icons.ttf\") format(\"truetype\"),\n    url(\"./assets/fonts/icons.svg#icons\") format(\"svg\");\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-decoration: inherit;\n  text-transform: none;\n}\n\ni.icon {\n  display: inline-block;\n  opacity: 1;\n  margin: 0em 0.25rem 0em 0em;\n  width: 1.18em;\n  height: 1em;\n  font-family: \"Icons\";\n  font-style: normal;\n  font-weight: normal;\n  text-decoration: inherit;\n  text-align: center;\n  speak: none;\n  font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n}\n\ni.icon:before {\n  background: none !important;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*--------------\n    Loading\n---------------*/\n\ni.icon.loading {\n  height: 1em;\n  line-height: 1;\n  -webkit-animation: icon-loading 2s linear infinite;\n  animation: icon-loading 2s linear infinite;\n}\n\n@-webkit-keyframes icon-loading {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes icon-loading {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n/*******************************\n            States\n*******************************/\n\ni.icon.hover {\n  opacity: 1 !important;\n}\n\ni.icon.active {\n  opacity: 1 !important;\n}\n\ni.emphasized.icon {\n  opacity: 1 !important;\n}\n\ni.disabled.icon {\n  opacity: 0.45 !important;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*-------------------\n        Fitted\n--------------------*/\n\ni.fitted.icon {\n  width: auto;\n  margin: 0em !important;\n}\n\n/*-------------------\n        Link\n--------------------*/\n\ni.link.icon,\ni.link.icons {\n  cursor: pointer;\n  opacity: 0.8;\n  -webkit-transition: opacity 0.1s ease;\n  transition: opacity 0.1s ease;\n}\n\ni.link.icon:hover,\ni.link.icons:hover {\n  opacity: 1 !important;\n}\n\n/*-------------------\n      Circular\n--------------------*/\n\ni.circular.icon {\n  border-radius: 500em !important;\n  line-height: 1 !important;\n  padding: 0.5em 0em !important;\n  -webkit-box-shadow: 0em 0em 0em 0.1em rgba(0, 0, 0, 0.1) inset;\n  box-shadow: 0em 0em 0em 0.1em rgba(0, 0, 0, 0.1) inset;\n  width: 2em !important;\n  height: 2em !important;\n}\n\ni.circular.inverted.icon {\n  border: none;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/*-------------------\n      Flipped\n--------------------*/\n\ni.flipped.icon,\ni.horizontally.flipped.icon {\n  -webkit-transform: scale(-1, 1);\n  transform: scale(-1, 1);\n}\n\ni.vertically.flipped.icon {\n  -webkit-transform: scale(1, -1);\n  transform: scale(1, -1);\n}\n\n/*-------------------\n      Rotated\n--------------------*/\n\ni.rotated.icon,\ni.right.rotated.icon,\ni.clockwise.rotated.icon {\n  -webkit-transform: rotate(90deg);\n  transform: rotate(90deg);\n}\n\ni.left.rotated.icon,\ni.counterclockwise.rotated.icon {\n  -webkit-transform: rotate(-90deg);\n  transform: rotate(-90deg);\n}\n\n/*-------------------\n      Bordered\n--------------------*/\n\ni.bordered.icon {\n  line-height: 1;\n  vertical-align: baseline;\n  width: 2em;\n  height: 2em;\n  padding: 0.5em 0em !important;\n  -webkit-box-shadow: 0em 0em 0em 0.1em rgba(0, 0, 0, 0.1) inset;\n  box-shadow: 0em 0em 0em 0.1em rgba(0, 0, 0, 0.1) inset;\n}\n\ni.bordered.inverted.icon {\n  border: none;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/*-------------------\n      Inverted\n--------------------*/\n\n/* Inverted Shapes */\n\ni.inverted.bordered.icon,\ni.inverted.circular.icon {\n  background-color: #1b1c1d !important;\n  color: #ffffff !important;\n}\n\ni.inverted.icon {\n  color: #ffffff;\n}\n\n/*-------------------\n      Colors\n--------------------*/\n\n/* Red */\n\ni.red.icon {\n  color: #db2828 !important;\n}\n\ni.inverted.red.icon {\n  color: #ff695e !important;\n}\n\ni.inverted.bordered.red.icon,\ni.inverted.circular.red.icon {\n  background-color: #db2828 !important;\n  color: #ffffff !important;\n}\n\n/* Orange */\n\ni.orange.icon {\n  color: #f2711c !important;\n}\n\ni.inverted.orange.icon {\n  color: #ff851b !important;\n}\n\ni.inverted.bordered.orange.icon,\ni.inverted.circular.orange.icon {\n  background-color: #f2711c !important;\n  color: #ffffff !important;\n}\n\n/* Yellow */\n\ni.yellow.icon {\n  color: #fbbd08 !important;\n}\n\ni.inverted.yellow.icon {\n  color: #ffe21f !important;\n}\n\ni.inverted.bordered.yellow.icon,\ni.inverted.circular.yellow.icon {\n  background-color: #fbbd08 !important;\n  color: #ffffff !important;\n}\n\n/* Olive */\n\ni.olive.icon {\n  color: #b5cc18 !important;\n}\n\ni.inverted.olive.icon {\n  color: #d9e778 !important;\n}\n\ni.inverted.bordered.olive.icon,\ni.inverted.circular.olive.icon {\n  background-color: #b5cc18 !important;\n  color: #ffffff !important;\n}\n\n/* Green */\n\ni.green.icon {\n  color: #21ba45 !important;\n}\n\ni.inverted.green.icon {\n  color: #2ecc40 !important;\n}\n\ni.inverted.bordered.green.icon,\ni.inverted.circular.green.icon {\n  background-color: #21ba45 !important;\n  color: #ffffff !important;\n}\n\n/* Teal */\n\ni.teal.icon {\n  color: #00b5ad !important;\n}\n\ni.inverted.teal.icon {\n  color: #6dffff !important;\n}\n\ni.inverted.bordered.teal.icon,\ni.inverted.circular.teal.icon {\n  background-color: #00b5ad !important;\n  color: #ffffff !important;\n}\n\n/* Blue */\n\ni.blue.icon {\n  color: #2185d0 !important;\n}\n\ni.inverted.blue.icon {\n  color: #54c8ff !important;\n}\n\ni.inverted.bordered.blue.icon,\ni.inverted.circular.blue.icon {\n  background-color: #2185d0 !important;\n  color: #ffffff !important;\n}\n\n/* Violet */\n\ni.violet.icon {\n  color: #6435c9 !important;\n}\n\ni.inverted.violet.icon {\n  color: #a291fb !important;\n}\n\ni.inverted.bordered.violet.icon,\ni.inverted.circular.violet.icon {\n  background-color: #6435c9 !important;\n  color: #ffffff !important;\n}\n\n/* Purple */\n\ni.purple.icon {\n  color: #a333c8 !important;\n}\n\ni.inverted.purple.icon {\n  color: #dc73ff !important;\n}\n\ni.inverted.bordered.purple.icon,\ni.inverted.circular.purple.icon {\n  background-color: #a333c8 !important;\n  color: #ffffff !important;\n}\n\n/* Pink */\n\ni.pink.icon {\n  color: #e03997 !important;\n}\n\ni.inverted.pink.icon {\n  color: #ff8edf !important;\n}\n\ni.inverted.bordered.pink.icon,\ni.inverted.circular.pink.icon {\n  background-color: #e03997 !important;\n  color: #ffffff !important;\n}\n\n/* Brown */\n\ni.brown.icon {\n  color: #a5673f !important;\n}\n\ni.inverted.brown.icon {\n  color: #d67c1c !important;\n}\n\ni.inverted.bordered.brown.icon,\ni.inverted.circular.brown.icon {\n  background-color: #a5673f !important;\n  color: #ffffff !important;\n}\n\n/* Grey */\n\ni.grey.icon {\n  color: #767676 !important;\n}\n\ni.inverted.grey.icon {\n  color: #dcddde !important;\n}\n\ni.inverted.bordered.grey.icon,\ni.inverted.circular.grey.icon {\n  background-color: #767676 !important;\n  color: #ffffff !important;\n}\n\n/* Black */\n\ni.black.icon {\n  color: #1b1c1d !important;\n}\n\ni.inverted.black.icon {\n  color: #545454 !important;\n}\n\ni.inverted.bordered.black.icon,\ni.inverted.circular.black.icon {\n  background-color: #1b1c1d !important;\n  color: #ffffff !important;\n}\n\n/*-------------------\n        Sizes\n--------------------*/\n\ni.mini.icon,\ni.mini.icons {\n  line-height: 1;\n  font-size: 0.4em;\n}\n\ni.tiny.icon,\ni.tiny.icons {\n  line-height: 1;\n  font-size: 0.5em;\n}\n\ni.small.icon,\ni.small.icons {\n  line-height: 1;\n  font-size: 0.75em;\n}\n\ni.icon,\ni.icons {\n  font-size: 1em;\n}\n\ni.large.icon,\ni.large.icons {\n  line-height: 1;\n  vertical-align: middle;\n  font-size: 1.5em;\n}\n\ni.big.icon,\ni.big.icons {\n  line-height: 1;\n  vertical-align: middle;\n  font-size: 2em;\n}\n\ni.huge.icon,\ni.huge.icons {\n  line-height: 1;\n  vertical-align: middle;\n  font-size: 4em;\n}\n\ni.massive.icon,\ni.massive.icons {\n  line-height: 1;\n  vertical-align: middle;\n  font-size: 8em;\n}\n\n/*******************************\n            Groups\n*******************************/\n\ni.icons {\n  display: inline-block;\n  position: relative;\n  line-height: 1;\n}\n\ni.icons .icon {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  -webkit-transform: translateX(-50%) translateY(-50%);\n  transform: translateX(-50%) translateY(-50%);\n  margin: 0em;\n  margin: 0;\n}\n\ni.icons .icon:first-child {\n  position: static;\n  width: auto;\n  height: auto;\n  vertical-align: top;\n  -webkit-transform: none;\n  transform: none;\n  margin-right: 0.25rem;\n}\n\n/* Corner Icon */\n\ni.icons .corner.icon {\n  top: auto;\n  left: auto;\n  right: 0;\n  bottom: 0;\n  -webkit-transform: none;\n  transform: none;\n  font-size: 0.45em;\n  text-shadow: -1px -1px 0 #ffffff, 1px -1px 0 #ffffff, -1px 1px 0 #ffffff,\n    1px 1px 0 #ffffff;\n}\n\ni.icons .top.right.corner.icon {\n  top: 0;\n  left: auto;\n  right: 0;\n  bottom: auto;\n}\n\ni.icons .top.left.corner.icon {\n  top: 0;\n  left: 0;\n  right: auto;\n  bottom: auto;\n}\n\ni.icons .bottom.left.corner.icon {\n  top: auto;\n  left: 0;\n  right: auto;\n  bottom: 0;\n}\n\ni.icons .bottom.right.corner.icon {\n  top: auto;\n  left: auto;\n  right: 0;\n  bottom: 0;\n}\n\ni.icons .inverted.corner.icon {\n  text-shadow: -1px -1px 0 #1b1c1d, 1px -1px 0 #1b1c1d, -1px 1px 0 #1b1c1d,\n    1px 1px 0 #1b1c1d;\n}\n\n/*\n* Font Awesome 5.0.8 by @fontawesome - http://fontawesome.io - @fontawesome\n* License - https://fontawesome.com/license (Icons: CC BY 4.0 License, Fonts: SIL OFL 1.1 License, CSS: MIT License)\n*/\n\n/*******************************\n\nSemantic-UI integration of font-awesome :\n\n///class names are separated\ni.icon.circle => i.icon.circle\ni.icon.circle-o => i.icon.circle.outline\n\n//abbreviation are replaced by full letters:\ni.icon.ellipsis-h => i.icon.ellipsis.horizontal\ni.icon.ellipsis-v => i.icon.ellipsis.vertical\n.alpha => .i.icon.alphabet\n.asc => .i.icon.ascending\n.desc => .i.icon.descending\n.alt =>.alternate\n\nASCII order is conserved for easier maintenance.\n\nIcons that only have one style 'outline', 'square' etc do not require this class\nfor instance `lemon icon` not `lemon outline icon` since there is only one lemon\n\n*******************************/\n\n/*******************************\n            Icons\n*******************************/\n\n/* Deprecated *In/Out Naming Conflict) */\n\ni.icon.linkedin.in:before {\n  content: \"\\f0e1\";\n}\n\ni.icon.zoom.in:before {\n  content: \"\\f00e\";\n}\n\ni.icon.zoom.out:before {\n  content: \"\\f010\";\n}\n\ni.icon.sign.in:before {\n  content: \"\\f2f6\";\n}\n\ni.icon.in.cart:before {\n  content: \"\\f218\";\n}\n\ni.icon.log.out:before {\n  content: \"\\f2f5\";\n}\n\ni.icon.sign.out:before {\n  content: \"\\f2f5\";\n}\n\n/* Icons */\n\ni.icon.\\35 00px:before {\n  content: \"\\f26e\";\n}\n\ni.icon.accessible.icon:before {\n  content: \"\\f368\";\n}\n\ni.icon.accusoft:before {\n  content: \"\\f369\";\n}\n\ni.icon.address.book:before {\n  content: \"\\f2b9\";\n}\n\ni.icon.address.card:before {\n  content: \"\\f2bb\";\n}\n\ni.icon.adjust:before {\n  content: \"\\f042\";\n}\n\ni.icon.adn:before {\n  content: \"\\f170\";\n}\n\ni.icon.adversal:before {\n  content: \"\\f36a\";\n}\n\ni.icon.affiliatetheme:before {\n  content: \"\\f36b\";\n}\n\ni.icon.algolia:before {\n  content: \"\\f36c\";\n}\n\ni.icon.align.center:before {\n  content: \"\\f037\";\n}\n\ni.icon.align.justify:before {\n  content: \"\\f039\";\n}\n\ni.icon.align.left:before {\n  content: \"\\f036\";\n}\n\ni.icon.align.right:before {\n  content: \"\\f038\";\n}\n\ni.icon.amazon:before {\n  content: \"\\f270\";\n}\n\ni.icon.amazon.pay:before {\n  content: \"\\f42c\";\n}\n\ni.icon.ambulance:before {\n  content: \"\\f0f9\";\n}\n\ni.icon.american.sign.language.interpreting:before {\n  content: \"\\f2a3\";\n}\n\ni.icon.amilia:before {\n  content: \"\\f36d\";\n}\n\ni.icon.anchor:before {\n  content: \"\\f13d\";\n}\n\ni.icon.android:before {\n  content: \"\\f17b\";\n}\n\ni.icon.angellist:before {\n  content: \"\\f209\";\n}\n\ni.icon.angle.double.down:before {\n  content: \"\\f103\";\n}\n\ni.icon.angle.double.left:before {\n  content: \"\\f100\";\n}\n\ni.icon.angle.double.right:before {\n  content: \"\\f101\";\n}\n\ni.icon.angle.double.up:before {\n  content: \"\\f102\";\n}\n\ni.icon.angle.down:before {\n  content: \"\\f107\";\n}\n\ni.icon.angle.left:before {\n  content: \"\\f104\";\n}\n\ni.icon.angle.right:before {\n  content: \"\\f105\";\n}\n\ni.icon.angle.up:before {\n  content: \"\\f106\";\n}\n\ni.icon.angrycreative:before {\n  content: \"\\f36e\";\n}\n\ni.icon.angular:before {\n  content: \"\\f420\";\n}\n\ni.icon.app.store:before {\n  content: \"\\f36f\";\n}\n\ni.icon.app.store.ios:before {\n  content: \"\\f370\";\n}\n\ni.icon.apper:before {\n  content: \"\\f371\";\n}\n\ni.icon.apple:before {\n  content: \"\\f179\";\n}\n\ni.icon.apple.pay:before {\n  content: \"\\f415\";\n}\n\ni.icon.archive:before {\n  content: \"\\f187\";\n}\n\ni.icon.arrow.alternate.circle.down:before {\n  content: \"\\f358\";\n}\n\ni.icon.arrow.alternate.circle.left:before {\n  content: \"\\f359\";\n}\n\ni.icon.arrow.alternate.circle.right:before {\n  content: \"\\f35a\";\n}\n\ni.icon.arrow.alternate.circle.up:before {\n  content: \"\\f35b\";\n}\n\ni.icon.arrow.circle.down:before {\n  content: \"\\f0ab\";\n}\n\ni.icon.arrow.circle.left:before {\n  content: \"\\f0a8\";\n}\n\ni.icon.arrow.circle.right:before {\n  content: \"\\f0a9\";\n}\n\ni.icon.arrow.circle.up:before {\n  content: \"\\f0aa\";\n}\n\ni.icon.arrow.down:before {\n  content: \"\\f063\";\n}\n\ni.icon.arrow.left:before {\n  content: \"\\f060\";\n}\n\ni.icon.arrow.right:before {\n  content: \"\\f061\";\n}\n\ni.icon.arrow.up:before {\n  content: \"\\f062\";\n}\n\ni.icon.arrows.alternate:before {\n  content: \"\\f0b2\";\n}\n\ni.icon.arrows.alternate.horizontal:before {\n  content: \"\\f337\";\n}\n\ni.icon.arrows.alternate.vertical:before {\n  content: \"\\f338\";\n}\n\ni.icon.assistive.listening.systems:before {\n  content: \"\\f2a2\";\n}\n\ni.icon.asterisk:before {\n  content: \"\\f069\";\n}\n\ni.icon.asymmetrik:before {\n  content: \"\\f372\";\n}\n\ni.icon.at:before {\n  content: \"\\f1fa\";\n}\n\ni.icon.audible:before {\n  content: \"\\f373\";\n}\n\ni.icon.audio.description:before {\n  content: \"\\f29e\";\n}\n\ni.icon.autoprefixer:before {\n  content: \"\\f41c\";\n}\n\ni.icon.avianex:before {\n  content: \"\\f374\";\n}\n\ni.icon.aviato:before {\n  content: \"\\f421\";\n}\n\ni.icon.aws:before {\n  content: \"\\f375\";\n}\n\ni.icon.backward:before {\n  content: \"\\f04a\";\n}\n\ni.icon.balance.scale:before {\n  content: \"\\f24e\";\n}\n\ni.icon.ban:before {\n  content: \"\\f05e\";\n}\n\ni.icon.band.aid:before {\n  content: \"\\f462\";\n}\n\ni.icon.bandcamp:before {\n  content: \"\\f2d5\";\n}\n\ni.icon.barcode:before {\n  content: \"\\f02a\";\n}\n\ni.icon.bars:before {\n  content: \"\\f0c9\";\n}\n\ni.icon.baseball.ball:before {\n  content: \"\\f433\";\n}\n\ni.icon.basketball.ball:before {\n  content: \"\\f434\";\n}\n\ni.icon.bath:before {\n  content: \"\\f2cd\";\n}\n\ni.icon.battery.empty:before {\n  content: \"\\f244\";\n}\n\ni.icon.battery.full:before {\n  content: \"\\f240\";\n}\n\ni.icon.battery.half:before {\n  content: \"\\f242\";\n}\n\ni.icon.battery.quarter:before {\n  content: \"\\f243\";\n}\n\ni.icon.battery.three.quarters:before {\n  content: \"\\f241\";\n}\n\ni.icon.bed:before {\n  content: \"\\f236\";\n}\n\ni.icon.beer:before {\n  content: \"\\f0fc\";\n}\n\ni.icon.behance:before {\n  content: \"\\f1b4\";\n}\n\ni.icon.behance.square:before {\n  content: \"\\f1b5\";\n}\n\ni.icon.bell:before {\n  content: \"\\f0f3\";\n}\n\ni.icon.bell.slash:before {\n  content: \"\\f1f6\";\n}\n\ni.icon.bicycle:before {\n  content: \"\\f206\";\n}\n\ni.icon.bimobject:before {\n  content: \"\\f378\";\n}\n\ni.icon.binoculars:before {\n  content: \"\\f1e5\";\n}\n\ni.icon.birthday.cake:before {\n  content: \"\\f1fd\";\n}\n\ni.icon.bitbucket:before {\n  content: \"\\f171\";\n}\n\ni.icon.bitcoin:before {\n  content: \"\\f379\";\n}\n\ni.icon.bity:before {\n  content: \"\\f37a\";\n}\n\ni.icon.black.tie:before {\n  content: \"\\f27e\";\n}\n\ni.icon.blackberry:before {\n  content: \"\\f37b\";\n}\n\ni.icon.blind:before {\n  content: \"\\f29d\";\n}\n\ni.icon.blogger:before {\n  content: \"\\f37c\";\n}\n\ni.icon.blogger.b:before {\n  content: \"\\f37d\";\n}\n\ni.icon.bluetooth:before {\n  content: \"\\f293\";\n}\n\ni.icon.bluetooth.b:before {\n  content: \"\\f294\";\n}\n\ni.icon.bold:before {\n  content: \"\\f032\";\n}\n\ni.icon.bolt:before {\n  content: \"\\f0e7\";\n}\n\ni.icon.bomb:before {\n  content: \"\\f1e2\";\n}\n\ni.icon.book:before {\n  content: \"\\f02d\";\n}\n\ni.icon.bookmark:before {\n  content: \"\\f02e\";\n}\n\ni.icon.bowling.ball:before {\n  content: \"\\f436\";\n}\n\ni.icon.box:before {\n  content: \"\\f466\";\n}\n\ni.icon.boxes:before {\n  content: \"\\f468\";\n}\n\ni.icon.braille:before {\n  content: \"\\f2a1\";\n}\n\ni.icon.briefcase:before {\n  content: \"\\f0b1\";\n}\n\ni.icon.btc:before {\n  content: \"\\f15a\";\n}\n\ni.icon.bug:before {\n  content: \"\\f188\";\n}\n\ni.icon.building:before {\n  content: \"\\f1ad\";\n}\n\ni.icon.bullhorn:before {\n  content: \"\\f0a1\";\n}\n\ni.icon.bullseye:before {\n  content: \"\\f140\";\n}\n\ni.icon.buromobelexperte:before {\n  content: \"\\f37f\";\n}\n\ni.icon.bus:before {\n  content: \"\\f207\";\n}\n\ni.icon.buysellads:before {\n  content: \"\\f20d\";\n}\n\ni.icon.calculator:before {\n  content: \"\\f1ec\";\n}\n\ni.icon.calendar:before {\n  content: \"\\f133\";\n}\n\ni.icon.calendar.alternate:before {\n  content: \"\\f073\";\n}\n\ni.icon.calendar.check:before {\n  content: \"\\f274\";\n}\n\ni.icon.calendar.minus:before {\n  content: \"\\f272\";\n}\n\ni.icon.calendar.plus:before {\n  content: \"\\f271\";\n}\n\ni.icon.calendar.times:before {\n  content: \"\\f273\";\n}\n\ni.icon.camera:before {\n  content: \"\\f030\";\n}\n\ni.icon.camera.retro:before {\n  content: \"\\f083\";\n}\n\ni.icon.car:before {\n  content: \"\\f1b9\";\n}\n\ni.icon.caret.down:before {\n  content: \"\\f0d7\";\n}\n\ni.icon.caret.left:before {\n  content: \"\\f0d9\";\n}\n\ni.icon.caret.right:before {\n  content: \"\\f0da\";\n}\n\ni.icon.caret.square.down:before {\n  content: \"\\f150\";\n}\n\ni.icon.caret.square.left:before {\n  content: \"\\f191\";\n}\n\ni.icon.caret.square.right:before {\n  content: \"\\f152\";\n}\n\ni.icon.caret.square.up:before {\n  content: \"\\f151\";\n}\n\ni.icon.caret.up:before {\n  content: \"\\f0d8\";\n}\n\ni.icon.cart.arrow.down:before {\n  content: \"\\f218\";\n}\n\ni.icon.cart.plus:before {\n  content: \"\\f217\";\n}\n\ni.icon.cc.amazon.pay:before {\n  content: \"\\f42d\";\n}\n\ni.icon.cc.amex:before {\n  content: \"\\f1f3\";\n}\n\ni.icon.cc.apple.pay:before {\n  content: \"\\f416\";\n}\n\ni.icon.cc.diners.club:before {\n  content: \"\\f24c\";\n}\n\ni.icon.cc.discover:before {\n  content: \"\\f1f2\";\n}\n\ni.icon.cc.jcb:before {\n  content: \"\\f24b\";\n}\n\ni.icon.cc.mastercard:before {\n  content: \"\\f1f1\";\n}\n\ni.icon.cc.paypal:before {\n  content: \"\\f1f4\";\n}\n\ni.icon.cc.stripe:before {\n  content: \"\\f1f5\";\n}\n\ni.icon.cc.visa:before {\n  content: \"\\f1f0\";\n}\n\ni.icon.centercode:before {\n  content: \"\\f380\";\n}\n\ni.icon.certificate:before {\n  content: \"\\f0a3\";\n}\n\ni.icon.chart.area:before {\n  content: \"\\f1fe\";\n}\n\ni.icon.chart.bar:before {\n  content: \"\\f080\";\n}\n\ni.icon.chart.line:before {\n  content: \"\\f201\";\n}\n\ni.icon.chart.pie:before {\n  content: \"\\f200\";\n}\n\ni.icon.check:before {\n  content: \"\\f00c\";\n}\n\ni.icon.check.circle:before {\n  content: \"\\f058\";\n}\n\ni.icon.check.square:before {\n  content: \"\\f14a\";\n}\n\ni.icon.chess:before {\n  content: \"\\f439\";\n}\n\ni.icon.chess.bishop:before {\n  content: \"\\f43a\";\n}\n\ni.icon.chess.board:before {\n  content: \"\\f43c\";\n}\n\ni.icon.chess.king:before {\n  content: \"\\f43f\";\n}\n\ni.icon.chess.knight:before {\n  content: \"\\f441\";\n}\n\ni.icon.chess.pawn:before {\n  content: \"\\f443\";\n}\n\ni.icon.chess.queen:before {\n  content: \"\\f445\";\n}\n\ni.icon.chess.rook:before {\n  content: \"\\f447\";\n}\n\ni.icon.chevron.circle.down:before {\n  content: \"\\f13a\";\n}\n\ni.icon.chevron.circle.left:before {\n  content: \"\\f137\";\n}\n\ni.icon.chevron.circle.right:before {\n  content: \"\\f138\";\n}\n\ni.icon.chevron.circle.up:before {\n  content: \"\\f139\";\n}\n\ni.icon.chevron.down:before {\n  content: \"\\f078\";\n}\n\ni.icon.chevron.left:before {\n  content: \"\\f053\";\n}\n\ni.icon.chevron.right:before {\n  content: \"\\f054\";\n}\n\ni.icon.chevron.up:before {\n  content: \"\\f077\";\n}\n\ni.icon.child:before {\n  content: \"\\f1ae\";\n}\n\ni.icon.chrome:before {\n  content: \"\\f268\";\n}\n\ni.icon.circle:before {\n  content: \"\\f111\";\n}\n\ni.icon.circle.notch:before {\n  content: \"\\f1ce\";\n}\n\ni.icon.clipboard:before {\n  content: \"\\f328\";\n}\n\ni.icon.clipboard.check:before {\n  content: \"\\f46c\";\n}\n\ni.icon.clipboard.list:before {\n  content: \"\\f46d\";\n}\n\ni.icon.clock:before {\n  content: \"\\f017\";\n}\n\ni.icon.clone:before {\n  content: \"\\f24d\";\n}\n\ni.icon.closed.captioning:before {\n  content: \"\\f20a\";\n}\n\ni.icon.cloud:before {\n  content: \"\\f0c2\";\n}\n\ni.icon.cloudscale:before {\n  content: \"\\f383\";\n}\n\ni.icon.cloudsmith:before {\n  content: \"\\f384\";\n}\n\ni.icon.cloudversify:before {\n  content: \"\\f385\";\n}\n\ni.icon.code:before {\n  content: \"\\f121\";\n}\n\ni.icon.code.branch:before {\n  content: \"\\f126\";\n}\n\ni.icon.codepen:before {\n  content: \"\\f1cb\";\n}\n\ni.icon.codiepie:before {\n  content: \"\\f284\";\n}\n\ni.icon.coffee:before {\n  content: \"\\f0f4\";\n}\n\ni.icon.cog:before {\n  content: \"\\f013\";\n}\n\ni.icon.cogs:before {\n  content: \"\\f085\";\n}\n\ni.icon.columns:before {\n  content: \"\\f0db\";\n}\n\ni.icon.comment:before {\n  content: \"\\f075\";\n}\n\ni.icon.comment.alternate:before {\n  content: \"\\f27a\";\n}\n\ni.icon.comments:before {\n  content: \"\\f086\";\n}\n\ni.icon.compass:before {\n  content: \"\\f14e\";\n}\n\ni.icon.compress:before {\n  content: \"\\f066\";\n}\n\ni.icon.connectdevelop:before {\n  content: \"\\f20e\";\n}\n\ni.icon.contao:before {\n  content: \"\\f26d\";\n}\n\ni.icon.copy:before {\n  content: \"\\f0c5\";\n}\n\ni.icon.copyright:before {\n  content: \"\\f1f9\";\n}\n\ni.icon.cpanel:before {\n  content: \"\\f388\";\n}\n\ni.icon.creative.commons:before {\n  content: \"\\f25e\";\n}\n\ni.icon.credit.card:before {\n  content: \"\\f09d\";\n}\n\ni.icon.crop:before {\n  content: \"\\f125\";\n}\n\ni.icon.crosshairs:before {\n  content: \"\\f05b\";\n}\n\ni.icon.css3:before {\n  content: \"\\f13c\";\n}\n\ni.icon.css3.alternate:before {\n  content: \"\\f38b\";\n}\n\ni.icon.cube:before {\n  content: \"\\f1b2\";\n}\n\ni.icon.cubes:before {\n  content: \"\\f1b3\";\n}\n\ni.icon.cut:before {\n  content: \"\\f0c4\";\n}\n\ni.icon.cuttlefish:before {\n  content: \"\\f38c\";\n}\n\ni.icon.d.and.d:before {\n  content: \"\\f38d\";\n}\n\ni.icon.dashcube:before {\n  content: \"\\f210\";\n}\n\ni.icon.database:before {\n  content: \"\\f1c0\";\n}\n\ni.icon.deaf:before {\n  content: \"\\f2a4\";\n}\n\ni.icon.delicious:before {\n  content: \"\\f1a5\";\n}\n\ni.icon.deploydog:before {\n  content: \"\\f38e\";\n}\n\ni.icon.deskpro:before {\n  content: \"\\f38f\";\n}\n\ni.icon.desktop:before {\n  content: \"\\f108\";\n}\n\ni.icon.deviantart:before {\n  content: \"\\f1bd\";\n}\n\ni.icon.digg:before {\n  content: \"\\f1a6\";\n}\n\ni.icon.digital.ocean:before {\n  content: \"\\f391\";\n}\n\ni.icon.discord:before {\n  content: \"\\f392\";\n}\n\ni.icon.discourse:before {\n  content: \"\\f393\";\n}\n\ni.icon.dna:before {\n  content: \"\\f471\";\n}\n\ni.icon.dochub:before {\n  content: \"\\f394\";\n}\n\ni.icon.docker:before {\n  content: \"\\f395\";\n}\n\ni.icon.dollar.sign:before {\n  content: \"\\f155\";\n}\n\ni.icon.dolly:before {\n  content: \"\\f472\";\n}\n\ni.icon.dolly.flatbed:before {\n  content: \"\\f474\";\n}\n\ni.icon.dot.circle:before {\n  content: \"\\f192\";\n}\n\ni.icon.download:before {\n  content: \"\\f019\";\n}\n\ni.icon.draft2digital:before {\n  content: \"\\f396\";\n}\n\ni.icon.dribbble:before {\n  content: \"\\f17d\";\n}\n\ni.icon.dribbble.square:before {\n  content: \"\\f397\";\n}\n\ni.icon.dropbox:before {\n  content: \"\\f16b\";\n}\n\ni.icon.drupal:before {\n  content: \"\\f1a9\";\n}\n\ni.icon.dyalog:before {\n  content: \"\\f399\";\n}\n\ni.icon.earlybirds:before {\n  content: \"\\f39a\";\n}\n\ni.icon.edge:before {\n  content: \"\\f282\";\n}\n\ni.icon.edit:before {\n  content: \"\\f044\";\n}\n\ni.icon.eject:before {\n  content: \"\\f052\";\n}\n\ni.icon.elementor:before {\n  content: \"\\f430\";\n}\n\ni.icon.ellipsis.horizontal:before {\n  content: \"\\f141\";\n}\n\ni.icon.ellipsis.vertical:before {\n  content: \"\\f142\";\n}\n\ni.icon.ember:before {\n  content: \"\\f423\";\n}\n\ni.icon.empire:before {\n  content: \"\\f1d1\";\n}\n\ni.icon.envelope:before {\n  content: \"\\f0e0\";\n}\n\ni.icon.envelope.open:before {\n  content: \"\\f2b6\";\n}\n\ni.icon.envelope.square:before {\n  content: \"\\f199\";\n}\n\ni.icon.envira:before {\n  content: \"\\f299\";\n}\n\ni.icon.eraser:before {\n  content: \"\\f12d\";\n}\n\ni.icon.erlang:before {\n  content: \"\\f39d\";\n}\n\ni.icon.ethereum:before {\n  content: \"\\f42e\";\n}\n\ni.icon.etsy:before {\n  content: \"\\f2d7\";\n}\n\ni.icon.euro.sign:before {\n  content: \"\\f153\";\n}\n\ni.icon.exchange.alternate:before {\n  content: \"\\f362\";\n}\n\ni.icon.exclamation:before {\n  content: \"\\f12a\";\n}\n\ni.icon.exclamation.circle:before {\n  content: \"\\f06a\";\n}\n\ni.icon.exclamation.triangle:before {\n  content: \"\\f071\";\n}\n\ni.icon.expand:before {\n  content: \"\\f065\";\n}\n\ni.icon.expand.arrows.alternate:before {\n  content: \"\\f31e\";\n}\n\ni.icon.expeditedssl:before {\n  content: \"\\f23e\";\n}\n\ni.icon.external.alternate:before {\n  content: \"\\f35d\";\n}\n\ni.icon.external.square.alternate:before {\n  content: \"\\f360\";\n}\n\ni.icon.eye:before {\n  content: \"\\f06e\";\n}\n\ni.icon.eye.dropper:before {\n  content: \"\\f1fb\";\n}\n\ni.icon.eye.slash:before {\n  content: \"\\f070\";\n}\n\ni.icon.facebook:before {\n  content: \"\\f09a\";\n}\n\ni.icon.facebook.f:before {\n  content: \"\\f39e\";\n}\n\ni.icon.facebook.messenger:before {\n  content: \"\\f39f\";\n}\n\ni.icon.facebook.square:before {\n  content: \"\\f082\";\n}\n\ni.icon.fast.backward:before {\n  content: \"\\f049\";\n}\n\ni.icon.fast.forward:before {\n  content: \"\\f050\";\n}\n\ni.icon.fax:before {\n  content: \"\\f1ac\";\n}\n\ni.icon.female:before {\n  content: \"\\f182\";\n}\n\ni.icon.fighter.jet:before {\n  content: \"\\f0fb\";\n}\n\ni.icon.file:before {\n  content: \"\\f15b\";\n}\n\ni.icon.file.alternate:before {\n  content: \"\\f15c\";\n}\n\ni.icon.file.archive:before {\n  content: \"\\f1c6\";\n}\n\ni.icon.file.audio:before {\n  content: \"\\f1c7\";\n}\n\ni.icon.file.code:before {\n  content: \"\\f1c9\";\n}\n\ni.icon.file.excel:before {\n  content: \"\\f1c3\";\n}\n\ni.icon.file.image:before {\n  content: \"\\f1c5\";\n}\n\ni.icon.file.pdf:before {\n  content: \"\\f1c1\";\n}\n\ni.icon.file.powerpoint:before {\n  content: \"\\f1c4\";\n}\n\ni.icon.file.video:before {\n  content: \"\\f1c8\";\n}\n\ni.icon.file.word:before {\n  content: \"\\f1c2\";\n}\n\ni.icon.film:before {\n  content: \"\\f008\";\n}\n\ni.icon.filter:before {\n  content: \"\\f0b0\";\n}\n\ni.icon.fire:before {\n  content: \"\\f06d\";\n}\n\ni.icon.fire.extinguisher:before {\n  content: \"\\f134\";\n}\n\ni.icon.firefox:before {\n  content: \"\\f269\";\n}\n\ni.icon.first.aid:before {\n  content: \"\\f479\";\n}\n\ni.icon.first.order:before {\n  content: \"\\f2b0\";\n}\n\ni.icon.firstdraft:before {\n  content: \"\\f3a1\";\n}\n\ni.icon.flag:before {\n  content: \"\\f024\";\n}\n\ni.icon.flag.checkered:before {\n  content: \"\\f11e\";\n}\n\ni.icon.flask:before {\n  content: \"\\f0c3\";\n}\n\ni.icon.flickr:before {\n  content: \"\\f16e\";\n}\n\ni.icon.flipboard:before {\n  content: \"\\f44d\";\n}\n\ni.icon.fly:before {\n  content: \"\\f417\";\n}\n\ni.icon.folder:before {\n  content: \"\\f07b\";\n}\n\ni.icon.folder.open:before {\n  content: \"\\f07c\";\n}\n\ni.icon.font:before {\n  content: \"\\f031\";\n}\n\ni.icon.font.awesome:before {\n  content: \"\\f2b4\";\n}\n\ni.icon.font.awesome.alternate:before {\n  content: \"\\f35c\";\n}\n\ni.icon.font.awesome.flag:before {\n  content: \"\\f425\";\n}\n\ni.icon.fonticons:before {\n  content: \"\\f280\";\n}\n\ni.icon.fonticons.fi:before {\n  content: \"\\f3a2\";\n}\n\ni.icon.football.ball:before {\n  content: \"\\f44e\";\n}\n\ni.icon.fort.awesome:before {\n  content: \"\\f286\";\n}\n\ni.icon.fort.awesome.alternate:before {\n  content: \"\\f3a3\";\n}\n\ni.icon.forumbee:before {\n  content: \"\\f211\";\n}\n\ni.icon.forward:before {\n  content: \"\\f04e\";\n}\n\ni.icon.foursquare:before {\n  content: \"\\f180\";\n}\n\ni.icon.free.code.camp:before {\n  content: \"\\f2c5\";\n}\n\ni.icon.freebsd:before {\n  content: \"\\f3a4\";\n}\n\ni.icon.frown:before {\n  content: \"\\f119\";\n}\n\ni.icon.futbol:before {\n  content: \"\\f1e3\";\n}\n\ni.icon.gamepad:before {\n  content: \"\\f11b\";\n}\n\ni.icon.gavel:before {\n  content: \"\\f0e3\";\n}\n\ni.icon.gem:before {\n  content: \"\\f3a5\";\n}\n\ni.icon.genderless:before {\n  content: \"\\f22d\";\n}\n\ni.icon.get.pocket:before {\n  content: \"\\f265\";\n}\n\ni.icon.gg:before {\n  content: \"\\f260\";\n}\n\ni.icon.gg.circle:before {\n  content: \"\\f261\";\n}\n\ni.icon.gift:before {\n  content: \"\\f06b\";\n}\n\ni.icon.git:before {\n  content: \"\\f1d3\";\n}\n\ni.icon.git.square:before {\n  content: \"\\f1d2\";\n}\n\ni.icon.github:before {\n  content: \"\\f09b\";\n}\n\ni.icon.github.alternate:before {\n  content: \"\\f113\";\n}\n\ni.icon.github.square:before {\n  content: \"\\f092\";\n}\n\ni.icon.gitkraken:before {\n  content: \"\\f3a6\";\n}\n\ni.icon.gitlab:before {\n  content: \"\\f296\";\n}\n\ni.icon.gitter:before {\n  content: \"\\f426\";\n}\n\ni.icon.glass.martini:before {\n  content: \"\\f000\";\n}\n\ni.icon.glide:before {\n  content: \"\\f2a5\";\n}\n\ni.icon.glide.g:before {\n  content: \"\\f2a6\";\n}\n\ni.icon.globe:before {\n  content: \"\\f0ac\";\n}\n\ni.icon.gofore:before {\n  content: \"\\f3a7\";\n}\n\ni.icon.golf.ball:before {\n  content: \"\\f450\";\n}\n\ni.icon.goodreads:before {\n  content: \"\\f3a8\";\n}\n\ni.icon.goodreads.g:before {\n  content: \"\\f3a9\";\n}\n\ni.icon.google:before {\n  content: \"\\f1a0\";\n}\n\ni.icon.google.drive:before {\n  content: \"\\f3aa\";\n}\n\ni.icon.google.play:before {\n  content: \"\\f3ab\";\n}\n\ni.icon.google.plus:before {\n  content: \"\\f2b3\";\n}\n\ni.icon.google.plus.g:before {\n  content: \"\\f0d5\";\n}\n\ni.icon.google.plus.square:before {\n  content: \"\\f0d4\";\n}\n\ni.icon.google.wallet:before {\n  content: \"\\f1ee\";\n}\n\ni.icon.graduation.cap:before {\n  content: \"\\f19d\";\n}\n\ni.icon.gratipay:before {\n  content: \"\\f184\";\n}\n\ni.icon.grav:before {\n  content: \"\\f2d6\";\n}\n\ni.icon.gripfire:before {\n  content: \"\\f3ac\";\n}\n\ni.icon.grunt:before {\n  content: \"\\f3ad\";\n}\n\ni.icon.gulp:before {\n  content: \"\\f3ae\";\n}\n\ni.icon.h.square:before {\n  content: \"\\f0fd\";\n}\n\ni.icon.hacker.news:before {\n  content: \"\\f1d4\";\n}\n\ni.icon.hacker.news.square:before {\n  content: \"\\f3af\";\n}\n\ni.icon.hand.lizard:before {\n  content: \"\\f258\";\n}\n\ni.icon.hand.paper:before {\n  content: \"\\f256\";\n}\n\ni.icon.hand.peace:before {\n  content: \"\\f25b\";\n}\n\ni.icon.hand.point.down:before {\n  content: \"\\f0a7\";\n}\n\ni.icon.hand.point.left:before {\n  content: \"\\f0a5\";\n}\n\ni.icon.hand.point.right:before {\n  content: \"\\f0a4\";\n}\n\ni.icon.hand.point.up:before {\n  content: \"\\f0a6\";\n}\n\ni.icon.hand.pointer:before {\n  content: \"\\f25a\";\n}\n\ni.icon.hand.rock:before {\n  content: \"\\f255\";\n}\n\ni.icon.hand.scissors:before {\n  content: \"\\f257\";\n}\n\ni.icon.hand.spock:before {\n  content: \"\\f259\";\n}\n\ni.icon.handshake:before {\n  content: \"\\f2b5\";\n}\n\ni.icon.hashtag:before {\n  content: \"\\f292\";\n}\n\ni.icon.hdd:before {\n  content: \"\\f0a0\";\n}\n\ni.icon.heading:before {\n  content: \"\\f1dc\";\n}\n\ni.icon.headphones:before {\n  content: \"\\f025\";\n}\n\ni.icon.heart:before {\n  content: \"\\f004\";\n}\n\ni.icon.heartbeat:before {\n  content: \"\\f21e\";\n}\n\ni.icon.hips:before {\n  content: \"\\f452\";\n}\n\ni.icon.hire.a.helper:before {\n  content: \"\\f3b0\";\n}\n\ni.icon.history:before {\n  content: \"\\f1da\";\n}\n\ni.icon.hockey.puck:before {\n  content: \"\\f453\";\n}\n\ni.icon.home:before {\n  content: \"\\f015\";\n}\n\ni.icon.hooli:before {\n  content: \"\\f427\";\n}\n\ni.icon.hospital:before {\n  content: \"\\f0f8\";\n}\n\ni.icon.hospital.symbol:before {\n  content: \"\\f47e\";\n}\n\ni.icon.hotjar:before {\n  content: \"\\f3b1\";\n}\n\ni.icon.hourglass:before {\n  content: \"\\f254\";\n}\n\ni.icon.hourglass.end:before {\n  content: \"\\f253\";\n}\n\ni.icon.hourglass.half:before {\n  content: \"\\f252\";\n}\n\ni.icon.hourglass.start:before {\n  content: \"\\f251\";\n}\n\ni.icon.houzz:before {\n  content: \"\\f27c\";\n}\n\ni.icon.html5:before {\n  content: \"\\f13b\";\n}\n\ni.icon.hubspot:before {\n  content: \"\\f3b2\";\n}\n\ni.icon.i.cursor:before {\n  content: \"\\f246\";\n}\n\ni.icon.id.badge:before {\n  content: \"\\f2c1\";\n}\n\ni.icon.id.card:before {\n  content: \"\\f2c2\";\n}\n\ni.icon.image:before {\n  content: \"\\f03e\";\n}\n\ni.icon.images:before {\n  content: \"\\f302\";\n}\n\ni.icon.imdb:before {\n  content: \"\\f2d8\";\n}\n\ni.icon.inbox:before {\n  content: \"\\f01c\";\n}\n\ni.icon.indent:before {\n  content: \"\\f03c\";\n}\n\ni.icon.industry:before {\n  content: \"\\f275\";\n}\n\ni.icon.info:before {\n  content: \"\\f129\";\n}\n\ni.icon.info.circle:before {\n  content: \"\\f05a\";\n}\n\ni.icon.instagram:before {\n  content: \"\\f16d\";\n}\n\ni.icon.internet.explorer:before {\n  content: \"\\f26b\";\n}\n\ni.icon.ioxhost:before {\n  content: \"\\f208\";\n}\n\ni.icon.italic:before {\n  content: \"\\f033\";\n}\n\ni.icon.itunes:before {\n  content: \"\\f3b4\";\n}\n\ni.icon.itunes.note:before {\n  content: \"\\f3b5\";\n}\n\ni.icon.jenkins:before {\n  content: \"\\f3b6\";\n}\n\ni.icon.joget:before {\n  content: \"\\f3b7\";\n}\n\ni.icon.joomla:before {\n  content: \"\\f1aa\";\n}\n\ni.icon.js:before {\n  content: \"\\f3b8\";\n}\n\ni.icon.js.square:before {\n  content: \"\\f3b9\";\n}\n\ni.icon.jsfiddle:before {\n  content: \"\\f1cc\";\n}\n\ni.icon.key:before {\n  content: \"\\f084\";\n}\n\ni.icon.keyboard:before {\n  content: \"\\f11c\";\n}\n\ni.icon.keycdn:before {\n  content: \"\\f3ba\";\n}\n\ni.icon.kickstarter:before {\n  content: \"\\f3bb\";\n}\n\ni.icon.kickstarter.k:before {\n  content: \"\\f3bc\";\n}\n\ni.icon.korvue:before {\n  content: \"\\f42f\";\n}\n\ni.icon.language:before {\n  content: \"\\f1ab\";\n}\n\ni.icon.laptop:before {\n  content: \"\\f109\";\n}\n\ni.icon.laravel:before {\n  content: \"\\f3bd\";\n}\n\ni.icon.lastfm:before {\n  content: \"\\f202\";\n}\n\ni.icon.lastfm.square:before {\n  content: \"\\f203\";\n}\n\ni.icon.leaf:before {\n  content: \"\\f06c\";\n}\n\ni.icon.leanpub:before {\n  content: \"\\f212\";\n}\n\ni.icon.lemon:before {\n  content: \"\\f094\";\n}\n\ni.icon.less:before {\n  content: \"\\f41d\";\n}\n\ni.icon.level.down.alternate:before {\n  content: \"\\f3be\";\n}\n\ni.icon.level.up.alternate:before {\n  content: \"\\f3bf\";\n}\n\ni.icon.life.ring:before {\n  content: \"\\f1cd\";\n}\n\ni.icon.lightbulb:before {\n  content: \"\\f0eb\";\n}\n\ni.icon.linechat:before {\n  content: \"\\f3c0\";\n}\n\ni.icon.linkify:before {\n  content: \"\\f0c1\";\n}\n\ni.icon.linkedin:before {\n  content: \"\\f08c\";\n}\n\ni.icon.linkedin.alt:before {\n  content: \"\\f0e1\";\n}\n\ni.icon.linode:before {\n  content: \"\\f2b8\";\n}\n\ni.icon.linux:before {\n  content: \"\\f17c\";\n}\n\ni.icon.lira.sign:before {\n  content: \"\\f195\";\n}\n\ni.icon.list:before {\n  content: \"\\f03a\";\n}\n\ni.icon.list.alternate:before {\n  content: \"\\f022\";\n}\n\ni.icon.list.ol:before {\n  content: \"\\f0cb\";\n}\n\ni.icon.list.ul:before {\n  content: \"\\f0ca\";\n}\n\ni.icon.location.arrow:before {\n  content: \"\\f124\";\n}\n\ni.icon.lock:before {\n  content: \"\\f023\";\n}\n\ni.icon.lock.open:before {\n  content: \"\\f3c1\";\n}\n\ni.icon.long.arrow.alternate.down:before {\n  content: \"\\f309\";\n}\n\ni.icon.long.arrow.alternate.left:before {\n  content: \"\\f30a\";\n}\n\ni.icon.long.arrow.alternate.right:before {\n  content: \"\\f30b\";\n}\n\ni.icon.long.arrow.alternate.up:before {\n  content: \"\\f30c\";\n}\n\ni.icon.low.vision:before {\n  content: \"\\f2a8\";\n}\n\ni.icon.lyft:before {\n  content: \"\\f3c3\";\n}\n\ni.icon.magento:before {\n  content: \"\\f3c4\";\n}\n\ni.icon.magic:before {\n  content: \"\\f0d0\";\n}\n\ni.icon.magnet:before {\n  content: \"\\f076\";\n}\n\ni.icon.male:before {\n  content: \"\\f183\";\n}\n\ni.icon.map:before {\n  content: \"\\f279\";\n}\n\ni.icon.map.marker:before {\n  content: \"\\f041\";\n}\n\ni.icon.map.marker.alternate:before {\n  content: \"\\f3c5\";\n}\n\ni.icon.map.pin:before {\n  content: \"\\f276\";\n}\n\ni.icon.map.signs:before {\n  content: \"\\f277\";\n}\n\ni.icon.mars:before {\n  content: \"\\f222\";\n}\n\ni.icon.mars.double:before {\n  content: \"\\f227\";\n}\n\ni.icon.mars.stroke:before {\n  content: \"\\f229\";\n}\n\ni.icon.mars.stroke.horizontal:before {\n  content: \"\\f22b\";\n}\n\ni.icon.mars.stroke.vertical:before {\n  content: \"\\f22a\";\n}\n\ni.icon.maxcdn:before {\n  content: \"\\f136\";\n}\n\ni.icon.medapps:before {\n  content: \"\\f3c6\";\n}\n\ni.icon.medium:before {\n  content: \"\\f23a\";\n}\n\ni.icon.medium.m:before {\n  content: \"\\f3c7\";\n}\n\ni.icon.medkit:before {\n  content: \"\\f0fa\";\n}\n\ni.icon.medrt:before {\n  content: \"\\f3c8\";\n}\n\ni.icon.meetup:before {\n  content: \"\\f2e0\";\n}\n\ni.icon.meh:before {\n  content: \"\\f11a\";\n}\n\ni.icon.mercury:before {\n  content: \"\\f223\";\n}\n\ni.icon.microchip:before {\n  content: \"\\f2db\";\n}\n\ni.icon.microphone:before {\n  content: \"\\f130\";\n}\n\ni.icon.microphone.slash:before {\n  content: \"\\f131\";\n}\n\ni.icon.microsoft:before {\n  content: \"\\f3ca\";\n}\n\ni.icon.minus:before {\n  content: \"\\f068\";\n}\n\ni.icon.minus.circle:before {\n  content: \"\\f056\";\n}\n\ni.icon.minus.square:before {\n  content: \"\\f146\";\n}\n\ni.icon.mix:before {\n  content: \"\\f3cb\";\n}\n\ni.icon.mixcloud:before {\n  content: \"\\f289\";\n}\n\ni.icon.mizuni:before {\n  content: \"\\f3cc\";\n}\n\ni.icon.mobile:before {\n  content: \"\\f10b\";\n}\n\ni.icon.mobile.alternate:before {\n  content: \"\\f3cd\";\n}\n\ni.icon.modx:before {\n  content: \"\\f285\";\n}\n\ni.icon.monero:before {\n  content: \"\\f3d0\";\n}\n\ni.icon.money.bill.alternate:before {\n  content: \"\\f3d1\";\n}\n\ni.icon.moon:before {\n  content: \"\\f186\";\n}\n\ni.icon.motorcycle:before {\n  content: \"\\f21c\";\n}\n\ni.icon.mouse.pointer:before {\n  content: \"\\f245\";\n}\n\ni.icon.music:before {\n  content: \"\\f001\";\n}\n\ni.icon.napster:before {\n  content: \"\\f3d2\";\n}\n\ni.icon.neuter:before {\n  content: \"\\f22c\";\n}\n\ni.icon.newspaper:before {\n  content: \"\\f1ea\";\n}\n\ni.icon.nintendo.switch:before {\n  content: \"\\f418\";\n}\n\ni.icon.node:before {\n  content: \"\\f419\";\n}\n\ni.icon.node.js:before {\n  content: \"\\f3d3\";\n}\n\ni.icon.npm:before {\n  content: \"\\f3d4\";\n}\n\ni.icon.ns8:before {\n  content: \"\\f3d5\";\n}\n\ni.icon.nutritionix:before {\n  content: \"\\f3d6\";\n}\n\ni.icon.object.group:before {\n  content: \"\\f247\";\n}\n\ni.icon.object.ungroup:before {\n  content: \"\\f248\";\n}\n\ni.icon.odnoklassniki:before {\n  content: \"\\f263\";\n}\n\ni.icon.odnoklassniki.square:before {\n  content: \"\\f264\";\n}\n\ni.icon.opencart:before {\n  content: \"\\f23d\";\n}\n\ni.icon.openid:before {\n  content: \"\\f19b\";\n}\n\ni.icon.opera:before {\n  content: \"\\f26a\";\n}\n\ni.icon.optin.monster:before {\n  content: \"\\f23c\";\n}\n\ni.icon.osi:before {\n  content: \"\\f41a\";\n}\n\ni.icon.outdent:before {\n  content: \"\\f03b\";\n}\n\ni.icon.page4:before {\n  content: \"\\f3d7\";\n}\n\ni.icon.pagelines:before {\n  content: \"\\f18c\";\n}\n\ni.icon.paint.brush:before {\n  content: \"\\f1fc\";\n}\n\ni.icon.palfed:before {\n  content: \"\\f3d8\";\n}\n\ni.icon.pallet:before {\n  content: \"\\f482\";\n}\n\ni.icon.paper.plane:before {\n  content: \"\\f1d8\";\n}\n\ni.icon.paperclip:before {\n  content: \"\\f0c6\";\n}\n\ni.icon.paragraph:before {\n  content: \"\\f1dd\";\n}\n\ni.icon.paste:before {\n  content: \"\\f0ea\";\n}\n\ni.icon.patreon:before {\n  content: \"\\f3d9\";\n}\n\ni.icon.pause:before {\n  content: \"\\f04c\";\n}\n\ni.icon.pause.circle:before {\n  content: \"\\f28b\";\n}\n\ni.icon.paw:before {\n  content: \"\\f1b0\";\n}\n\ni.icon.paypal:before {\n  content: \"\\f1ed\";\n}\n\ni.icon.pen.square:before {\n  content: \"\\f14b\";\n}\n\ni.icon.pencil.alternate:before {\n  content: \"\\f303\";\n}\n\ni.icon.percent:before {\n  content: \"\\f295\";\n}\n\ni.icon.periscope:before {\n  content: \"\\f3da\";\n}\n\ni.icon.phabricator:before {\n  content: \"\\f3db\";\n}\n\ni.icon.phoenix.framework:before {\n  content: \"\\f3dc\";\n}\n\ni.icon.phone:before {\n  content: \"\\f095\";\n}\n\ni.icon.phone.square:before {\n  content: \"\\f098\";\n}\n\ni.icon.phone.volume:before {\n  content: \"\\f2a0\";\n}\n\ni.icon.php:before {\n  content: \"\\f457\";\n}\n\ni.icon.pied.piper:before {\n  content: \"\\f2ae\";\n}\n\ni.icon.pied.piper.alternate:before {\n  content: \"\\f1a8\";\n}\n\ni.icon.pied.piper.pp:before {\n  content: \"\\f1a7\";\n}\n\ni.icon.pills:before {\n  content: \"\\f484\";\n}\n\ni.icon.pinterest:before {\n  content: \"\\f0d2\";\n}\n\ni.icon.pinterest.p:before {\n  content: \"\\f231\";\n}\n\ni.icon.pinterest.square:before {\n  content: \"\\f0d3\";\n}\n\ni.icon.plane:before {\n  content: \"\\f072\";\n}\n\ni.icon.play:before {\n  content: \"\\f04b\";\n}\n\ni.icon.play.circle:before {\n  content: \"\\f144\";\n}\n\ni.icon.playstation:before {\n  content: \"\\f3df\";\n}\n\ni.icon.plug:before {\n  content: \"\\f1e6\";\n}\n\ni.icon.plus:before {\n  content: \"\\f067\";\n}\n\ni.icon.plus.circle:before {\n  content: \"\\f055\";\n}\n\ni.icon.plus.square:before {\n  content: \"\\f0fe\";\n}\n\ni.icon.podcast:before {\n  content: \"\\f2ce\";\n}\n\ni.icon.pound.sign:before {\n  content: \"\\f154\";\n}\n\ni.icon.power.off:before {\n  content: \"\\f011\";\n}\n\ni.icon.print:before {\n  content: \"\\f02f\";\n}\n\ni.icon.product.hunt:before {\n  content: \"\\f288\";\n}\n\ni.icon.pushed:before {\n  content: \"\\f3e1\";\n}\n\ni.icon.puzzle.piece:before {\n  content: \"\\f12e\";\n}\n\ni.icon.python:before {\n  content: \"\\f3e2\";\n}\n\ni.icon.qq:before {\n  content: \"\\f1d6\";\n}\n\ni.icon.qrcode:before {\n  content: \"\\f029\";\n}\n\ni.icon.question:before {\n  content: \"\\f128\";\n}\n\ni.icon.question.circle:before {\n  content: \"\\f059\";\n}\n\ni.icon.quidditch:before {\n  content: \"\\f458\";\n}\n\ni.icon.quinscape:before {\n  content: \"\\f459\";\n}\n\ni.icon.quora:before {\n  content: \"\\f2c4\";\n}\n\ni.icon.quote.left:before {\n  content: \"\\f10d\";\n}\n\ni.icon.quote.right:before {\n  content: \"\\f10e\";\n}\n\ni.icon.random:before {\n  content: \"\\f074\";\n}\n\ni.icon.ravelry:before {\n  content: \"\\f2d9\";\n}\n\ni.icon.react:before {\n  content: \"\\f41b\";\n}\n\ni.icon.rebel:before {\n  content: \"\\f1d0\";\n}\n\ni.icon.recycle:before {\n  content: \"\\f1b8\";\n}\n\ni.icon.redriver:before {\n  content: \"\\f3e3\";\n}\n\ni.icon.reddit:before {\n  content: \"\\f1a1\";\n}\n\ni.icon.reddit.alien:before {\n  content: \"\\f281\";\n}\n\ni.icon.reddit.square:before {\n  content: \"\\f1a2\";\n}\n\ni.icon.redo:before {\n  content: \"\\f01e\";\n}\n\ni.icon.redo.alternate:before {\n  content: \"\\f2f9\";\n}\n\ni.icon.registered:before {\n  content: \"\\f25d\";\n}\n\ni.icon.rendact:before {\n  content: \"\\f3e4\";\n}\n\ni.icon.renren:before {\n  content: \"\\f18b\";\n}\n\ni.icon.reply:before {\n  content: \"\\f3e5\";\n}\n\ni.icon.reply.all:before {\n  content: \"\\f122\";\n}\n\ni.icon.replyd:before {\n  content: \"\\f3e6\";\n}\n\ni.icon.resolving:before {\n  content: \"\\f3e7\";\n}\n\ni.icon.retweet:before {\n  content: \"\\f079\";\n}\n\ni.icon.road:before {\n  content: \"\\f018\";\n}\n\ni.icon.rocket:before {\n  content: \"\\f135\";\n}\n\ni.icon.rocketchat:before {\n  content: \"\\f3e8\";\n}\n\ni.icon.rockrms:before {\n  content: \"\\f3e9\";\n}\n\ni.icon.rss:before {\n  content: \"\\f09e\";\n}\n\ni.icon.rss.square:before {\n  content: \"\\f143\";\n}\n\ni.icon.ruble.sign:before {\n  content: \"\\f158\";\n}\n\ni.icon.rupee.sign:before {\n  content: \"\\f156\";\n}\n\ni.icon.safari:before {\n  content: \"\\f267\";\n}\n\ni.icon.sass:before {\n  content: \"\\f41e\";\n}\n\ni.icon.save:before {\n  content: \"\\f0c7\";\n}\n\ni.icon.schlix:before {\n  content: \"\\f3ea\";\n}\n\ni.icon.scribd:before {\n  content: \"\\f28a\";\n}\n\ni.icon.search:before {\n  content: \"\\f002\";\n}\n\ni.icon.search.minus:before {\n  content: \"\\f010\";\n}\n\ni.icon.search.plus:before {\n  content: \"\\f00e\";\n}\n\ni.icon.searchengin:before {\n  content: \"\\f3eb\";\n}\n\ni.icon.sellcast:before {\n  content: \"\\f2da\";\n}\n\ni.icon.sellsy:before {\n  content: \"\\f213\";\n}\n\ni.icon.server:before {\n  content: \"\\f233\";\n}\n\ni.icon.servicestack:before {\n  content: \"\\f3ec\";\n}\n\ni.icon.share:before {\n  content: \"\\f064\";\n}\n\ni.icon.share.alternate:before {\n  content: \"\\f1e0\";\n}\n\ni.icon.share.alternate.square:before {\n  content: \"\\f1e1\";\n}\n\ni.icon.share.square:before {\n  content: \"\\f14d\";\n}\n\ni.icon.shekel.sign:before {\n  content: \"\\f20b\";\n}\n\ni.icon.shield.alternate:before {\n  content: \"\\f3ed\";\n}\n\ni.icon.ship:before {\n  content: \"\\f21a\";\n}\n\ni.icon.shipping.fast:before {\n  content: \"\\f48b\";\n}\n\ni.icon.shirtsinbulk:before {\n  content: \"\\f214\";\n}\n\ni.icon.shopping.bag:before {\n  content: \"\\f290\";\n}\n\ni.icon.shopping.basket:before {\n  content: \"\\f291\";\n}\n\ni.icon.shopping.cart:before {\n  content: \"\\f07a\";\n}\n\ni.icon.shower:before {\n  content: \"\\f2cc\";\n}\n\ni.icon.sign.language:before {\n  content: \"\\f2a7\";\n}\n\ni.icon.signal:before {\n  content: \"\\f012\";\n}\n\ni.icon.simplybuilt:before {\n  content: \"\\f215\";\n}\n\ni.icon.sistrix:before {\n  content: \"\\f3ee\";\n}\n\ni.icon.sitemap:before {\n  content: \"\\f0e8\";\n}\n\ni.icon.skyatlas:before {\n  content: \"\\f216\";\n}\n\ni.icon.skype:before {\n  content: \"\\f17e\";\n}\n\ni.icon.slack:before {\n  content: \"\\f198\";\n}\n\ni.icon.slack.hash:before {\n  content: \"\\f3ef\";\n}\n\ni.icon.sliders.horizontal:before {\n  content: \"\\f1de\";\n}\n\ni.icon.slideshare:before {\n  content: \"\\f1e7\";\n}\n\ni.icon.smile:before {\n  content: \"\\f118\";\n}\n\ni.icon.snapchat:before {\n  content: \"\\f2ab\";\n}\n\ni.icon.snapchat.ghost:before {\n  content: \"\\f2ac\";\n}\n\ni.icon.snapchat.square:before {\n  content: \"\\f2ad\";\n}\n\ni.icon.snowflake:before {\n  content: \"\\f2dc\";\n}\n\ni.icon.sort:before {\n  content: \"\\f0dc\";\n}\n\ni.icon.sort.alphabet.down:before {\n  content: \"\\f15d\";\n}\n\ni.icon.sort.alphabet.up:before {\n  content: \"\\f15e\";\n}\n\ni.icon.sort.amount.down:before {\n  content: \"\\f160\";\n}\n\ni.icon.sort.amount.up:before {\n  content: \"\\f161\";\n}\n\ni.icon.sort.down:before {\n  content: \"\\f0dd\";\n}\n\ni.icon.sort.numeric.down:before {\n  content: \"\\f162\";\n}\n\ni.icon.sort.numeric.up:before {\n  content: \"\\f163\";\n}\n\ni.icon.sort.up:before {\n  content: \"\\f0de\";\n}\n\ni.icon.soundcloud:before {\n  content: \"\\f1be\";\n}\n\ni.icon.space.shuttle:before {\n  content: \"\\f197\";\n}\n\ni.icon.speakap:before {\n  content: \"\\f3f3\";\n}\n\ni.icon.spinner:before {\n  content: \"\\f110\";\n}\n\ni.icon.spotify:before {\n  content: \"\\f1bc\";\n}\n\ni.icon.square:before {\n  content: \"\\f0c8\";\n}\n\ni.icon.square.full:before {\n  content: \"\\f45c\";\n}\n\ni.icon.stack.exchange:before {\n  content: \"\\f18d\";\n}\n\ni.icon.stack.overflow:before {\n  content: \"\\f16c\";\n}\n\ni.icon.star:before {\n  content: \"\\f005\";\n}\n\ni.icon.star.half:before {\n  content: \"\\f089\";\n}\n\ni.icon.staylinked:before {\n  content: \"\\f3f5\";\n}\n\ni.icon.steam:before {\n  content: \"\\f1b6\";\n}\n\ni.icon.steam.square:before {\n  content: \"\\f1b7\";\n}\n\ni.icon.steam.symbol:before {\n  content: \"\\f3f6\";\n}\n\ni.icon.step.backward:before {\n  content: \"\\f048\";\n}\n\ni.icon.step.forward:before {\n  content: \"\\f051\";\n}\n\ni.icon.stethoscope:before {\n  content: \"\\f0f1\";\n}\n\ni.icon.sticker.mule:before {\n  content: \"\\f3f7\";\n}\n\ni.icon.sticky.note:before {\n  content: \"\\f249\";\n}\n\ni.icon.stop:before {\n  content: \"\\f04d\";\n}\n\ni.icon.stop.circle:before {\n  content: \"\\f28d\";\n}\n\ni.icon.stopwatch:before {\n  content: \"\\f2f2\";\n}\n\ni.icon.strava:before {\n  content: \"\\f428\";\n}\n\ni.icon.street.view:before {\n  content: \"\\f21d\";\n}\n\ni.icon.strikethrough:before {\n  content: \"\\f0cc\";\n}\n\ni.icon.stripe:before {\n  content: \"\\f429\";\n}\n\ni.icon.stripe.s:before {\n  content: \"\\f42a\";\n}\n\ni.icon.studiovinari:before {\n  content: \"\\f3f8\";\n}\n\ni.icon.stumbleupon:before {\n  content: \"\\f1a4\";\n}\n\ni.icon.stumbleupon.circle:before {\n  content: \"\\f1a3\";\n}\n\ni.icon.subscript:before {\n  content: \"\\f12c\";\n}\n\ni.icon.subway:before {\n  content: \"\\f239\";\n}\n\ni.icon.suitcase:before {\n  content: \"\\f0f2\";\n}\n\ni.icon.sun:before {\n  content: \"\\f185\";\n}\n\ni.icon.superpowers:before {\n  content: \"\\f2dd\";\n}\n\ni.icon.superscript:before {\n  content: \"\\f12b\";\n}\n\ni.icon.supple:before {\n  content: \"\\f3f9\";\n}\n\ni.icon.sync:before {\n  content: \"\\f021\";\n}\n\ni.icon.sync.alternate:before {\n  content: \"\\f2f1\";\n}\n\ni.icon.syringe:before {\n  content: \"\\f48e\";\n}\n\ni.icon.table:before {\n  content: \"\\f0ce\";\n}\n\ni.icon.table.tennis:before {\n  content: \"\\f45d\";\n}\n\ni.icon.tablet:before {\n  content: \"\\f10a\";\n}\n\ni.icon.tablet.alternate:before {\n  content: \"\\f3fa\";\n}\n\ni.icon.tachometer.alternate:before {\n  content: \"\\f3fd\";\n}\n\ni.icon.tag:before {\n  content: \"\\f02b\";\n}\n\ni.icon.tags:before {\n  content: \"\\f02c\";\n}\n\ni.icon.tasks:before {\n  content: \"\\f0ae\";\n}\n\ni.icon.taxi:before {\n  content: \"\\f1ba\";\n}\n\ni.icon.telegram:before {\n  content: \"\\f2c6\";\n}\n\ni.icon.telegram.plane:before {\n  content: \"\\f3fe\";\n}\n\ni.icon.tencent.weibo:before {\n  content: \"\\f1d5\";\n}\n\ni.icon.terminal:before {\n  content: \"\\f120\";\n}\n\ni.icon.text.height:before {\n  content: \"\\f034\";\n}\n\ni.icon.text.width:before {\n  content: \"\\f035\";\n}\n\ni.icon.th:before {\n  content: \"\\f00a\";\n}\n\ni.icon.th.large:before {\n  content: \"\\f009\";\n}\n\ni.icon.th.list:before {\n  content: \"\\f00b\";\n}\n\ni.icon.themeisle:before {\n  content: \"\\f2b2\";\n}\n\ni.icon.thermometer:before {\n  content: \"\\f491\";\n}\n\ni.icon.thermometer.empty:before {\n  content: \"\\f2cb\";\n}\n\ni.icon.thermometer.full:before {\n  content: \"\\f2c7\";\n}\n\ni.icon.thermometer.half:before {\n  content: \"\\f2c9\";\n}\n\ni.icon.thermometer.quarter:before {\n  content: \"\\f2ca\";\n}\n\ni.icon.thermometer.three.quarters:before {\n  content: \"\\f2c8\";\n}\n\ni.icon.thumbs.down:before {\n  content: \"\\f165\";\n}\n\ni.icon.thumbs.up:before {\n  content: \"\\f164\";\n}\n\ni.icon.thumbtack:before {\n  content: \"\\f08d\";\n}\n\ni.icon.ticket.alternate:before {\n  content: \"\\f3ff\";\n}\n\ni.icon.times:before {\n  content: \"\\f00d\";\n}\n\ni.icon.times.circle:before {\n  content: \"\\f057\";\n}\n\ni.icon.tint:before {\n  content: \"\\f043\";\n}\n\ni.icon.toggle.off:before {\n  content: \"\\f204\";\n}\n\ni.icon.toggle.on:before {\n  content: \"\\f205\";\n}\n\ni.icon.trademark:before {\n  content: \"\\f25c\";\n}\n\ni.icon.train:before {\n  content: \"\\f238\";\n}\n\ni.icon.transgender:before {\n  content: \"\\f224\";\n}\n\ni.icon.transgender.alternate:before {\n  content: \"\\f225\";\n}\n\ni.icon.trash:before {\n  content: \"\\f1f8\";\n}\n\ni.icon.trash.alternate:before {\n  content: \"\\f2ed\";\n}\n\ni.icon.tree:before {\n  content: \"\\f1bb\";\n}\n\ni.icon.trello:before {\n  content: \"\\f181\";\n}\n\ni.icon.tripadvisor:before {\n  content: \"\\f262\";\n}\n\ni.icon.trophy:before {\n  content: \"\\f091\";\n}\n\ni.icon.truck:before {\n  content: \"\\f0d1\";\n}\n\ni.icon.tty:before {\n  content: \"\\f1e4\";\n}\n\ni.icon.tumblr:before {\n  content: \"\\f173\";\n}\n\ni.icon.tumblr.square:before {\n  content: \"\\f174\";\n}\n\ni.icon.tv:before {\n  content: \"\\f26c\";\n}\n\ni.icon.twitch:before {\n  content: \"\\f1e8\";\n}\n\ni.icon.twitter:before {\n  content: \"\\f099\";\n}\n\ni.icon.twitter.square:before {\n  content: \"\\f081\";\n}\n\ni.icon.typo3:before {\n  content: \"\\f42b\";\n}\n\ni.icon.uber:before {\n  content: \"\\f402\";\n}\n\ni.icon.uikit:before {\n  content: \"\\f403\";\n}\n\ni.icon.umbrella:before {\n  content: \"\\f0e9\";\n}\n\ni.icon.underline:before {\n  content: \"\\f0cd\";\n}\n\ni.icon.undo:before {\n  content: \"\\f0e2\";\n}\n\ni.icon.undo.alternate:before {\n  content: \"\\f2ea\";\n}\n\ni.icon.uniregistry:before {\n  content: \"\\f404\";\n}\n\ni.icon.universal.access:before {\n  content: \"\\f29a\";\n}\n\ni.icon.university:before {\n  content: \"\\f19c\";\n}\n\ni.icon.unlink:before {\n  content: \"\\f127\";\n}\n\ni.icon.unlock:before {\n  content: \"\\f09c\";\n}\n\ni.icon.unlock.alternate:before {\n  content: \"\\f13e\";\n}\n\ni.icon.untappd:before {\n  content: \"\\f405\";\n}\n\ni.icon.upload:before {\n  content: \"\\f093\";\n}\n\ni.icon.usb:before {\n  content: \"\\f287\";\n}\n\ni.icon.user:before {\n  content: \"\\f007\";\n}\n\ni.icon.user.circle:before {\n  content: \"\\f2bd\";\n}\n\ni.icon.user.md:before {\n  content: \"\\f0f0\";\n}\n\ni.icon.user.plus:before {\n  content: \"\\f234\";\n}\n\ni.icon.user.secret:before {\n  content: \"\\f21b\";\n}\n\ni.icon.user.times:before {\n  content: \"\\f235\";\n}\n\ni.icon.users:before {\n  content: \"\\f0c0\";\n}\n\ni.icon.ussunnah:before {\n  content: \"\\f407\";\n}\n\ni.icon.utensil.spoon:before {\n  content: \"\\f2e5\";\n}\n\ni.icon.utensils:before {\n  content: \"\\f2e7\";\n}\n\ni.icon.vaadin:before {\n  content: \"\\f408\";\n}\n\ni.icon.venus:before {\n  content: \"\\f221\";\n}\n\ni.icon.venus.double:before {\n  content: \"\\f226\";\n}\n\ni.icon.venus.mars:before {\n  content: \"\\f228\";\n}\n\ni.icon.viacoin:before {\n  content: \"\\f237\";\n}\n\ni.icon.viadeo:before {\n  content: \"\\f2a9\";\n}\n\ni.icon.viadeo.square:before {\n  content: \"\\f2aa\";\n}\n\ni.icon.viber:before {\n  content: \"\\f409\";\n}\n\ni.icon.video:before {\n  content: \"\\f03d\";\n}\n\ni.icon.vimeo:before {\n  content: \"\\f40a\";\n}\n\ni.icon.vimeo.square:before {\n  content: \"\\f194\";\n}\n\ni.icon.vimeo.v:before {\n  content: \"\\f27d\";\n}\n\ni.icon.vine:before {\n  content: \"\\f1ca\";\n}\n\ni.icon.vk:before {\n  content: \"\\f189\";\n}\n\ni.icon.vnv:before {\n  content: \"\\f40b\";\n}\n\ni.icon.volleyball.ball:before {\n  content: \"\\f45f\";\n}\n\ni.icon.volume.down:before {\n  content: \"\\f027\";\n}\n\ni.icon.volume.off:before {\n  content: \"\\f026\";\n}\n\ni.icon.volume.up:before {\n  content: \"\\f028\";\n}\n\ni.icon.vuejs:before {\n  content: \"\\f41f\";\n}\n\ni.icon.warehouse:before {\n  content: \"\\f494\";\n}\n\ni.icon.weibo:before {\n  content: \"\\f18a\";\n}\n\ni.icon.weight:before {\n  content: \"\\f496\";\n}\n\ni.icon.weixin:before {\n  content: \"\\f1d7\";\n}\n\ni.icon.whatsapp:before {\n  content: \"\\f232\";\n}\n\ni.icon.whatsapp.square:before {\n  content: \"\\f40c\";\n}\n\ni.icon.wheelchair:before {\n  content: \"\\f193\";\n}\n\ni.icon.whmcs:before {\n  content: \"\\f40d\";\n}\n\ni.icon.wifi:before {\n  content: \"\\f1eb\";\n}\n\ni.icon.wikipedia.w:before {\n  content: \"\\f266\";\n}\n\ni.icon.window.close:before {\n  content: \"\\f410\";\n}\n\ni.icon.window.maximize:before {\n  content: \"\\f2d0\";\n}\n\ni.icon.window.minimize:before {\n  content: \"\\f2d1\";\n}\n\ni.icon.window.restore:before {\n  content: \"\\f2d2\";\n}\n\ni.icon.windows:before {\n  content: \"\\f17a\";\n}\n\ni.icon.won.sign:before {\n  content: \"\\f159\";\n}\n\ni.icon.wordpress:before {\n  content: \"\\f19a\";\n}\n\ni.icon.wordpress.simple:before {\n  content: \"\\f411\";\n}\n\ni.icon.wpbeginner:before {\n  content: \"\\f297\";\n}\n\ni.icon.wpexplorer:before {\n  content: \"\\f2de\";\n}\n\ni.icon.wpforms:before {\n  content: \"\\f298\";\n}\n\ni.icon.wrench:before {\n  content: \"\\f0ad\";\n}\n\ni.icon.xbox:before {\n  content: \"\\f412\";\n}\n\ni.icon.xing:before {\n  content: \"\\f168\";\n}\n\ni.icon.xing.square:before {\n  content: \"\\f169\";\n}\n\ni.icon.y.combinator:before {\n  content: \"\\f23b\";\n}\n\ni.icon.yahoo:before {\n  content: \"\\f19e\";\n}\n\ni.icon.yandex:before {\n  content: \"\\f413\";\n}\n\ni.icon.yandex.international:before {\n  content: \"\\f414\";\n}\n\ni.icon.yelp:before {\n  content: \"\\f1e9\";\n}\n\ni.icon.yen.sign:before {\n  content: \"\\f157\";\n}\n\ni.icon.yoast:before {\n  content: \"\\f2b1\";\n}\n\ni.icon.youtube:before {\n  content: \"\\f167\";\n}\n\ni.icon.youtube.square:before {\n  content: \"\\f431\";\n}\n\n/* Aliases */\n\ni.icon.chess.rock:before {\n  content: \"\\f447\";\n}\n\ni.icon.ordered.list:before {\n  content: \"\\f0cb\";\n}\n\ni.icon.unordered.list:before {\n  content: \"\\f0ca\";\n}\n\ni.icon.user.doctor:before {\n  content: \"\\f0f0\";\n}\n\ni.icon.shield:before {\n  content: \"\\f3ed\";\n}\n\ni.icon.puzzle:before {\n  content: \"\\f12e\";\n}\n\ni.icon.credit.card.amazon.pay:before {\n  content: \"\\f42d\";\n}\n\ni.icon.credit.card.american.express:before {\n  content: \"\\f1f3\";\n}\n\ni.icon.credit.card.diners.club:before {\n  content: \"\\f24c\";\n}\n\ni.icon.credit.card.discover:before {\n  content: \"\\f1f2\";\n}\n\ni.icon.credit.card.jcb:before {\n  content: \"\\f24b\";\n}\n\ni.icon.credit.card.mastercard:before {\n  content: \"\\f1f1\";\n}\n\ni.icon.credit.card.paypal:before {\n  content: \"\\f1f4\";\n}\n\ni.icon.credit.card.stripe:before {\n  content: \"\\f1f5\";\n}\n\ni.icon.credit.card.visa:before {\n  content: \"\\f1f0\";\n}\n\ni.icon.add.circle:before {\n  content: \"\\f055\";\n}\n\ni.icon.add.square:before {\n  content: \"\\f0fe\";\n}\n\ni.icon.add.to.calendar:before {\n  content: \"\\f271\";\n}\n\ni.icon.add.to.cart:before {\n  content: \"\\f217\";\n}\n\ni.icon.add.user:before {\n  content: \"\\f234\";\n}\n\ni.icon.add:before {\n  content: \"\\f067\";\n}\n\ni.icon.alarm.mute:before {\n  content: \"\\f1f6\";\n}\n\ni.icon.alarm:before {\n  content: \"\\f0f3\";\n}\n\ni.icon.ald:before {\n  content: \"\\f2a2\";\n}\n\ni.icon.als:before {\n  content: \"\\f2a2\";\n}\n\ni.icon.american.express.card:before {\n  content: \"\\f1f3\";\n}\n\ni.icon.american.express:before {\n  content: \"\\f1f3\";\n}\n\ni.icon.amex:before {\n  content: \"\\f1f3\";\n}\n\ni.icon.announcement:before {\n  content: \"\\f0a1\";\n}\n\ni.icon.area.chart:before {\n  content: \"\\f1fe\";\n}\n\ni.icon.area.graph:before {\n  content: \"\\f1fe\";\n}\n\ni.icon.arrow.down.cart:before {\n  content: \"\\f218\";\n}\n\ni.icon.asexual:before {\n  content: \"\\f22d\";\n}\n\ni.icon.asl.interpreting:before {\n  content: \"\\f2a3\";\n}\n\ni.icon.asl:before {\n  content: \"\\f2a3\";\n}\n\ni.icon.assistive.listening.devices:before {\n  content: \"\\f2a2\";\n}\n\ni.icon.attach:before {\n  content: \"\\f0c6\";\n}\n\ni.icon.attention:before {\n  content: \"\\f06a\";\n}\n\ni.icon.balance:before {\n  content: \"\\f24e\";\n}\n\ni.icon.bar:before {\n  content: \"\\f0fc\";\n}\n\ni.icon.bathtub:before {\n  content: \"\\f2cd\";\n}\n\ni.icon.battery.four:before {\n  content: \"\\f240\";\n}\n\ni.icon.battery.high:before {\n  content: \"\\f241\";\n}\n\ni.icon.battery.low:before {\n  content: \"\\f243\";\n}\n\ni.icon.battery.medium:before {\n  content: \"\\f242\";\n}\n\ni.icon.battery.one:before {\n  content: \"\\f243\";\n}\n\ni.icon.battery.three:before {\n  content: \"\\f241\";\n}\n\ni.icon.battery.two:before {\n  content: \"\\f242\";\n}\n\ni.icon.battery.zero:before {\n  content: \"\\f244\";\n}\n\ni.icon.birthday:before {\n  content: \"\\f1fd\";\n}\n\ni.icon.block.layout:before {\n  content: \"\\f009\";\n}\n\ni.icon.bluetooth.alternative:before {\n  content: \"\\f294\";\n}\n\ni.icon.broken.chain:before {\n  content: \"\\f127\";\n}\n\ni.icon.browser:before {\n  content: \"\\f022\";\n}\n\ni.icon.call.square:before {\n  content: \"\\f098\";\n}\n\ni.icon.call:before {\n  content: \"\\f095\";\n}\n\ni.icon.cancel:before {\n  content: \"\\f00d\";\n}\n\ni.icon.cart:before {\n  content: \"\\f07a\";\n}\n\ni.icon.cc:before {\n  content: \"\\f20a\";\n}\n\ni.icon.chain:before {\n  content: \"\\f0c1\";\n}\n\ni.icon.chat:before {\n  content: \"\\f075\";\n}\n\ni.icon.checked.calendar:before {\n  content: \"\\f274\";\n}\n\ni.icon.checkmark:before {\n  content: \"\\f00c\";\n}\n\ni.icon.circle.notched:before {\n  content: \"\\f1ce\";\n}\n\ni.icon.close:before {\n  content: \"\\f00d\";\n}\n\ni.icon.cny:before {\n  content: \"\\f157\";\n}\n\ni.icon.cocktail:before {\n  content: \"\\f000\";\n}\n\ni.icon.commenting:before {\n  content: \"\\f27a\";\n}\n\ni.icon.computer:before {\n  content: \"\\f108\";\n}\n\ni.icon.configure:before {\n  content: \"\\f0ad\";\n}\n\ni.icon.content:before {\n  content: \"\\f0c9\";\n}\n\ni.icon.deafness:before {\n  content: \"\\f2a4\";\n}\n\ni.icon.delete.calendar:before {\n  content: \"\\f273\";\n}\n\ni.icon.delete:before {\n  content: \"\\f00d\";\n}\n\ni.icon.detective:before {\n  content: \"\\f21b\";\n}\n\ni.icon.diners.club.card:before {\n  content: \"\\f24c\";\n}\n\ni.icon.diners.club:before {\n  content: \"\\f24c\";\n}\n\ni.icon.discover.card:before {\n  content: \"\\f1f2\";\n}\n\ni.icon.discover:before {\n  content: \"\\f1f2\";\n}\n\ni.icon.discussions:before {\n  content: \"\\f086\";\n}\n\ni.icon.doctor:before {\n  content: \"\\f0f0\";\n}\n\ni.icon.dollar:before {\n  content: \"\\f155\";\n}\n\ni.icon.dont:before {\n  content: \"\\f05e\";\n}\n\ni.icon.dribble:before {\n  content: \"\\f17d\";\n}\n\ni.icon.drivers.license:before {\n  content: \"\\f2c2\";\n}\n\ni.icon.dropdown:before {\n  content: \"\\f0d7\";\n}\n\ni.icon.eercast:before {\n  content: \"\\f2da\";\n}\n\ni.icon.emergency:before {\n  content: \"\\f0f9\";\n}\n\ni.icon.envira.gallery:before {\n  content: \"\\f299\";\n}\n\ni.icon.erase:before {\n  content: \"\\f12d\";\n}\n\ni.icon.eur:before {\n  content: \"\\f153\";\n}\n\ni.icon.euro:before {\n  content: \"\\f153\";\n}\n\ni.icon.eyedropper:before {\n  content: \"\\f1fb\";\n}\n\ni.icon.fa:before {\n  content: \"\\f2b4\";\n}\n\ni.icon.factory:before {\n  content: \"\\f275\";\n}\n\ni.icon.favorite:before {\n  content: \"\\f005\";\n}\n\ni.icon.feed:before {\n  content: \"\\f09e\";\n}\n\ni.icon.female.homosexual:before {\n  content: \"\\f226\";\n}\n\ni.icon.file.text:before {\n  content: \"\\f15c\";\n}\n\ni.icon.find:before {\n  content: \"\\f1e5\";\n}\n\ni.icon.first.aid:before {\n  content: \"\\f0fa\";\n}\n\ni.icon.five.hundred.pixels:before {\n  content: \"\\f26e\";\n}\n\ni.icon.fork:before {\n  content: \"\\f126\";\n}\n\ni.icon.game:before {\n  content: \"\\f11b\";\n}\n\ni.icon.gay:before {\n  content: \"\\f227\";\n}\n\ni.icon.gbp:before {\n  content: \"\\f154\";\n}\n\ni.icon.gittip:before {\n  content: \"\\f184\";\n}\n\ni.icon.google.plus.circle:before {\n  content: \"\\f2b3\";\n}\n\ni.icon.google.plus.official:before {\n  content: \"\\f2b3\";\n}\n\ni.icon.grab:before {\n  content: \"\\f255\";\n}\n\ni.icon.graduation:before {\n  content: \"\\f19d\";\n}\n\ni.icon.grid.layout:before {\n  content: \"\\f00a\";\n}\n\ni.icon.group:before {\n  content: \"\\f0c0\";\n}\n\ni.icon.h:before {\n  content: \"\\f0fd\";\n}\n\ni.icon.hand.victory:before {\n  content: \"\\f25b\";\n}\n\ni.icon.handicap:before {\n  content: \"\\f193\";\n}\n\ni.icon.hard.of.hearing:before {\n  content: \"\\f2a4\";\n}\n\ni.icon.header:before {\n  content: \"\\f1dc\";\n}\n\ni.icon.help.circle:before {\n  content: \"\\f059\";\n}\n\ni.icon.help:before {\n  content: \"\\f128\";\n}\n\ni.icon.heterosexual:before {\n  content: \"\\f228\";\n}\n\ni.icon.hide:before {\n  content: \"\\f070\";\n}\n\ni.icon.hotel:before {\n  content: \"\\f236\";\n}\n\ni.icon.hourglass.four:before {\n  content: \"\\f254\";\n}\n\ni.icon.hourglass.full:before {\n  content: \"\\f254\";\n}\n\ni.icon.hourglass.one:before {\n  content: \"\\f251\";\n}\n\ni.icon.hourglass.three:before {\n  content: \"\\f253\";\n}\n\ni.icon.hourglass.two:before {\n  content: \"\\f252\";\n}\n\ni.icon.idea:before {\n  content: \"\\f0eb\";\n}\n\ni.icon.ils:before {\n  content: \"\\f20b\";\n}\n\ni.icon.in-cart:before {\n  content: \"\\f218\";\n}\n\ni.icon.inr:before {\n  content: \"\\f156\";\n}\n\ni.icon.intergender:before {\n  content: \"\\f224\";\n}\n\ni.icon.intersex:before {\n  content: \"\\f224\";\n}\n\ni.icon.japan.credit.bureau.card:before {\n  content: \"\\f24b\";\n}\n\ni.icon.japan.credit.bureau:before {\n  content: \"\\f24b\";\n}\n\ni.icon.jcb:before {\n  content: \"\\f24b\";\n}\n\ni.icon.jpy:before {\n  content: \"\\f157\";\n}\n\ni.icon.krw:before {\n  content: \"\\f159\";\n}\n\ni.icon.lab:before {\n  content: \"\\f0c3\";\n}\n\ni.icon.law:before {\n  content: \"\\f24e\";\n}\n\ni.icon.legal:before {\n  content: \"\\f0e3\";\n}\n\ni.icon.lesbian:before {\n  content: \"\\f226\";\n}\n\ni.icon.lightning:before {\n  content: \"\\f0e7\";\n}\n\ni.icon.like:before {\n  content: \"\\f004\";\n}\n\ni.icon.line.graph:before {\n  content: \"\\f201\";\n}\n\ni.icon.linkedin.square:before {\n  content: \"\\f08c\";\n}\n\ni.icon.linkify:before {\n  content: \"\\f0c1\";\n}\n\ni.icon.lira:before {\n  content: \"\\f195\";\n}\n\ni.icon.list.layout:before {\n  content: \"\\f00b\";\n}\n\ni.icon.magnify:before {\n  content: \"\\f00e\";\n}\n\ni.icon.mail.forward:before {\n  content: \"\\f064\";\n}\n\ni.icon.mail.square:before {\n  content: \"\\f199\";\n}\n\ni.icon.mail:before {\n  content: \"\\f0e0\";\n}\n\ni.icon.male.homosexual:before {\n  content: \"\\f227\";\n}\n\ni.icon.man:before {\n  content: \"\\f222\";\n}\n\ni.icon.marker:before {\n  content: \"\\f041\";\n}\n\ni.icon.mars.alternate:before {\n  content: \"\\f229\";\n}\n\ni.icon.mars.horizontal:before {\n  content: \"\\f22b\";\n}\n\ni.icon.mars.vertical:before {\n  content: \"\\f22a\";\n}\n\ni.icon.mastercard.card:before {\n  content: \"\\f1f1\";\n}\n\ni.icon.mastercard:before {\n  content: \"\\f1f1\";\n}\n\ni.icon.microsoft.edge:before {\n  content: \"\\f282\";\n}\n\ni.icon.military:before {\n  content: \"\\f0fb\";\n}\n\ni.icon.ms.edge:before {\n  content: \"\\f282\";\n}\n\ni.icon.mute:before {\n  content: \"\\f131\";\n}\n\ni.icon.new.pied.piper:before {\n  content: \"\\f2ae\";\n}\n\ni.icon.non.binary.transgender:before {\n  content: \"\\f223\";\n}\n\ni.icon.numbered.list:before {\n  content: \"\\f0cb\";\n}\n\ni.icon.optinmonster:before {\n  content: \"\\f23c\";\n}\n\ni.icon.options:before {\n  content: \"\\f1de\";\n}\n\ni.icon.other.gender.horizontal:before {\n  content: \"\\f22b\";\n}\n\ni.icon.other.gender.vertical:before {\n  content: \"\\f22a\";\n}\n\ni.icon.other.gender:before {\n  content: \"\\f229\";\n}\n\ni.icon.payment:before {\n  content: \"\\f09d\";\n}\n\ni.icon.paypal.card:before {\n  content: \"\\f1f4\";\n}\n\ni.icon.pencil.square:before {\n  content: \"\\f14b\";\n}\n\ni.icon.photo:before {\n  content: \"\\f030\";\n}\n\ni.icon.picture:before {\n  content: \"\\f03e\";\n}\n\ni.icon.pie.chart:before {\n  content: \"\\f200\";\n}\n\ni.icon.pie.graph:before {\n  content: \"\\f200\";\n}\n\ni.icon.pied.piper.hat:before {\n  content: \"\\f2ae\";\n}\n\ni.icon.pin:before {\n  content: \"\\f08d\";\n}\n\ni.icon.plus.cart:before {\n  content: \"\\f217\";\n}\n\ni.icon.pocket:before {\n  content: \"\\f265\";\n}\n\ni.icon.point:before {\n  content: \"\\f041\";\n}\n\ni.icon.pointing.down:before {\n  content: \"\\f0a7\";\n}\n\ni.icon.pointing.left:before {\n  content: \"\\f0a5\";\n}\n\ni.icon.pointing.right:before {\n  content: \"\\f0a4\";\n}\n\ni.icon.pointing.up:before {\n  content: \"\\f0a6\";\n}\n\ni.icon.pound:before {\n  content: \"\\f154\";\n}\n\ni.icon.power.cord:before {\n  content: \"\\f1e6\";\n}\n\ni.icon.power:before {\n  content: \"\\f011\";\n}\n\ni.icon.privacy:before {\n  content: \"\\f084\";\n}\n\ni.icon.r.circle:before {\n  content: \"\\f25d\";\n}\n\ni.icon.rain:before {\n  content: \"\\f0e9\";\n}\n\ni.icon.record:before {\n  content: \"\\f03d\";\n}\n\ni.icon.refresh:before {\n  content: \"\\f021\";\n}\n\ni.icon.remove.circle:before {\n  content: \"\\f057\";\n}\n\ni.icon.remove.from.calendar:before {\n  content: \"\\f272\";\n}\n\ni.icon.remove.user:before {\n  content: \"\\f235\";\n}\n\ni.icon.remove:before {\n  content: \"\\f00d\";\n}\n\ni.icon.repeat:before {\n  content: \"\\f01e\";\n}\n\ni.icon.rmb:before {\n  content: \"\\f157\";\n}\n\ni.icon.rouble:before {\n  content: \"\\f158\";\n}\n\ni.icon.rub:before {\n  content: \"\\f158\";\n}\n\ni.icon.ruble:before {\n  content: \"\\f158\";\n}\n\ni.icon.rupee:before {\n  content: \"\\f156\";\n}\n\ni.icon.s15:before {\n  content: \"\\f2cd\";\n}\n\ni.icon.selected.radio:before {\n  content: \"\\f192\";\n}\n\ni.icon.send:before {\n  content: \"\\f1d8\";\n}\n\ni.icon.setting:before {\n  content: \"\\f013\";\n}\n\ni.icon.settings:before {\n  content: \"\\f085\";\n}\n\ni.icon.shekel:before {\n  content: \"\\f20b\";\n}\n\ni.icon.sheqel:before {\n  content: \"\\f20b\";\n}\n\ni.icon.shipping:before {\n  content: \"\\f0d1\";\n}\n\ni.icon.shop:before {\n  content: \"\\f07a\";\n}\n\ni.icon.shuffle:before {\n  content: \"\\f074\";\n}\n\ni.icon.shutdown:before {\n  content: \"\\f011\";\n}\n\ni.icon.sidebar:before {\n  content: \"\\f0c9\";\n}\n\ni.icon.signing:before {\n  content: \"\\f2a7\";\n}\n\ni.icon.signup:before {\n  content: \"\\f044\";\n}\n\ni.icon.sliders:before {\n  content: \"\\f1de\";\n}\n\ni.icon.soccer:before {\n  content: \"\\f1e3\";\n}\n\ni.icon.sort.alphabet.ascending:before {\n  content: \"\\f15d\";\n}\n\ni.icon.sort.alphabet.descending:before {\n  content: \"\\f15e\";\n}\n\ni.icon.sort.ascending:before {\n  content: \"\\f0de\";\n}\n\ni.icon.sort.content.ascending:before {\n  content: \"\\f160\";\n}\n\ni.icon.sort.content.descending:before {\n  content: \"\\f161\";\n}\n\ni.icon.sort.descending:before {\n  content: \"\\f0dd\";\n}\n\ni.icon.sort.numeric.ascending:before {\n  content: \"\\f162\";\n}\n\ni.icon.sort.numeric.descending:before {\n  content: \"\\f163\";\n}\n\ni.icon.sound:before {\n  content: \"\\f025\";\n}\n\ni.icon.spy:before {\n  content: \"\\f21b\";\n}\n\ni.icon.stripe.card:before {\n  content: \"\\f1f5\";\n}\n\ni.icon.student:before {\n  content: \"\\f19d\";\n}\n\ni.icon.talk:before {\n  content: \"\\f27a\";\n}\n\ni.icon.target:before {\n  content: \"\\f140\";\n}\n\ni.icon.teletype:before {\n  content: \"\\f1e4\";\n}\n\ni.icon.television:before {\n  content: \"\\f26c\";\n}\n\ni.icon.text.cursor:before {\n  content: \"\\f246\";\n}\n\ni.icon.text.telephone:before {\n  content: \"\\f1e4\";\n}\n\ni.icon.theme.isle:before {\n  content: \"\\f2b2\";\n}\n\ni.icon.theme:before {\n  content: \"\\f043\";\n}\n\ni.icon.thermometer:before {\n  content: \"\\f2c7\";\n}\n\ni.icon.thumb.tack:before {\n  content: \"\\f08d\";\n}\n\ni.icon.time:before {\n  content: \"\\f017\";\n}\n\ni.icon.tm:before {\n  content: \"\\f25c\";\n}\n\ni.icon.toggle.down:before {\n  content: \"\\f150\";\n}\n\ni.icon.toggle.left:before {\n  content: \"\\f191\";\n}\n\ni.icon.toggle.right:before {\n  content: \"\\f152\";\n}\n\ni.icon.toggle.up:before {\n  content: \"\\f151\";\n}\n\ni.icon.translate:before {\n  content: \"\\f1ab\";\n}\n\ni.icon.travel:before {\n  content: \"\\f0b1\";\n}\n\ni.icon.treatment:before {\n  content: \"\\f0f1\";\n}\n\ni.icon.triangle.down:before {\n  content: \"\\f0d7\";\n}\n\ni.icon.triangle.left:before {\n  content: \"\\f0d9\";\n}\n\ni.icon.triangle.right:before {\n  content: \"\\f0da\";\n}\n\ni.icon.triangle.up:before {\n  content: \"\\f0d8\";\n}\n\ni.icon.try:before {\n  content: \"\\f195\";\n}\n\ni.icon.unhide:before {\n  content: \"\\f06e\";\n}\n\ni.icon.unlinkify:before {\n  content: \"\\f127\";\n}\n\ni.icon.unmute:before {\n  content: \"\\f130\";\n}\n\ni.icon.usd:before {\n  content: \"\\f155\";\n}\n\ni.icon.user.cancel:before {\n  content: \"\\f235\";\n}\n\ni.icon.user.close:before {\n  content: \"\\f235\";\n}\n\ni.icon.user.delete:before {\n  content: \"\\f235\";\n}\n\ni.icon.user.x:before {\n  content: \"\\f235\";\n}\n\ni.icon.vcard:before {\n  content: \"\\f2bb\";\n}\n\ni.icon.video.camera:before {\n  content: \"\\f03d\";\n}\n\ni.icon.video.play:before {\n  content: \"\\f144\";\n}\n\ni.icon.visa.card:before {\n  content: \"\\f1f0\";\n}\n\ni.icon.visa:before {\n  content: \"\\f1f0\";\n}\n\ni.icon.volume.control.phone:before {\n  content: \"\\f2a0\";\n}\n\ni.icon.wait:before {\n  content: \"\\f017\";\n}\n\ni.icon.warning.circle:before {\n  content: \"\\f06a\";\n}\n\ni.icon.warning.sign:before {\n  content: \"\\f071\";\n}\n\ni.icon.warning:before {\n  content: \"\\f12a\";\n}\n\ni.icon.wechat:before {\n  content: \"\\f1d7\";\n}\n\ni.icon.wi-fi:before {\n  content: \"\\f1eb\";\n}\n\ni.icon.wikipedia:before {\n  content: \"\\f266\";\n}\n\ni.icon.winner:before {\n  content: \"\\f091\";\n}\n\ni.icon.wizard:before {\n  content: \"\\f0d0\";\n}\n\ni.icon.woman:before {\n  content: \"\\f221\";\n}\n\ni.icon.won:before {\n  content: \"\\f159\";\n}\n\ni.icon.wordpress.beginner:before {\n  content: \"\\f297\";\n}\n\ni.icon.wordpress.forms:before {\n  content: \"\\f298\";\n}\n\ni.icon.world:before {\n  content: \"\\f0ac\";\n}\n\ni.icon.write.square:before {\n  content: \"\\f14b\";\n}\n\ni.icon.x:before {\n  content: \"\\f00d\";\n}\n\ni.icon.yc:before {\n  content: \"\\f23b\";\n}\n\ni.icon.ycombinator:before {\n  content: \"\\f23b\";\n}\n\ni.icon.yen:before {\n  content: \"\\f157\";\n}\n\ni.icon.zip:before {\n  content: \"\\f187\";\n}\n\ni.icon.zoom-in:before {\n  content: \"\\f00e\";\n}\n\ni.icon.zoom-out:before {\n  content: \"\\f010\";\n}\n\ni.icon.zoom:before {\n  content: \"\\f00e\";\n}\n\ni.icon.bitbucket.square:before {\n  content: \"\\f171\";\n}\n\ni.icon.checkmark.box:before {\n  content: \"\\f14a\";\n}\n\ni.icon.circle.thin:before {\n  content: \"\\f111\";\n}\n\ni.icon.cloud.download:before {\n  content: \"\\f381\";\n}\n\ni.icon.cloud.upload:before {\n  content: \"\\f382\";\n}\n\ni.icon.compose:before {\n  content: \"\\f303\";\n}\n\ni.icon.conversation:before {\n  content: \"\\f086\";\n}\n\ni.icon.credit.card.alternative:before {\n  content: \"\\f09d\";\n}\n\ni.icon.currency:before {\n  content: \"\\f3d1\";\n}\n\ni.icon.dashboard:before {\n  content: \"\\f3fd\";\n}\n\ni.icon.diamond:before {\n  content: \"\\f3a5\";\n}\n\ni.icon.disk:before {\n  content: \"\\f0a0\";\n}\n\ni.icon.exchange:before {\n  content: \"\\f362\";\n}\n\ni.icon.external.share:before {\n  content: \"\\f14d\";\n}\n\ni.icon.external.square:before {\n  content: \"\\f360\";\n}\n\ni.icon.external:before {\n  content: \"\\f35d\";\n}\n\ni.icon.facebook.official:before {\n  content: \"\\f082\";\n}\n\ni.icon.food:before {\n  content: \"\\f2e7\";\n}\n\ni.icon.hourglass.zero:before {\n  content: \"\\f253\";\n}\n\ni.icon.level.down:before {\n  content: \"\\f3be\";\n}\n\ni.icon.level.up:before {\n  content: \"\\f3bf\";\n}\n\ni.icon.logout:before {\n  content: \"\\f2f5\";\n}\n\ni.icon.meanpath:before {\n  content: \"\\f0c8\";\n}\n\ni.icon.money:before {\n  content: \"\\f3d1\";\n}\n\ni.icon.move:before {\n  content: \"\\f0b2\";\n}\n\ni.icon.pencil:before {\n  content: \"\\f303\";\n}\n\ni.icon.protect:before {\n  content: \"\\f023\";\n}\n\ni.icon.radio:before {\n  content: \"\\f192\";\n}\n\ni.icon.remove.bookmark:before {\n  content: \"\\f02e\";\n}\n\ni.icon.resize.horizontal:before {\n  content: \"\\f337\";\n}\n\ni.icon.resize.vertical:before {\n  content: \"\\f338\";\n}\n\ni.icon.sign-in:before {\n  content: \"\\f2f6\";\n}\n\ni.icon.sign-out:before {\n  content: \"\\f2f5\";\n}\n\ni.icon.spoon:before {\n  content: \"\\f2e5\";\n}\n\ni.icon.star.half.empty:before {\n  content: \"\\f089\";\n}\n\ni.icon.star.half.full:before {\n  content: \"\\f089\";\n}\n\ni.icon.ticket:before {\n  content: \"\\f3ff\";\n}\n\ni.icon.times.rectangle:before {\n  content: \"\\f410\";\n}\n\ni.icon.write:before {\n  content: \"\\f303\";\n}\n\ni.icon.youtube.play:before {\n  content: \"\\f167\";\n}\n\n/*******************************\n        Outline Icons\n*******************************/\n\n/* Outline Icon */\n\n/* Load & Define Icon Font */\n\n@font-face {\n  font-family: \"outline-icons\";\n  src: url(\"./assets/fonts/outline-icons.eot\");\n  src: url(\"./assets/fonts/outline-icons.eot?#iefix\") format(\"embedded-opentype\"),\n    url(\"./assets/fonts/outline-icons.woff2\") format(\"woff2\"),\n    url(\"./assets/fonts/outline-icons.woff\") format(\"woff\"),\n    url(\"./assets/fonts/outline-icons.ttf\") format(\"truetype\"),\n    url(\"./assets/fonts/outline-icons.svg#icons\") format(\"svg\");\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-decoration: inherit;\n  text-transform: none;\n}\n\ni.icon.outline {\n  font-family: \"outline-icons\";\n}\n\n/* Icon Definitions */\n\ni.icon.address.book.outline:before {\n  content: \"\\f2b9\";\n}\n\ni.icon.address.card.outline:before {\n  content: \"\\f2bb\";\n}\n\ni.icon.arrow.alternate.circle.down.outline:before {\n  content: \"\\f358\";\n}\n\ni.icon.arrow.alternate.circle.left.outline:before {\n  content: \"\\f359\";\n}\n\ni.icon.arrow.alternate.circle.right.outline:before {\n  content: \"\\f35a\";\n}\n\ni.icon.arrow.alternate.circle.up.outline:before {\n  content: \"\\f35b\";\n}\n\ni.icon.bell.outline:before {\n  content: \"\\f0f3\";\n}\n\ni.icon.bell.slash.outline:before {\n  content: \"\\f1f6\";\n}\n\ni.icon.bookmark.outline:before {\n  content: \"\\f02e\";\n}\n\ni.icon.building.outline:before {\n  content: \"\\f1ad\";\n}\n\ni.icon.calendar.outline:before {\n  content: \"\\f133\";\n}\n\ni.icon.calendar.alternate.outline:before {\n  content: \"\\f073\";\n}\n\ni.icon.calendar.check.outline:before {\n  content: \"\\f274\";\n}\n\ni.icon.calendar.minus.outline:before {\n  content: \"\\f272\";\n}\n\ni.icon.calendar.plus.outline:before {\n  content: \"\\f271\";\n}\n\ni.icon.calendar.times.outline:before {\n  content: \"\\f273\";\n}\n\ni.icon.caret.square.down.outline:before {\n  content: \"\\f150\";\n}\n\ni.icon.caret.square.left.outline:before {\n  content: \"\\f191\";\n}\n\ni.icon.caret.square.right.outline:before {\n  content: \"\\f152\";\n}\n\ni.icon.caret.square.up.outline:before {\n  content: \"\\f151\";\n}\n\ni.icon.chart.bar.outline:before {\n  content: \"\\f080\";\n}\n\ni.icon.check.circle.outline:before {\n  content: \"\\f058\";\n}\n\ni.icon.check.square.outline:before {\n  content: \"\\f14a\";\n}\n\ni.icon.circle.outline:before {\n  content: \"\\f111\";\n}\n\ni.icon.clipboard.outline:before {\n  content: \"\\f328\";\n}\n\ni.icon.clock.outline:before {\n  content: \"\\f017\";\n}\n\ni.icon.clone.outline:before {\n  content: \"\\f24d\";\n}\n\ni.icon.closed.captioning.outline:before {\n  content: \"\\f20a\";\n}\n\ni.icon.comment.outline:before {\n  content: \"\\f075\";\n}\n\ni.icon.comment.alternate.outline:before {\n  content: \"\\f27a\";\n}\n\ni.icon.comments.outline:before {\n  content: \"\\f086\";\n}\n\ni.icon.compass.outline:before {\n  content: \"\\f14e\";\n}\n\ni.icon.copy.outline:before {\n  content: \"\\f0c5\";\n}\n\ni.icon.copyright.outline:before {\n  content: \"\\f1f9\";\n}\n\ni.icon.credit.card.outline:before {\n  content: \"\\f09d\";\n}\n\ni.icon.dot.circle.outline:before {\n  content: \"\\f192\";\n}\n\ni.icon.edit.outline:before {\n  content: \"\\f044\";\n}\n\ni.icon.envelope.outline:before {\n  content: \"\\f0e0\";\n}\n\ni.icon.envelope.open.outline:before {\n  content: \"\\f2b6\";\n}\n\ni.icon.eye.slash.outline:before {\n  content: \"\\f070\";\n}\n\ni.icon.file.outline:before {\n  content: \"\\f15b\";\n}\n\ni.icon.file.alternate.outline:before {\n  content: \"\\f15c\";\n}\n\ni.icon.file.archive.outline:before {\n  content: \"\\f1c6\";\n}\n\ni.icon.file.audio.outline:before {\n  content: \"\\f1c7\";\n}\n\ni.icon.file.code.outline:before {\n  content: \"\\f1c9\";\n}\n\ni.icon.file.excel.outline:before {\n  content: \"\\f1c3\";\n}\n\ni.icon.file.image.outline:before {\n  content: \"\\f1c5\";\n}\n\ni.icon.file.pdf.outline:before {\n  content: \"\\f1c1\";\n}\n\ni.icon.file.powerpoint.outline:before {\n  content: \"\\f1c4\";\n}\n\ni.icon.file.video.outline:before {\n  content: \"\\f1c8\";\n}\n\ni.icon.file.word.outline:before {\n  content: \"\\f1c2\";\n}\n\ni.icon.flag.outline:before {\n  content: \"\\f024\";\n}\n\ni.icon.folder.outline:before {\n  content: \"\\f07b\";\n}\n\ni.icon.folder.open.outline:before {\n  content: \"\\f07c\";\n}\n\ni.icon.frown.outline:before {\n  content: \"\\f119\";\n}\n\ni.icon.futbol.outline:before {\n  content: \"\\f1e3\";\n}\n\ni.icon.gem.outline:before {\n  content: \"\\f3a5\";\n}\n\ni.icon.hand.lizard.outline:before {\n  content: \"\\f258\";\n}\n\ni.icon.hand.paper.outline:before {\n  content: \"\\f256\";\n}\n\ni.icon.hand.peace.outline:before {\n  content: \"\\f25b\";\n}\n\ni.icon.hand.point.down.outline:before {\n  content: \"\\f0a7\";\n}\n\ni.icon.hand.point.left.outline:before {\n  content: \"\\f0a5\";\n}\n\ni.icon.hand.point.right.outline:before {\n  content: \"\\f0a4\";\n}\n\ni.icon.hand.point.up.outline:before {\n  content: \"\\f0a6\";\n}\n\ni.icon.hand.pointer.outline:before {\n  content: \"\\f25a\";\n}\n\ni.icon.hand.rock.outline:before {\n  content: \"\\f255\";\n}\n\ni.icon.hand.scissors.outline:before {\n  content: \"\\f257\";\n}\n\ni.icon.hand.spock.outline:before {\n  content: \"\\f259\";\n}\n\ni.icon.handshake.outline:before {\n  content: \"\\f2b5\";\n}\n\ni.icon.hdd.outline:before {\n  content: \"\\f0a0\";\n}\n\ni.icon.heart.outline:before {\n  content: \"\\f004\";\n}\n\ni.icon.hospital.outline:before {\n  content: \"\\f0f8\";\n}\n\ni.icon.hourglass.outline:before {\n  content: \"\\f254\";\n}\n\ni.icon.id.badge.outline:before {\n  content: \"\\f2c1\";\n}\n\ni.icon.id.card.outline:before {\n  content: \"\\f2c2\";\n}\n\ni.icon.image.outline:before {\n  content: \"\\f03e\";\n}\n\ni.icon.images.outline:before {\n  content: \"\\f302\";\n}\n\ni.icon.keyboard.outline:before {\n  content: \"\\f11c\";\n}\n\ni.icon.lemon.outline:before {\n  content: \"\\f094\";\n}\n\ni.icon.life.ring.outline:before {\n  content: \"\\f1cd\";\n}\n\ni.icon.lightbulb.outline:before {\n  content: \"\\f0eb\";\n}\n\ni.icon.list.alternate.outline:before {\n  content: \"\\f022\";\n}\n\ni.icon.map.outline:before {\n  content: \"\\f279\";\n}\n\ni.icon.meh.outline:before {\n  content: \"\\f11a\";\n}\n\ni.icon.minus.square.outline:before {\n  content: \"\\f146\";\n}\n\ni.icon.money.bill.alternate.outline:before {\n  content: \"\\f3d1\";\n}\n\ni.icon.moon.outline:before {\n  content: \"\\f186\";\n}\n\ni.icon.newspaper.outline:before {\n  content: \"\\f1ea\";\n}\n\ni.icon.object.group.outline:before {\n  content: \"\\f247\";\n}\n\ni.icon.object.ungroup.outline:before {\n  content: \"\\f248\";\n}\n\ni.icon.paper.plane.outline:before {\n  content: \"\\f1d8\";\n}\n\ni.icon.pause.circle.outline:before {\n  content: \"\\f28b\";\n}\n\ni.icon.play.circle.outline:before {\n  content: \"\\f144\";\n}\n\ni.icon.plus.square.outline:before {\n  content: \"\\f0fe\";\n}\n\ni.icon.question.circle.outline:before {\n  content: \"\\f059\";\n}\n\ni.icon.registered.outline:before {\n  content: \"\\f25d\";\n}\n\ni.icon.save.outline:before {\n  content: \"\\f0c7\";\n}\n\ni.icon.share.square.outline:before {\n  content: \"\\f14d\";\n}\n\ni.icon.smile.outline:before {\n  content: \"\\f118\";\n}\n\ni.icon.snowflake.outline:before {\n  content: \"\\f2dc\";\n}\n\ni.icon.square.outline:before {\n  content: \"\\f0c8\";\n}\n\ni.icon.star.outline:before {\n  content: \"\\f005\";\n}\n\ni.icon.star.half.outline:before {\n  content: \"\\f089\";\n}\n\ni.icon.sticky.note.outline:before {\n  content: \"\\f249\";\n}\n\ni.icon.stop.circle.outline:before {\n  content: \"\\f28d\";\n}\n\ni.icon.sun.outline:before {\n  content: \"\\f185\";\n}\n\ni.icon.thumbs.down.outline:before {\n  content: \"\\f165\";\n}\n\ni.icon.thumbs.up.outline:before {\n  content: \"\\f164\";\n}\n\ni.icon.times.circle.outline:before {\n  content: \"\\f057\";\n}\n\ni.icon.trash.alternate.outline:before {\n  content: \"\\f2ed\";\n}\n\ni.icon.user.outline:before {\n  content: \"\\f007\";\n}\n\ni.icon.user.circle.outline:before {\n  content: \"\\f2bd\";\n}\n\ni.icon.window.close.outline:before {\n  content: \"\\f410\";\n}\n\ni.icon.window.maximize.outline:before {\n  content: \"\\f2d0\";\n}\n\ni.icon.window.minimize.outline:before {\n  content: \"\\f2d1\";\n}\n\ni.icon.window.restore.outline:before {\n  content: \"\\f2d2\";\n}\n\n/* Outline Aliases */\n\ni.icon.disk.outline:before {\n  content: \"\\f0a0\";\n}\n\ni.icon.heart.empty,\ni.icon.star.empty {\n  font-family: \"outline-icons\";\n}\n\ni.icon.heart.empty:before {\n  content: \"\\f004\";\n}\n\ni.icon.star.empty:before {\n  content: \"\\f089\";\n}\n\n/*******************************\n          Brand Icons\n*******************************/\n\n/* Load & Define Brand Font */\n\n@font-face {\n  font-family: \"brand-icons\";\n  src: url(\"./assets/fonts/brand-icons.eot\");\n  src: url(\"./assets/fonts/brand-icons.eot?#iefix\") format(\"embedded-opentype\"),\n    url(\"./assets/fonts/brand-icons.woff2\") format(\"woff2\"),\n    url(\"./assets/fonts/brand-icons.woff\") format(\"woff\"),\n    url(\"./assets/fonts/brand-icons.ttf\") format(\"truetype\"),\n    url(\"./assets/fonts/brand-icons.svg#icons\") format(\"svg\");\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-decoration: inherit;\n  text-transform: none;\n}\n\n/* Brand Icon Font Family */\n\ni.icon.\\35 00px,\ni.icon.accessible.icon,\ni.icon.accusoft,\ni.icon.adn,\ni.icon.adversal,\ni.icon.affiliatetheme,\ni.icon.algolia,\ni.icon.amazon,\ni.icon.amazon.pay,\ni.icon.amilia,\ni.icon.android,\ni.icon.angellist,\ni.icon.angrycreative,\ni.icon.angular,\ni.icon.app.store,\ni.icon.app.store.ios,\ni.icon.apper,\ni.icon.apple,\ni.icon.apple.pay,\ni.icon.asymmetrik,\ni.icon.audible,\ni.icon.autoprefixer,\ni.icon.avianex,\ni.icon.aviato,\ni.icon.aws,\ni.icon.bandcamp,\ni.icon.behance,\ni.icon.behance.square,\ni.icon.bimobject,\ni.icon.bitbucket,\ni.icon.bitcoin,\ni.icon.bity,\ni.icon.black.tie,\ni.icon.blackberry,\ni.icon.blogger,\ni.icon.blogger.b,\ni.icon.bluetooth,\ni.icon.bluetooth.b,\ni.icon.btc,\ni.icon.buromobelexperte,\ni.icon.buysellads,\ni.icon.cc.amazon.pay,\ni.icon.cc.amex,\ni.icon.cc.apple.pay,\ni.icon.cc.diners.club,\ni.icon.cc.discover,\ni.icon.cc.jcb,\ni.icon.cc.mastercard,\ni.icon.cc.paypal,\ni.icon.cc.stripe,\ni.icon.cc.visa,\ni.icon.centercode,\ni.icon.chrome,\ni.icon.cloudscale,\ni.icon.cloudsmith,\ni.icon.cloudversify,\ni.icon.codepen,\ni.icon.codiepie,\ni.icon.connectdevelop,\ni.icon.contao,\ni.icon.cpanel,\ni.icon.creative.commons,\ni.icon.css3,\ni.icon.css3.alternate,\ni.icon.cuttlefish,\ni.icon.d.and.d,\ni.icon.dashcube,\ni.icon.delicious,\ni.icon.deploydog,\ni.icon.deskpro,\ni.icon.deviantart,\ni.icon.digg,\ni.icon.digital.ocean,\ni.icon.discord,\ni.icon.discourse,\ni.icon.dochub,\ni.icon.docker,\ni.icon.draft2digital,\ni.icon.dribbble,\ni.icon.dribbble.square,\ni.icon.dropbox,\ni.icon.drupal,\ni.icon.dyalog,\ni.icon.earlybirds,\ni.icon.edge,\ni.icon.elementor,\ni.icon.ember,\ni.icon.empire,\ni.icon.envira,\ni.icon.erlang,\ni.icon.ethereum,\ni.icon.etsy,\ni.icon.expeditedssl,\ni.icon.facebook,\ni.icon.facebook.f,\ni.icon.facebook.messenger,\ni.icon.facebook.square,\ni.icon.firefox,\ni.icon.first.order,\ni.icon.firstdraft,\ni.icon.flickr,\ni.icon.flipboard,\ni.icon.fly,\ni.icon.font.awesome,\ni.icon.font.awesome.alternate,\ni.icon.font.awesome.flag,\ni.icon.fonticons,\ni.icon.fonticons.fi,\ni.icon.fort.awesome,\ni.icon.fort.awesome.alternate,\ni.icon.forumbee,\ni.icon.foursquare,\ni.icon.free.code.camp,\ni.icon.freebsd,\ni.icon.get.pocket,\ni.icon.gg,\ni.icon.gg.circle,\ni.icon.git,\ni.icon.git.square,\ni.icon.github,\ni.icon.github.alternate,\ni.icon.github.square,\ni.icon.gitkraken,\ni.icon.gitlab,\ni.icon.gitter,\ni.icon.glide,\ni.icon.glide.g,\ni.icon.gofore,\ni.icon.goodreads,\ni.icon.goodreads.g,\ni.icon.google,\ni.icon.google.drive,\ni.icon.google.play,\ni.icon.google.plus,\ni.icon.google.plus.g,\ni.icon.google.plus.square,\ni.icon.google.wallet,\ni.icon.gratipay,\ni.icon.grav,\ni.icon.gripfire,\ni.icon.grunt,\ni.icon.gulp,\ni.icon.hacker.news,\ni.icon.hacker.news.square,\ni.icon.hips,\ni.icon.hire.a.helper,\ni.icon.hooli,\ni.icon.hotjar,\ni.icon.houzz,\ni.icon.html5,\ni.icon.hubspot,\ni.icon.imdb,\ni.icon.instagram,\ni.icon.internet.explorer,\ni.icon.ioxhost,\ni.icon.itunes,\ni.icon.itunes.note,\ni.icon.jenkins,\ni.icon.joget,\ni.icon.joomla,\ni.icon.js,\ni.icon.js.square,\ni.icon.jsfiddle,\ni.icon.keycdn,\ni.icon.kickstarter,\ni.icon.kickstarter.k,\ni.icon.korvue,\ni.icon.laravel,\ni.icon.lastfm,\ni.icon.lastfm.square,\ni.icon.leanpub,\ni.icon.less,\ni.icon.linechat,\ni.icon.linkedin,\ni.icon.linkedin.alternate,\ni.icon.linkedin.in,\ni.icon.linode,\ni.icon.linux,\ni.icon.lyft,\ni.icon.magento,\ni.icon.maxcdn,\ni.icon.medapps,\ni.icon.medium,\ni.icon.medium.m,\ni.icon.medrt,\ni.icon.meetup,\ni.icon.microsoft,\ni.icon.mix,\ni.icon.mixcloud,\ni.icon.mizuni,\ni.icon.modx,\ni.icon.monero,\ni.icon.napster,\ni.icon.nintendo.switch,\ni.icon.node,\ni.icon.node.js,\ni.icon.npm,\ni.icon.ns8,\ni.icon.nutritionix,\ni.icon.odnoklassniki,\ni.icon.odnoklassniki.square,\ni.icon.opencart,\ni.icon.openid,\ni.icon.opera,\ni.icon.optin.monster,\ni.icon.osi,\ni.icon.page4,\ni.icon.pagelines,\ni.icon.palfed,\ni.icon.patreon,\ni.icon.paypal,\ni.icon.periscope,\ni.icon.phabricator,\ni.icon.phoenix.framework,\ni.icon.php,\ni.icon.pied.piper,\ni.icon.pied.piper.alternate,\ni.icon.pied.piper.pp,\ni.icon.pinterest,\ni.icon.pinterest.p,\ni.icon.pinterest.square,\ni.icon.playstation,\ni.icon.product.hunt,\ni.icon.pushed,\ni.icon.python,\ni.icon.qq,\ni.icon.quinscape,\ni.icon.quora,\ni.icon.ravelry,\ni.icon.react,\ni.icon.rebel,\ni.icon.redriver,\ni.icon.reddit,\ni.icon.reddit.alien,\ni.icon.reddit.square,\ni.icon.rendact,\ni.icon.renren,\ni.icon.replyd,\ni.icon.resolving,\ni.icon.rocketchat,\ni.icon.rockrms,\ni.icon.safari,\ni.icon.sass,\ni.icon.schlix,\ni.icon.scribd,\ni.icon.searchengin,\ni.icon.sellcast,\ni.icon.sellsy,\ni.icon.servicestack,\ni.icon.shirtsinbulk,\ni.icon.simplybuilt,\ni.icon.sistrix,\ni.icon.skyatlas,\ni.icon.skype,\ni.icon.slack,\ni.icon.slack.hash,\ni.icon.slideshare,\ni.icon.snapchat,\ni.icon.snapchat.ghost,\ni.icon.snapchat.square,\ni.icon.soundcloud,\ni.icon.speakap,\ni.icon.spotify,\ni.icon.stack.exchange,\ni.icon.stack.overflow,\ni.icon.staylinked,\ni.icon.steam,\ni.icon.steam.square,\ni.icon.steam.symbol,\ni.icon.sticker.mule,\ni.icon.strava,\ni.icon.stripe,\ni.icon.stripe.s,\ni.icon.studiovinari,\ni.icon.stumbleupon,\ni.icon.stumbleupon.circle,\ni.icon.superpowers,\ni.icon.supple,\ni.icon.telegram,\ni.icon.telegram.plane,\ni.icon.tencent.weibo,\ni.icon.themeisle,\ni.icon.trello,\ni.icon.tripadvisor,\ni.icon.tumblr,\ni.icon.tumblr.square,\ni.icon.twitch,\ni.icon.twitter,\ni.icon.twitter.square,\ni.icon.typo3,\ni.icon.uber,\ni.icon.uikit,\ni.icon.uniregistry,\ni.icon.untappd,\ni.icon.usb,\ni.icon.ussunnah,\ni.icon.vaadin,\ni.icon.viacoin,\ni.icon.viadeo,\ni.icon.viadeo.square,\ni.icon.viber,\ni.icon.vimeo,\ni.icon.vimeo.square,\ni.icon.vimeo.v,\ni.icon.vine,\ni.icon.vk,\ni.icon.vnv,\ni.icon.vuejs,\ni.icon.wechat,\ni.icon.weibo,\ni.icon.weixin,\ni.icon.whatsapp,\ni.icon.whatsapp.square,\ni.icon.whmcs,\ni.icon.wikipedia.w,\ni.icon.windows,\ni.icon.wordpress,\ni.icon.wordpress.simple,\ni.icon.wpbeginner,\ni.icon.wpexplorer,\ni.icon.wpforms,\ni.icon.xbox,\ni.icon.xing,\ni.icon.xing.square,\ni.icon.y.combinator,\ni.icon.yahoo,\ni.icon.yandex,\ni.icon.yandex.international,\ni.icon.yelp,\ni.icon.yoast,\ni.icon.youtube,\ni.icon.youtube.square {\n  font-family: \"brand-icons\";\n}\n\n/* Brand Icons Ideally Would Be Defined Here */\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Image\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Image\n*******************************/\n\n.ui.image {\n  position: relative;\n  display: inline-block;\n  vertical-align: middle;\n  max-width: 100%;\n  background-color: transparent;\n}\n\nimg.ui.image {\n  display: block;\n}\n\n.ui.image svg,\n.ui.image img {\n  display: block;\n  max-width: 100%;\n  height: auto;\n}\n\n/*******************************\n            States\n*******************************/\n\n.ui.hidden.images,\n.ui.hidden.image {\n  display: none;\n}\n\n.ui.hidden.transition.images,\n.ui.hidden.transition.image {\n  display: block;\n  visibility: hidden;\n}\n\n.ui.images > .hidden.transition {\n  display: inline-block;\n  visibility: hidden;\n}\n\n.ui.disabled.images,\n.ui.disabled.image {\n  cursor: default;\n  opacity: 0.45;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Inline\n---------------*/\n\n.ui.inline.image,\n.ui.inline.image svg,\n.ui.inline.image img {\n  display: inline-block;\n}\n\n/*------------------\n  Vertical Aligned\n-------------------*/\n\n.ui.top.aligned.images .image,\n.ui.top.aligned.image,\n.ui.top.aligned.image svg,\n.ui.top.aligned.image img {\n  display: inline-block;\n  vertical-align: top;\n}\n\n.ui.middle.aligned.images .image,\n.ui.middle.aligned.image,\n.ui.middle.aligned.image svg,\n.ui.middle.aligned.image img {\n  display: inline-block;\n  vertical-align: middle;\n}\n\n.ui.bottom.aligned.images .image,\n.ui.bottom.aligned.image,\n.ui.bottom.aligned.image svg,\n.ui.bottom.aligned.image img {\n  display: inline-block;\n  vertical-align: bottom;\n}\n\n/*--------------\n    Rounded\n---------------*/\n\n.ui.rounded.images .image,\n.ui.rounded.image,\n.ui.rounded.images .image > *,\n.ui.rounded.image > * {\n  border-radius: 0.3125em;\n}\n\n/*--------------\n    Bordered\n---------------*/\n\n.ui.bordered.images .image,\n.ui.bordered.images img,\n.ui.bordered.images svg,\n.ui.bordered.image img,\n.ui.bordered.image svg,\nimg.ui.bordered.image {\n  border: 1px solid rgba(0, 0, 0, 0.1);\n}\n\n/*--------------\n    Circular\n---------------*/\n\n.ui.circular.images,\n.ui.circular.image {\n  overflow: hidden;\n}\n\n.ui.circular.images .image,\n.ui.circular.image,\n.ui.circular.images .image > *,\n.ui.circular.image > * {\n  border-radius: 500rem;\n}\n\n/*--------------\n    Fluid\n---------------*/\n\n.ui.fluid.images,\n.ui.fluid.image,\n.ui.fluid.images img,\n.ui.fluid.images svg,\n.ui.fluid.image svg,\n.ui.fluid.image img {\n  display: block;\n  width: 100%;\n  height: auto;\n}\n\n/*--------------\n    Avatar\n---------------*/\n\n.ui.avatar.images .image,\n.ui.avatar.images img,\n.ui.avatar.images svg,\n.ui.avatar.image img,\n.ui.avatar.image svg,\n.ui.avatar.image {\n  margin-right: 0.25em;\n  display: inline-block;\n  width: 2em;\n  height: 2em;\n  border-radius: 500rem;\n}\n\n/*-------------------\n      Spaced\n--------------------*/\n\n.ui.spaced.image {\n  display: inline-block !important;\n  margin-left: 0.5em;\n  margin-right: 0.5em;\n}\n\n.ui[class*=\"left spaced\"].image {\n  margin-left: 0.5em;\n  margin-right: 0em;\n}\n\n.ui[class*=\"right spaced\"].image {\n  margin-left: 0em;\n  margin-right: 0.5em;\n}\n\n/*-------------------\n      Floated\n--------------------*/\n\n.ui.floated.image,\n.ui.floated.images {\n  float: left;\n  margin-right: 1em;\n  margin-bottom: 1em;\n}\n\n.ui.right.floated.images,\n.ui.right.floated.image {\n  float: right;\n  margin-right: 0em;\n  margin-bottom: 1em;\n  margin-left: 1em;\n}\n\n.ui.floated.images:last-child,\n.ui.floated.image:last-child {\n  margin-bottom: 0em;\n}\n\n.ui.centered.images,\n.ui.centered.image {\n  margin-left: auto;\n  margin-right: auto;\n}\n\n/*--------------\n    Sizes\n---------------*/\n\n.ui.mini.images .image,\n.ui.mini.images img,\n.ui.mini.images svg,\n.ui.mini.image {\n  width: 35px;\n  height: auto;\n  font-size: 0.78571429rem;\n}\n\n.ui.tiny.images .image,\n.ui.tiny.images img,\n.ui.tiny.images svg,\n.ui.tiny.image {\n  width: 80px;\n  height: auto;\n  font-size: 0.85714286rem;\n}\n\n.ui.small.images .image,\n.ui.small.images img,\n.ui.small.images svg,\n.ui.small.image {\n  width: 150px;\n  height: auto;\n  font-size: 0.92857143rem;\n}\n\n.ui.medium.images .image,\n.ui.medium.images img,\n.ui.medium.images svg,\n.ui.medium.image {\n  width: 300px;\n  height: auto;\n  font-size: 1rem;\n}\n\n.ui.large.images .image,\n.ui.large.images img,\n.ui.large.images svg,\n.ui.large.image {\n  width: 450px;\n  height: auto;\n  font-size: 1.14285714rem;\n}\n\n.ui.big.images .image,\n.ui.big.images img,\n.ui.big.images svg,\n.ui.big.image {\n  width: 600px;\n  height: auto;\n  font-size: 1.28571429rem;\n}\n\n.ui.huge.images .image,\n.ui.huge.images img,\n.ui.huge.images svg,\n.ui.huge.image {\n  width: 800px;\n  height: auto;\n  font-size: 1.42857143rem;\n}\n\n.ui.massive.images .image,\n.ui.massive.images img,\n.ui.massive.images svg,\n.ui.massive.image {\n  width: 960px;\n  height: auto;\n  font-size: 1.71428571rem;\n}\n\n/*******************************\n              Groups\n*******************************/\n\n.ui.images {\n  font-size: 0em;\n  margin: 0em -0.25rem 0rem;\n}\n\n.ui.images .image,\n.ui.images > img,\n.ui.images > svg {\n  display: inline-block;\n  margin: 0em 0.25rem 0.5rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Input\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n          Standard\n*******************************/\n\n/*--------------------\n        Inputs\n---------------------*/\n\n.ui.input {\n  position: relative;\n  font-weight: normal;\n  font-style: normal;\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.input > input {\n  margin: 0em;\n  max-width: 100%;\n  -webkit-box-flex: 1;\n  -ms-flex: 1 0 auto;\n  flex: 1 0 auto;\n  outline: none;\n  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);\n  text-align: left;\n  line-height: 1.21428571em;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  padding: 0.67857143em 1em;\n  background: #ffffff;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  color: rgba(0, 0, 0, 0.87);\n  border-radius: 0.28571429rem;\n  -webkit-transition: border-color 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: border-color 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: box-shadow 0.1s ease, border-color 0.1s ease;\n  transition: box-shadow 0.1s ease, border-color 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/*--------------------\n      Placeholder\n---------------------*/\n\n/* browsers require these rules separate */\n\n.ui.input > input::-webkit-input-placeholder {\n  color: rgba(191, 191, 191, 0.87);\n}\n\n.ui.input > input::-moz-placeholder {\n  color: rgba(191, 191, 191, 0.87);\n}\n\n.ui.input > input:-ms-input-placeholder {\n  color: rgba(191, 191, 191, 0.87);\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------------\n        Disabled\n---------------------*/\n\n.ui.disabled.input,\n.ui.input:not(.disabled) input[disabled] {\n  opacity: 0.45;\n}\n\n.ui.disabled.input > input,\n.ui.input:not(.disabled) input[disabled] {\n  pointer-events: none;\n}\n\n/*--------------------\n        Active\n---------------------*/\n\n.ui.input > input:active,\n.ui.input.down input {\n  border-color: rgba(0, 0, 0, 0.3);\n  background: #fafafa;\n  color: rgba(0, 0, 0, 0.87);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/*--------------------\n      Loading\n---------------------*/\n\n.ui.loading.loading.input > i.icon:before {\n  position: absolute;\n  content: \"\";\n  top: 50%;\n  left: 50%;\n  margin: -0.64285714em 0em 0em -0.64285714em;\n  width: 1.28571429em;\n  height: 1.28571429em;\n  border-radius: 500rem;\n  border: 0.2em solid rgba(0, 0, 0, 0.1);\n}\n\n.ui.loading.loading.input > i.icon:after {\n  position: absolute;\n  content: \"\";\n  top: 50%;\n  left: 50%;\n  margin: -0.64285714em 0em 0em -0.64285714em;\n  width: 1.28571429em;\n  height: 1.28571429em;\n  -webkit-animation: button-spin 0.6s linear;\n  animation: button-spin 0.6s linear;\n  -webkit-animation-iteration-count: infinite;\n  animation-iteration-count: infinite;\n  border-radius: 500rem;\n  border-color: #767676 transparent transparent;\n  border-style: solid;\n  border-width: 0.2em;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent;\n  box-shadow: 0px 0px 0px 1px transparent;\n}\n\n/*--------------------\n        Focus\n---------------------*/\n\n.ui.input.focus > input,\n.ui.input > input:focus {\n  border-color: #85b7d9;\n  background: #ffffff;\n  color: rgba(0, 0, 0, 0.8);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.input.focus > input::-webkit-input-placeholder,\n.ui.input > input:focus::-webkit-input-placeholder {\n  color: rgba(115, 115, 115, 0.87);\n}\n\n.ui.input.focus > input::-moz-placeholder,\n.ui.input > input:focus::-moz-placeholder {\n  color: rgba(115, 115, 115, 0.87);\n}\n\n.ui.input.focus > input:-ms-input-placeholder,\n.ui.input > input:focus:-ms-input-placeholder {\n  color: rgba(115, 115, 115, 0.87);\n}\n\n/*--------------------\n        Error\n---------------------*/\n\n.ui.input.error > input {\n  background-color: #fff6f6;\n  border-color: #e0b4b4;\n  color: #9f3a38;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Error Placeholder */\n\n.ui.input.error > input::-webkit-input-placeholder {\n  color: #e7bdbc;\n}\n\n.ui.input.error > input::-moz-placeholder {\n  color: #e7bdbc;\n}\n\n.ui.input.error > input:-ms-input-placeholder {\n  color: #e7bdbc !important;\n}\n\n/* Focused Error Placeholder */\n\n.ui.input.error > input:focus::-webkit-input-placeholder {\n  color: #da9796;\n}\n\n.ui.input.error > input:focus::-moz-placeholder {\n  color: #da9796;\n}\n\n.ui.input.error > input:focus:-ms-input-placeholder {\n  color: #da9796 !important;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------------\n      Transparent\n---------------------*/\n\n.ui.transparent.input > input {\n  border-color: transparent !important;\n  background-color: transparent !important;\n  padding: 0em !important;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  border-radius: 0px !important;\n}\n\n/* Transparent Icon */\n\n.ui.transparent.icon.input > i.icon {\n  width: 1.1em;\n}\n\n.ui.transparent.icon.input > input {\n  padding-left: 0em !important;\n  padding-right: 2em !important;\n}\n\n.ui.transparent[class*=\"left icon\"].input > input {\n  padding-left: 2em !important;\n  padding-right: 0em !important;\n}\n\n/* Transparent Inverted */\n\n.ui.transparent.inverted.input {\n  color: #ffffff;\n}\n\n.ui.transparent.inverted.input > input {\n  color: inherit;\n}\n\n.ui.transparent.inverted.input > input::-webkit-input-placeholder {\n  color: rgba(255, 255, 255, 0.5);\n}\n\n.ui.transparent.inverted.input > input::-moz-placeholder {\n  color: rgba(255, 255, 255, 0.5);\n}\n\n.ui.transparent.inverted.input > input:-ms-input-placeholder {\n  color: rgba(255, 255, 255, 0.5);\n}\n\n/*--------------------\n        Icon\n---------------------*/\n\n.ui.icon.input > i.icon {\n  cursor: default;\n  position: absolute;\n  line-height: 1;\n  text-align: center;\n  top: 0px;\n  right: 0px;\n  margin: 0em;\n  height: 100%;\n  width: 2.67142857em;\n  opacity: 0.5;\n  border-radius: 0em 0.28571429rem 0.28571429rem 0em;\n  -webkit-transition: opacity 0.3s ease;\n  transition: opacity 0.3s ease;\n}\n\n.ui.icon.input > i.icon:not(.link) {\n  pointer-events: none;\n}\n\n.ui.icon.input > input {\n  padding-right: 2.67142857em !important;\n}\n\n.ui.icon.input > i.icon:before,\n.ui.icon.input > i.icon:after {\n  left: 0;\n  position: absolute;\n  text-align: center;\n  top: 50%;\n  width: 100%;\n  margin-top: -0.5em;\n}\n\n.ui.icon.input > i.link.icon {\n  cursor: pointer;\n}\n\n.ui.icon.input > i.circular.icon {\n  top: 0.35em;\n  right: 0.5em;\n}\n\n/* Left Icon Input */\n\n.ui[class*=\"left icon\"].input > i.icon {\n  right: auto;\n  left: 1px;\n  border-radius: 0.28571429rem 0em 0em 0.28571429rem;\n}\n\n.ui[class*=\"left icon\"].input > i.circular.icon {\n  right: auto;\n  left: 0.5em;\n}\n\n.ui[class*=\"left icon\"].input > input {\n  padding-left: 2.67142857em !important;\n  padding-right: 1em !important;\n}\n\n/* Focus */\n\n.ui.icon.input > input:focus ~ i.icon {\n  opacity: 1;\n}\n\n/*--------------------\n        Labeled\n---------------------*/\n\n/* Adjacent Label */\n\n.ui.labeled.input > .label {\n  -webkit-box-flex: 0;\n  -ms-flex: 0 0 auto;\n  flex: 0 0 auto;\n  margin: 0;\n  font-size: 1em;\n}\n\n.ui.labeled.input > .label:not(.corner) {\n  padding-top: 0.78571429em;\n  padding-bottom: 0.78571429em;\n}\n\n/* Regular Label on Left */\n\n.ui.labeled.input:not([class*=\"corner labeled\"]) .label:first-child {\n  border-top-right-radius: 0px;\n  border-bottom-right-radius: 0px;\n}\n\n.ui.labeled.input:not([class*=\"corner labeled\"]) .label:first-child + input {\n  border-top-left-radius: 0px;\n  border-bottom-left-radius: 0px;\n  border-left-color: transparent;\n}\n\n.ui.labeled.input:not([class*=\"corner labeled\"]) .label:first-child + input:focus {\n  border-left-color: #85b7d9;\n}\n\n/* Regular Label on Right */\n\n.ui[class*=\"right labeled\"].input > input {\n  border-top-right-radius: 0px !important;\n  border-bottom-right-radius: 0px !important;\n  border-right-color: transparent !important;\n}\n\n.ui[class*=\"right labeled\"].input > input + .label {\n  border-top-left-radius: 0px;\n  border-bottom-left-radius: 0px;\n}\n\n.ui[class*=\"right labeled\"].input > input:focus {\n  border-right-color: #85b7d9 !important;\n}\n\n/* Corner Label */\n\n.ui.labeled.input .corner.label {\n  top: 1px;\n  right: 1px;\n  font-size: 0.64285714em;\n  border-radius: 0em 0.28571429rem 0em 0em;\n}\n\n/* Spacing with corner label */\n\n.ui[class*=\"corner labeled\"]:not([class*=\"left corner labeled\"]).labeled.input > input {\n  padding-right: 2.5em !important;\n}\n\n.ui[class*=\"corner labeled\"].icon.input:not([class*=\"left corner labeled\"]) > input {\n  padding-right: 3.25em !important;\n}\n\n.ui[class*=\"corner labeled\"].icon.input:not([class*=\"left corner labeled\"]) > .icon {\n  margin-right: 1.25em;\n}\n\n/* Left Labeled */\n\n.ui[class*=\"left corner labeled\"].labeled.input > input {\n  padding-left: 2.5em !important;\n}\n\n.ui[class*=\"left corner labeled\"].icon.input > input {\n  padding-left: 3.25em !important;\n}\n\n.ui[class*=\"left corner labeled\"].icon.input > .icon {\n  margin-left: 1.25em;\n}\n\n/* Corner Label Position  */\n\n.ui.input > .ui.corner.label {\n  top: 1px;\n  right: 1px;\n}\n\n.ui.input > .ui.left.corner.label {\n  right: auto;\n  left: 1px;\n}\n\n/*--------------------\n        Action\n---------------------*/\n\n.ui.action.input > .button,\n.ui.action.input > .buttons {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n  -webkit-box-flex: 0;\n  -ms-flex: 0 0 auto;\n  flex: 0 0 auto;\n}\n\n.ui.action.input > .button,\n.ui.action.input > .buttons > .button {\n  padding-top: 0.78571429em;\n  padding-bottom: 0.78571429em;\n  margin: 0;\n}\n\n/* Button on Right */\n\n.ui.action.input:not([class*=\"left action\"]) > input {\n  border-top-right-radius: 0px !important;\n  border-bottom-right-radius: 0px !important;\n  border-right-color: transparent !important;\n}\n\n.ui.action.input:not([class*=\"left action\"]) > .dropdown:not(:first-child),\n.ui.action.input:not([class*=\"left action\"]) > .button:not(:first-child),\n.ui.action.input:not([class*=\"left action\"]) > .buttons:not(:first-child) > .button {\n  border-radius: 0px;\n}\n\n.ui.action.input:not([class*=\"left action\"]) > .dropdown:last-child,\n.ui.action.input:not([class*=\"left action\"]) > .button:last-child,\n.ui.action.input:not([class*=\"left action\"]) > .buttons:last-child > .button {\n  border-radius: 0px 0.28571429rem 0.28571429rem 0px;\n}\n\n/* Input Focus */\n\n.ui.action.input:not([class*=\"left action\"]) > input:focus {\n  border-right-color: #85b7d9 !important;\n}\n\n/* Button on Left */\n\n.ui[class*=\"left action\"].input > input {\n  border-top-left-radius: 0px !important;\n  border-bottom-left-radius: 0px !important;\n  border-left-color: transparent !important;\n}\n\n.ui[class*=\"left action\"].input > .dropdown,\n.ui[class*=\"left action\"].input > .button,\n.ui[class*=\"left action\"].input > .buttons > .button {\n  border-radius: 0px;\n}\n\n.ui[class*=\"left action\"].input > .dropdown:first-child,\n.ui[class*=\"left action\"].input > .button:first-child,\n.ui[class*=\"left action\"].input > .buttons:first-child > .button {\n  border-radius: 0.28571429rem 0px 0px 0.28571429rem;\n}\n\n/* Input Focus */\n\n.ui[class*=\"left action\"].input > input:focus {\n  border-left-color: #85b7d9 !important;\n}\n\n/*--------------------\n      Inverted\n---------------------*/\n\n/* Standard */\n\n.ui.inverted.input > input {\n  border: none;\n}\n\n/*--------------------\n        Fluid\n---------------------*/\n\n.ui.fluid.input {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n}\n\n.ui.fluid.input > input {\n  width: 0px !important;\n}\n\n/*--------------------\n        Size\n---------------------*/\n\n.ui.mini.input {\n  font-size: 0.78571429em;\n}\n\n.ui.small.input {\n  font-size: 0.92857143em;\n}\n\n.ui.input {\n  font-size: 1em;\n}\n\n.ui.large.input {\n  font-size: 1.14285714em;\n}\n\n.ui.big.input {\n  font-size: 1.28571429em;\n}\n\n.ui.huge.input {\n  font-size: 1.42857143em;\n}\n\n.ui.massive.input {\n  font-size: 1.71428571em;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Label\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Label\n*******************************/\n\n.ui.label {\n  display: inline-block;\n  line-height: 1;\n  vertical-align: baseline;\n  margin: 0em 0.14285714em;\n  background-color: #e8e8e8;\n  background-image: none;\n  padding: 0.5833em 0.833em;\n  color: rgba(0, 0, 0, 0.6);\n  text-transform: none;\n  font-weight: bold;\n  border: 0px solid transparent;\n  border-radius: 0.28571429rem;\n  -webkit-transition: background 0.1s ease;\n  transition: background 0.1s ease;\n}\n\n.ui.label:first-child {\n  margin-left: 0em;\n}\n\n.ui.label:last-child {\n  margin-right: 0em;\n}\n\n/* Link */\n\na.ui.label {\n  cursor: pointer;\n}\n\n/* Inside Link */\n\n.ui.label > a {\n  cursor: pointer;\n  color: inherit;\n  opacity: 0.5;\n  -webkit-transition: 0.1s opacity ease;\n  transition: 0.1s opacity ease;\n}\n\n.ui.label > a:hover {\n  opacity: 1;\n}\n\n/* Image */\n\n.ui.label > img {\n  width: auto !important;\n  vertical-align: middle;\n  height: 2.1666em !important;\n}\n\n/* Icon */\n\n.ui.label > .icon {\n  width: auto;\n  margin: 0em 0.75em 0em 0em;\n}\n\n/* Detail */\n\n.ui.label > .detail {\n  display: inline-block;\n  vertical-align: top;\n  font-weight: bold;\n  margin-left: 1em;\n  opacity: 0.8;\n}\n\n.ui.label > .detail .icon {\n  margin: 0em 0.25em 0em 0em;\n}\n\n/* Removable label */\n\n.ui.label > .close.icon,\n.ui.label > .delete.icon {\n  cursor: pointer;\n  margin-right: 0em;\n  margin-left: 0.5em;\n  font-size: 0.92857143em;\n  opacity: 0.5;\n  -webkit-transition: background 0.1s ease;\n  transition: background 0.1s ease;\n}\n\n.ui.label > .delete.icon:hover {\n  opacity: 1;\n}\n\n/*-------------------\n      Group\n--------------------*/\n\n.ui.labels > .label {\n  margin: 0em 0.5em 0.5em 0em;\n}\n\n/*-------------------\n      Coupling\n--------------------*/\n\n.ui.header > .ui.label {\n  margin-top: -0.29165em;\n}\n\n/* Remove border radius on attached segment */\n\n.ui.attached.segment > .ui.top.left.attached.label,\n.ui.bottom.attached.segment > .ui.top.left.attached.label {\n  border-top-left-radius: 0;\n}\n\n.ui.attached.segment > .ui.top.right.attached.label,\n.ui.bottom.attached.segment > .ui.top.right.attached.label {\n  border-top-right-radius: 0;\n}\n\n.ui.top.attached.segment > .ui.bottom.left.attached.label {\n  border-bottom-left-radius: 0;\n}\n\n.ui.top.attached.segment > .ui.bottom.right.attached.label {\n  border-bottom-right-radius: 0;\n}\n\n/* Padding on next content after a label */\n\n.ui.top.attached.label:first-child + :not(.attached),\n.ui.top.attached.label + [class*=\"right floated\"] + * {\n  margin-top: 2rem !important;\n}\n\n.ui.bottom.attached.label:first-child ~ :last-child:not(.attached) {\n  margin-top: 0em;\n  margin-bottom: 2rem !important;\n}\n\n/*******************************\n            Types\n*******************************/\n\n.ui.image.label {\n  width: auto !important;\n  margin-top: 0em;\n  margin-bottom: 0em;\n  max-width: 9999px;\n  vertical-align: baseline;\n  text-transform: none;\n  background: #e8e8e8;\n  padding: 0.5833em 0.833em 0.5833em 0.5em;\n  border-radius: 0.28571429rem;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.image.label img {\n  display: inline-block;\n  vertical-align: top;\n  height: 2.1666em;\n  margin: -0.5833em 0.5em -0.5833em -0.5em;\n  border-radius: 0.28571429rem 0em 0em 0.28571429rem;\n}\n\n.ui.image.label .detail {\n  background: rgba(0, 0, 0, 0.1);\n  margin: -0.5833em -0.833em -0.5833em 0.5em;\n  padding: 0.5833em 0.833em;\n  border-radius: 0em 0.28571429rem 0.28571429rem 0em;\n}\n\n/*-------------------\n        Tag\n--------------------*/\n\n.ui.tag.labels .label,\n.ui.tag.label {\n  margin-left: 1em;\n  position: relative;\n  padding-left: 1.5em;\n  padding-right: 1.5em;\n  border-radius: 0em 0.28571429rem 0.28571429rem 0em;\n  -webkit-transition: none;\n  transition: none;\n}\n\n.ui.tag.labels .label:before,\n.ui.tag.label:before {\n  position: absolute;\n  -webkit-transform: translateY(-50%) translateX(50%) rotate(-45deg);\n  transform: translateY(-50%) translateX(50%) rotate(-45deg);\n  top: 50%;\n  right: 100%;\n  content: \"\";\n  background-color: inherit;\n  background-image: none;\n  width: 1.56em;\n  height: 1.56em;\n  -webkit-transition: none;\n  transition: none;\n}\n\n.ui.tag.labels .label:after,\n.ui.tag.label:after {\n  position: absolute;\n  content: \"\";\n  top: 50%;\n  left: -0.25em;\n  margin-top: -0.25em;\n  background-color: #ffffff !important;\n  width: 0.5em;\n  height: 0.5em;\n  -webkit-box-shadow: 0 -1px 1px 0 rgba(0, 0, 0, 0.3);\n  box-shadow: 0 -1px 1px 0 rgba(0, 0, 0, 0.3);\n  border-radius: 500rem;\n}\n\n/*-------------------\n    Corner Label\n--------------------*/\n\n.ui.corner.label {\n  position: absolute;\n  top: 0em;\n  right: 0em;\n  margin: 0em;\n  padding: 0em;\n  text-align: center;\n  border-color: #e8e8e8;\n  width: 4em;\n  height: 4em;\n  z-index: 1;\n  -webkit-transition: border-color 0.1s ease;\n  transition: border-color 0.1s ease;\n}\n\n/* Icon Label */\n\n.ui.corner.label {\n  background-color: transparent !important;\n}\n\n.ui.corner.label:after {\n  position: absolute;\n  content: \"\";\n  right: 0em;\n  top: 0em;\n  z-index: -1;\n  width: 0em;\n  height: 0em;\n  background-color: transparent !important;\n  border-top: 0em solid transparent;\n  border-right: 4em solid transparent;\n  border-bottom: 4em solid transparent;\n  border-left: 0em solid transparent;\n  border-right-color: inherit;\n  -webkit-transition: border-color 0.1s ease;\n  transition: border-color 0.1s ease;\n}\n\n.ui.corner.label .icon {\n  cursor: default;\n  position: relative;\n  top: 0.64285714em;\n  left: 0.78571429em;\n  font-size: 1.14285714em;\n  margin: 0em;\n}\n\n/* Left Corner */\n\n.ui.left.corner.label,\n.ui.left.corner.label:after {\n  right: auto;\n  left: 0em;\n}\n\n.ui.left.corner.label:after {\n  border-top: 4em solid transparent;\n  border-right: 4em solid transparent;\n  border-bottom: 0em solid transparent;\n  border-left: 0em solid transparent;\n  border-top-color: inherit;\n}\n\n.ui.left.corner.label .icon {\n  left: -0.78571429em;\n}\n\n/* Segment */\n\n.ui.segment > .ui.corner.label {\n  top: -1px;\n  right: -1px;\n}\n\n.ui.segment > .ui.left.corner.label {\n  right: auto;\n  left: -1px;\n}\n\n/*-------------------\n      Ribbon\n--------------------*/\n\n.ui.ribbon.label {\n  position: relative;\n  margin: 0em;\n  min-width: -webkit-max-content;\n  min-width: -moz-max-content;\n  min-width: max-content;\n  border-radius: 0em 0.28571429rem 0.28571429rem 0em;\n  border-color: rgba(0, 0, 0, 0.15);\n}\n\n.ui.ribbon.label:after {\n  position: absolute;\n  content: \"\";\n  top: 100%;\n  left: 0%;\n  background-color: transparent !important;\n  border-style: solid;\n  border-width: 0em 1.2em 1.2em 0em;\n  border-color: transparent;\n  border-right-color: inherit;\n  width: 0em;\n  height: 0em;\n}\n\n/* Positioning */\n\n.ui.ribbon.label {\n  left: calc(-1rem - 1.2em);\n  margin-right: -1.2em;\n  padding-left: calc(1rem + 1.2em);\n  padding-right: 1.2em;\n}\n\n.ui[class*=\"right ribbon\"].label {\n  left: calc(100% + 1rem + 1.2em);\n  padding-left: 1.2em;\n  padding-right: calc(1rem + 1.2em);\n}\n\n/* Right Ribbon */\n\n.ui[class*=\"right ribbon\"].label {\n  text-align: left;\n  -webkit-transform: translateX(-100%);\n  transform: translateX(-100%);\n  border-radius: 0.28571429rem 0em 0em 0.28571429rem;\n}\n\n.ui[class*=\"right ribbon\"].label:after {\n  left: auto;\n  right: 0%;\n  border-style: solid;\n  border-width: 1.2em 1.2em 0em 0em;\n  border-color: transparent;\n  border-top-color: inherit;\n}\n\n/* Inside Table */\n\n.ui.image > .ribbon.label,\n.ui.card .image > .ribbon.label {\n  position: absolute;\n  top: 1rem;\n}\n\n.ui.card .image > .ui.ribbon.label,\n.ui.image > .ui.ribbon.label {\n  left: calc(0.05rem - 1.2em);\n}\n\n.ui.card .image > .ui[class*=\"right ribbon\"].label,\n.ui.image > .ui[class*=\"right ribbon\"].label {\n  left: calc(100% + -0.05rem + 1.2em);\n  padding-left: 0.833em;\n}\n\n/* Inside Table */\n\n.ui.table td > .ui.ribbon.label {\n  left: calc(-0.78571429em - 1.2em);\n}\n\n.ui.table td > .ui[class*=\"right ribbon\"].label {\n  left: calc(100% + 0.78571429em + 1.2em);\n  padding-left: 0.833em;\n}\n\n/*-------------------\n      Attached\n--------------------*/\n\n.ui[class*=\"top attached\"].label,\n.ui.attached.label {\n  width: 100%;\n  position: absolute;\n  margin: 0em;\n  top: 0em;\n  left: 0em;\n  padding: 0.75em 1em;\n  border-radius: 0.21428571rem 0.21428571rem 0em 0em;\n}\n\n.ui[class*=\"bottom attached\"].label {\n  top: auto;\n  bottom: 0em;\n  border-radius: 0em 0em 0.21428571rem 0.21428571rem;\n}\n\n.ui[class*=\"top left attached\"].label {\n  width: auto;\n  margin-top: 0em !important;\n  border-radius: 0.21428571rem 0em 0.28571429rem 0em;\n}\n\n.ui[class*=\"top right attached\"].label {\n  width: auto;\n  left: auto;\n  right: 0em;\n  border-radius: 0em 0.21428571rem 0em 0.28571429rem;\n}\n\n.ui[class*=\"bottom left attached\"].label {\n  width: auto;\n  top: auto;\n  bottom: 0em;\n  border-radius: 0em 0.28571429rem 0em 0.21428571rem;\n}\n\n.ui[class*=\"bottom right attached\"].label {\n  top: auto;\n  bottom: 0em;\n  left: auto;\n  right: 0em;\n  width: auto;\n  border-radius: 0.28571429rem 0em 0.21428571rem 0em;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*-------------------\n      Disabled\n--------------------*/\n\n.ui.label.disabled {\n  opacity: 0.5;\n}\n\n/*-------------------\n        Hover\n--------------------*/\n\na.ui.labels .label:hover,\na.ui.label:hover {\n  background-color: #e0e0e0;\n  border-color: #e0e0e0;\n  background-image: none;\n  color: rgba(0, 0, 0, 0.8);\n}\n\n.ui.labels a.label:hover:before,\na.ui.label:hover:before {\n  color: rgba(0, 0, 0, 0.8);\n}\n\n/*-------------------\n        Active\n--------------------*/\n\n.ui.active.label {\n  background-color: #d0d0d0;\n  border-color: #d0d0d0;\n  background-image: none;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n.ui.active.label:before {\n  background-color: #d0d0d0;\n  background-image: none;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*-------------------\n    Active Hover\n--------------------*/\n\na.ui.labels .active.label:hover,\na.ui.active.label:hover {\n  background-color: #c8c8c8;\n  border-color: #c8c8c8;\n  background-image: none;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n.ui.labels a.active.label:activehover:before,\na.ui.active.label:activehover:before {\n  background-color: #c8c8c8;\n  background-image: none;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*-------------------\n      Visible\n--------------------*/\n\n.ui.labels.visible .label,\n.ui.label.visible:not(.dropdown) {\n  display: inline-block !important;\n}\n\n/*-------------------\n      Hidden\n--------------------*/\n\n.ui.labels.hidden .label,\n.ui.label.hidden {\n  display: none !important;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*-------------------\n      Colors\n--------------------*/\n\n/*--- Red ---*/\n\n.ui.red.labels .label,\n.ui.red.label {\n  background-color: #db2828 !important;\n  border-color: #db2828 !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.red.labels .label:hover,\na.ui.red.label:hover {\n  background-color: #d01919 !important;\n  border-color: #d01919 !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.red.corner.label,\n.ui.red.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.red.ribbon.label {\n  border-color: #b21e1e !important;\n}\n\n/* Basic */\n\n.ui.basic.red.label {\n  background: none #ffffff !important;\n  color: #db2828 !important;\n  border-color: #db2828 !important;\n}\n\n.ui.basic.red.labels a.label:hover,\na.ui.basic.red.label:hover {\n  background-color: #ffffff !important;\n  color: #d01919 !important;\n  border-color: #d01919 !important;\n}\n\n/*--- Orange ---*/\n\n.ui.orange.labels .label,\n.ui.orange.label {\n  background-color: #f2711c !important;\n  border-color: #f2711c !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.orange.labels .label:hover,\na.ui.orange.label:hover {\n  background-color: #f26202 !important;\n  border-color: #f26202 !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.orange.corner.label,\n.ui.orange.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.orange.ribbon.label {\n  border-color: #cf590c !important;\n}\n\n/* Basic */\n\n.ui.basic.orange.label {\n  background: none #ffffff !important;\n  color: #f2711c !important;\n  border-color: #f2711c !important;\n}\n\n.ui.basic.orange.labels a.label:hover,\na.ui.basic.orange.label:hover {\n  background-color: #ffffff !important;\n  color: #f26202 !important;\n  border-color: #f26202 !important;\n}\n\n/*--- Yellow ---*/\n\n.ui.yellow.labels .label,\n.ui.yellow.label {\n  background-color: #fbbd08 !important;\n  border-color: #fbbd08 !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.yellow.labels .label:hover,\na.ui.yellow.label:hover {\n  background-color: #eaae00 !important;\n  border-color: #eaae00 !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.yellow.corner.label,\n.ui.yellow.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.yellow.ribbon.label {\n  border-color: #cd9903 !important;\n}\n\n/* Basic */\n\n.ui.basic.yellow.label {\n  background: none #ffffff !important;\n  color: #fbbd08 !important;\n  border-color: #fbbd08 !important;\n}\n\n.ui.basic.yellow.labels a.label:hover,\na.ui.basic.yellow.label:hover {\n  background-color: #ffffff !important;\n  color: #eaae00 !important;\n  border-color: #eaae00 !important;\n}\n\n/*--- Olive ---*/\n\n.ui.olive.labels .label,\n.ui.olive.label {\n  background-color: #b5cc18 !important;\n  border-color: #b5cc18 !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.olive.labels .label:hover,\na.ui.olive.label:hover {\n  background-color: #a7bd0d !important;\n  border-color: #a7bd0d !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.olive.corner.label,\n.ui.olive.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.olive.ribbon.label {\n  border-color: #198f35 !important;\n}\n\n/* Basic */\n\n.ui.basic.olive.label {\n  background: none #ffffff !important;\n  color: #b5cc18 !important;\n  border-color: #b5cc18 !important;\n}\n\n.ui.basic.olive.labels a.label:hover,\na.ui.basic.olive.label:hover {\n  background-color: #ffffff !important;\n  color: #a7bd0d !important;\n  border-color: #a7bd0d !important;\n}\n\n/*--- Green ---*/\n\n.ui.green.labels .label,\n.ui.green.label {\n  background-color: #21ba45 !important;\n  border-color: #21ba45 !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.green.labels .label:hover,\na.ui.green.label:hover {\n  background-color: #16ab39 !important;\n  border-color: #16ab39 !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.green.corner.label,\n.ui.green.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.green.ribbon.label {\n  border-color: #198f35 !important;\n}\n\n/* Basic */\n\n.ui.basic.green.label {\n  background: none #ffffff !important;\n  color: #21ba45 !important;\n  border-color: #21ba45 !important;\n}\n\n.ui.basic.green.labels a.label:hover,\na.ui.basic.green.label:hover {\n  background-color: #ffffff !important;\n  color: #16ab39 !important;\n  border-color: #16ab39 !important;\n}\n\n/*--- Teal ---*/\n\n.ui.teal.labels .label,\n.ui.teal.label {\n  background-color: #00b5ad !important;\n  border-color: #00b5ad !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.teal.labels .label:hover,\na.ui.teal.label:hover {\n  background-color: #009c95 !important;\n  border-color: #009c95 !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.teal.corner.label,\n.ui.teal.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.teal.ribbon.label {\n  border-color: #00827c !important;\n}\n\n/* Basic */\n\n.ui.basic.teal.label {\n  background: none #ffffff !important;\n  color: #00b5ad !important;\n  border-color: #00b5ad !important;\n}\n\n.ui.basic.teal.labels a.label:hover,\na.ui.basic.teal.label:hover {\n  background-color: #ffffff !important;\n  color: #009c95 !important;\n  border-color: #009c95 !important;\n}\n\n/*--- Blue ---*/\n\n.ui.blue.labels .label,\n.ui.blue.label {\n  background-color: #2185d0 !important;\n  border-color: #2185d0 !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.blue.labels .label:hover,\na.ui.blue.label:hover {\n  background-color: #1678c2 !important;\n  border-color: #1678c2 !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.blue.corner.label,\n.ui.blue.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.blue.ribbon.label {\n  border-color: #1a69a4 !important;\n}\n\n/* Basic */\n\n.ui.basic.blue.label {\n  background: none #ffffff !important;\n  color: #2185d0 !important;\n  border-color: #2185d0 !important;\n}\n\n.ui.basic.blue.labels a.label:hover,\na.ui.basic.blue.label:hover {\n  background-color: #ffffff !important;\n  color: #1678c2 !important;\n  border-color: #1678c2 !important;\n}\n\n/*--- Violet ---*/\n\n.ui.violet.labels .label,\n.ui.violet.label {\n  background-color: #6435c9 !important;\n  border-color: #6435c9 !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.violet.labels .label:hover,\na.ui.violet.label:hover {\n  background-color: #5829bb !important;\n  border-color: #5829bb !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.violet.corner.label,\n.ui.violet.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.violet.ribbon.label {\n  border-color: #502aa1 !important;\n}\n\n/* Basic */\n\n.ui.basic.violet.label {\n  background: none #ffffff !important;\n  color: #6435c9 !important;\n  border-color: #6435c9 !important;\n}\n\n.ui.basic.violet.labels a.label:hover,\na.ui.basic.violet.label:hover {\n  background-color: #ffffff !important;\n  color: #5829bb !important;\n  border-color: #5829bb !important;\n}\n\n/*--- Purple ---*/\n\n.ui.purple.labels .label,\n.ui.purple.label {\n  background-color: #a333c8 !important;\n  border-color: #a333c8 !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.purple.labels .label:hover,\na.ui.purple.label:hover {\n  background-color: #9627ba !important;\n  border-color: #9627ba !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.purple.corner.label,\n.ui.purple.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.purple.ribbon.label {\n  border-color: #82299f !important;\n}\n\n/* Basic */\n\n.ui.basic.purple.label {\n  background: none #ffffff !important;\n  color: #a333c8 !important;\n  border-color: #a333c8 !important;\n}\n\n.ui.basic.purple.labels a.label:hover,\na.ui.basic.purple.label:hover {\n  background-color: #ffffff !important;\n  color: #9627ba !important;\n  border-color: #9627ba !important;\n}\n\n/*--- Pink ---*/\n\n.ui.pink.labels .label,\n.ui.pink.label {\n  background-color: #e03997 !important;\n  border-color: #e03997 !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.pink.labels .label:hover,\na.ui.pink.label:hover {\n  background-color: #e61a8d !important;\n  border-color: #e61a8d !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.pink.corner.label,\n.ui.pink.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.pink.ribbon.label {\n  border-color: #c71f7e !important;\n}\n\n/* Basic */\n\n.ui.basic.pink.label {\n  background: none #ffffff !important;\n  color: #e03997 !important;\n  border-color: #e03997 !important;\n}\n\n.ui.basic.pink.labels a.label:hover,\na.ui.basic.pink.label:hover {\n  background-color: #ffffff !important;\n  color: #e61a8d !important;\n  border-color: #e61a8d !important;\n}\n\n/*--- Brown ---*/\n\n.ui.brown.labels .label,\n.ui.brown.label {\n  background-color: #a5673f !important;\n  border-color: #a5673f !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.brown.labels .label:hover,\na.ui.brown.label:hover {\n  background-color: #975b33 !important;\n  border-color: #975b33 !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.brown.corner.label,\n.ui.brown.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.brown.ribbon.label {\n  border-color: #805031 !important;\n}\n\n/* Basic */\n\n.ui.basic.brown.label {\n  background: none #ffffff !important;\n  color: #a5673f !important;\n  border-color: #a5673f !important;\n}\n\n.ui.basic.brown.labels a.label:hover,\na.ui.basic.brown.label:hover {\n  background-color: #ffffff !important;\n  color: #975b33 !important;\n  border-color: #975b33 !important;\n}\n\n/*--- Grey ---*/\n\n.ui.grey.labels .label,\n.ui.grey.label {\n  background-color: #767676 !important;\n  border-color: #767676 !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.grey.labels .label:hover,\na.ui.grey.label:hover {\n  background-color: #838383 !important;\n  border-color: #838383 !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.grey.corner.label,\n.ui.grey.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.grey.ribbon.label {\n  border-color: #805031 !important;\n}\n\n/* Basic */\n\n.ui.basic.grey.label {\n  background: none #ffffff !important;\n  color: #767676 !important;\n  border-color: #767676 !important;\n}\n\n.ui.basic.grey.labels a.label:hover,\na.ui.basic.grey.label:hover {\n  background-color: #ffffff !important;\n  color: #838383 !important;\n  border-color: #838383 !important;\n}\n\n/*--- Black ---*/\n\n.ui.black.labels .label,\n.ui.black.label {\n  background-color: #1b1c1d !important;\n  border-color: #1b1c1d !important;\n  color: #ffffff !important;\n}\n\n/* Link */\n\n.ui.black.labels .label:hover,\na.ui.black.label:hover {\n  background-color: #27292a !important;\n  border-color: #27292a !important;\n  color: #ffffff !important;\n}\n\n/* Corner */\n\n.ui.black.corner.label,\n.ui.black.corner.label:hover {\n  background-color: transparent !important;\n}\n\n/* Ribbon */\n\n.ui.black.ribbon.label {\n  border-color: #805031 !important;\n}\n\n/* Basic */\n\n.ui.basic.black.label {\n  background: none #ffffff !important;\n  color: #1b1c1d !important;\n  border-color: #1b1c1d !important;\n}\n\n.ui.basic.black.labels a.label:hover,\na.ui.basic.black.label:hover {\n  background-color: #ffffff !important;\n  color: #27292a !important;\n  border-color: #27292a !important;\n}\n\n/*-------------------\n        Basic\n--------------------*/\n\n.ui.basic.label {\n  background: none #ffffff;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  color: rgba(0, 0, 0, 0.87);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Link */\n\na.ui.basic.label:hover {\n  text-decoration: none;\n  background: none #ffffff;\n  color: #1e70bf;\n  -webkit-box-shadow: 1px solid rgba(34, 36, 38, 0.15);\n  box-shadow: 1px solid rgba(34, 36, 38, 0.15);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Pointing */\n\n.ui.basic.pointing.label:before {\n  border-color: inherit;\n}\n\n/*-------------------\n      Fluid\n--------------------*/\n\n.ui.label.fluid,\n.ui.fluid.labels > .label {\n  width: 100%;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n}\n\n/*-------------------\n      Inverted\n--------------------*/\n\n.ui.inverted.labels .label,\n.ui.inverted.label {\n  color: rgba(255, 255, 255, 0.9) !important;\n}\n\n/*-------------------\n    Horizontal\n--------------------*/\n\n.ui.horizontal.labels .label,\n.ui.horizontal.label {\n  margin: 0em 0.5em 0em 0em;\n  padding: 0.4em 0.833em;\n  min-width: 3em;\n  text-align: center;\n}\n\n/*-------------------\n      Circular\n--------------------*/\n\n.ui.circular.labels .label,\n.ui.circular.label {\n  min-width: 2em;\n  min-height: 2em;\n  padding: 0.5em !important;\n  line-height: 1em;\n  text-align: center;\n  border-radius: 500rem;\n}\n\n.ui.empty.circular.labels .label,\n.ui.empty.circular.label {\n  min-width: 0em;\n  min-height: 0em;\n  overflow: hidden;\n  width: 0.5em;\n  height: 0.5em;\n  vertical-align: baseline;\n}\n\n/*-------------------\n      Pointing\n--------------------*/\n\n.ui.pointing.label {\n  position: relative;\n}\n\n.ui.attached.pointing.label {\n  position: absolute;\n}\n\n.ui.pointing.label:before {\n  background-color: inherit;\n  background-image: inherit;\n  border-width: none;\n  border-style: solid;\n  border-color: inherit;\n}\n\n/* Arrow */\n\n.ui.pointing.label:before {\n  position: absolute;\n  content: \"\";\n  -webkit-transform: rotate(45deg);\n  transform: rotate(45deg);\n  background-image: none;\n  z-index: 2;\n  width: 0.6666em;\n  height: 0.6666em;\n  -webkit-transition: background 0.1s ease;\n  transition: background 0.1s ease;\n}\n\n/*--- Above ---*/\n\n.ui.pointing.label,\n.ui[class*=\"pointing above\"].label {\n  margin-top: 1em;\n}\n\n.ui.pointing.label:before,\n.ui[class*=\"pointing above\"].label:before {\n  border-width: 1px 0px 0px 1px;\n  -webkit-transform: translateX(-50%) translateY(-50%) rotate(45deg);\n  transform: translateX(-50%) translateY(-50%) rotate(45deg);\n  top: 0%;\n  left: 50%;\n}\n\n/*--- Below ---*/\n\n.ui[class*=\"bottom pointing\"].label,\n.ui[class*=\"pointing below\"].label {\n  margin-top: 0em;\n  margin-bottom: 1em;\n}\n\n.ui[class*=\"bottom pointing\"].label:before,\n.ui[class*=\"pointing below\"].label:before {\n  border-width: 0px 1px 1px 0px;\n  top: auto;\n  right: auto;\n  -webkit-transform: translateX(-50%) translateY(-50%) rotate(45deg);\n  transform: translateX(-50%) translateY(-50%) rotate(45deg);\n  top: 100%;\n  left: 50%;\n}\n\n/*--- Left ---*/\n\n.ui[class*=\"left pointing\"].label {\n  margin-top: 0em;\n  margin-left: 0.6666em;\n}\n\n.ui[class*=\"left pointing\"].label:before {\n  border-width: 0px 0px 1px 1px;\n  -webkit-transform: translateX(-50%) translateY(-50%) rotate(45deg);\n  transform: translateX(-50%) translateY(-50%) rotate(45deg);\n  bottom: auto;\n  right: auto;\n  top: 50%;\n  left: 0em;\n}\n\n/*--- Right ---*/\n\n.ui[class*=\"right pointing\"].label {\n  margin-top: 0em;\n  margin-right: 0.6666em;\n}\n\n.ui[class*=\"right pointing\"].label:before {\n  border-width: 1px 1px 0px 0px;\n  -webkit-transform: translateX(50%) translateY(-50%) rotate(45deg);\n  transform: translateX(50%) translateY(-50%) rotate(45deg);\n  top: 50%;\n  right: 0%;\n  bottom: auto;\n  left: auto;\n}\n\n/* Basic Pointing */\n\n/*--- Above ---*/\n\n.ui.basic.pointing.label:before,\n.ui.basic[class*=\"pointing above\"].label:before {\n  margin-top: -1px;\n}\n\n/*--- Below ---*/\n\n.ui.basic[class*=\"bottom pointing\"].label:before,\n.ui.basic[class*=\"pointing below\"].label:before {\n  bottom: auto;\n  top: 100%;\n  margin-top: 1px;\n}\n\n/*--- Left ---*/\n\n.ui.basic[class*=\"left pointing\"].label:before {\n  top: 50%;\n  left: -1px;\n}\n\n/*--- Right ---*/\n\n.ui.basic[class*=\"right pointing\"].label:before {\n  top: 50%;\n  right: -1px;\n}\n\n/*------------------\n  Floating Label\n-------------------*/\n\n.ui.floating.label {\n  position: absolute;\n  z-index: 100;\n  top: -1em;\n  left: 100%;\n  margin: 0em 0em 0em -1.5em !important;\n}\n\n/*-------------------\n        Sizes\n--------------------*/\n\n.ui.mini.labels .label,\n.ui.mini.label {\n  font-size: 0.64285714rem;\n}\n\n.ui.tiny.labels .label,\n.ui.tiny.label {\n  font-size: 0.71428571rem;\n}\n\n.ui.small.labels .label,\n.ui.small.label {\n  font-size: 0.78571429rem;\n}\n\n.ui.labels .label,\n.ui.label {\n  font-size: 0.85714286rem;\n}\n\n.ui.large.labels .label,\n.ui.large.label {\n  font-size: 1rem;\n}\n\n.ui.big.labels .label,\n.ui.big.label {\n  font-size: 1.28571429rem;\n}\n\n.ui.huge.labels .label,\n.ui.huge.label {\n  font-size: 1.42857143rem;\n}\n\n.ui.massive.labels .label,\n.ui.massive.label {\n  font-size: 1.71428571rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - List\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            List\n*******************************/\n\nul.ui.list,\nol.ui.list,\n.ui.list {\n  list-style-type: none;\n  margin: 1em 0em;\n  padding: 0em 0em;\n}\n\nul.ui.list:first-child,\nol.ui.list:first-child,\n.ui.list:first-child {\n  margin-top: 0em;\n  padding-top: 0em;\n}\n\nul.ui.list:last-child,\nol.ui.list:last-child,\n.ui.list:last-child {\n  margin-bottom: 0em;\n  padding-bottom: 0em;\n}\n\n/*******************************\n            Content\n*******************************/\n\n/* List Item */\n\nul.ui.list li,\nol.ui.list li,\n.ui.list > .item,\n.ui.list .list > .item {\n  display: list-item;\n  table-layout: fixed;\n  list-style-type: none;\n  list-style-position: outside;\n  padding: 0.21428571em 0em;\n  line-height: 1.14285714em;\n}\n\nul.ui.list > li:first-child:after,\nol.ui.list > li:first-child:after,\n.ui.list > .list > .item,\n.ui.list > .item:after {\n  content: \"\";\n  display: block;\n  height: 0;\n  clear: both;\n  visibility: hidden;\n}\n\nul.ui.list li:first-child,\nol.ui.list li:first-child,\n.ui.list .list > .item:first-child,\n.ui.list > .item:first-child {\n  padding-top: 0em;\n}\n\nul.ui.list li:last-child,\nol.ui.list li:last-child,\n.ui.list .list > .item:last-child,\n.ui.list > .item:last-child {\n  padding-bottom: 0em;\n}\n\n/* Child List */\n\nul.ui.list ul,\nol.ui.list ol,\n.ui.list .list {\n  clear: both;\n  margin: 0em;\n  padding: 0.75em 0em 0.25em 0.5em;\n}\n\n/* Child Item */\n\nul.ui.list ul li,\nol.ui.list ol li,\n.ui.list .list > .item {\n  padding: 0.14285714em 0em;\n  line-height: inherit;\n}\n\n/* Icon */\n\n.ui.list .list > .item > i.icon,\n.ui.list > .item > i.icon {\n  display: table-cell;\n  margin: 0em;\n  padding-top: 0em;\n  padding-right: 0.28571429em;\n  vertical-align: top;\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n}\n\n.ui.list .list > .item > i.icon:only-child,\n.ui.list > .item > i.icon:only-child {\n  display: inline-block;\n  vertical-align: top;\n}\n\n/* Image */\n\n.ui.list .list > .item > .image,\n.ui.list > .item > .image {\n  display: table-cell;\n  background-color: transparent;\n  margin: 0em;\n  vertical-align: top;\n}\n\n.ui.list .list > .item > .image:not(:only-child):not(img),\n.ui.list > .item > .image:not(:only-child):not(img) {\n  padding-right: 0.5em;\n}\n\n.ui.list .list > .item > .image img,\n.ui.list > .item > .image img {\n  vertical-align: top;\n}\n\n.ui.list .list > .item > img.image,\n.ui.list .list > .item > .image:only-child,\n.ui.list > .item > img.image,\n.ui.list > .item > .image:only-child {\n  display: inline-block;\n}\n\n/* Content */\n\n.ui.list .list > .item > .content,\n.ui.list > .item > .content {\n  line-height: 1.14285714em;\n}\n\n.ui.list .list > .item > .image + .content,\n.ui.list .list > .item > .icon + .content,\n.ui.list > .item > .image + .content,\n.ui.list > .item > .icon + .content {\n  display: table-cell;\n  width: 100%;\n  padding: 0em 0em 0em 0.5em;\n  vertical-align: top;\n}\n\n.ui.list .list > .item > img.image + .content,\n.ui.list > .item > img.image + .content {\n  display: inline-block;\n  width: auto;\n}\n\n.ui.list .list > .item > .content > .list,\n.ui.list > .item > .content > .list {\n  margin-left: 0em;\n  padding-left: 0em;\n}\n\n/* Header */\n\n.ui.list .list > .item .header,\n.ui.list > .item .header {\n  display: block;\n  margin: 0em;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-weight: bold;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* Description */\n\n.ui.list .list > .item .description,\n.ui.list > .item .description {\n  display: block;\n  color: rgba(0, 0, 0, 0.7);\n}\n\n/* Child Link */\n\n.ui.list > .item a,\n.ui.list .list > .item a {\n  cursor: pointer;\n}\n\n/* Linking Item */\n\n.ui.list .list > a.item,\n.ui.list > a.item {\n  cursor: pointer;\n  color: #4183c4;\n}\n\n.ui.list .list > a.item:hover,\n.ui.list > a.item:hover {\n  color: #1e70bf;\n}\n\n/* Linked Item Icons */\n\n.ui.list .list > a.item i.icon,\n.ui.list > a.item i.icon {\n  color: rgba(0, 0, 0, 0.4);\n}\n\n/* Header Link */\n\n.ui.list .list > .item a.header,\n.ui.list > .item a.header {\n  cursor: pointer;\n  color: #4183c4 !important;\n}\n\n.ui.list .list > .item a.header:hover,\n.ui.list > .item a.header:hover {\n  color: #1e70bf !important;\n}\n\n/* Floated Content */\n\n.ui[class*=\"left floated\"].list {\n  float: left;\n}\n\n.ui[class*=\"right floated\"].list {\n  float: right;\n}\n\n.ui.list .list > .item [class*=\"left floated\"],\n.ui.list > .item [class*=\"left floated\"] {\n  float: left;\n  margin: 0em 1em 0em 0em;\n}\n\n.ui.list .list > .item [class*=\"right floated\"],\n.ui.list > .item [class*=\"right floated\"] {\n  float: right;\n  margin: 0em 0em 0em 1em;\n}\n\n/*******************************\n            Coupling\n*******************************/\n\n.ui.menu .ui.list > .item,\n.ui.menu .ui.list .list > .item {\n  display: list-item;\n  table-layout: fixed;\n  background-color: transparent;\n  list-style-type: none;\n  list-style-position: outside;\n  padding: 0.21428571em 0em;\n  line-height: 1.14285714em;\n}\n\n.ui.menu .ui.list .list > .item:before,\n.ui.menu .ui.list > .item:before {\n  border: none;\n  background: none;\n}\n\n.ui.menu .ui.list .list > .item:first-child,\n.ui.menu .ui.list > .item:first-child {\n  padding-top: 0em;\n}\n\n.ui.menu .ui.list .list > .item:last-child,\n.ui.menu .ui.list > .item:last-child {\n  padding-bottom: 0em;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*-------------------\n      Horizontal\n--------------------*/\n\n.ui.horizontal.list {\n  display: inline-block;\n  font-size: 0em;\n}\n\n.ui.horizontal.list > .item {\n  display: inline-block;\n  margin-left: 1em;\n  font-size: 1rem;\n}\n\n.ui.horizontal.list:not(.celled) > .item:first-child {\n  margin-left: 0em !important;\n  padding-left: 0em !important;\n}\n\n.ui.horizontal.list .list {\n  padding-left: 0em;\n  padding-bottom: 0em;\n}\n\n.ui.horizontal.list > .item > .image,\n.ui.horizontal.list .list > .item > .image,\n.ui.horizontal.list > .item > .icon,\n.ui.horizontal.list .list > .item > .icon,\n.ui.horizontal.list > .item > .content,\n.ui.horizontal.list .list > .item > .content {\n  vertical-align: middle;\n}\n\n/* Padding on all elements */\n\n.ui.horizontal.list > .item:first-child,\n.ui.horizontal.list > .item:last-child {\n  padding-top: 0.21428571em;\n  padding-bottom: 0.21428571em;\n}\n\n/* Horizontal List */\n\n.ui.horizontal.list > .item > i.icon {\n  margin: 0em;\n  padding: 0em 0.25em 0em 0em;\n}\n\n.ui.horizontal.list > .item > .icon,\n.ui.horizontal.list > .item > .icon + .content {\n  float: none;\n  display: inline-block;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*-------------------\n      Disabled\n--------------------*/\n\n.ui.list .list > .disabled.item,\n.ui.list > .disabled.item {\n  pointer-events: none;\n  color: rgba(40, 40, 40, 0.3) !important;\n}\n\n.ui.inverted.list .list > .disabled.item,\n.ui.inverted.list > .disabled.item {\n  color: rgba(225, 225, 225, 0.3) !important;\n}\n\n/*-------------------\n        Hover\n--------------------*/\n\n.ui.list .list > a.item:hover .icon,\n.ui.list > a.item:hover .icon {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*-------------------\n      Inverted\n--------------------*/\n\n.ui.inverted.list .list > a.item > .icon,\n.ui.inverted.list > a.item > .icon {\n  color: rgba(255, 255, 255, 0.7);\n}\n\n.ui.inverted.list .list > .item .header,\n.ui.inverted.list > .item .header {\n  color: rgba(255, 255, 255, 0.9);\n}\n\n.ui.inverted.list .list > .item .description,\n.ui.inverted.list > .item .description {\n  color: rgba(255, 255, 255, 0.7);\n}\n\n/* Item Link */\n\n.ui.inverted.list .list > a.item,\n.ui.inverted.list > a.item {\n  cursor: pointer;\n  color: rgba(255, 255, 255, 0.9);\n}\n\n.ui.inverted.list .list > a.item:hover,\n.ui.inverted.list > a.item:hover {\n  color: #1e70bf;\n}\n\n/* Linking Content */\n\n.ui.inverted.list .item a:not(.ui) {\n  color: rgba(255, 255, 255, 0.9) !important;\n}\n\n.ui.inverted.list .item a:not(.ui):hover {\n  color: #1e70bf !important;\n}\n\n/*-------------------\n      Aligned\n--------------------*/\n\n.ui.list[class*=\"top aligned\"] .image,\n.ui.list[class*=\"top aligned\"] .content,\n.ui.list [class*=\"top aligned\"] {\n  vertical-align: top !important;\n}\n\n.ui.list[class*=\"middle aligned\"] .image,\n.ui.list[class*=\"middle aligned\"] .content,\n.ui.list [class*=\"middle aligned\"] {\n  vertical-align: middle !important;\n}\n\n.ui.list[class*=\"bottom aligned\"] .image,\n.ui.list[class*=\"bottom aligned\"] .content,\n.ui.list [class*=\"bottom aligned\"] {\n  vertical-align: bottom !important;\n}\n\n/*-------------------\n      Link\n--------------------*/\n\n.ui.link.list .item,\n.ui.link.list a.item,\n.ui.link.list .item a:not(.ui) {\n  color: rgba(0, 0, 0, 0.4);\n  -webkit-transition: 0.1s color ease;\n  transition: 0.1s color ease;\n}\n\n.ui.link.list.list a.item:hover,\n.ui.link.list.list .item a:not(.ui):hover {\n  color: rgba(0, 0, 0, 0.8);\n}\n\n.ui.link.list.list a.item:active,\n.ui.link.list.list .item a:not(.ui):active {\n  color: rgba(0, 0, 0, 0.9);\n}\n\n.ui.link.list.list .active.item,\n.ui.link.list.list .active.item a:not(.ui) {\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/* Inverted */\n\n.ui.inverted.link.list .item,\n.ui.inverted.link.list a.item,\n.ui.inverted.link.list .item a:not(.ui) {\n  color: rgba(255, 255, 255, 0.5);\n}\n\n.ui.inverted.link.list.list a.item:hover,\n.ui.inverted.link.list.list .item a:not(.ui):hover {\n  color: #ffffff;\n}\n\n.ui.inverted.link.list.list a.item:active,\n.ui.inverted.link.list.list .item a:not(.ui):active {\n  color: #ffffff;\n}\n\n.ui.inverted.link.list.list a.active.item,\n.ui.inverted.link.list.list .active.item a:not(.ui) {\n  color: #ffffff;\n}\n\n/*-------------------\n      Selection\n--------------------*/\n\n.ui.selection.list .list > .item,\n.ui.selection.list > .item {\n  cursor: pointer;\n  background: transparent;\n  padding: 0.5em 0.5em;\n  margin: 0em;\n  color: rgba(0, 0, 0, 0.4);\n  border-radius: 0.5em;\n  -webkit-transition: 0.1s color ease, 0.1s padding-left ease,\n    0.1s background-color ease;\n  transition: 0.1s color ease, 0.1s padding-left ease,\n    0.1s background-color ease;\n}\n\n.ui.selection.list .list > .item:last-child,\n.ui.selection.list > .item:last-child {\n  margin-bottom: 0em;\n}\n\n.ui.selection.list.list > .item:hover,\n.ui.selection.list > .item:hover {\n  background: rgba(0, 0, 0, 0.03);\n  color: rgba(0, 0, 0, 0.8);\n}\n\n.ui.selection.list .list > .item:active,\n.ui.selection.list > .item:active {\n  background: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.9);\n}\n\n.ui.selection.list .list > .item.active,\n.ui.selection.list > .item.active {\n  background: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/* Inverted */\n\n.ui.inverted.selection.list > .item,\n.ui.inverted.selection.list > .item {\n  background: transparent;\n  color: rgba(255, 255, 255, 0.5);\n}\n\n.ui.inverted.selection.list > .item:hover,\n.ui.inverted.selection.list > .item:hover {\n  background: rgba(255, 255, 255, 0.02);\n  color: #ffffff;\n}\n\n.ui.inverted.selection.list > .item:active,\n.ui.inverted.selection.list > .item:active {\n  background: rgba(255, 255, 255, 0.08);\n  color: #ffffff;\n}\n\n.ui.inverted.selection.list > .item.active,\n.ui.inverted.selection.list > .item.active {\n  background: rgba(255, 255, 255, 0.08);\n  color: #ffffff;\n}\n\n/* Celled / Divided Selection List */\n\n.ui.celled.selection.list .list > .item,\n.ui.divided.selection.list .list > .item,\n.ui.celled.selection.list > .item,\n.ui.divided.selection.list > .item {\n  border-radius: 0em;\n}\n\n/*-------------------\n      Animated\n--------------------*/\n\n.ui.animated.list > .item {\n  -webkit-transition: 0.25s color ease 0.1s, 0.25s padding-left ease 0.1s,\n    0.25s background-color ease 0.1s;\n  transition: 0.25s color ease 0.1s, 0.25s padding-left ease 0.1s,\n    0.25s background-color ease 0.1s;\n}\n\n.ui.animated.list:not(.horizontal) > .item:hover {\n  padding-left: 1em;\n}\n\n/*-------------------\n      Fitted\n--------------------*/\n\n.ui.fitted.list:not(.selection) .list > .item,\n.ui.fitted.list:not(.selection) > .item {\n  padding-left: 0em;\n  padding-right: 0em;\n}\n\n.ui.fitted.selection.list .list > .item,\n.ui.fitted.selection.list > .item {\n  margin-left: -0.5em;\n  margin-right: -0.5em;\n}\n\n/*-------------------\n      Bulleted\n--------------------*/\n\nul.ui.list,\n.ui.bulleted.list {\n  margin-left: 1.25rem;\n}\n\nul.ui.list li,\n.ui.bulleted.list .list > .item,\n.ui.bulleted.list > .item {\n  position: relative;\n}\n\nul.ui.list li:before,\n.ui.bulleted.list .list > .item:before,\n.ui.bulleted.list > .item:before {\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  pointer-events: none;\n  position: absolute;\n  top: auto;\n  left: auto;\n  font-weight: normal;\n  margin-left: -1.25rem;\n  content: \"•\";\n  opacity: 1;\n  color: inherit;\n  vertical-align: top;\n}\n\nul.ui.list li:before,\n.ui.bulleted.list .list > a.item:before,\n.ui.bulleted.list > a.item:before {\n  color: rgba(0, 0, 0, 0.87);\n}\n\nul.ui.list ul,\n.ui.bulleted.list .list {\n  padding-left: 1.25rem;\n}\n\n/* Horizontal Bulleted */\n\nul.ui.horizontal.bulleted.list,\n.ui.horizontal.bulleted.list {\n  margin-left: 0em;\n}\n\nul.ui.horizontal.bulleted.list li,\n.ui.horizontal.bulleted.list > .item {\n  margin-left: 1.75rem;\n}\n\nul.ui.horizontal.bulleted.list li:first-child,\n.ui.horizontal.bulleted.list > .item:first-child {\n  margin-left: 0em;\n}\n\nul.ui.horizontal.bulleted.list li::before,\n.ui.horizontal.bulleted.list > .item::before {\n  color: rgba(0, 0, 0, 0.87);\n}\n\nul.ui.horizontal.bulleted.list li:first-child::before,\n.ui.horizontal.bulleted.list > .item:first-child::before {\n  display: none;\n}\n\n/*-------------------\n      Ordered\n--------------------*/\n\nol.ui.list,\n.ui.ordered.list,\n.ui.ordered.list .list,\nol.ui.list ol {\n  counter-reset: ordered;\n  margin-left: 1.25rem;\n  list-style-type: none;\n}\n\nol.ui.list li,\n.ui.ordered.list .list > .item,\n.ui.ordered.list > .item {\n  list-style-type: none;\n  position: relative;\n}\n\nol.ui.list li:before,\n.ui.ordered.list .list > .item:before,\n.ui.ordered.list > .item:before {\n  position: absolute;\n  top: auto;\n  left: auto;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  pointer-events: none;\n  margin-left: -1.25rem;\n  counter-increment: ordered;\n  content: counters(ordered, \".\") \" \";\n  text-align: right;\n  color: rgba(0, 0, 0, 0.87);\n  vertical-align: middle;\n  opacity: 0.8;\n}\n\nol.ui.inverted.list li:before,\n.ui.ordered.inverted.list .list > .item:before,\n.ui.ordered.inverted.list > .item:before {\n  color: rgba(255, 255, 255, 0.7);\n}\n\n/* Value */\n\n.ui.ordered.list > .list > .item[data-value],\n.ui.ordered.list > .item[data-value] {\n  content: attr(data-value);\n}\n\nol.ui.list li[value]:before {\n  content: attr(value);\n}\n\n/* Child Lists */\n\nol.ui.list ol,\n.ui.ordered.list .list {\n  margin-left: 1em;\n}\n\nol.ui.list ol li:before,\n.ui.ordered.list .list > .item:before {\n  margin-left: -2em;\n}\n\n/* Horizontal Ordered */\n\nol.ui.horizontal.list,\n.ui.ordered.horizontal.list {\n  margin-left: 0em;\n}\n\nol.ui.horizontal.list li:before,\n.ui.ordered.horizontal.list .list > .item:before,\n.ui.ordered.horizontal.list > .item:before {\n  position: static;\n  margin: 0em 0.5em 0em 0em;\n}\n\n/*-------------------\n      Divided\n--------------------*/\n\n.ui.divided.list > .item {\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.divided.list .list > .item {\n  border-top: none;\n}\n\n.ui.divided.list .item .list > .item {\n  border-top: none;\n}\n\n.ui.divided.list .list > .item:first-child,\n.ui.divided.list > .item:first-child {\n  border-top: none;\n}\n\n/* Sub Menu */\n\n.ui.divided.list:not(.horizontal) .list > .item:first-child {\n  border-top-width: 1px;\n}\n\n/* Divided bulleted */\n\n.ui.divided.bulleted.list:not(.horizontal),\n.ui.divided.bulleted.list .list {\n  margin-left: 0em;\n  padding-left: 0em;\n}\n\n.ui.divided.bulleted.list > .item:not(.horizontal) {\n  padding-left: 1.25rem;\n}\n\n/* Divided Ordered */\n\n.ui.divided.ordered.list {\n  margin-left: 0em;\n}\n\n.ui.divided.ordered.list .list > .item,\n.ui.divided.ordered.list > .item {\n  padding-left: 1.25rem;\n}\n\n.ui.divided.ordered.list .item .list {\n  margin-left: 0em;\n  margin-right: 0em;\n  padding-bottom: 0.21428571em;\n}\n\n.ui.divided.ordered.list .item .list > .item {\n  padding-left: 1em;\n}\n\n/* Divided Selection */\n\n.ui.divided.selection.list .list > .item,\n.ui.divided.selection.list > .item {\n  margin: 0em;\n  border-radius: 0em;\n}\n\n/* Divided horizontal */\n\n.ui.divided.horizontal.list {\n  margin-left: 0em;\n}\n\n.ui.divided.horizontal.list > .item:not(:first-child) {\n  padding-left: 0.5em;\n}\n\n.ui.divided.horizontal.list > .item:not(:last-child) {\n  padding-right: 0.5em;\n}\n\n.ui.divided.horizontal.list > .item {\n  border-top: none;\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n  margin: 0em;\n  line-height: 0.6;\n}\n\n.ui.horizontal.divided.list > .item:first-child {\n  border-left: none;\n}\n\n/* Inverted */\n\n.ui.divided.inverted.list > .item,\n.ui.divided.inverted.list > .list,\n.ui.divided.inverted.horizontal.list > .item {\n  border-color: rgba(255, 255, 255, 0.1);\n}\n\n/*-------------------\n        Celled\n--------------------*/\n\n.ui.celled.list > .item,\n.ui.celled.list > .list {\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n  padding-left: 0.5em;\n  padding-right: 0.5em;\n}\n\n.ui.celled.list > .item:last-child {\n  border-bottom: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n/* Padding on all elements */\n\n.ui.celled.list > .item:first-child,\n.ui.celled.list > .item:last-child {\n  padding-top: 0.21428571em;\n  padding-bottom: 0.21428571em;\n}\n\n/* Sub Menu */\n\n.ui.celled.list .item .list > .item {\n  border-width: 0px;\n}\n\n.ui.celled.list .list > .item:first-child {\n  border-top-width: 0px;\n}\n\n/* Celled Bulleted */\n\n.ui.celled.bulleted.list {\n  margin-left: 0em;\n}\n\n.ui.celled.bulleted.list .list > .item,\n.ui.celled.bulleted.list > .item {\n  padding-left: 1.25rem;\n}\n\n.ui.celled.bulleted.list .item .list {\n  margin-left: -1.25rem;\n  margin-right: -1.25rem;\n  padding-bottom: 0.21428571em;\n}\n\n/* Celled Ordered */\n\n.ui.celled.ordered.list {\n  margin-left: 0em;\n}\n\n.ui.celled.ordered.list .list > .item,\n.ui.celled.ordered.list > .item {\n  padding-left: 1.25rem;\n}\n\n.ui.celled.ordered.list .item .list {\n  margin-left: 0em;\n  margin-right: 0em;\n  padding-bottom: 0.21428571em;\n}\n\n.ui.celled.ordered.list .list > .item {\n  padding-left: 1em;\n}\n\n/* Celled Horizontal */\n\n.ui.horizontal.celled.list {\n  margin-left: 0em;\n}\n\n.ui.horizontal.celled.list .list > .item,\n.ui.horizontal.celled.list > .item {\n  border-top: none;\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n  margin: 0em;\n  padding-left: 0.5em;\n  padding-right: 0.5em;\n  line-height: 0.6;\n}\n\n.ui.horizontal.celled.list .list > .item:last-child,\n.ui.horizontal.celled.list > .item:last-child {\n  border-bottom: none;\n  border-right: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n/* Inverted */\n\n.ui.celled.inverted.list > .item,\n.ui.celled.inverted.list > .list {\n  border-color: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n.ui.celled.inverted.horizontal.list .list > .item,\n.ui.celled.inverted.horizontal.list > .item {\n  border-color: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n/*-------------------\n      Relaxed\n--------------------*/\n\n.ui.relaxed.list:not(.horizontal) > .item:not(:first-child) {\n  padding-top: 0.42857143em;\n}\n\n.ui.relaxed.list:not(.horizontal) > .item:not(:last-child) {\n  padding-bottom: 0.42857143em;\n}\n\n.ui.horizontal.relaxed.list .list > .item:not(:first-child),\n.ui.horizontal.relaxed.list > .item:not(:first-child) {\n  padding-left: 1rem;\n}\n\n.ui.horizontal.relaxed.list .list > .item:not(:last-child),\n.ui.horizontal.relaxed.list > .item:not(:last-child) {\n  padding-right: 1rem;\n}\n\n/* Very Relaxed */\n\n.ui[class*=\"very relaxed\"].list:not(.horizontal) > .item:not(:first-child) {\n  padding-top: 0.85714286em;\n}\n\n.ui[class*=\"very relaxed\"].list:not(.horizontal) > .item:not(:last-child) {\n  padding-bottom: 0.85714286em;\n}\n\n.ui.horizontal[class*=\"very relaxed\"].list .list > .item:not(:first-child),\n.ui.horizontal[class*=\"very relaxed\"].list > .item:not(:first-child) {\n  padding-left: 1.5rem;\n}\n\n.ui.horizontal[class*=\"very relaxed\"].list .list > .item:not(:last-child),\n.ui.horizontal[class*=\"very relaxed\"].list > .item:not(:last-child) {\n  padding-right: 1.5rem;\n}\n\n/*-------------------\n      Sizes\n--------------------*/\n\n.ui.mini.list {\n  font-size: 0.78571429em;\n}\n\n.ui.tiny.list {\n  font-size: 0.85714286em;\n}\n\n.ui.small.list {\n  font-size: 0.92857143em;\n}\n\n.ui.list {\n  font-size: 1em;\n}\n\n.ui.large.list {\n  font-size: 1.14285714em;\n}\n\n.ui.big.list {\n  font-size: 1.28571429em;\n}\n\n.ui.huge.list {\n  font-size: 1.42857143em;\n}\n\n.ui.massive.list {\n  font-size: 1.71428571em;\n}\n\n.ui.mini.horizontal.list .list > .item,\n.ui.mini.horizontal.list > .item {\n  font-size: 0.78571429rem;\n}\n\n.ui.tiny.horizontal.list .list > .item,\n.ui.tiny.horizontal.list > .item {\n  font-size: 0.85714286rem;\n}\n\n.ui.small.horizontal.list .list > .item,\n.ui.small.horizontal.list > .item {\n  font-size: 0.92857143rem;\n}\n\n.ui.horizontal.list .list > .item,\n.ui.horizontal.list > .item {\n  font-size: 1rem;\n}\n\n.ui.large.horizontal.list .list > .item,\n.ui.large.horizontal.list > .item {\n  font-size: 1.14285714rem;\n}\n\n.ui.big.horizontal.list .list > .item,\n.ui.big.horizontal.list > .item {\n  font-size: 1.28571429rem;\n}\n\n.ui.huge.horizontal.list .list > .item,\n.ui.huge.horizontal.list > .item {\n  font-size: 1.42857143rem;\n}\n\n.ui.massive.horizontal.list .list > .item,\n.ui.massive.horizontal.list > .item {\n  font-size: 1.71428571rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n    User Variable Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Loader\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Loader\n*******************************/\n\n/* Standard Size */\n\n.ui.loader {\n  display: none;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  margin: 0px;\n  text-align: center;\n  z-index: 1000;\n  -webkit-transform: translateX(-50%) translateY(-50%);\n  transform: translateX(-50%) translateY(-50%);\n}\n\n/* Static Shape */\n\n.ui.loader:before {\n  position: absolute;\n  content: \"\";\n  top: 0%;\n  left: 50%;\n  width: 100%;\n  height: 100%;\n  border-radius: 500rem;\n  border: 0.2em solid rgba(0, 0, 0, 0.1);\n}\n\n/* Active Shape */\n\n.ui.loader:after {\n  position: absolute;\n  content: \"\";\n  top: 0%;\n  left: 50%;\n  width: 100%;\n  height: 100%;\n  -webkit-animation: loader 0.6s linear;\n  animation: loader 0.6s linear;\n  -webkit-animation-iteration-count: infinite;\n  animation-iteration-count: infinite;\n  border-radius: 500rem;\n  border-color: #767676 transparent transparent;\n  border-style: solid;\n  border-width: 0.2em;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent;\n  box-shadow: 0px 0px 0px 1px transparent;\n}\n\n/* Active Animation */\n\n@-webkit-keyframes loader {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes loader {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n/* Sizes */\n\n.ui.mini.loader:before,\n.ui.mini.loader:after {\n  width: 1rem;\n  height: 1rem;\n  margin: 0em 0em 0em -0.5rem;\n}\n\n.ui.tiny.loader:before,\n.ui.tiny.loader:after {\n  width: 1.14285714rem;\n  height: 1.14285714rem;\n  margin: 0em 0em 0em -0.57142857rem;\n}\n\n.ui.small.loader:before,\n.ui.small.loader:after {\n  width: 1.71428571rem;\n  height: 1.71428571rem;\n  margin: 0em 0em 0em -0.85714286rem;\n}\n\n.ui.loader:before,\n.ui.loader:after {\n  width: 2.28571429rem;\n  height: 2.28571429rem;\n  margin: 0em 0em 0em -1.14285714rem;\n}\n\n.ui.large.loader:before,\n.ui.large.loader:after {\n  width: 3.42857143rem;\n  height: 3.42857143rem;\n  margin: 0em 0em 0em -1.71428571rem;\n}\n\n.ui.big.loader:before,\n.ui.big.loader:after {\n  width: 3.71428571rem;\n  height: 3.71428571rem;\n  margin: 0em 0em 0em -1.85714286rem;\n}\n\n.ui.huge.loader:before,\n.ui.huge.loader:after {\n  width: 4.14285714rem;\n  height: 4.14285714rem;\n  margin: 0em 0em 0em -2.07142857rem;\n}\n\n.ui.massive.loader:before,\n.ui.massive.loader:after {\n  width: 4.57142857rem;\n  height: 4.57142857rem;\n  margin: 0em 0em 0em -2.28571429rem;\n}\n\n/*-------------------\n      Coupling\n--------------------*/\n\n/* Show inside active dimmer */\n\n.ui.dimmer .loader {\n  display: block;\n}\n\n/* Black Dimmer */\n\n.ui.dimmer .ui.loader {\n  color: rgba(255, 255, 255, 0.9);\n}\n\n.ui.dimmer .ui.loader:before {\n  border-color: rgba(255, 255, 255, 0.15);\n}\n\n.ui.dimmer .ui.loader:after {\n  border-color: #ffffff transparent transparent;\n}\n\n/* White Dimmer (Inverted) */\n\n.ui.inverted.dimmer .ui.loader {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.inverted.dimmer .ui.loader:before {\n  border-color: rgba(0, 0, 0, 0.1);\n}\n\n.ui.inverted.dimmer .ui.loader:after {\n  border-color: #767676 transparent transparent;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*-------------------\n        Text\n--------------------*/\n\n.ui.text.loader {\n  width: auto !important;\n  height: auto !important;\n  text-align: center;\n  font-style: normal;\n}\n\n/*******************************\n            States\n*******************************/\n\n.ui.indeterminate.loader:after {\n  animation-direction: reverse;\n  -webkit-animation-duration: 1.2s;\n  animation-duration: 1.2s;\n}\n\n.ui.loader.active,\n.ui.loader.visible {\n  display: block;\n}\n\n.ui.loader.disabled,\n.ui.loader.hidden {\n  display: none;\n}\n\n/*******************************\n            Variations\n*******************************/\n\n/*-------------------\n        Sizes\n--------------------*/\n\n/* Loader */\n\n.ui.inverted.dimmer .ui.mini.loader,\n.ui.mini.loader {\n  width: 1rem;\n  height: 1rem;\n  font-size: 0.78571429em;\n}\n\n.ui.inverted.dimmer .ui.tiny.loader,\n.ui.tiny.loader {\n  width: 1.14285714rem;\n  height: 1.14285714rem;\n  font-size: 0.85714286em;\n}\n\n.ui.inverted.dimmer .ui.small.loader,\n.ui.small.loader {\n  width: 1.71428571rem;\n  height: 1.71428571rem;\n  font-size: 0.92857143em;\n}\n\n.ui.inverted.dimmer .ui.loader,\n.ui.loader {\n  width: 2.28571429rem;\n  height: 2.28571429rem;\n  font-size: 1em;\n}\n\n.ui.inverted.dimmer .ui.large.loader,\n.ui.large.loader {\n  width: 3.42857143rem;\n  height: 3.42857143rem;\n  font-size: 1.14285714em;\n}\n\n.ui.inverted.dimmer .ui.big.loader,\n.ui.big.loader {\n  width: 3.71428571rem;\n  height: 3.71428571rem;\n  font-size: 1.28571429em;\n}\n\n.ui.inverted.dimmer .ui.huge.loader,\n.ui.huge.loader {\n  width: 4.14285714rem;\n  height: 4.14285714rem;\n  font-size: 1.42857143em;\n}\n\n.ui.inverted.dimmer .ui.massive.loader,\n.ui.massive.loader {\n  width: 4.57142857rem;\n  height: 4.57142857rem;\n  font-size: 1.71428571em;\n}\n\n/* Text Loader */\n\n.ui.mini.text.loader {\n  min-width: 1rem;\n  padding-top: 1.78571429rem;\n}\n\n.ui.tiny.text.loader {\n  min-width: 1.14285714rem;\n  padding-top: 1.92857143rem;\n}\n\n.ui.small.text.loader {\n  min-width: 1.71428571rem;\n  padding-top: 2.5rem;\n}\n\n.ui.text.loader {\n  min-width: 2.28571429rem;\n  padding-top: 3.07142857rem;\n}\n\n.ui.large.text.loader {\n  min-width: 3.42857143rem;\n  padding-top: 4.21428571rem;\n}\n\n.ui.big.text.loader {\n  min-width: 3.71428571rem;\n  padding-top: 4.5rem;\n}\n\n.ui.huge.text.loader {\n  min-width: 4.14285714rem;\n  padding-top: 4.92857143rem;\n}\n\n.ui.massive.text.loader {\n  min-width: 4.57142857rem;\n  padding-top: 5.35714286rem;\n}\n\n/*-------------------\n      Inverted\n--------------------*/\n\n.ui.inverted.loader {\n  color: rgba(255, 255, 255, 0.9);\n}\n\n.ui.inverted.loader:before {\n  border-color: rgba(255, 255, 255, 0.15);\n}\n\n.ui.inverted.loader:after {\n  border-top-color: #ffffff;\n}\n\n/*-------------------\n      Inline\n--------------------*/\n\n.ui.inline.loader {\n  position: relative;\n  vertical-align: middle;\n  margin: 0em;\n  left: 0em;\n  top: 0em;\n  -webkit-transform: none;\n  transform: none;\n}\n\n.ui.inline.loader.active,\n.ui.inline.loader.visible {\n  display: inline-block;\n}\n\n/* Centered Inline */\n\n.ui.centered.inline.loader.active,\n.ui.centered.inline.loader.visible {\n  display: block;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Loader\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*-------------------\n      Content\n--------------------*/\n\n.ui.placeholder {\n  position: static;\n  overflow: hidden;\n  -webkit-animation: placeholderShimmer 2s linear;\n  animation: placeholderShimmer 2s linear;\n  -webkit-animation-iteration-count: infinite;\n  animation-iteration-count: infinite;\n  background-color: #ffffff;\n  background-image: -webkit-gradient(linear,\n      left top,\n      right top,\n      from(rgba(0, 0, 0, 0.08)),\n      color-stop(15%, rgba(0, 0, 0, 0.15)),\n      color-stop(30%, rgba(0, 0, 0, 0.08)));\n  background-image: -webkit-linear-gradient(left,\n      rgba(0, 0, 0, 0.08) 0%,\n      rgba(0, 0, 0, 0.15) 15%,\n      rgba(0, 0, 0, 0.08) 30%);\n  background-image: linear-gradient(to right,\n      rgba(0, 0, 0, 0.08) 0%,\n      rgba(0, 0, 0, 0.15) 15%,\n      rgba(0, 0, 0, 0.08) 30%);\n  background-size: 1200px 100%;\n  max-width: 30rem;\n}\n\n@-webkit-keyframes placeholderShimmer {\n  0% {\n    background-position: -1200px 0;\n  }\n\n  100% {\n    background-position: 1200px 0;\n  }\n}\n\n@keyframes placeholderShimmer {\n  0% {\n    background-position: -1200px 0;\n  }\n\n  100% {\n    background-position: 1200px 0;\n  }\n}\n\n.ui.placeholder + .ui.placeholder {\n  margin-top: 2rem;\n}\n\n.ui.placeholder + .ui.placeholder {\n  -webkit-animation-delay: 0.15s;\n  animation-delay: 0.15s;\n}\n\n.ui.placeholder + .ui.placeholder + .ui.placeholder {\n  -webkit-animation-delay: 0.3s;\n  animation-delay: 0.3s;\n}\n\n.ui.placeholder + .ui.placeholder + .ui.placeholder + .ui.placeholder {\n  -webkit-animation-delay: 0.45s;\n  animation-delay: 0.45s;\n}\n\n.ui.placeholder + .ui.placeholder + .ui.placeholder + .ui.placeholder + .ui.placeholder {\n  -webkit-animation-delay: 0.6s;\n  animation-delay: 0.6s;\n}\n\n.ui.placeholder,\n.ui.placeholder > :before,\n.ui.placeholder .image.header:after,\n.ui.placeholder .line,\n.ui.placeholder .line:after {\n  background-color: #ffffff;\n}\n\n/* Image */\n\n.ui.placeholder .image:not(.header):not(.ui) {\n  height: 100px;\n}\n\n.ui.placeholder .square.image:not(.header) {\n  height: 0px;\n  overflow: hidden;\n  /* 1/1 aspect ratio */\n  padding-top: 100%;\n}\n\n.ui.placeholder .rectangular.image:not(.header) {\n  height: 0px;\n  overflow: hidden;\n  /* 4/3 aspect ratio */\n  padding-top: 75%;\n}\n\n/* Lines */\n\n.ui.placeholder .line {\n  position: relative;\n  height: 0.85714286em;\n}\n\n.ui.placeholder .line:before,\n.ui.placeholder .line:after {\n  top: 100%;\n  position: absolute;\n  content: \"\";\n  background-color: inherit;\n}\n\n.ui.placeholder .line:before {\n  left: 0px;\n}\n\n.ui.placeholder .line:after {\n  right: 0px;\n}\n\n/* Any Lines */\n\n.ui.placeholder .line {\n  margin-bottom: 0.5em;\n}\n\n.ui.placeholder .line:before,\n.ui.placeholder .line:after {\n  height: 0.5em;\n}\n\n.ui.placeholder .line:not(:first-child) {\n  margin-top: 0.5em;\n}\n\n/* Header Image + 2 Lines */\n\n.ui.placeholder .header {\n  position: relative;\n  overflow: hidden;\n}\n\n/* Line Outdent */\n\n.ui.placeholder .line:nth-child(1):after {\n  width: 0%;\n}\n\n.ui.placeholder .line:nth-child(2):after {\n  width: 50%;\n}\n\n.ui.placeholder .line:nth-child(3):after {\n  width: 10%;\n}\n\n.ui.placeholder .line:nth-child(4):after {\n  width: 35%;\n}\n\n.ui.placeholder .line:nth-child(5):after {\n  width: 65%;\n}\n\n/* Header Line 1 & 2*/\n\n.ui.placeholder .header .line {\n  margin-bottom: 0.64285714em;\n}\n\n.ui.placeholder .header .line:before,\n.ui.placeholder .header .line:after {\n  height: 0.64285714em;\n}\n\n.ui.placeholder .header .line:not(:first-child) {\n  margin-top: 0.64285714em;\n}\n\n.ui.placeholder .header .line:after {\n  width: 20%;\n}\n\n.ui.placeholder .header .line:nth-child(2):after {\n  width: 60%;\n}\n\n/* Image Header */\n\n.ui.placeholder .image.header .line {\n  margin-left: 3em;\n}\n\n.ui.placeholder .image.header .line:before {\n  width: 0.71428571rem;\n}\n\n.ui.placeholder .image.header:after {\n  display: block;\n  height: 0.85714286em;\n  content: \"\";\n  margin-left: 3em;\n}\n\n/* Spacing */\n\n.ui.placeholder .image .line:first-child,\n.ui.placeholder .paragraph .line:first-child,\n.ui.placeholder .header .line:first-child {\n  height: 0.01px;\n}\n\n.ui.placeholder .image:not(:first-child):before,\n.ui.placeholder .paragraph:not(:first-child):before,\n.ui.placeholder .header:not(:first-child):before {\n  height: 1.42857143em;\n  content: \"\";\n  display: block;\n}\n\n/* Inverted Content Loader */\n\n.ui.inverted.placeholder {\n  background-image: -webkit-gradient(linear,\n      left top,\n      right top,\n      from(rgba(255, 255, 255, 0.08)),\n      color-stop(15%, rgba(255, 255, 255, 0.14)),\n      color-stop(30%, rgba(255, 255, 255, 0.08)));\n  background-image: -webkit-linear-gradient(left,\n      rgba(255, 255, 255, 0.08) 0%,\n      rgba(255, 255, 255, 0.14) 15%,\n      rgba(255, 255, 255, 0.08) 30%);\n  background-image: linear-gradient(to right,\n      rgba(255, 255, 255, 0.08) 0%,\n      rgba(255, 255, 255, 0.14) 15%,\n      rgba(255, 255, 255, 0.08) 30%);\n}\n\n.ui.inverted.placeholder,\n.ui.inverted.placeholder > :before,\n.ui.inverted.placeholder .image.header:after,\n.ui.inverted.placeholder .line,\n.ui.inverted.placeholder .line:after {\n  background-color: #1b1c1d;\n}\n\n/*******************************\n            Variations\n*******************************/\n\n/*-------------------\n        Sizes\n--------------------*/\n\n.ui.placeholder .full.line.line.line:after {\n  width: 0%;\n}\n\n.ui.placeholder .very.long.line.line.line:after {\n  width: 10%;\n}\n\n.ui.placeholder .long.line.line.line:after {\n  width: 35%;\n}\n\n.ui.placeholder .medium.line.line.line:after {\n  width: 50%;\n}\n\n.ui.placeholder .short.line.line.line:after {\n  width: 65%;\n}\n\n.ui.placeholder .very.short.line.line.line:after {\n  width: 80%;\n}\n\n/*-------------------\n        Fluid\n--------------------*/\n\n.ui.fluid.placeholder {\n  max-width: none;\n}\n\n/*!\n* # Semantic UI 2.4.0 - Rail\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Rails\n*******************************/\n\n.ui.rail {\n  position: absolute;\n  top: 0%;\n  width: 300px;\n  height: 100%;\n}\n\n.ui.left.rail {\n  left: auto;\n  right: 100%;\n  padding: 0em 2rem 0em 0em;\n  margin: 0em 2rem 0em 0em;\n}\n\n.ui.right.rail {\n  left: 100%;\n  right: auto;\n  padding: 0em 0em 0em 2rem;\n  margin: 0em 0em 0em 2rem;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Internal\n---------------*/\n\n.ui.left.internal.rail {\n  left: 0%;\n  right: auto;\n  padding: 0em 0em 0em 2rem;\n  margin: 0em 0em 0em 2rem;\n}\n\n.ui.right.internal.rail {\n  left: auto;\n  right: 0%;\n  padding: 0em 2rem 0em 0em;\n  margin: 0em 2rem 0em 0em;\n}\n\n/*--------------\n    Dividing\n---------------*/\n\n.ui.dividing.rail {\n  width: 302.5px;\n}\n\n.ui.left.dividing.rail {\n  padding: 0em 2.5rem 0em 0em;\n  margin: 0em 2.5rem 0em 0em;\n  border-right: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.right.dividing.rail {\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n  padding: 0em 0em 0em 2.5rem;\n  margin: 0em 0em 0em 2.5rem;\n}\n\n/*--------------\n    Distance\n---------------*/\n\n.ui.close.rail {\n  width: calc(300px + 1em);\n}\n\n.ui.close.left.rail {\n  padding: 0em 1em 0em 0em;\n  margin: 0em 1em 0em 0em;\n}\n\n.ui.close.right.rail {\n  padding: 0em 0em 0em 1em;\n  margin: 0em 0em 0em 1em;\n}\n\n.ui.very.close.rail {\n  width: calc(300px + 0.5em);\n}\n\n.ui.very.close.left.rail {\n  padding: 0em 0.5em 0em 0em;\n  margin: 0em 0.5em 0em 0em;\n}\n\n.ui.very.close.right.rail {\n  padding: 0em 0em 0em 0.5em;\n  margin: 0em 0em 0em 0.5em;\n}\n\n/*--------------\n    Attached\n---------------*/\n\n.ui.attached.left.rail,\n.ui.attached.right.rail {\n  padding: 0em;\n  margin: 0em;\n}\n\n/*--------------\n    Sizing\n---------------*/\n\n.ui.mini.rail {\n  font-size: 0.78571429rem;\n}\n\n.ui.tiny.rail {\n  font-size: 0.85714286rem;\n}\n\n.ui.small.rail {\n  font-size: 0.92857143rem;\n}\n\n.ui.rail {\n  font-size: 1rem;\n}\n\n.ui.large.rail {\n  font-size: 1.14285714rem;\n}\n\n.ui.big.rail {\n  font-size: 1.28571429rem;\n}\n\n.ui.huge.rail {\n  font-size: 1.42857143rem;\n}\n\n.ui.massive.rail {\n  font-size: 1.71428571rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Reveal\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Reveal\n*******************************/\n\n.ui.reveal {\n  display: inherit;\n  position: relative !important;\n  font-size: 0em !important;\n}\n\n.ui.reveal > .visible.content {\n  position: absolute !important;\n  top: 0em !important;\n  left: 0em !important;\n  z-index: 3 !important;\n  -webkit-transition: all 0.5s ease 0.1s;\n  transition: all 0.5s ease 0.1s;\n}\n\n.ui.reveal > .hidden.content {\n  position: relative !important;\n  z-index: 2 !important;\n}\n\n/* Make sure hovered element is on top of other reveal */\n\n.ui.active.reveal .visible.content,\n.ui.reveal:hover .visible.content {\n  z-index: 4 !important;\n}\n\n/*******************************\n              Types\n*******************************/\n\n/*--------------\n      Slide\n---------------*/\n\n.ui.slide.reveal {\n  position: relative !important;\n  overflow: hidden !important;\n  white-space: nowrap;\n}\n\n.ui.slide.reveal > .content {\n  display: block;\n  width: 100%;\n  white-space: normal;\n  float: left;\n  margin: 0em;\n  -webkit-transition: -webkit-transform 0.5s ease 0.1s;\n  transition: -webkit-transform 0.5s ease 0.1s;\n  transition: transform 0.5s ease 0.1s;\n  transition: transform 0.5s ease 0.1s, -webkit-transform 0.5s ease 0.1s;\n}\n\n.ui.slide.reveal > .visible.content {\n  position: relative !important;\n}\n\n.ui.slide.reveal > .hidden.content {\n  position: absolute !important;\n  left: 0% !important;\n  width: 100% !important;\n  -webkit-transform: translateX(100%) !important;\n  transform: translateX(100%) !important;\n}\n\n.ui.slide.active.reveal > .visible.content,\n.ui.slide.reveal:hover > .visible.content {\n  -webkit-transform: translateX(-100%) !important;\n  transform: translateX(-100%) !important;\n}\n\n.ui.slide.active.reveal > .hidden.content,\n.ui.slide.reveal:hover > .hidden.content {\n  -webkit-transform: translateX(0%) !important;\n  transform: translateX(0%) !important;\n}\n\n.ui.slide.right.reveal > .visible.content {\n  -webkit-transform: translateX(0%) !important;\n  transform: translateX(0%) !important;\n}\n\n.ui.slide.right.reveal > .hidden.content {\n  -webkit-transform: translateX(-100%) !important;\n  transform: translateX(-100%) !important;\n}\n\n.ui.slide.right.active.reveal > .visible.content,\n.ui.slide.right.reveal:hover > .visible.content {\n  -webkit-transform: translateX(100%) !important;\n  transform: translateX(100%) !important;\n}\n\n.ui.slide.right.active.reveal > .hidden.content,\n.ui.slide.right.reveal:hover > .hidden.content {\n  -webkit-transform: translateX(0%) !important;\n  transform: translateX(0%) !important;\n}\n\n.ui.slide.up.reveal > .hidden.content {\n  -webkit-transform: translateY(100%) !important;\n  transform: translateY(100%) !important;\n}\n\n.ui.slide.up.active.reveal > .visible.content,\n.ui.slide.up.reveal:hover > .visible.content {\n  -webkit-transform: translateY(-100%) !important;\n  transform: translateY(-100%) !important;\n}\n\n.ui.slide.up.active.reveal > .hidden.content,\n.ui.slide.up.reveal:hover > .hidden.content {\n  -webkit-transform: translateY(0%) !important;\n  transform: translateY(0%) !important;\n}\n\n.ui.slide.down.reveal > .hidden.content {\n  -webkit-transform: translateY(-100%) !important;\n  transform: translateY(-100%) !important;\n}\n\n.ui.slide.down.active.reveal > .visible.content,\n.ui.slide.down.reveal:hover > .visible.content {\n  -webkit-transform: translateY(100%) !important;\n  transform: translateY(100%) !important;\n}\n\n.ui.slide.down.active.reveal > .hidden.content,\n.ui.slide.down.reveal:hover > .hidden.content {\n  -webkit-transform: translateY(0%) !important;\n  transform: translateY(0%) !important;\n}\n\n/*--------------\n      Fade\n---------------*/\n\n.ui.fade.reveal > .visible.content {\n  opacity: 1;\n}\n\n.ui.fade.active.reveal > .visible.content,\n.ui.fade.reveal:hover > .visible.content {\n  opacity: 0;\n}\n\n/*--------------\n      Move\n---------------*/\n\n.ui.move.reveal {\n  position: relative !important;\n  overflow: hidden !important;\n  white-space: nowrap;\n}\n\n.ui.move.reveal > .content {\n  display: block;\n  float: left;\n  white-space: normal;\n  margin: 0em;\n  -webkit-transition: -webkit-transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1) 0.1s;\n  transition: -webkit-transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1) 0.1s;\n  transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1) 0.1s;\n  transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1) 0.1s,\n    -webkit-transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1) 0.1s;\n}\n\n.ui.move.reveal > .visible.content {\n  position: relative !important;\n}\n\n.ui.move.reveal > .hidden.content {\n  position: absolute !important;\n  left: 0% !important;\n  width: 100% !important;\n}\n\n.ui.move.active.reveal > .visible.content,\n.ui.move.reveal:hover > .visible.content {\n  -webkit-transform: translateX(-100%) !important;\n  transform: translateX(-100%) !important;\n}\n\n.ui.move.right.active.reveal > .visible.content,\n.ui.move.right.reveal:hover > .visible.content {\n  -webkit-transform: translateX(100%) !important;\n  transform: translateX(100%) !important;\n}\n\n.ui.move.up.active.reveal > .visible.content,\n.ui.move.up.reveal:hover > .visible.content {\n  -webkit-transform: translateY(-100%) !important;\n  transform: translateY(-100%) !important;\n}\n\n.ui.move.down.active.reveal > .visible.content,\n.ui.move.down.reveal:hover > .visible.content {\n  -webkit-transform: translateY(100%) !important;\n  transform: translateY(100%) !important;\n}\n\n/*--------------\n    Rotate\n---------------*/\n\n.ui.rotate.reveal > .visible.content {\n  -webkit-transition-duration: 0.5s;\n  transition-duration: 0.5s;\n  -webkit-transform: rotate(0deg);\n  transform: rotate(0deg);\n}\n\n.ui.rotate.reveal > .visible.content,\n.ui.rotate.right.reveal > .visible.content {\n  -webkit-transform-origin: bottom right;\n  transform-origin: bottom right;\n}\n\n.ui.rotate.active.reveal > .visible.content,\n.ui.rotate.reveal:hover > .visible.content,\n.ui.rotate.right.active.reveal > .visible.content,\n.ui.rotate.right.reveal:hover > .visible.content {\n  -webkit-transform: rotate(110deg);\n  transform: rotate(110deg);\n}\n\n.ui.rotate.left.reveal > .visible.content {\n  -webkit-transform-origin: bottom left;\n  transform-origin: bottom left;\n}\n\n.ui.rotate.left.active.reveal > .visible.content,\n.ui.rotate.left.reveal:hover > .visible.content {\n  -webkit-transform: rotate(-110deg);\n  transform: rotate(-110deg);\n}\n\n/*******************************\n              States\n*******************************/\n\n.ui.disabled.reveal:hover > .visible.visible.content {\n  position: static !important;\n  display: block !important;\n  opacity: 1 !important;\n  top: 0 !important;\n  left: 0 !important;\n  right: auto !important;\n  bottom: auto !important;\n  -webkit-transform: none !important;\n  transform: none !important;\n}\n\n.ui.disabled.reveal:hover > .hidden.hidden.content {\n  display: none !important;\n}\n\n/*******************************\n          Coupling\n*******************************/\n\n.ui.reveal > .ui.ribbon.label {\n  z-index: 5;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Visible\n---------------*/\n\n.ui.visible.reveal {\n  overflow: visible;\n}\n\n/*--------------\n    Instant\n---------------*/\n\n.ui.instant.reveal > .content {\n  -webkit-transition-delay: 0s !important;\n  transition-delay: 0s !important;\n}\n\n/*--------------\n    Sizing\n---------------*/\n\n.ui.reveal > .content {\n  font-size: 1rem !important;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Segment\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Segment\n*******************************/\n\n.ui.segment {\n  position: relative;\n  background: #ffffff;\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n  margin: 1rem 0em;\n  padding: 1em 1em;\n  border-radius: 0.28571429rem;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.segment:first-child {\n  margin-top: 0em;\n}\n\n.ui.segment:last-child {\n  margin-bottom: 0em;\n}\n\n/* Vertical */\n\n.ui.vertical.segment {\n  margin: 0em;\n  padding-left: 0em;\n  padding-right: 0em;\n  background: none transparent;\n  border-radius: 0px;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border: none;\n  border-bottom: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.vertical.segment:last-child {\n  border-bottom: none;\n}\n\n/*-------------------\n    Loose Coupling\n--------------------*/\n\n/* Header */\n\n.ui.inverted.segment > .ui.header {\n  color: #ffffff;\n}\n\n/* Label */\n\n.ui[class*=\"bottom attached\"].segment > [class*=\"top attached\"].label {\n  border-top-left-radius: 0em;\n  border-top-right-radius: 0em;\n}\n\n.ui[class*=\"top attached\"].segment > [class*=\"bottom attached\"].label {\n  border-bottom-left-radius: 0em;\n  border-bottom-right-radius: 0em;\n}\n\n.ui.attached.segment:not(.top):not(.bottom) > [class*=\"top attached\"].label {\n  border-top-left-radius: 0em;\n  border-top-right-radius: 0em;\n}\n\n.ui.attached.segment:not(.top):not(.bottom) > [class*=\"bottom attached\"].label {\n  border-bottom-left-radius: 0em;\n  border-bottom-right-radius: 0em;\n}\n\n/* Grid */\n\n.ui.page.grid.segment,\n.ui.grid > .row > .ui.segment.column,\n.ui.grid > .ui.segment.column {\n  padding-top: 2em;\n  padding-bottom: 2em;\n}\n\n.ui.grid.segment {\n  margin: 1rem 0em;\n  border-radius: 0.28571429rem;\n}\n\n/* Table */\n\n.ui.basic.table.segment {\n  background: #ffffff;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n}\n\n.ui[class*=\"very basic\"].table.segment {\n  padding: 1em 1em;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*-------------------\n    Placeholder\n--------------------*/\n\n.ui.placeholder.segment {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  -webkit-box-pack: center;\n  -ms-flex-pack: center;\n  justify-content: center;\n  -webkit-box-align: stretch;\n  -ms-flex-align: stretch;\n  align-items: stretch;\n  max-width: initial;\n  -webkit-animation: none;\n  animation: none;\n  overflow: visible;\n  padding: 1em 1em;\n  min-height: 18rem;\n  background: #f9fafb;\n  border-color: rgba(34, 36, 38, 0.15);\n  -webkit-box-shadow: 0px 2px 25px 0 rgba(34, 36, 38, 0.05) inset;\n  box-shadow: 0px 2px 25px 0 rgba(34, 36, 38, 0.05) inset;\n}\n\n.ui.placeholder.segment .button,\n.ui.placeholder.segment textarea {\n  display: block;\n}\n\n.ui.placeholder.segment .field,\n.ui.placeholder.segment textarea,\n.ui.placeholder.segment > .ui.input,\n.ui.placeholder.segment .button {\n  max-width: 15rem;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.ui.placeholder.segment .column .button,\n.ui.placeholder.segment .column .field,\n.ui.placeholder.segment .column textarea,\n.ui.placeholder.segment .column > .ui.input {\n  max-width: 15rem;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.ui.placeholder.segment > .inline {\n  -ms-flex-item-align: center;\n  align-self: center;\n}\n\n.ui.placeholder.segment > .inline > .button {\n  display: inline-block;\n  width: auto;\n  margin: 0px 0.35714286rem 0px 0px;\n}\n\n.ui.placeholder.segment > .inline > .button:last-child {\n  margin-right: 0px;\n}\n\n/*-------------------\n        Piled\n--------------------*/\n\n.ui.piled.segments,\n.ui.piled.segment {\n  margin: 3em 0em;\n  -webkit-box-shadow: \"\";\n  box-shadow: \"\";\n  z-index: auto;\n}\n\n.ui.piled.segment:first-child {\n  margin-top: 0em;\n}\n\n.ui.piled.segment:last-child {\n  margin-bottom: 0em;\n}\n\n.ui.piled.segments:after,\n.ui.piled.segments:before,\n.ui.piled.segment:after,\n.ui.piled.segment:before {\n  background-color: #ffffff;\n  visibility: visible;\n  content: \"\";\n  display: block;\n  height: 100%;\n  left: 0px;\n  position: absolute;\n  width: 100%;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  -webkit-box-shadow: \"\";\n  box-shadow: \"\";\n}\n\n.ui.piled.segments:before,\n.ui.piled.segment:before {\n  -webkit-transform: rotate(-1.2deg);\n  transform: rotate(-1.2deg);\n  top: 0;\n  z-index: -2;\n}\n\n.ui.piled.segments:after,\n.ui.piled.segment:after {\n  -webkit-transform: rotate(1.2deg);\n  transform: rotate(1.2deg);\n  top: 0;\n  z-index: -1;\n}\n\n/* Piled Attached */\n\n.ui[class*=\"top attached\"].piled.segment {\n  margin-top: 3em;\n  margin-bottom: 0em;\n}\n\n.ui.piled.segment[class*=\"top attached\"]:first-child {\n  margin-top: 0em;\n}\n\n.ui.piled.segment[class*=\"bottom attached\"] {\n  margin-top: 0em;\n  margin-bottom: 3em;\n}\n\n.ui.piled.segment[class*=\"bottom attached\"]:last-child {\n  margin-bottom: 0em;\n}\n\n/*-------------------\n      Stacked\n--------------------*/\n\n.ui.stacked.segment {\n  padding-bottom: 1.4em;\n}\n\n.ui.stacked.segments:before,\n.ui.stacked.segments:after,\n.ui.stacked.segment:before,\n.ui.stacked.segment:after {\n  content: \"\";\n  position: absolute;\n  bottom: -3px;\n  left: 0%;\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n  background: rgba(0, 0, 0, 0.03);\n  width: 100%;\n  height: 6px;\n  visibility: visible;\n}\n\n.ui.stacked.segments:before,\n.ui.stacked.segment:before {\n  display: none;\n}\n\n/* Add additional page */\n\n.ui.tall.stacked.segments:before,\n.ui.tall.stacked.segment:before {\n  display: block;\n  bottom: 0px;\n}\n\n/* Inverted */\n\n.ui.stacked.inverted.segments:before,\n.ui.stacked.inverted.segments:after,\n.ui.stacked.inverted.segment:before,\n.ui.stacked.inverted.segment:after {\n  background-color: rgba(0, 0, 0, 0.03);\n  border-top: 1px solid rgba(34, 36, 38, 0.35);\n}\n\n/*-------------------\n      Padded\n--------------------*/\n\n.ui.padded.segment {\n  padding: 1.5em;\n}\n\n.ui[class*=\"very padded\"].segment {\n  padding: 3em;\n}\n\n/* Padded vertical */\n\n.ui.padded.segment.vertical.segment,\n.ui[class*=\"very padded\"].vertical.segment {\n  padding-left: 0px;\n  padding-right: 0px;\n}\n\n/*-------------------\n      Compact\n--------------------*/\n\n.ui.compact.segment {\n  display: table;\n}\n\n/* Compact Group */\n\n.ui.compact.segments {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n}\n\n.ui.compact.segments .segment,\n.ui.segments .compact.segment {\n  display: block;\n  -webkit-box-flex: 0;\n  -ms-flex: 0 1 auto;\n  flex: 0 1 auto;\n}\n\n/*-------------------\n      Circular\n--------------------*/\n\n.ui.circular.segment {\n  display: table-cell;\n  padding: 2em;\n  text-align: center;\n  vertical-align: middle;\n  border-radius: 500em;\n}\n\n/*-------------------\n      Raised\n--------------------*/\n\n.ui.raised.segments,\n.ui.raised.segment {\n  -webkit-box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n}\n\n/*******************************\n            Groups\n*******************************/\n\n/* Group */\n\n.ui.segments {\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  position: relative;\n  margin: 1rem 0em;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n  border-radius: 0.28571429rem;\n}\n\n.ui.segments:first-child {\n  margin-top: 0em;\n}\n\n.ui.segments:last-child {\n  margin-bottom: 0em;\n}\n\n/* Nested Segment */\n\n.ui.segments > .segment {\n  top: 0px;\n  bottom: 0px;\n  border-radius: 0px;\n  margin: 0em;\n  width: auto;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border: none;\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.segments:not(.horizontal) > .segment:first-child {\n  border-top: none;\n  margin-top: 0em;\n  bottom: 0px;\n  margin-bottom: 0em;\n  top: 0px;\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n/* Bottom */\n\n.ui.segments:not(.horizontal) > .segment:last-child {\n  top: 0px;\n  bottom: 0px;\n  margin-top: 0em;\n  margin-bottom: 0em;\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), none;\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), none;\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n/* Only */\n\n.ui.segments:not(.horizontal) > .segment:only-child {\n  border-radius: 0.28571429rem;\n}\n\n/* Nested Group */\n\n.ui.segments > .ui.segments {\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n  margin: 1rem 1rem;\n}\n\n.ui.segments > .segments:first-child {\n  border-top: none;\n}\n\n.ui.segments > .segment + .segments:not(.horizontal) {\n  margin-top: 0em;\n}\n\n/* Horizontal Group */\n\n.ui.horizontal.segments {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n  background-color: transparent;\n  border-radius: 0px;\n  padding: 0em;\n  background-color: #ffffff;\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n  margin: 1rem 0em;\n  border-radius: 0.28571429rem;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n/* Nested Horizontal Group */\n\n.ui.segments > .horizontal.segments {\n  margin: 0em;\n  background-color: transparent;\n  border-radius: 0px;\n  border: none;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n/* Horizontal Segment */\n\n.ui.horizontal.segments > .segment {\n  -webkit-box-flex: 1;\n  flex: 1 1 auto;\n  -ms-flex: 1 1 0px;\n  /* Solves #2550 MS Flex */\n  margin: 0em;\n  min-width: 0px;\n  background-color: transparent;\n  border-radius: 0px;\n  border: none;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n/* Border Fixes */\n\n.ui.segments > .horizontal.segments:first-child {\n  border-top: none;\n}\n\n.ui.horizontal.segments > .segment:first-child {\n  border-left: none;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------\n    Disabled\n---------------*/\n\n.ui.disabled.segment {\n  opacity: 0.45;\n  color: rgba(40, 40, 40, 0.3);\n}\n\n/*--------------\n    Loading\n---------------*/\n\n.ui.loading.segment {\n  position: relative;\n  cursor: default;\n  pointer-events: none;\n  text-shadow: none !important;\n  color: transparent !important;\n  -webkit-transition: all 0s linear;\n  transition: all 0s linear;\n}\n\n.ui.loading.segment:before {\n  position: absolute;\n  content: \"\";\n  top: 0%;\n  left: 0%;\n  background: rgba(255, 255, 255, 0.8);\n  width: 100%;\n  height: 100%;\n  border-radius: 0.28571429rem;\n  z-index: 100;\n}\n\n.ui.loading.segment:after {\n  position: absolute;\n  content: \"\";\n  top: 50%;\n  left: 50%;\n  margin: -1.5em 0em 0em -1.5em;\n  width: 3em;\n  height: 3em;\n  -webkit-animation: segment-spin 0.6s linear;\n  animation: segment-spin 0.6s linear;\n  -webkit-animation-iteration-count: infinite;\n  animation-iteration-count: infinite;\n  border-radius: 500rem;\n  border-color: #767676 rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1);\n  border-style: solid;\n  border-width: 0.2em;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent;\n  box-shadow: 0px 0px 0px 1px transparent;\n  visibility: visible;\n  z-index: 101;\n}\n\n@-webkit-keyframes segment-spin {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes segment-spin {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*-------------------\n      Basic\n--------------------*/\n\n.ui.basic.segment {\n  background: none transparent;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border: none;\n  border-radius: 0px;\n}\n\n/*-------------------\n      Clearing\n--------------------*/\n\n.ui.clearing.segment:after {\n  content: \".\";\n  display: block;\n  height: 0;\n  clear: both;\n  visibility: hidden;\n}\n\n/*-------------------\n      Colors\n--------------------*/\n\n/* Red */\n\n.ui.red.segment:not(.inverted) {\n  border-top: 2px solid #db2828 !important;\n}\n\n.ui.inverted.red.segment {\n  background-color: #db2828 !important;\n  color: #ffffff !important;\n}\n\n/* Orange */\n\n.ui.orange.segment:not(.inverted) {\n  border-top: 2px solid #f2711c !important;\n}\n\n.ui.inverted.orange.segment {\n  background-color: #f2711c !important;\n  color: #ffffff !important;\n}\n\n/* Yellow */\n\n.ui.yellow.segment:not(.inverted) {\n  border-top: 2px solid #fbbd08 !important;\n}\n\n.ui.inverted.yellow.segment {\n  background-color: #fbbd08 !important;\n  color: #ffffff !important;\n}\n\n/* Olive */\n\n.ui.olive.segment:not(.inverted) {\n  border-top: 2px solid #b5cc18 !important;\n}\n\n.ui.inverted.olive.segment {\n  background-color: #b5cc18 !important;\n  color: #ffffff !important;\n}\n\n/* Green */\n\n.ui.green.segment:not(.inverted) {\n  border-top: 2px solid #21ba45 !important;\n}\n\n.ui.inverted.green.segment {\n  background-color: #21ba45 !important;\n  color: #ffffff !important;\n}\n\n/* Teal */\n\n.ui.teal.segment:not(.inverted) {\n  border-top: 2px solid #00b5ad !important;\n}\n\n.ui.inverted.teal.segment {\n  background-color: #00b5ad !important;\n  color: #ffffff !important;\n}\n\n/* Blue */\n\n.ui.blue.segment:not(.inverted) {\n  border-top: 2px solid #2185d0 !important;\n}\n\n.ui.inverted.blue.segment {\n  background-color: #2185d0 !important;\n  color: #ffffff !important;\n}\n\n/* Violet */\n\n.ui.violet.segment:not(.inverted) {\n  border-top: 2px solid #6435c9 !important;\n}\n\n.ui.inverted.violet.segment {\n  background-color: #6435c9 !important;\n  color: #ffffff !important;\n}\n\n/* Purple */\n\n.ui.purple.segment:not(.inverted) {\n  border-top: 2px solid #a333c8 !important;\n}\n\n.ui.inverted.purple.segment {\n  background-color: #a333c8 !important;\n  color: #ffffff !important;\n}\n\n/* Pink */\n\n.ui.pink.segment:not(.inverted) {\n  border-top: 2px solid #e03997 !important;\n}\n\n.ui.inverted.pink.segment {\n  background-color: #e03997 !important;\n  color: #ffffff !important;\n}\n\n/* Brown */\n\n.ui.brown.segment:not(.inverted) {\n  border-top: 2px solid #a5673f !important;\n}\n\n.ui.inverted.brown.segment {\n  background-color: #a5673f !important;\n  color: #ffffff !important;\n}\n\n/* Grey */\n\n.ui.grey.segment:not(.inverted) {\n  border-top: 2px solid #767676 !important;\n}\n\n.ui.inverted.grey.segment {\n  background-color: #767676 !important;\n  color: #ffffff !important;\n}\n\n/* Black */\n\n.ui.black.segment:not(.inverted) {\n  border-top: 2px solid #1b1c1d !important;\n}\n\n.ui.inverted.black.segment {\n  background-color: #1b1c1d !important;\n  color: #ffffff !important;\n}\n\n/*-------------------\n      Aligned\n--------------------*/\n\n.ui[class*=\"left aligned\"].segment {\n  text-align: left;\n}\n\n.ui[class*=\"right aligned\"].segment {\n  text-align: right;\n}\n\n.ui[class*=\"center aligned\"].segment {\n  text-align: center;\n}\n\n/*-------------------\n      Floated\n--------------------*/\n\n.ui.floated.segment,\n.ui[class*=\"left floated\"].segment {\n  float: left;\n  margin-right: 1em;\n}\n\n.ui[class*=\"right floated\"].segment {\n  float: right;\n  margin-left: 1em;\n}\n\n/*-------------------\n      Inverted\n--------------------*/\n\n.ui.inverted.segment {\n  border: none;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.inverted.segment,\n.ui.primary.inverted.segment {\n  background: #1b1c1d;\n  color: rgba(255, 255, 255, 0.9);\n}\n\n/* Nested */\n\n.ui.inverted.segment .segment {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.inverted.segment .inverted.segment {\n  color: rgba(255, 255, 255, 0.9);\n}\n\n/* Attached */\n\n.ui.inverted.attached.segment {\n  border-color: #555555;\n}\n\n/*-------------------\n    Emphasis\n--------------------*/\n\n/* Secondary */\n\n.ui.secondary.segment {\n  background: #f3f4f5;\n  color: rgba(0, 0, 0, 0.6);\n}\n\n.ui.secondary.inverted.segment {\n  background: #4c4f52 -webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.2)), to(rgba(255, 255, 255, 0.2)));\n  background: #4c4f52 -webkit-linear-gradient(rgba(255, 255, 255, 0.2) 0%, rgba(255,\n        255,\n        255,\n        0.2) 100%);\n  background: #4c4f52 linear-gradient(rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.2) 100%);\n  color: rgba(255, 255, 255, 0.8);\n}\n\n/* Tertiary */\n\n.ui.tertiary.segment {\n  background: #dcddde;\n  color: rgba(0, 0, 0, 0.6);\n}\n\n.ui.tertiary.inverted.segment {\n  background: #717579 -webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.35)), to(rgba(255, 255, 255, 0.35)));\n  background: #717579 -webkit-linear-gradient(rgba(255, 255, 255, 0.35) 0%, rgba(255,\n        255,\n        255,\n        0.35) 100%);\n  background: #717579 linear-gradient(rgba(255, 255, 255, 0.35) 0%,\n      rgba(255, 255, 255, 0.35) 100%);\n  color: rgba(255, 255, 255, 0.8);\n}\n\n/*-------------------\n      Attached\n--------------------*/\n\n/* Middle */\n\n.ui.attached.segment {\n  top: 0px;\n  bottom: 0px;\n  border-radius: 0px;\n  margin: 0em -1px;\n  width: calc(100% + 2px);\n  max-width: calc(100% + 2px);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border: 1px solid #d4d4d5;\n}\n\n.ui.attached:not(.message) + .ui.attached.segment:not(.top) {\n  border-top: none;\n}\n\n/* Top */\n\n.ui[class*=\"top attached\"].segment {\n  bottom: 0px;\n  margin-bottom: 0em;\n  top: 0px;\n  margin-top: 1rem;\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n.ui.segment[class*=\"top attached\"]:first-child {\n  margin-top: 0em;\n}\n\n/* Bottom */\n\n.ui.segment[class*=\"bottom attached\"] {\n  bottom: 0px;\n  margin-top: 0em;\n  top: 0px;\n  margin-bottom: 1rem;\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), none;\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), none;\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n.ui.segment[class*=\"bottom attached\"]:last-child {\n  margin-bottom: 0em;\n}\n\n/*-------------------\n        Size\n--------------------*/\n\n.ui.mini.segments .segment,\n.ui.mini.segment {\n  font-size: 0.78571429rem;\n}\n\n.ui.tiny.segments .segment,\n.ui.tiny.segment {\n  font-size: 0.85714286rem;\n}\n\n.ui.small.segments .segment,\n.ui.small.segment {\n  font-size: 0.92857143rem;\n}\n\n.ui.segments .segment,\n.ui.segment {\n  font-size: 1rem;\n}\n\n.ui.large.segments .segment,\n.ui.large.segment {\n  font-size: 1.14285714rem;\n}\n\n.ui.big.segments .segment,\n.ui.big.segment {\n  font-size: 1.28571429rem;\n}\n\n.ui.huge.segments .segment,\n.ui.huge.segment {\n  font-size: 1.42857143rem;\n}\n\n.ui.massive.segments .segment,\n.ui.massive.segment {\n  font-size: 1.71428571rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Step\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Plural\n*******************************/\n\n.ui.steps {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n  -webkit-box-align: stretch;\n  -ms-flex-align: stretch;\n  align-items: stretch;\n  margin: 1em 0em;\n  background: \"\";\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  line-height: 1.14285714em;\n  border-radius: 0.28571429rem;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n/* First Steps */\n\n.ui.steps:first-child {\n  margin-top: 0em;\n}\n\n/* Last Steps */\n\n.ui.steps:last-child {\n  margin-bottom: 0em;\n}\n\n/*******************************\n          Singular\n*******************************/\n\n.ui.steps .step {\n  position: relative;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-flex: 1;\n  -ms-flex: 1 0 auto;\n  flex: 1 0 auto;\n  -ms-flex-wrap: wrap;\n  flex-wrap: wrap;\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n  vertical-align: middle;\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n  -webkit-box-pack: center;\n  -ms-flex-pack: center;\n  justify-content: center;\n  margin: 0em 0em;\n  padding: 1.14285714em 2em;\n  background: #ffffff;\n  color: rgba(0, 0, 0, 0.87);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border-radius: 0em;\n  border: none;\n  border-right: 1px solid rgba(34, 36, 38, 0.15);\n  -webkit-transition: background-color 0.1s ease, opacity 0.1s ease,\n    color 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: background-color 0.1s ease, opacity 0.1s ease, color 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n  transition: background-color 0.1s ease, opacity 0.1s ease, color 0.1s ease,\n    box-shadow 0.1s ease;\n  transition: background-color 0.1s ease, opacity 0.1s ease, color 0.1s ease,\n    box-shadow 0.1s ease, -webkit-box-shadow 0.1s ease;\n}\n\n/* Arrow */\n\n.ui.steps .step:after {\n  display: none;\n  position: absolute;\n  z-index: 2;\n  content: \"\";\n  top: 50%;\n  right: 0%;\n  border: medium none;\n  background-color: #ffffff;\n  width: 1.14285714em;\n  height: 1.14285714em;\n  border-style: solid;\n  border-color: rgba(34, 36, 38, 0.15);\n  border-width: 0px 1px 1px 0px;\n  -webkit-transition: background-color 0.1s ease, opacity 0.1s ease,\n    color 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: background-color 0.1s ease, opacity 0.1s ease, color 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n  transition: background-color 0.1s ease, opacity 0.1s ease, color 0.1s ease,\n    box-shadow 0.1s ease;\n  transition: background-color 0.1s ease, opacity 0.1s ease, color 0.1s ease,\n    box-shadow 0.1s ease, -webkit-box-shadow 0.1s ease;\n  -webkit-transform: translateY(-50%) translateX(50%) rotate(-45deg);\n  transform: translateY(-50%) translateX(50%) rotate(-45deg);\n}\n\n/* First Step */\n\n.ui.steps .step:first-child {\n  padding-left: 2em;\n  border-radius: 0.28571429rem 0em 0em 0.28571429rem;\n}\n\n/* Last Step */\n\n.ui.steps .step:last-child {\n  border-radius: 0em 0.28571429rem 0.28571429rem 0em;\n}\n\n.ui.steps .step:last-child {\n  border-right: none;\n  margin-right: 0em;\n}\n\n/* Only Step */\n\n.ui.steps .step:only-child {\n  border-radius: 0.28571429rem;\n}\n\n/*******************************\n            Content\n*******************************/\n\n/* Title */\n\n.ui.steps .step .title {\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-size: 1.14285714em;\n  font-weight: bold;\n}\n\n.ui.steps .step > .title {\n  width: 100%;\n}\n\n/* Description */\n\n.ui.steps .step .description {\n  font-weight: normal;\n  font-size: 0.92857143em;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.steps .step > .description {\n  width: 100%;\n}\n\n.ui.steps .step .title ~ .description {\n  margin-top: 0.25em;\n}\n\n/* Icon */\n\n.ui.steps .step > .icon {\n  line-height: 1;\n  font-size: 2.5em;\n  margin: 0em 1rem 0em 0em;\n}\n\n.ui.steps .step > .icon,\n.ui.steps .step > .icon ~ .content {\n  display: block;\n  -webkit-box-flex: 0;\n  -ms-flex: 0 1 auto;\n  flex: 0 1 auto;\n  -ms-flex-item-align: middle;\n  align-self: middle;\n}\n\n.ui.steps .step > .icon ~ .content {\n  -webkit-box-flex: 1 0 auto;\n  -ms-flex-positive: 1 0 auto;\n  flex-grow: 1 0 auto;\n}\n\n/* Horizontal Icon */\n\n.ui.steps:not(.vertical) .step > .icon {\n  width: auto;\n}\n\n/* Link */\n\n.ui.steps .link.step,\n.ui.steps a.step {\n  cursor: pointer;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*--------------\n    Ordered\n---------------*/\n\n.ui.ordered.steps {\n  counter-reset: ordered;\n}\n\n.ui.ordered.steps .step:before {\n  display: block;\n  position: static;\n  text-align: center;\n  content: counters(ordered, \".\");\n  -ms-flex-item-align: middle;\n  align-self: middle;\n  margin-right: 1rem;\n  font-size: 2.5em;\n  counter-increment: ordered;\n  font-family: inherit;\n  font-weight: bold;\n}\n\n.ui.ordered.steps .step > * {\n  display: block;\n  -ms-flex-item-align: middle;\n  align-self: middle;\n}\n\n/*--------------\n    Vertical\n---------------*/\n\n.ui.vertical.steps {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  overflow: visible;\n}\n\n.ui.vertical.steps .step {\n  -webkit-box-pack: start;\n  -ms-flex-pack: start;\n  justify-content: flex-start;\n  border-radius: 0em;\n  padding: 1.14285714em 2em;\n  border-right: none;\n  border-bottom: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.vertical.steps .step:first-child {\n  padding: 1.14285714em 2em;\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n.ui.vertical.steps .step:last-child {\n  border-bottom: none;\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n.ui.vertical.steps .step:only-child {\n  border-radius: 0.28571429rem;\n}\n\n/* Arrow */\n\n.ui.vertical.steps .step:after {\n  display: none;\n}\n\n.ui.vertical.steps .step:after {\n  top: 50%;\n  right: 0%;\n  border-width: 0px 1px 1px 0px;\n}\n\n.ui.vertical.steps .step:after {\n  display: none;\n}\n\n.ui.vertical.steps .active.step:after {\n  display: block;\n}\n\n.ui.vertical.steps .step:last-child:after {\n  display: none;\n}\n\n.ui.vertical.steps .active.step:last-child:after {\n  display: block;\n}\n\n/*---------------\n    Responsive\n----------------*/\n\n/* Mobile (Default) */\n\n@media only screen and (width < 768px) {\n  .ui.steps:not(.unstackable) {\n    display: -webkit-inline-box;\n    display: -ms-inline-flexbox;\n    display: inline-flex;\n    overflow: visible;\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    -ms-flex-direction: column;\n    flex-direction: column;\n  }\n\n  .ui.steps:not(.unstackable) .step {\n    width: 100% !important;\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    -ms-flex-direction: column;\n    flex-direction: column;\n    border-radius: 0em;\n    padding: 1.14285714em 2em;\n  }\n\n  .ui.steps:not(.unstackable) .step:first-child {\n    padding: 1.14285714em 2em;\n    border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n  }\n\n  .ui.steps:not(.unstackable) .step:last-child {\n    border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n  }\n\n  /* Arrow */\n\n  .ui.steps:not(.unstackable) .step:after {\n    display: none !important;\n  }\n\n  /* Content */\n\n  .ui.steps:not(.unstackable) .step .content {\n    text-align: center;\n  }\n\n  /* Icon */\n\n  .ui.steps:not(.unstackable) .step > .icon,\n  .ui.ordered.steps:not(.unstackable) .step:before {\n    margin: 0em 0em 1rem 0em;\n  }\n}\n\n/*******************************\n            States\n*******************************/\n\n/* Link Hover */\n\n.ui.steps .link.step:hover::after,\n.ui.steps .link.step:hover,\n.ui.steps a.step:hover::after,\n.ui.steps a.step:hover {\n  background: #f9fafb;\n  color: rgba(0, 0, 0, 0.8);\n}\n\n/* Link Down */\n\n.ui.steps .link.step:active::after,\n.ui.steps .link.step:active,\n.ui.steps a.step:active::after,\n.ui.steps a.step:active {\n  background: #f3f4f5;\n  color: rgba(0, 0, 0, 0.9);\n}\n\n/* Active */\n\n.ui.steps .step.active {\n  cursor: auto;\n  background: #f3f4f5;\n}\n\n.ui.steps .step.active:after {\n  background: #f3f4f5;\n}\n\n.ui.steps .step.active .title {\n  color: #4183c4;\n}\n\n.ui.ordered.steps .step.active:before,\n.ui.steps .active.step .icon {\n  color: rgba(0, 0, 0, 0.85);\n}\n\n/* Active Arrow */\n\n.ui.steps .step:after {\n  display: block;\n}\n\n.ui.steps .active.step:after {\n  display: block;\n}\n\n.ui.steps .step:last-child:after {\n  display: none;\n}\n\n.ui.steps .active.step:last-child:after {\n  display: none;\n}\n\n/* Active Hover */\n\n.ui.steps .link.active.step:hover::after,\n.ui.steps .link.active.step:hover,\n.ui.steps a.active.step:hover::after,\n.ui.steps a.active.step:hover {\n  cursor: pointer;\n  background: #dcddde;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* Completed */\n\n.ui.steps .step.completed > .icon:before,\n.ui.ordered.steps .step.completed:before {\n  color: #21ba45;\n}\n\n/* Disabled */\n\n.ui.steps .disabled.step {\n  cursor: auto;\n  background: #ffffff;\n  pointer-events: none;\n}\n\n.ui.steps .disabled.step,\n.ui.steps .disabled.step .title,\n.ui.steps .disabled.step .description {\n  color: rgba(40, 40, 40, 0.3);\n}\n\n.ui.steps .disabled.step:after {\n  background: #ffffff;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n  Stackable\n---------------*/\n\n/* Tablet Or Below */\n\n@media only screen and (width < 992px) {\n  .ui[class*=\"tablet stackable\"].steps {\n    display: -webkit-inline-box;\n    display: -ms-inline-flexbox;\n    display: inline-flex;\n    overflow: visible;\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    -ms-flex-direction: column;\n    flex-direction: column;\n  }\n\n  /* Steps */\n\n  .ui[class*=\"tablet stackable\"].steps .step {\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    -ms-flex-direction: column;\n    flex-direction: column;\n    border-radius: 0em;\n    padding: 1.14285714em 2em;\n  }\n\n  .ui[class*=\"tablet stackable\"].steps .step:first-child {\n    padding: 1.14285714em 2em;\n    border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n  }\n\n  .ui[class*=\"tablet stackable\"].steps .step:last-child {\n    border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n  }\n\n  /* Arrow */\n\n  .ui[class*=\"tablet stackable\"].steps .step:after {\n    display: none !important;\n  }\n\n  /* Content */\n\n  .ui[class*=\"tablet stackable\"].steps .step .content {\n    text-align: center;\n  }\n\n  /* Icon */\n\n  .ui[class*=\"tablet stackable\"].steps .step > .icon,\n  .ui[class*=\"tablet stackable\"].ordered.steps .step:before {\n    margin: 0em 0em 1rem 0em;\n  }\n}\n\n/*--------------\n      Fluid\n---------------*/\n\n/* Fluid */\n\n.ui.fluid.steps {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  width: 100%;\n}\n\n/*--------------\n    Attached\n---------------*/\n\n/* Top */\n\n.ui.attached.steps {\n  width: calc(100% + 2px) !important;\n  margin: 0em -1px 0;\n  max-width: calc(100% + 2px);\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n.ui.attached.steps .step:first-child {\n  border-radius: 0.28571429rem 0em 0em 0em;\n}\n\n.ui.attached.steps .step:last-child {\n  border-radius: 0em 0.28571429rem 0em 0em;\n}\n\n/* Bottom */\n\n.ui.bottom.attached.steps {\n  margin: 0 -1px 0em;\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n.ui.bottom.attached.steps .step:first-child {\n  border-radius: 0em 0em 0em 0.28571429rem;\n}\n\n.ui.bottom.attached.steps .step:last-child {\n  border-radius: 0em 0em 0.28571429rem 0em;\n}\n\n/*-------------------\n    Evenly Divided\n--------------------*/\n\n.ui.one.steps,\n.ui.two.steps,\n.ui.three.steps,\n.ui.four.steps,\n.ui.five.steps,\n.ui.six.steps,\n.ui.seven.steps,\n.ui.eight.steps {\n  width: 100%;\n}\n\n.ui.one.steps > .step,\n.ui.two.steps > .step,\n.ui.three.steps > .step,\n.ui.four.steps > .step,\n.ui.five.steps > .step,\n.ui.six.steps > .step,\n.ui.seven.steps > .step,\n.ui.eight.steps > .step {\n  -ms-flex-wrap: nowrap;\n  flex-wrap: nowrap;\n}\n\n.ui.one.steps > .step {\n  width: 100%;\n}\n\n.ui.two.steps > .step {\n  width: 50%;\n}\n\n.ui.three.steps > .step {\n  width: 33.333%;\n}\n\n.ui.four.steps > .step {\n  width: 25%;\n}\n\n.ui.five.steps > .step {\n  width: 20%;\n}\n\n.ui.six.steps > .step {\n  width: 16.666%;\n}\n\n.ui.seven.steps > .step {\n  width: 14.285%;\n}\n\n.ui.eight.steps > .step {\n  width: 12.5%;\n}\n\n/*-------------------\n      Sizes\n--------------------*/\n\n.ui.mini.steps .step,\n.ui.mini.step {\n  font-size: 0.78571429rem;\n}\n\n.ui.tiny.steps .step,\n.ui.tiny.step {\n  font-size: 0.85714286rem;\n}\n\n.ui.small.steps .step,\n.ui.small.step {\n  font-size: 0.92857143rem;\n}\n\n.ui.steps .step,\n.ui.step {\n  font-size: 1rem;\n}\n\n.ui.large.steps .step,\n.ui.large.step {\n  font-size: 1.14285714rem;\n}\n\n.ui.big.steps .step,\n.ui.big.step {\n  font-size: 1.28571429rem;\n}\n\n.ui.huge.steps .step,\n.ui.huge.step {\n  font-size: 1.42857143rem;\n}\n\n.ui.massive.steps .step,\n.ui.massive.step {\n  font-size: 1.71428571rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n@font-face {\n  font-family: \"Step\";\n  src: url(data:application/x-font-ttf;charset=utf-8;base64,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) format(\"truetype\"),\n    url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAoUAA4AAAAAEPQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAEQAAABWPeFJAWNtYXAAAAGIAAAAOgAAAUrQEhm3Y3Z0IAAAAcQAAAAUAAAAHAZJ/5RmcGdtAAAB2AAABPkAAAmRigp4O2dhc3AAAAbUAAAACAAAAAgAAAAQZ2x5ZgAABtwAAACuAAAAtt9nBHZoZWFkAAAHjAAAADUAAAA2ASs8e2hoZWEAAAfEAAAAIAAAACQHUwNNaG10eAAAB+QAAAAMAAAADAspAABsb2NhAAAH8AAAAAgAAAAIADgAW21heHAAAAf4AAAAIAAAACAApgm8bmFtZQAACBgAAAF3AAACzcydGhxwb3N0AAAJkAAAACoAAAA7rr1AmHByZXAAAAm8AAAAVgAAAFaSoZr/eJxjYGTewTiBgZWBg6mKaQ8DA0MPhGZ8wGDIyMTAwMTAysyAFQSkuaYwOLxgeMHIHPQ/iyGKmZvBHyjMCJIDAPe9C2B4nGNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZgYGF4w/v8PUvCCAURLMELVAwEjG8OIBwBk5AavAAB4nGNgQANGDEbM3P83gjAAELQD4XicnVXZdtNWFJU8ZHASOmSgoA7X3DhQ68qEKRgwaSrFdiEdHAitBB2kDHTkncc+62uOQrtWH/m07n09JLR0rbYsls++R1tn2DrnRhwjKn0aiGvUoZKXA6msPZZK90lc13Uvj5UMBnFdthJPSZuonSRKat3sUC7xWOsqWSdYJ+PlIFZPVZ5noAziFB5lSUQbRBuplyZJ4onjJ4kWZxAfJUkgJaMQp9LIUEI1GsRS1aFM6dCr1xNx00DKRqMedVhU90PFJ8c1p9SsA0YqVznCFevVRr4bpwMve5DEOsGzrYcxHnisfpQqkIqR6cg/dkpOlIaBVHHUoVbi6DCTX/eRTCrNQKaMYkWl7oG43f102xYxPXQ6vi5KlUaqurnOKJrt0fGogygP2cbppNzQ2fbw5RlTVKtdcbPtQGYNXErJbHSfRAAdJlLj6QFONZwCqRn1R8XZ588BEslclKo8VTKHegOZMzt7cTHtbiersnCknwcyb3Z2452HQ6dXh3/R+hdM4cxHj+Jifj5C+lBqfiJOJKVGWMzyp4YfcVcgQrkxiAsXyuBThDl0RdrZZl3jtTH2hs/5SqlhPQna6KP4fgr9TiQrHGdRo/VInM1j13Wt3GdQS7W7Fzsyr0OVIu7vCwuuM+eEYZ4WC1VfnvneBTT/Bohn/EDeNIVL+5YpSrRvm6JMu2iKCu0SVKVdNsUU7YoppmnPmmKG9h1TzNKeMzLj/8vc55H7HN7xkJv2XeSmfQ+5ad9HbtoPkJtWITdtHblpLyA3rUZu2lWjOnYEGgZpF1IVQdA0svph3Fab9UDWjDR8aWDyLmLI+upER521tcofxX914gsHcmmip7siF5viLq/bFj483e6rj5pG3bDV+MaR8jAeRnocmtBZ+c3hv+1N3S6a7jKqMugBFUwKwABl7UAC0zrbCaT1mqf48gdgXIZ4zkpDtVSfO4am7+V5X/exOfG+x+3GLrdcd3kJWdYNcmP28N9SZKrrH+UtrVQnR6wrJ49VaxhDKrwour6SlHu0tRu/KKmy8l6U1srnk5CbPYMbQlu27mGwI0xpyiUeXlOlKD3UUo6yQyxvKco84JSLC1qGxLgOdQ9qa8TpoXoYGwshhqG0vRBwSCldFd+0ynfxHqtr2Oj4xRXh6XpyEhGf4ir7UfBU10b96A7avGbdMoMpVaqn+4xPsa/b9lFZaaSOsxe3VAfXNOsaORXTT+Rr4HRvOGjdAz1UfDRBI1U1x+jGKGM0ljXl3wR0MVZ+w2jVYvs93E+dpFWsuUuY7JsT9+C0u/0q+7WcW0bW/dcGvW3kip8jMb8tCvw7B2K3ZA3UO5OBGAvIWdAYxhYmdxiug23EbfY/Jqf/34aFRXJXOxq7eerD1ZNRJXfZ8rjLTXZZ16M2R9VOGvsIjS0PN+bY4XIstsRgQbb+wf8x7gF3aVEC4NDIZZiI2nShnurh6h6rsW04VxIBds2x43QAegAuQd8cu9bzCYD13CPnLsB9cgh2yCH4lByCz8i5BfA5OQRfkEMwIIdgl5w7AA/IIXhIDsEeOQSPyNkE+JIcgq/IIYjJIUjIuQ3wmByCJ+QQfE0OwTdGrk5k/pYH2QD6zqKbQKmdGhzaOGRGrk3Y+zxY9oFFZB9aROqRkesT6lMeLPV7i0j9wSJSfzRyY0L9iQdL/dkiUn+xiNRnxpeZIymvDp7zjg7+BJfqrV4AAAAAAQAB//8AD3icY2BkAALmJUwzGEQZZBwk+RkZGBmdGJgYmbIYgMwsoGSiiLgIs5A2owg7I5uSOqOaiT2jmZE8I5gQY17C/09BQEfg3yt+fh8gvYQxD0j68DOJiQn8U+DnZxQDcQUEljLmCwBpBgbG/3//b2SOZ+Zm4GEQcuAH2sblDLSEm8FFVJhJEGgLH6OSHpMdo5EcI3Nk0bEXJ/LYqvZ82VXHGFd6pKTkyCsQwQAAq+QkqAAAeJxjYGRgYADiw5VSsfH8Nl8ZuJlfAEUYzpvO6IXQCb7///7fyLyEmRvI5WBgAokCAFb/DJAAAAB4nGNgZGBgDvqfxRDF/IKB4f935iUMQBEUwAwAi5YFpgPoAAAD6AAAA1kAAAAAAAAAOABbAAEAAAADABYAAQAAAAAAAgAGABMAbgAAAC0JkQAAAAB4nHWQy2rCQBSG//HSi0JbWui2sypKabxgN4IgWHTTbqS4LTHGJBIzMhkFX6Pv0IfpS/RZ+puMpShNmMx3vjlz5mQAXOMbAvnzxJGzwBmjnAs4Rc9ykf7Zcon8YrmMKt4sn9C/W67gAYHlKm7wwQqidM5ogU/LAlfi0nIBF+LOcpH+0XKJ3LNcxq14tXxC71muYCJSy1Xci6+BWm11FIRG1gZ12W62OnK6lYoqStxYumsTKp3KvpyrxPhxrBxPLfc89oN17Op9uJ8nvk4jlciW09yrkZ/42jX+bFc93QRtY+ZyrtVSDm2GXGm18D3jhMasuo3G3/MwgMIKW2hEvKoQBhI12jrnNppooUOaMkMyM8+KkMBFTONizR1htpIy7nPMGSW0PjNisgOP3+WRH5MC7o9ZRR+tHsYT0u6MKPOSfTns7jBrREqyTDezs9/eU2x4WpvWcNeuS511JTE8qCF5H7u1BY1H72S3Ymi7aPD95/9+AN1fhEsAeJxjYGKAAC4G7ICZgYGRiZGZMzkjNTk7N7Eomy05syg5J5WBAQBE1QZBAABLuADIUlixAQGOWbkIAAgAYyCwASNEsAMjcLIEKAlFUkSyCgIHKrEGAUSxJAGIUViwQIhYsQYDRLEmAYhRWLgEAIhYsQYBRFlZWVm4Af+FsASNsQUARAAA) format(\"woff\");\n}\n\n.ui.steps .step.completed > .icon:before,\n.ui.ordered.steps .step.completed:before {\n  font-family: \"Step\";\n  content: \"\\e800\";\n  /* '' */\n}\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Breadcrumb\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n          Breadcrumb\n*******************************/\n\n.ui.breadcrumb {\n  line-height: 1;\n  display: inline-block;\n  margin: 0em 0em;\n  vertical-align: middle;\n}\n\n.ui.breadcrumb:first-child {\n  margin-top: 0em;\n}\n\n.ui.breadcrumb:last-child {\n  margin-bottom: 0em;\n}\n\n/*******************************\n          Content\n*******************************/\n\n/* Divider */\n\n.ui.breadcrumb .divider {\n  display: inline-block;\n  opacity: 0.7;\n  margin: 0em 0.21428571rem 0em;\n  font-size: 0.92857143em;\n  color: rgba(0, 0, 0, 0.4);\n  vertical-align: baseline;\n}\n\n/* Link */\n\n.ui.breadcrumb a {\n  color: #4183c4;\n}\n\n.ui.breadcrumb a:hover {\n  color: #1e70bf;\n}\n\n/* Icon Divider */\n\n.ui.breadcrumb .icon.divider {\n  font-size: 0.85714286em;\n  vertical-align: baseline;\n}\n\n/* Section */\n\n.ui.breadcrumb a.section {\n  cursor: pointer;\n}\n\n.ui.breadcrumb .section {\n  display: inline-block;\n  margin: 0em;\n  padding: 0em;\n}\n\n/* Loose Coupling */\n\n.ui.breadcrumb.segment {\n  display: inline-block;\n  padding: 0.78571429em 1em;\n}\n\n/*******************************\n            States\n*******************************/\n\n.ui.breadcrumb .active.section {\n  font-weight: bold;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n.ui.mini.breadcrumb {\n  font-size: 0.78571429rem;\n}\n\n.ui.tiny.breadcrumb {\n  font-size: 0.85714286rem;\n}\n\n.ui.small.breadcrumb {\n  font-size: 0.92857143rem;\n}\n\n.ui.breadcrumb {\n  font-size: 1rem;\n}\n\n.ui.large.breadcrumb {\n  font-size: 1.14285714rem;\n}\n\n.ui.big.breadcrumb {\n  font-size: 1.28571429rem;\n}\n\n.ui.huge.breadcrumb {\n  font-size: 1.42857143rem;\n}\n\n.ui.massive.breadcrumb {\n  font-size: 1.71428571rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Form\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Elements\n*******************************/\n\n/*--------------------\n        Form\n---------------------*/\n\n.ui.form {\n  position: relative;\n  max-width: 100%;\n}\n\n/*--------------------\n        Content\n---------------------*/\n\n.ui.form > p {\n  margin: 1em 0em;\n}\n\n/*--------------------\n        Field\n---------------------*/\n\n.ui.form .field {\n  clear: both;\n  margin: 0em 0em 1em;\n}\n\n.ui.form .field:last-child,\n.ui.form .fields:last-child .field {\n  margin-bottom: 0em;\n}\n\n.ui.form .fields .field {\n  clear: both;\n  margin: 0em;\n}\n\n/*--------------------\n        Labels\n---------------------*/\n\n.ui.form .field > label {\n  display: block;\n  margin: 0em 0em 0.28571429rem 0em;\n  color: rgba(0, 0, 0, 0.87);\n  font-size: 0.92857143em;\n  font-weight: bold;\n  text-transform: none;\n}\n\n/*--------------------\n    Standard Inputs\n---------------------*/\n\n.ui.form textarea,\n.ui.form input:not([type]),\n.ui.form input[type=\"date\"],\n.ui.form input[type=\"datetime-local\"],\n.ui.form input[type=\"email\"],\n.ui.form input[type=\"number\"],\n.ui.form input[type=\"password\"],\n.ui.form input[type=\"search\"],\n.ui.form input[type=\"tel\"],\n.ui.form input[type=\"time\"],\n.ui.form input[type=\"text\"],\n.ui.form input[type=\"file\"],\n.ui.form input[type=\"url\"] {\n  width: 100%;\n  vertical-align: top;\n}\n\n/* Set max height on unusual input */\n\n.ui.form ::-webkit-datetime-edit,\n.ui.form ::-webkit-inner-spin-button {\n  height: 1.21428571em;\n}\n\n.ui.form input:not([type]),\n.ui.form input[type=\"date\"],\n.ui.form input[type=\"datetime-local\"],\n.ui.form input[type=\"email\"],\n.ui.form input[type=\"number\"],\n.ui.form input[type=\"password\"],\n.ui.form input[type=\"search\"],\n.ui.form input[type=\"tel\"],\n.ui.form input[type=\"time\"],\n.ui.form input[type=\"text\"],\n.ui.form input[type=\"file\"],\n.ui.form input[type=\"url\"] {\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  margin: 0em;\n  outline: none;\n  -webkit-appearance: none;\n  tap-highlight-color: rgba(255, 255, 255, 0);\n  line-height: 1.21428571em;\n  padding: 0.67857143em 1em;\n  font-size: 1em;\n  background: #ffffff;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  color: rgba(0, 0, 0, 0.87);\n  border-radius: 0.28571429rem;\n  -webkit-box-shadow: 0em 0em 0em 0em transparent inset;\n  box-shadow: 0em 0em 0em 0em transparent inset;\n  -webkit-transition: color 0.1s ease, border-color 0.1s ease;\n  transition: color 0.1s ease, border-color 0.1s ease;\n}\n\n/* Text Area */\n\n.ui.form textarea {\n  margin: 0em;\n  -webkit-appearance: none;\n  tap-highlight-color: rgba(255, 255, 255, 0);\n  padding: 0.78571429em 1em;\n  background: #ffffff;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  outline: none;\n  color: rgba(0, 0, 0, 0.87);\n  border-radius: 0.28571429rem;\n  -webkit-box-shadow: 0em 0em 0em 0em transparent inset;\n  box-shadow: 0em 0em 0em 0em transparent inset;\n  -webkit-transition: color 0.1s ease, border-color 0.1s ease;\n  transition: color 0.1s ease, border-color 0.1s ease;\n  font-size: 1em;\n  line-height: 1.2857;\n  resize: vertical;\n}\n\n.ui.form textarea:not([rows]) {\n  height: 12em;\n  min-height: 8em;\n  max-height: 24em;\n}\n\n.ui.form textarea,\n.ui.form input[type=\"checkbox\"] {\n  vertical-align: top;\n}\n\n/*--------------------------\n  Input w/ attached Button\n---------------------------*/\n\n.ui.form input.attached {\n  width: auto;\n}\n\n/*--------------------\n    Basic Select\n---------------------*/\n\n.ui.form select {\n  display: block;\n  height: auto;\n  width: 100%;\n  background: #ffffff;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  border-radius: 0.28571429rem;\n  -webkit-box-shadow: 0em 0em 0em 0em transparent inset;\n  box-shadow: 0em 0em 0em 0em transparent inset;\n  padding: 0.62em 1em;\n  color: rgba(0, 0, 0, 0.87);\n  -webkit-transition: color 0.1s ease, border-color 0.1s ease;\n  transition: color 0.1s ease, border-color 0.1s ease;\n}\n\n/*--------------------\n      Dropdown\n---------------------*/\n\n/* Block */\n\n.ui.form .field > .selection.dropdown {\n  width: 100%;\n}\n\n.ui.form .field > .selection.dropdown > .dropdown.icon {\n  float: right;\n}\n\n/* Inline */\n\n.ui.form .inline.fields .field > .selection.dropdown,\n.ui.form .inline.field > .selection.dropdown {\n  width: auto;\n}\n\n.ui.form .inline.fields .field > .selection.dropdown > .dropdown.icon,\n.ui.form .inline.field > .selection.dropdown > .dropdown.icon {\n  float: none;\n}\n\n/*--------------------\n      UI Input\n---------------------*/\n\n/* Block */\n\n.ui.form .field .ui.input,\n.ui.form .fields .field .ui.input,\n.ui.form .wide.field .ui.input {\n  width: 100%;\n}\n\n/* Inline  */\n\n.ui.form .inline.fields .field:not(.wide) .ui.input,\n.ui.form .inline.field:not(.wide) .ui.input {\n  width: auto;\n  vertical-align: middle;\n}\n\n/* Auto Input */\n\n.ui.form .fields .field .ui.input input,\n.ui.form .field .ui.input input {\n  width: auto;\n}\n\n/* Full Width Input */\n\n.ui.form .ten.fields .ui.input input,\n.ui.form .nine.fields .ui.input input,\n.ui.form .eight.fields .ui.input input,\n.ui.form .seven.fields .ui.input input,\n.ui.form .six.fields .ui.input input,\n.ui.form .five.fields .ui.input input,\n.ui.form .four.fields .ui.input input,\n.ui.form .three.fields .ui.input input,\n.ui.form .two.fields .ui.input input,\n.ui.form .wide.field .ui.input input {\n  -webkit-box-flex: 1;\n  -ms-flex: 1 0 auto;\n  flex: 1 0 auto;\n  width: 0px;\n}\n\n/*--------------------\n  Types of Messages\n---------------------*/\n\n.ui.form .success.message,\n.ui.form .warning.message,\n.ui.form .error.message {\n  display: none;\n}\n\n/* Assumptions */\n\n.ui.form .message:first-child {\n  margin-top: 0px;\n}\n\n/*--------------------\n  Validation Prompt\n---------------------*/\n\n.ui.form .field .prompt.label {\n  white-space: normal;\n  background: #ffffff !important;\n  border: 1px solid #e0b4b4 !important;\n  color: #9f3a38 !important;\n}\n\n.ui.form .inline.fields .field .prompt,\n.ui.form .inline.field .prompt {\n  vertical-align: top;\n  margin: -0.25em 0em -0.5em 0.5em;\n}\n\n.ui.form .inline.fields .field .prompt:before,\n.ui.form .inline.field .prompt:before {\n  border-width: 0px 0px 1px 1px;\n  bottom: auto;\n  right: auto;\n  top: 50%;\n  left: 0em;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------------\n      Autofilled\n---------------------*/\n\n.ui.form .field.field input:-webkit-autofill {\n  -webkit-box-shadow: 0px 0px 0px 100px #fffff0 inset !important;\n  box-shadow: 0px 0px 0px 100px #fffff0 inset !important;\n  border-color: #e5dfa1 !important;\n}\n\n/* Focus */\n\n.ui.form .field.field input:-webkit-autofill:focus {\n  -webkit-box-shadow: 0px 0px 0px 100px #fffff0 inset !important;\n  box-shadow: 0px 0px 0px 100px #fffff0 inset !important;\n  border-color: #d5c315 !important;\n}\n\n/* Error */\n\n.ui.form .error.error input:-webkit-autofill {\n  -webkit-box-shadow: 0px 0px 0px 100px #fffaf0 inset !important;\n  box-shadow: 0px 0px 0px 100px #fffaf0 inset !important;\n  border-color: #e0b4b4 !important;\n}\n\n/*--------------------\n      Placeholder\n---------------------*/\n\n/* browsers require these rules separate */\n\n.ui.form ::-webkit-input-placeholder {\n  color: rgba(191, 191, 191, 0.87);\n}\n\n.ui.form :-ms-input-placeholder {\n  color: rgba(191, 191, 191, 0.87) !important;\n}\n\n.ui.form ::-moz-placeholder {\n  color: rgba(191, 191, 191, 0.87);\n}\n\n.ui.form :focus::-webkit-input-placeholder {\n  color: rgba(115, 115, 115, 0.87);\n}\n\n.ui.form :focus:-ms-input-placeholder {\n  color: rgba(115, 115, 115, 0.87) !important;\n}\n\n.ui.form :focus::-moz-placeholder {\n  color: rgba(115, 115, 115, 0.87);\n}\n\n/* Error Placeholder */\n\n.ui.form .error ::-webkit-input-placeholder {\n  color: #e7bdbc;\n}\n\n.ui.form .error :-ms-input-placeholder {\n  color: #e7bdbc !important;\n}\n\n.ui.form .error ::-moz-placeholder {\n  color: #e7bdbc;\n}\n\n.ui.form .error :focus::-webkit-input-placeholder {\n  color: #da9796;\n}\n\n.ui.form .error :focus:-ms-input-placeholder {\n  color: #da9796 !important;\n}\n\n.ui.form .error :focus::-moz-placeholder {\n  color: #da9796;\n}\n\n/*--------------------\n        Focus\n---------------------*/\n\n.ui.form input:not([type]):focus,\n.ui.form input[type=\"date\"]:focus,\n.ui.form input[type=\"datetime-local\"]:focus,\n.ui.form input[type=\"email\"]:focus,\n.ui.form input[type=\"number\"]:focus,\n.ui.form input[type=\"password\"]:focus,\n.ui.form input[type=\"search\"]:focus,\n.ui.form input[type=\"tel\"]:focus,\n.ui.form input[type=\"time\"]:focus,\n.ui.form input[type=\"text\"]:focus,\n.ui.form input[type=\"file\"]:focus,\n.ui.form input[type=\"url\"]:focus {\n  color: rgba(0, 0, 0, 0.95);\n  border-color: #85b7d9;\n  border-radius: 0.28571429rem;\n  background: #ffffff;\n  -webkit-box-shadow: 0px 0em 0em 0em rgba(34, 36, 38, 0.35) inset;\n  box-shadow: 0px 0em 0em 0em rgba(34, 36, 38, 0.35) inset;\n}\n\n.ui.form textarea:focus {\n  color: rgba(0, 0, 0, 0.95);\n  border-color: #85b7d9;\n  border-radius: 0.28571429rem;\n  background: #ffffff;\n  -webkit-box-shadow: 0px 0em 0em 0em rgba(34, 36, 38, 0.35) inset;\n  box-shadow: 0px 0em 0em 0em rgba(34, 36, 38, 0.35) inset;\n  -webkit-appearance: none;\n}\n\n/*--------------------\n        Success\n---------------------*/\n\n/* On Form */\n\n.ui.form.success .success.message:not(:empty) {\n  display: block;\n}\n\n.ui.form.success .compact.success.message:not(:empty) {\n  display: inline-block;\n}\n\n.ui.form.success .icon.success.message:not(:empty) {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n}\n\n/*--------------------\n        Warning\n---------------------*/\n\n/* On Form */\n\n.ui.form.warning .warning.message:not(:empty) {\n  display: block;\n}\n\n.ui.form.warning .compact.warning.message:not(:empty) {\n  display: inline-block;\n}\n\n.ui.form.warning .icon.warning.message:not(:empty) {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n}\n\n/*--------------------\n        Error\n---------------------*/\n\n/* On Form */\n\n.ui.form.error .error.message:not(:empty) {\n  display: block;\n}\n\n.ui.form.error .compact.error.message:not(:empty) {\n  display: inline-block;\n}\n\n.ui.form.error .icon.error.message:not(:empty) {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n}\n\n/* On Field(s) */\n\n.ui.form .fields.error .field label,\n.ui.form .field.error label,\n.ui.form .fields.error .field .input,\n.ui.form .field.error .input {\n  color: #9f3a38;\n}\n\n.ui.form .fields.error .field .corner.label,\n.ui.form .field.error .corner.label {\n  border-color: #9f3a38;\n  color: #ffffff;\n}\n\n.ui.form .fields.error .field textarea,\n.ui.form .fields.error .field select,\n.ui.form .fields.error .field input:not([type]),\n.ui.form .fields.error .field input[type=\"date\"],\n.ui.form .fields.error .field input[type=\"datetime-local\"],\n.ui.form .fields.error .field input[type=\"email\"],\n.ui.form .fields.error .field input[type=\"number\"],\n.ui.form .fields.error .field input[type=\"password\"],\n.ui.form .fields.error .field input[type=\"search\"],\n.ui.form .fields.error .field input[type=\"tel\"],\n.ui.form .fields.error .field input[type=\"time\"],\n.ui.form .fields.error .field input[type=\"text\"],\n.ui.form .fields.error .field input[type=\"file\"],\n.ui.form .fields.error .field input[type=\"url\"],\n.ui.form .field.error textarea,\n.ui.form .field.error select,\n.ui.form .field.error input:not([type]),\n.ui.form .field.error input[type=\"date\"],\n.ui.form .field.error input[type=\"datetime-local\"],\n.ui.form .field.error input[type=\"email\"],\n.ui.form .field.error input[type=\"number\"],\n.ui.form .field.error input[type=\"password\"],\n.ui.form .field.error input[type=\"search\"],\n.ui.form .field.error input[type=\"tel\"],\n.ui.form .field.error input[type=\"time\"],\n.ui.form .field.error input[type=\"text\"],\n.ui.form .field.error input[type=\"file\"],\n.ui.form .field.error input[type=\"url\"] {\n  background: #fff6f6;\n  border-color: #e0b4b4;\n  color: #9f3a38;\n  border-radius: \"\";\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.form .field.error textarea:focus,\n.ui.form .field.error select:focus,\n.ui.form .field.error input:not([type]):focus,\n.ui.form .field.error input[type=\"date\"]:focus,\n.ui.form .field.error input[type=\"datetime-local\"]:focus,\n.ui.form .field.error input[type=\"email\"]:focus,\n.ui.form .field.error input[type=\"number\"]:focus,\n.ui.form .field.error input[type=\"password\"]:focus,\n.ui.form .field.error input[type=\"search\"]:focus,\n.ui.form .field.error input[type=\"tel\"]:focus,\n.ui.form .field.error input[type=\"time\"]:focus,\n.ui.form .field.error input[type=\"text\"]:focus,\n.ui.form .field.error input[type=\"file\"]:focus,\n.ui.form .field.error input[type=\"url\"]:focus {\n  background: #fff6f6;\n  border-color: #e0b4b4;\n  color: #9f3a38;\n  -webkit-appearance: none;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Preserve Native Select Stylings */\n\n.ui.form .field.error select {\n  -webkit-appearance: menulist-button;\n}\n\n/*------------------\n    Dropdown Error\n--------------------*/\n\n.ui.form .fields.error .field .ui.dropdown,\n.ui.form .fields.error .field .ui.dropdown .item,\n.ui.form .field.error .ui.dropdown,\n.ui.form .field.error .ui.dropdown .text,\n.ui.form .field.error .ui.dropdown .item {\n  background: #fff6f6;\n  color: #9f3a38;\n}\n\n.ui.form .fields.error .field .ui.dropdown,\n.ui.form .field.error .ui.dropdown {\n  border-color: #e0b4b4 !important;\n}\n\n.ui.form .fields.error .field .ui.dropdown:hover,\n.ui.form .field.error .ui.dropdown:hover {\n  border-color: #e0b4b4 !important;\n}\n\n.ui.form .fields.error .field .ui.dropdown:hover .menu,\n.ui.form .field.error .ui.dropdown:hover .menu {\n  border-color: #e0b4b4;\n}\n\n.ui.form .fields.error .field .ui.multiple.selection.dropdown > .label,\n.ui.form .field.error .ui.multiple.selection.dropdown > .label {\n  background-color: #eacbcb;\n  color: #9f3a38;\n}\n\n/* Hover */\n\n.ui.form .fields.error .field .ui.dropdown .menu .item:hover,\n.ui.form .field.error .ui.dropdown .menu .item:hover {\n  background-color: #fbe7e7;\n}\n\n/* Selected */\n\n.ui.form .fields.error .field .ui.dropdown .menu .selected.item,\n.ui.form .field.error .ui.dropdown .menu .selected.item {\n  background-color: #fbe7e7;\n}\n\n/* Active */\n\n.ui.form .fields.error .field .ui.dropdown .menu .active.item,\n.ui.form .field.error .ui.dropdown .menu .active.item {\n  background-color: #fdcfcf !important;\n}\n\n/*--------------------\n    Checkbox Error\n---------------------*/\n\n.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label,\n.ui.form .field.error .checkbox:not(.toggle):not(.slider) label,\n.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box,\n.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box {\n  color: #9f3a38;\n}\n\n.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label:before,\n.ui.form .field.error .checkbox:not(.toggle):not(.slider) label:before,\n.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box:before,\n.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box:before {\n  background: #fff6f6;\n  border-color: #e0b4b4;\n}\n\n.ui.form .fields.error .field .checkbox label:after,\n.ui.form .field.error .checkbox label:after,\n.ui.form .fields.error .field .checkbox .box:after,\n.ui.form .field.error .checkbox .box:after {\n  color: #9f3a38;\n}\n\n/*--------------------\n      Disabled\n---------------------*/\n\n.ui.form .disabled.fields .field,\n.ui.form .disabled.field,\n.ui.form .field :disabled {\n  pointer-events: none;\n  opacity: 0.45;\n}\n\n.ui.form .field.disabled > label,\n.ui.form .fields.disabled > label {\n  opacity: 0.45;\n}\n\n.ui.form .field.disabled :disabled {\n  opacity: 1;\n}\n\n/*--------------\n    Loading\n---------------*/\n\n.ui.loading.form {\n  position: relative;\n  cursor: default;\n  pointer-events: none;\n}\n\n.ui.loading.form:before {\n  position: absolute;\n  content: \"\";\n  top: 0%;\n  left: 0%;\n  background: rgba(255, 255, 255, 0.8);\n  width: 100%;\n  height: 100%;\n  z-index: 100;\n}\n\n.ui.loading.form:after {\n  position: absolute;\n  content: \"\";\n  top: 50%;\n  left: 50%;\n  margin: -1.5em 0em 0em -1.5em;\n  width: 3em;\n  height: 3em;\n  -webkit-animation: form-spin 0.6s linear;\n  animation: form-spin 0.6s linear;\n  -webkit-animation-iteration-count: infinite;\n  animation-iteration-count: infinite;\n  border-radius: 500rem;\n  border-color: #767676 rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1);\n  border-style: solid;\n  border-width: 0.2em;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent;\n  box-shadow: 0px 0px 0px 1px transparent;\n  visibility: visible;\n  z-index: 101;\n}\n\n@-webkit-keyframes form-spin {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes form-spin {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n/*******************************\n        Element Types\n*******************************/\n\n/*--------------------\n    Required Field\n---------------------*/\n\n.ui.form .required.fields:not(.grouped) > .field > label:after,\n.ui.form .required.fields.grouped > label:after,\n.ui.form .required.field > label:after,\n.ui.form .required.fields:not(.grouped) > .field > .checkbox:after,\n.ui.form .required.field > .checkbox:after {\n  margin: -0.2em 0em 0em 0.2em;\n  content: \"*\";\n  color: #db2828;\n}\n\n.ui.form .required.fields:not(.grouped) > .field > label:after,\n.ui.form .required.fields.grouped > label:after,\n.ui.form .required.field > label:after {\n  display: inline-block;\n  vertical-align: top;\n}\n\n.ui.form .required.fields:not(.grouped) > .field > .checkbox:after,\n.ui.form .required.field > .checkbox:after {\n  position: absolute;\n  top: 0%;\n  left: 100%;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------------\n    Inverted Colors\n---------------------*/\n\n.ui.inverted.form label,\n.ui.form .inverted.segment label,\n.ui.form .inverted.segment .ui.checkbox label,\n.ui.form .inverted.segment .ui.checkbox .box,\n.ui.inverted.form .ui.checkbox label,\n.ui.inverted.form .ui.checkbox .box,\n.ui.inverted.form .inline.fields > label,\n.ui.inverted.form .inline.fields .field > label,\n.ui.inverted.form .inline.fields .field > p,\n.ui.inverted.form .inline.field > label,\n.ui.inverted.form .inline.field > p {\n  color: rgba(255, 255, 255, 0.9);\n}\n\n/* Inverted Field */\n\n.ui.inverted.form input:not([type]),\n.ui.inverted.form input[type=\"date\"],\n.ui.inverted.form input[type=\"datetime-local\"],\n.ui.inverted.form input[type=\"email\"],\n.ui.inverted.form input[type=\"number\"],\n.ui.inverted.form input[type=\"password\"],\n.ui.inverted.form input[type=\"search\"],\n.ui.inverted.form input[type=\"tel\"],\n.ui.inverted.form input[type=\"time\"],\n.ui.inverted.form input[type=\"text\"],\n.ui.inverted.form input[type=\"file\"],\n.ui.inverted.form input[type=\"url\"] {\n  background: #ffffff;\n  border-color: rgba(255, 255, 255, 0.1);\n  color: rgba(0, 0, 0, 0.87);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/*--------------------\n    Field Groups\n---------------------*/\n\n/* Grouped Vertically */\n\n.ui.form .grouped.fields {\n  display: block;\n  margin: 0em 0em 1em;\n}\n\n.ui.form .grouped.fields:last-child {\n  margin-bottom: 0em;\n}\n\n.ui.form .grouped.fields > label {\n  margin: 0em 0em 0.28571429rem 0em;\n  color: rgba(0, 0, 0, 0.87);\n  font-size: 0.92857143em;\n  font-weight: bold;\n  text-transform: none;\n}\n\n.ui.form .grouped.fields .field,\n.ui.form .grouped.inline.fields .field {\n  display: block;\n  margin: 0.5em 0em;\n  padding: 0em;\n}\n\n/*--------------------\n        Fields\n---------------------*/\n\n/* Split fields */\n\n.ui.form .fields {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n  margin: 0em -0.5em 1em;\n}\n\n.ui.form .fields > .field {\n  -webkit-box-flex: 0;\n  -ms-flex: 0 1 auto;\n  flex: 0 1 auto;\n  padding-left: 0.5em;\n  padding-right: 0.5em;\n}\n\n.ui.form .fields > .field:first-child {\n  border-left: none;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Other Combinations */\n\n.ui.form .two.fields > .fields,\n.ui.form .two.fields > .field {\n  width: 50%;\n}\n\n.ui.form .three.fields > .fields,\n.ui.form .three.fields > .field {\n  width: 33.33333333%;\n}\n\n.ui.form .four.fields > .fields,\n.ui.form .four.fields > .field {\n  width: 25%;\n}\n\n.ui.form .five.fields > .fields,\n.ui.form .five.fields > .field {\n  width: 20%;\n}\n\n.ui.form .six.fields > .fields,\n.ui.form .six.fields > .field {\n  width: 16.66666667%;\n}\n\n.ui.form .seven.fields > .fields,\n.ui.form .seven.fields > .field {\n  width: 14.28571429%;\n}\n\n.ui.form .eight.fields > .fields,\n.ui.form .eight.fields > .field {\n  width: 12.5%;\n}\n\n.ui.form .nine.fields > .fields,\n.ui.form .nine.fields > .field {\n  width: 11.11111111%;\n}\n\n.ui.form .ten.fields > .fields,\n.ui.form .ten.fields > .field {\n  width: 10%;\n}\n\n/* Swap to full width on mobile */\n\n@media only screen and (width < 768px) {\n  .ui.form .fields {\n    -ms-flex-wrap: wrap;\n    flex-wrap: wrap;\n  }\n\n  .ui[class*=\"equal width\"].form:not(.unstackable) .fields > .field,\n  .ui.form:not(.unstackable) [class*=\"equal width\"].fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .two.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .two.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .three.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .three.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .four.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .four.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .five.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .five.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .six.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .six.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .seven.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .seven.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .eight.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .eight.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .nine.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .nine.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .ten.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .ten.fields:not(.unstackable) > .field {\n    width: 100% !important;\n    margin: 0em 0em 1em;\n  }\n}\n\n/* Sizing Combinations */\n\n.ui.form .fields .wide.field {\n  width: 6.25%;\n  padding-left: 0.5em;\n  padding-right: 0.5em;\n}\n\n.ui.form .one.wide.field {\n  width: 6.25% !important;\n}\n\n.ui.form .two.wide.field {\n  width: 12.5% !important;\n}\n\n.ui.form .three.wide.field {\n  width: 18.75% !important;\n}\n\n.ui.form .four.wide.field {\n  width: 25% !important;\n}\n\n.ui.form .five.wide.field {\n  width: 31.25% !important;\n}\n\n.ui.form .six.wide.field {\n  width: 37.5% !important;\n}\n\n.ui.form .seven.wide.field {\n  width: 43.75% !important;\n}\n\n.ui.form .eight.wide.field {\n  width: 50% !important;\n}\n\n.ui.form .nine.wide.field {\n  width: 56.25% !important;\n}\n\n.ui.form .ten.wide.field {\n  width: 62.5% !important;\n}\n\n.ui.form .eleven.wide.field {\n  width: 68.75% !important;\n}\n\n.ui.form .twelve.wide.field {\n  width: 75% !important;\n}\n\n.ui.form .thirteen.wide.field {\n  width: 81.25% !important;\n}\n\n.ui.form .fourteen.wide.field {\n  width: 87.5% !important;\n}\n\n.ui.form .fifteen.wide.field {\n  width: 93.75% !important;\n}\n\n.ui.form .sixteen.wide.field {\n  width: 100% !important;\n}\n\n/* Swap to full width on mobile */\n\n@media only screen and (width < 768px) {\n\n  .ui.form:not(.unstackable) .two.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .two.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .three.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .three.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .four.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .four.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .five.fields:not(.unstackable) > .fields,\n  .ui.form:not(.unstackable) .five.fields:not(.unstackable) > .field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .two.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .three.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .four.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .five.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .six.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .seven.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .eight.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .nine.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .ten.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .eleven.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .twelve.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .thirteen.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .fourteen.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .fifteen.wide.field,\n  .ui.form:not(.unstackable) .fields:not(.unstackable) > .sixteen.wide.field {\n    width: 100% !important;\n  }\n\n  .ui.form .fields {\n    margin-bottom: 0em;\n  }\n}\n\n/*--------------------\n    Equal Width\n---------------------*/\n\n.ui[class*=\"equal width\"].form .fields > .field,\n.ui.form [class*=\"equal width\"].fields > .field {\n  width: 100%;\n  -webkit-box-flex: 1;\n  -ms-flex: 1 1 auto;\n  flex: 1 1 auto;\n}\n\n/*--------------------\n    Inline Fields\n---------------------*/\n\n.ui.form .inline.fields {\n  margin: 0em 0em 1em;\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n}\n\n.ui.form .inline.fields .field {\n  margin: 0em;\n  padding: 0em 1em 0em 0em;\n}\n\n/* Inline Label */\n\n.ui.form .inline.fields > label,\n.ui.form .inline.fields .field > label,\n.ui.form .inline.fields .field > p,\n.ui.form .inline.field > label,\n.ui.form .inline.field > p {\n  display: inline-block;\n  width: auto;\n  margin-top: 0em;\n  margin-bottom: 0em;\n  vertical-align: baseline;\n  font-size: 0.92857143em;\n  font-weight: bold;\n  color: rgba(0, 0, 0, 0.87);\n  text-transform: none;\n}\n\n/* Grouped Inline Label */\n\n.ui.form .inline.fields > label {\n  margin: 0.035714em 1em 0em 0em;\n}\n\n/* Inline Input */\n\n.ui.form .inline.fields .field > input,\n.ui.form .inline.fields .field > select,\n.ui.form .inline.field > input,\n.ui.form .inline.field > select {\n  display: inline-block;\n  width: auto;\n  margin-top: 0em;\n  margin-bottom: 0em;\n  vertical-align: middle;\n  font-size: 1em;\n}\n\n/* Label */\n\n.ui.form .inline.fields .field > :first-child,\n.ui.form .inline.field > :first-child {\n  margin: 0em 0.85714286em 0em 0em;\n}\n\n.ui.form .inline.fields .field > :only-child,\n.ui.form .inline.field > :only-child {\n  margin: 0em;\n}\n\n/* Wide */\n\n.ui.form .inline.fields .wide.field {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n}\n\n.ui.form .inline.fields .wide.field > input,\n.ui.form .inline.fields .wide.field > select {\n  width: 100%;\n}\n\n/*--------------------\n        Sizes\n---------------------*/\n\n.ui.mini.form {\n  font-size: 0.78571429rem;\n}\n\n.ui.tiny.form {\n  font-size: 0.85714286rem;\n}\n\n.ui.small.form {\n  font-size: 0.92857143rem;\n}\n\n.ui.form {\n  font-size: 1rem;\n}\n\n.ui.large.form {\n  font-size: 1.14285714rem;\n}\n\n.ui.big.form {\n  font-size: 1.28571429rem;\n}\n\n.ui.huge.form {\n  font-size: 1.42857143rem;\n}\n\n.ui.massive.form {\n  font-size: 1.71428571rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Grid\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Standard\n*******************************/\n\n.ui.grid {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n  -ms-flex-wrap: wrap;\n  flex-wrap: wrap;\n  -webkit-box-align: stretch;\n  -ms-flex-align: stretch;\n  align-items: stretch;\n  padding: 0em;\n}\n\n/*----------------------\n      Remove Gutters\n-----------------------*/\n\n.ui.grid {\n  margin-top: -1rem;\n  margin-bottom: -1rem;\n  margin-left: -1rem;\n  margin-right: -1rem;\n}\n\n.ui.relaxed.grid {\n  margin-left: -1.5rem;\n  margin-right: -1.5rem;\n}\n\n.ui[class*=\"very relaxed\"].grid {\n  margin-left: -2.5rem;\n  margin-right: -2.5rem;\n}\n\n/* Preserve Rows Spacing on Consecutive Grids */\n\n.ui.grid + .grid {\n  margin-top: 1rem;\n}\n\n/*-------------------\n      Columns\n--------------------*/\n\n/* Standard 16 column */\n\n.ui.grid > .column:not(.row),\n.ui.grid > .row > .column {\n  position: relative;\n  display: inline-block;\n  width: 6.25%;\n  padding-left: 1rem;\n  padding-right: 1rem;\n  vertical-align: top;\n}\n\n.ui.grid > * {\n  padding-left: 1rem;\n  padding-right: 1rem;\n}\n\n/*-------------------\n        Rows\n--------------------*/\n\n.ui.grid > .row {\n  position: relative;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n  -ms-flex-wrap: wrap;\n  flex-wrap: wrap;\n  -webkit-box-pack: inherit;\n  -ms-flex-pack: inherit;\n  justify-content: inherit;\n  -webkit-box-align: stretch;\n  -ms-flex-align: stretch;\n  align-items: stretch;\n  width: 100% !important;\n  padding: 0rem;\n  padding-top: 1rem;\n  padding-bottom: 1rem;\n}\n\n/*-------------------\n      Columns\n--------------------*/\n\n/* Vertical padding when no rows */\n\n.ui.grid > .column:not(.row) {\n  padding-top: 1rem;\n  padding-bottom: 1rem;\n}\n\n.ui.grid > .row > .column {\n  margin-top: 0em;\n  margin-bottom: 0em;\n}\n\n/*-------------------\n      Content\n--------------------*/\n\n.ui.grid > .row > img,\n.ui.grid > .row > .column > img {\n  max-width: 100%;\n}\n\n/*-------------------\n    Loose Coupling\n--------------------*/\n\n/* Collapse Margin on Consecutive Grid */\n\n.ui.grid > .ui.grid:first-child {\n  margin-top: 0em;\n}\n\n.ui.grid > .ui.grid:last-child {\n  margin-bottom: 0em;\n}\n\n/* Segment inside Aligned Grid */\n\n.ui.grid .aligned.row > .column > .segment:not(.compact):not(.attached),\n.ui.aligned.grid .column > .segment:not(.compact):not(.attached) {\n  width: 100%;\n}\n\n/* Align Dividers with Gutter */\n\n.ui.grid .row + .ui.divider {\n  -webkit-box-flex: 1;\n  -ms-flex-positive: 1;\n  flex-grow: 1;\n  margin: 1rem 1rem;\n}\n\n.ui.grid .column + .ui.vertical.divider {\n  height: calc(50% - 1rem);\n}\n\n/* Remove Border on Last Horizontal Segment */\n\n.ui.grid > .row > .column:last-child > .horizontal.segment,\n.ui.grid > .column:last-child > .horizontal.segment {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*-----------------------\n      Page Grid\n-------------------------*/\n\n@media only screen and (width < 768px) {\n  .ui.page.grid {\n    width: auto;\n    padding-left: 0em;\n    padding-right: 0em;\n    margin-left: 0em;\n    margin-right: 0em;\n  }\n}\n\n@media only screen and (768px <= width < 992px) {\n  .ui.page.grid {\n    width: auto;\n    margin-left: 0em;\n    margin-right: 0em;\n    padding-left: 2em;\n    padding-right: 2em;\n  }\n}\n\n@media only screen and (992px <= width < 1200px) {\n  .ui.page.grid {\n    width: auto;\n    margin-left: 0em;\n    margin-right: 0em;\n    padding-left: 3%;\n    padding-right: 3%;\n  }\n}\n\n@media only screen and (1200px <= width < 1920px) {\n  .ui.page.grid {\n    width: auto;\n    margin-left: 0em;\n    margin-right: 0em;\n    padding-left: 15%;\n    padding-right: 15%;\n  }\n}\n\n@media only screen and (width >= 1920px) {\n  .ui.page.grid {\n    width: auto;\n    margin-left: 0em;\n    margin-right: 0em;\n    padding-left: 23%;\n    padding-right: 23%;\n  }\n}\n\n/*-------------------\n    Column Count\n--------------------*/\n\n/* Assume full width with one column */\n\n.ui.grid > .column:only-child,\n.ui.grid > .row > .column:only-child {\n  width: 100%;\n}\n\n/* Grid Based */\n\n.ui[class*=\"one column\"].grid > .row > .column,\n.ui[class*=\"one column\"].grid > .column:not(.row) {\n  width: 100%;\n}\n\n.ui[class*=\"two column\"].grid > .row > .column,\n.ui[class*=\"two column\"].grid > .column:not(.row) {\n  width: 50%;\n}\n\n.ui[class*=\"three column\"].grid > .row > .column,\n.ui[class*=\"three column\"].grid > .column:not(.row) {\n  width: 33.33333333%;\n}\n\n.ui[class*=\"four column\"].grid > .row > .column,\n.ui[class*=\"four column\"].grid > .column:not(.row) {\n  width: 25%;\n}\n\n.ui[class*=\"five column\"].grid > .row > .column,\n.ui[class*=\"five column\"].grid > .column:not(.row) {\n  width: 20%;\n}\n\n.ui[class*=\"six column\"].grid > .row > .column,\n.ui[class*=\"six column\"].grid > .column:not(.row) {\n  width: 16.66666667%;\n}\n\n.ui[class*=\"seven column\"].grid > .row > .column,\n.ui[class*=\"seven column\"].grid > .column:not(.row) {\n  width: 14.28571429%;\n}\n\n.ui[class*=\"eight column\"].grid > .row > .column,\n.ui[class*=\"eight column\"].grid > .column:not(.row) {\n  width: 12.5%;\n}\n\n.ui[class*=\"nine column\"].grid > .row > .column,\n.ui[class*=\"nine column\"].grid > .column:not(.row) {\n  width: 11.11111111%;\n}\n\n.ui[class*=\"ten column\"].grid > .row > .column,\n.ui[class*=\"ten column\"].grid > .column:not(.row) {\n  width: 10%;\n}\n\n.ui[class*=\"eleven column\"].grid > .row > .column,\n.ui[class*=\"eleven column\"].grid > .column:not(.row) {\n  width: 9.09090909%;\n}\n\n.ui[class*=\"twelve column\"].grid > .row > .column,\n.ui[class*=\"twelve column\"].grid > .column:not(.row) {\n  width: 8.33333333%;\n}\n\n.ui[class*=\"thirteen column\"].grid > .row > .column,\n.ui[class*=\"thirteen column\"].grid > .column:not(.row) {\n  width: 7.69230769%;\n}\n\n.ui[class*=\"fourteen column\"].grid > .row > .column,\n.ui[class*=\"fourteen column\"].grid > .column:not(.row) {\n  width: 7.14285714%;\n}\n\n.ui[class*=\"fifteen column\"].grid > .row > .column,\n.ui[class*=\"fifteen column\"].grid > .column:not(.row) {\n  width: 6.66666667%;\n}\n\n.ui[class*=\"sixteen column\"].grid > .row > .column,\n.ui[class*=\"sixteen column\"].grid > .column:not(.row) {\n  width: 6.25%;\n}\n\n/* Row Based Overrides */\n\n.ui.grid > [class*=\"one column\"].row > .column {\n  width: 100% !important;\n}\n\n.ui.grid > [class*=\"two column\"].row > .column {\n  width: 50% !important;\n}\n\n.ui.grid > [class*=\"three column\"].row > .column {\n  width: 33.33333333% !important;\n}\n\n.ui.grid > [class*=\"four column\"].row > .column {\n  width: 25% !important;\n}\n\n.ui.grid > [class*=\"five column\"].row > .column {\n  width: 20% !important;\n}\n\n.ui.grid > [class*=\"six column\"].row > .column {\n  width: 16.66666667% !important;\n}\n\n.ui.grid > [class*=\"seven column\"].row > .column {\n  width: 14.28571429% !important;\n}\n\n.ui.grid > [class*=\"eight column\"].row > .column {\n  width: 12.5% !important;\n}\n\n.ui.grid > [class*=\"nine column\"].row > .column {\n  width: 11.11111111% !important;\n}\n\n.ui.grid > [class*=\"ten column\"].row > .column {\n  width: 10% !important;\n}\n\n.ui.grid > [class*=\"eleven column\"].row > .column {\n  width: 9.09090909% !important;\n}\n\n.ui.grid > [class*=\"twelve column\"].row > .column {\n  width: 8.33333333% !important;\n}\n\n.ui.grid > [class*=\"thirteen column\"].row > .column {\n  width: 7.69230769% !important;\n}\n\n.ui.grid > [class*=\"fourteen column\"].row > .column {\n  width: 7.14285714% !important;\n}\n\n.ui.grid > [class*=\"fifteen column\"].row > .column {\n  width: 6.66666667% !important;\n}\n\n.ui.grid > [class*=\"sixteen column\"].row > .column {\n  width: 6.25% !important;\n}\n\n/* Celled Page */\n\n.ui.celled.page.grid {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/*-------------------\n    Column Width\n--------------------*/\n\n/* Sizing Combinations */\n\n.ui.grid > .row > [class*=\"one wide\"].column,\n.ui.grid > .column.row > [class*=\"one wide\"].column,\n.ui.grid > [class*=\"one wide\"].column,\n.ui.column.grid > [class*=\"one wide\"].column {\n  width: 6.25% !important;\n}\n\n.ui.grid > .row > [class*=\"two wide\"].column,\n.ui.grid > .column.row > [class*=\"two wide\"].column,\n.ui.grid > [class*=\"two wide\"].column,\n.ui.column.grid > [class*=\"two wide\"].column {\n  width: 12.5% !important;\n}\n\n.ui.grid > .row > [class*=\"three wide\"].column,\n.ui.grid > .column.row > [class*=\"three wide\"].column,\n.ui.grid > [class*=\"three wide\"].column,\n.ui.column.grid > [class*=\"three wide\"].column {\n  width: 18.75% !important;\n}\n\n.ui.grid > .row > [class*=\"four wide\"].column,\n.ui.grid > .column.row > [class*=\"four wide\"].column,\n.ui.grid > [class*=\"four wide\"].column,\n.ui.column.grid > [class*=\"four wide\"].column {\n  width: 25% !important;\n}\n\n.ui.grid > .row > [class*=\"five wide\"].column,\n.ui.grid > .column.row > [class*=\"five wide\"].column,\n.ui.grid > [class*=\"five wide\"].column,\n.ui.column.grid > [class*=\"five wide\"].column {\n  width: 31.25% !important;\n}\n\n.ui.grid > .row > [class*=\"six wide\"].column,\n.ui.grid > .column.row > [class*=\"six wide\"].column,\n.ui.grid > [class*=\"six wide\"].column,\n.ui.column.grid > [class*=\"six wide\"].column {\n  width: 37.5% !important;\n}\n\n.ui.grid > .row > [class*=\"seven wide\"].column,\n.ui.grid > .column.row > [class*=\"seven wide\"].column,\n.ui.grid > [class*=\"seven wide\"].column,\n.ui.column.grid > [class*=\"seven wide\"].column {\n  width: 43.75% !important;\n}\n\n.ui.grid > .row > [class*=\"eight wide\"].column,\n.ui.grid > .column.row > [class*=\"eight wide\"].column,\n.ui.grid > [class*=\"eight wide\"].column,\n.ui.column.grid > [class*=\"eight wide\"].column {\n  width: 50% !important;\n}\n\n.ui.grid > .row > [class*=\"nine wide\"].column,\n.ui.grid > .column.row > [class*=\"nine wide\"].column,\n.ui.grid > [class*=\"nine wide\"].column,\n.ui.column.grid > [class*=\"nine wide\"].column {\n  width: 56.25% !important;\n}\n\n.ui.grid > .row > [class*=\"ten wide\"].column,\n.ui.grid > .column.row > [class*=\"ten wide\"].column,\n.ui.grid > [class*=\"ten wide\"].column,\n.ui.column.grid > [class*=\"ten wide\"].column {\n  width: 62.5% !important;\n}\n\n.ui.grid > .row > [class*=\"eleven wide\"].column,\n.ui.grid > .column.row > [class*=\"eleven wide\"].column,\n.ui.grid > [class*=\"eleven wide\"].column,\n.ui.column.grid > [class*=\"eleven wide\"].column {\n  width: 68.75% !important;\n}\n\n.ui.grid > .row > [class*=\"twelve wide\"].column,\n.ui.grid > .column.row > [class*=\"twelve wide\"].column,\n.ui.grid > [class*=\"twelve wide\"].column,\n.ui.column.grid > [class*=\"twelve wide\"].column {\n  width: 75% !important;\n}\n\n.ui.grid > .row > [class*=\"thirteen wide\"].column,\n.ui.grid > .column.row > [class*=\"thirteen wide\"].column,\n.ui.grid > [class*=\"thirteen wide\"].column,\n.ui.column.grid > [class*=\"thirteen wide\"].column {\n  width: 81.25% !important;\n}\n\n.ui.grid > .row > [class*=\"fourteen wide\"].column,\n.ui.grid > .column.row > [class*=\"fourteen wide\"].column,\n.ui.grid > [class*=\"fourteen wide\"].column,\n.ui.column.grid > [class*=\"fourteen wide\"].column {\n  width: 87.5% !important;\n}\n\n.ui.grid > .row > [class*=\"fifteen wide\"].column,\n.ui.grid > .column.row > [class*=\"fifteen wide\"].column,\n.ui.grid > [class*=\"fifteen wide\"].column,\n.ui.column.grid > [class*=\"fifteen wide\"].column {\n  width: 93.75% !important;\n}\n\n.ui.grid > .row > [class*=\"sixteen wide\"].column,\n.ui.grid > .column.row > [class*=\"sixteen wide\"].column,\n.ui.grid > [class*=\"sixteen wide\"].column,\n.ui.column.grid > [class*=\"sixteen wide\"].column {\n  width: 100% !important;\n}\n\n/*----------------------\n    Width per Device\n-----------------------*/\n\n/* Mobile Sizing Combinations */\n\n@media only screen and (320px <= width < 768px) {\n\n  .ui.grid > .row > [class*=\"one wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"one wide mobile\"].column,\n  .ui.grid > [class*=\"one wide mobile\"].column,\n  .ui.column.grid > [class*=\"one wide mobile\"].column {\n    width: 6.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"two wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"two wide mobile\"].column,\n  .ui.grid > [class*=\"two wide mobile\"].column,\n  .ui.column.grid > [class*=\"two wide mobile\"].column {\n    width: 12.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"three wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"three wide mobile\"].column,\n  .ui.grid > [class*=\"three wide mobile\"].column,\n  .ui.column.grid > [class*=\"three wide mobile\"].column {\n    width: 18.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"four wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"four wide mobile\"].column,\n  .ui.grid > [class*=\"four wide mobile\"].column,\n  .ui.column.grid > [class*=\"four wide mobile\"].column {\n    width: 25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"five wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"five wide mobile\"].column,\n  .ui.grid > [class*=\"five wide mobile\"].column,\n  .ui.column.grid > [class*=\"five wide mobile\"].column {\n    width: 31.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"six wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"six wide mobile\"].column,\n  .ui.grid > [class*=\"six wide mobile\"].column,\n  .ui.column.grid > [class*=\"six wide mobile\"].column {\n    width: 37.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"seven wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"seven wide mobile\"].column,\n  .ui.grid > [class*=\"seven wide mobile\"].column,\n  .ui.column.grid > [class*=\"seven wide mobile\"].column {\n    width: 43.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"eight wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"eight wide mobile\"].column,\n  .ui.grid > [class*=\"eight wide mobile\"].column,\n  .ui.column.grid > [class*=\"eight wide mobile\"].column {\n    width: 50% !important;\n  }\n\n  .ui.grid > .row > [class*=\"nine wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"nine wide mobile\"].column,\n  .ui.grid > [class*=\"nine wide mobile\"].column,\n  .ui.column.grid > [class*=\"nine wide mobile\"].column {\n    width: 56.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"ten wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"ten wide mobile\"].column,\n  .ui.grid > [class*=\"ten wide mobile\"].column,\n  .ui.column.grid > [class*=\"ten wide mobile\"].column {\n    width: 62.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"eleven wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"eleven wide mobile\"].column,\n  .ui.grid > [class*=\"eleven wide mobile\"].column,\n  .ui.column.grid > [class*=\"eleven wide mobile\"].column {\n    width: 68.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"twelve wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"twelve wide mobile\"].column,\n  .ui.grid > [class*=\"twelve wide mobile\"].column,\n  .ui.column.grid > [class*=\"twelve wide mobile\"].column {\n    width: 75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"thirteen wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"thirteen wide mobile\"].column,\n  .ui.grid > [class*=\"thirteen wide mobile\"].column,\n  .ui.column.grid > [class*=\"thirteen wide mobile\"].column {\n    width: 81.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"fourteen wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"fourteen wide mobile\"].column,\n  .ui.grid > [class*=\"fourteen wide mobile\"].column,\n  .ui.column.grid > [class*=\"fourteen wide mobile\"].column {\n    width: 87.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"fifteen wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"fifteen wide mobile\"].column,\n  .ui.grid > [class*=\"fifteen wide mobile\"].column,\n  .ui.column.grid > [class*=\"fifteen wide mobile\"].column {\n    width: 93.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"sixteen wide mobile\"].column,\n  .ui.grid > .column.row > [class*=\"sixteen wide mobile\"].column,\n  .ui.grid > [class*=\"sixteen wide mobile\"].column,\n  .ui.column.grid > [class*=\"sixteen wide mobile\"].column {\n    width: 100% !important;\n  }\n}\n\n/* Tablet Sizing Combinations */\n\n@media only screen and (768px <= width < 992px) {\n\n  .ui.grid > .row > [class*=\"one wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"one wide tablet\"].column,\n  .ui.grid > [class*=\"one wide tablet\"].column,\n  .ui.column.grid > [class*=\"one wide tablet\"].column {\n    width: 6.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"two wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"two wide tablet\"].column,\n  .ui.grid > [class*=\"two wide tablet\"].column,\n  .ui.column.grid > [class*=\"two wide tablet\"].column {\n    width: 12.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"three wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"three wide tablet\"].column,\n  .ui.grid > [class*=\"three wide tablet\"].column,\n  .ui.column.grid > [class*=\"three wide tablet\"].column {\n    width: 18.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"four wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"four wide tablet\"].column,\n  .ui.grid > [class*=\"four wide tablet\"].column,\n  .ui.column.grid > [class*=\"four wide tablet\"].column {\n    width: 25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"five wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"five wide tablet\"].column,\n  .ui.grid > [class*=\"five wide tablet\"].column,\n  .ui.column.grid > [class*=\"five wide tablet\"].column {\n    width: 31.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"six wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"six wide tablet\"].column,\n  .ui.grid > [class*=\"six wide tablet\"].column,\n  .ui.column.grid > [class*=\"six wide tablet\"].column {\n    width: 37.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"seven wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"seven wide tablet\"].column,\n  .ui.grid > [class*=\"seven wide tablet\"].column,\n  .ui.column.grid > [class*=\"seven wide tablet\"].column {\n    width: 43.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"eight wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"eight wide tablet\"].column,\n  .ui.grid > [class*=\"eight wide tablet\"].column,\n  .ui.column.grid > [class*=\"eight wide tablet\"].column {\n    width: 50% !important;\n  }\n\n  .ui.grid > .row > [class*=\"nine wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"nine wide tablet\"].column,\n  .ui.grid > [class*=\"nine wide tablet\"].column,\n  .ui.column.grid > [class*=\"nine wide tablet\"].column {\n    width: 56.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"ten wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"ten wide tablet\"].column,\n  .ui.grid > [class*=\"ten wide tablet\"].column,\n  .ui.column.grid > [class*=\"ten wide tablet\"].column {\n    width: 62.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"eleven wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"eleven wide tablet\"].column,\n  .ui.grid > [class*=\"eleven wide tablet\"].column,\n  .ui.column.grid > [class*=\"eleven wide tablet\"].column {\n    width: 68.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"twelve wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"twelve wide tablet\"].column,\n  .ui.grid > [class*=\"twelve wide tablet\"].column,\n  .ui.column.grid > [class*=\"twelve wide tablet\"].column {\n    width: 75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"thirteen wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"thirteen wide tablet\"].column,\n  .ui.grid > [class*=\"thirteen wide tablet\"].column,\n  .ui.column.grid > [class*=\"thirteen wide tablet\"].column {\n    width: 81.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"fourteen wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"fourteen wide tablet\"].column,\n  .ui.grid > [class*=\"fourteen wide tablet\"].column,\n  .ui.column.grid > [class*=\"fourteen wide tablet\"].column {\n    width: 87.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"fifteen wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"fifteen wide tablet\"].column,\n  .ui.grid > [class*=\"fifteen wide tablet\"].column,\n  .ui.column.grid > [class*=\"fifteen wide tablet\"].column {\n    width: 93.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"sixteen wide tablet\"].column,\n  .ui.grid > .column.row > [class*=\"sixteen wide tablet\"].column,\n  .ui.grid > [class*=\"sixteen wide tablet\"].column,\n  .ui.column.grid > [class*=\"sixteen wide tablet\"].column {\n    width: 100% !important;\n  }\n}\n\n/* Computer/Desktop Sizing Combinations */\n\n@media only screen and (width >= 992px) {\n\n  .ui.grid > .row > [class*=\"one wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"one wide computer\"].column,\n  .ui.grid > [class*=\"one wide computer\"].column,\n  .ui.column.grid > [class*=\"one wide computer\"].column {\n    width: 6.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"two wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"two wide computer\"].column,\n  .ui.grid > [class*=\"two wide computer\"].column,\n  .ui.column.grid > [class*=\"two wide computer\"].column {\n    width: 12.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"three wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"three wide computer\"].column,\n  .ui.grid > [class*=\"three wide computer\"].column,\n  .ui.column.grid > [class*=\"three wide computer\"].column {\n    width: 18.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"four wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"four wide computer\"].column,\n  .ui.grid > [class*=\"four wide computer\"].column,\n  .ui.column.grid > [class*=\"four wide computer\"].column {\n    width: 25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"five wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"five wide computer\"].column,\n  .ui.grid > [class*=\"five wide computer\"].column,\n  .ui.column.grid > [class*=\"five wide computer\"].column {\n    width: 31.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"six wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"six wide computer\"].column,\n  .ui.grid > [class*=\"six wide computer\"].column,\n  .ui.column.grid > [class*=\"six wide computer\"].column {\n    width: 37.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"seven wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"seven wide computer\"].column,\n  .ui.grid > [class*=\"seven wide computer\"].column,\n  .ui.column.grid > [class*=\"seven wide computer\"].column {\n    width: 43.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"eight wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"eight wide computer\"].column,\n  .ui.grid > [class*=\"eight wide computer\"].column,\n  .ui.column.grid > [class*=\"eight wide computer\"].column {\n    width: 50% !important;\n  }\n\n  .ui.grid > .row > [class*=\"nine wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"nine wide computer\"].column,\n  .ui.grid > [class*=\"nine wide computer\"].column,\n  .ui.column.grid > [class*=\"nine wide computer\"].column {\n    width: 56.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"ten wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"ten wide computer\"].column,\n  .ui.grid > [class*=\"ten wide computer\"].column,\n  .ui.column.grid > [class*=\"ten wide computer\"].column {\n    width: 62.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"eleven wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"eleven wide computer\"].column,\n  .ui.grid > [class*=\"eleven wide computer\"].column,\n  .ui.column.grid > [class*=\"eleven wide computer\"].column {\n    width: 68.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"twelve wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"twelve wide computer\"].column,\n  .ui.grid > [class*=\"twelve wide computer\"].column,\n  .ui.column.grid > [class*=\"twelve wide computer\"].column {\n    width: 75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"thirteen wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"thirteen wide computer\"].column,\n  .ui.grid > [class*=\"thirteen wide computer\"].column,\n  .ui.column.grid > [class*=\"thirteen wide computer\"].column {\n    width: 81.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"fourteen wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"fourteen wide computer\"].column,\n  .ui.grid > [class*=\"fourteen wide computer\"].column,\n  .ui.column.grid > [class*=\"fourteen wide computer\"].column {\n    width: 87.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"fifteen wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"fifteen wide computer\"].column,\n  .ui.grid > [class*=\"fifteen wide computer\"].column,\n  .ui.column.grid > [class*=\"fifteen wide computer\"].column {\n    width: 93.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"sixteen wide computer\"].column,\n  .ui.grid > .column.row > [class*=\"sixteen wide computer\"].column,\n  .ui.grid > [class*=\"sixteen wide computer\"].column,\n  .ui.column.grid > [class*=\"sixteen wide computer\"].column {\n    width: 100% !important;\n  }\n}\n\n/* Large Monitor Sizing Combinations */\n\n@media only screen and (1200px <= width < 1920px) {\n\n  .ui.grid > .row > [class*=\"one wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"one wide large screen\"].column,\n  .ui.grid > [class*=\"one wide large screen\"].column,\n  .ui.column.grid > [class*=\"one wide large screen\"].column {\n    width: 6.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"two wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"two wide large screen\"].column,\n  .ui.grid > [class*=\"two wide large screen\"].column,\n  .ui.column.grid > [class*=\"two wide large screen\"].column {\n    width: 12.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"three wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"three wide large screen\"].column,\n  .ui.grid > [class*=\"three wide large screen\"].column,\n  .ui.column.grid > [class*=\"three wide large screen\"].column {\n    width: 18.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"four wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"four wide large screen\"].column,\n  .ui.grid > [class*=\"four wide large screen\"].column,\n  .ui.column.grid > [class*=\"four wide large screen\"].column {\n    width: 25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"five wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"five wide large screen\"].column,\n  .ui.grid > [class*=\"five wide large screen\"].column,\n  .ui.column.grid > [class*=\"five wide large screen\"].column {\n    width: 31.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"six wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"six wide large screen\"].column,\n  .ui.grid > [class*=\"six wide large screen\"].column,\n  .ui.column.grid > [class*=\"six wide large screen\"].column {\n    width: 37.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"seven wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"seven wide large screen\"].column,\n  .ui.grid > [class*=\"seven wide large screen\"].column,\n  .ui.column.grid > [class*=\"seven wide large screen\"].column {\n    width: 43.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"eight wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"eight wide large screen\"].column,\n  .ui.grid > [class*=\"eight wide large screen\"].column,\n  .ui.column.grid > [class*=\"eight wide large screen\"].column {\n    width: 50% !important;\n  }\n\n  .ui.grid > .row > [class*=\"nine wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"nine wide large screen\"].column,\n  .ui.grid > [class*=\"nine wide large screen\"].column,\n  .ui.column.grid > [class*=\"nine wide large screen\"].column {\n    width: 56.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"ten wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"ten wide large screen\"].column,\n  .ui.grid > [class*=\"ten wide large screen\"].column,\n  .ui.column.grid > [class*=\"ten wide large screen\"].column {\n    width: 62.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"eleven wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"eleven wide large screen\"].column,\n  .ui.grid > [class*=\"eleven wide large screen\"].column,\n  .ui.column.grid > [class*=\"eleven wide large screen\"].column {\n    width: 68.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"twelve wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"twelve wide large screen\"].column,\n  .ui.grid > [class*=\"twelve wide large screen\"].column,\n  .ui.column.grid > [class*=\"twelve wide large screen\"].column {\n    width: 75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"thirteen wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"thirteen wide large screen\"].column,\n  .ui.grid > [class*=\"thirteen wide large screen\"].column,\n  .ui.column.grid > [class*=\"thirteen wide large screen\"].column {\n    width: 81.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"fourteen wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"fourteen wide large screen\"].column,\n  .ui.grid > [class*=\"fourteen wide large screen\"].column,\n  .ui.column.grid > [class*=\"fourteen wide large screen\"].column {\n    width: 87.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"fifteen wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"fifteen wide large screen\"].column,\n  .ui.grid > [class*=\"fifteen wide large screen\"].column,\n  .ui.column.grid > [class*=\"fifteen wide large screen\"].column {\n    width: 93.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"sixteen wide large screen\"].column,\n  .ui.grid > .column.row > [class*=\"sixteen wide large screen\"].column,\n  .ui.grid > [class*=\"sixteen wide large screen\"].column,\n  .ui.column.grid > [class*=\"sixteen wide large screen\"].column {\n    width: 100% !important;\n  }\n}\n\n/* Widescreen Sizing Combinations */\n\n@media only screen and (width >= 1920px) {\n\n  .ui.grid > .row > [class*=\"one wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"one wide widescreen\"].column,\n  .ui.grid > [class*=\"one wide widescreen\"].column,\n  .ui.column.grid > [class*=\"one wide widescreen\"].column {\n    width: 6.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"two wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"two wide widescreen\"].column,\n  .ui.grid > [class*=\"two wide widescreen\"].column,\n  .ui.column.grid > [class*=\"two wide widescreen\"].column {\n    width: 12.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"three wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"three wide widescreen\"].column,\n  .ui.grid > [class*=\"three wide widescreen\"].column,\n  .ui.column.grid > [class*=\"three wide widescreen\"].column {\n    width: 18.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"four wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"four wide widescreen\"].column,\n  .ui.grid > [class*=\"four wide widescreen\"].column,\n  .ui.column.grid > [class*=\"four wide widescreen\"].column {\n    width: 25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"five wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"five wide widescreen\"].column,\n  .ui.grid > [class*=\"five wide widescreen\"].column,\n  .ui.column.grid > [class*=\"five wide widescreen\"].column {\n    width: 31.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"six wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"six wide widescreen\"].column,\n  .ui.grid > [class*=\"six wide widescreen\"].column,\n  .ui.column.grid > [class*=\"six wide widescreen\"].column {\n    width: 37.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"seven wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"seven wide widescreen\"].column,\n  .ui.grid > [class*=\"seven wide widescreen\"].column,\n  .ui.column.grid > [class*=\"seven wide widescreen\"].column {\n    width: 43.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"eight wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"eight wide widescreen\"].column,\n  .ui.grid > [class*=\"eight wide widescreen\"].column,\n  .ui.column.grid > [class*=\"eight wide widescreen\"].column {\n    width: 50% !important;\n  }\n\n  .ui.grid > .row > [class*=\"nine wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"nine wide widescreen\"].column,\n  .ui.grid > [class*=\"nine wide widescreen\"].column,\n  .ui.column.grid > [class*=\"nine wide widescreen\"].column {\n    width: 56.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"ten wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"ten wide widescreen\"].column,\n  .ui.grid > [class*=\"ten wide widescreen\"].column,\n  .ui.column.grid > [class*=\"ten wide widescreen\"].column {\n    width: 62.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"eleven wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"eleven wide widescreen\"].column,\n  .ui.grid > [class*=\"eleven wide widescreen\"].column,\n  .ui.column.grid > [class*=\"eleven wide widescreen\"].column {\n    width: 68.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"twelve wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"twelve wide widescreen\"].column,\n  .ui.grid > [class*=\"twelve wide widescreen\"].column,\n  .ui.column.grid > [class*=\"twelve wide widescreen\"].column {\n    width: 75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"thirteen wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"thirteen wide widescreen\"].column,\n  .ui.grid > [class*=\"thirteen wide widescreen\"].column,\n  .ui.column.grid > [class*=\"thirteen wide widescreen\"].column {\n    width: 81.25% !important;\n  }\n\n  .ui.grid > .row > [class*=\"fourteen wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"fourteen wide widescreen\"].column,\n  .ui.grid > [class*=\"fourteen wide widescreen\"].column,\n  .ui.column.grid > [class*=\"fourteen wide widescreen\"].column {\n    width: 87.5% !important;\n  }\n\n  .ui.grid > .row > [class*=\"fifteen wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"fifteen wide widescreen\"].column,\n  .ui.grid > [class*=\"fifteen wide widescreen\"].column,\n  .ui.column.grid > [class*=\"fifteen wide widescreen\"].column {\n    width: 93.75% !important;\n  }\n\n  .ui.grid > .row > [class*=\"sixteen wide widescreen\"].column,\n  .ui.grid > .column.row > [class*=\"sixteen wide widescreen\"].column,\n  .ui.grid > [class*=\"sixteen wide widescreen\"].column,\n  .ui.column.grid > [class*=\"sixteen wide widescreen\"].column {\n    width: 100% !important;\n  }\n}\n\n/*----------------------\n        Centered\n-----------------------*/\n\n.ui.centered.grid,\n.ui.centered.grid > .row,\n.ui.grid > .centered.row {\n  text-align: center;\n  -webkit-box-pack: center;\n  -ms-flex-pack: center;\n  justify-content: center;\n}\n\n.ui.centered.grid > .column:not(.aligned):not(.justified):not(.row),\n.ui.centered.grid > .row > .column:not(.aligned):not(.justified),\n.ui.grid .centered.row > .column:not(.aligned):not(.justified) {\n  text-align: left;\n}\n\n.ui.grid > .centered.column,\n.ui.grid > .row > .centered.column {\n  display: block;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n/*----------------------\n        Relaxed\n-----------------------*/\n\n.ui.relaxed.grid > .column:not(.row),\n.ui.relaxed.grid > .row > .column,\n.ui.grid > .relaxed.row > .column {\n  padding-left: 1.5rem;\n  padding-right: 1.5rem;\n}\n\n.ui[class*=\"very relaxed\"].grid > .column:not(.row),\n.ui[class*=\"very relaxed\"].grid > .row > .column,\n.ui.grid > [class*=\"very relaxed\"].row > .column {\n  padding-left: 2.5rem;\n  padding-right: 2.5rem;\n}\n\n/* Coupling with UI Divider */\n\n.ui.relaxed.grid .row + .ui.divider,\n.ui.grid .relaxed.row + .ui.divider {\n  margin-left: 1.5rem;\n  margin-right: 1.5rem;\n}\n\n.ui[class*=\"very relaxed\"].grid .row + .ui.divider,\n.ui.grid [class*=\"very relaxed\"].row + .ui.divider {\n  margin-left: 2.5rem;\n  margin-right: 2.5rem;\n}\n\n/*----------------------\n        Padded\n-----------------------*/\n\n.ui.padded.grid:not(.vertically):not(.horizontally) {\n  margin: 0em !important;\n}\n\n[class*=\"horizontally padded\"].ui.grid {\n  margin-left: 0em !important;\n  margin-right: 0em !important;\n}\n\n[class*=\"vertically padded\"].ui.grid {\n  margin-top: 0em !important;\n  margin-bottom: 0em !important;\n}\n\n/*----------------------\n      \"Floated\"\n-----------------------*/\n\n.ui.grid [class*=\"left floated\"].column {\n  margin-right: auto;\n}\n\n.ui.grid [class*=\"right floated\"].column {\n  margin-left: auto;\n}\n\n/*----------------------\n        Divided\n-----------------------*/\n\n.ui.divided.grid:not([class*=\"vertically divided\"]) > .column:not(.row),\n.ui.divided.grid:not([class*=\"vertically divided\"]) > .row > .column {\n  -webkit-box-shadow: -1px 0px 0px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: -1px 0px 0px 0px rgba(34, 36, 38, 0.15);\n}\n\n/* Swap from padding to margin on columns to have dividers align */\n\n.ui[class*=\"vertically divided\"].grid > .column:not(.row),\n.ui[class*=\"vertically divided\"].grid > .row > .column {\n  margin-top: 1rem;\n  margin-bottom: 1rem;\n  padding-top: 0rem;\n  padding-bottom: 0rem;\n}\n\n.ui[class*=\"vertically divided\"].grid > .row {\n  margin-top: 0em;\n  margin-bottom: 0em;\n}\n\n/* No divider on first column on row */\n\n.ui.divided.grid:not([class*=\"vertically divided\"]) > .column:first-child,\n.ui.divided.grid:not([class*=\"vertically divided\"]) > .row > .column:first-child {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* No space on top of first row */\n\n.ui[class*=\"vertically divided\"].grid > .row:first-child > .column {\n  margin-top: 0em;\n}\n\n/* Divided Row */\n\n.ui.grid > .divided.row > .column {\n  -webkit-box-shadow: -1px 0px 0px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: -1px 0px 0px 0px rgba(34, 36, 38, 0.15);\n}\n\n.ui.grid > .divided.row > .column:first-child {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Vertically Divided */\n\n.ui[class*=\"vertically divided\"].grid > .row {\n  position: relative;\n}\n\n.ui[class*=\"vertically divided\"].grid > .row:before {\n  position: absolute;\n  content: \"\";\n  top: 0em;\n  left: 0px;\n  width: calc(100% - 2rem);\n  height: 1px;\n  margin: 0% 1rem;\n  -webkit-box-shadow: 0px -1px 0px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px -1px 0px 0px rgba(34, 36, 38, 0.15);\n}\n\n/* Padded Horizontally Divided */\n\n[class*=\"horizontally padded\"].ui.divided.grid,\n.ui.padded.divided.grid:not(.vertically):not(.horizontally) {\n  width: 100%;\n}\n\n/* First Row Vertically Divided */\n\n.ui[class*=\"vertically divided\"].grid > .row:first-child:before {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Inverted Divided */\n\n.ui.inverted.divided.grid:not([class*=\"vertically divided\"]) > .column:not(.row),\n.ui.inverted.divided.grid:not([class*=\"vertically divided\"]) > .row > .column {\n  -webkit-box-shadow: -1px 0px 0px 0px rgba(255, 255, 255, 0.1);\n  box-shadow: -1px 0px 0px 0px rgba(255, 255, 255, 0.1);\n}\n\n.ui.inverted.divided.grid:not([class*=\"vertically divided\"]) > .column:not(.row):first-child,\n.ui.inverted.divided.grid:not([class*=\"vertically divided\"]) > .row > .column:first-child {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.inverted[class*=\"vertically divided\"].grid > .row:before {\n  -webkit-box-shadow: 0px -1px 0px 0px rgba(255, 255, 255, 0.1);\n  box-shadow: 0px -1px 0px 0px rgba(255, 255, 255, 0.1);\n}\n\n/* Relaxed */\n\n.ui.relaxed[class*=\"vertically divided\"].grid > .row:before {\n  margin-left: 1.5rem;\n  margin-right: 1.5rem;\n  width: calc(100% - 3rem);\n}\n\n.ui[class*=\"very relaxed\"][class*=\"vertically divided\"].grid > .row:before {\n  margin-left: 5rem;\n  margin-right: 5rem;\n  width: calc(100% - 5rem);\n}\n\n/*----------------------\n        Celled\n-----------------------*/\n\n.ui.celled.grid {\n  width: 100%;\n  margin: 1em 0em;\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5;\n}\n\n.ui.celled.grid > .row {\n  width: 100% !important;\n  margin: 0em;\n  padding: 0em;\n  -webkit-box-shadow: 0px -1px 0px 0px #d4d4d5;\n  box-shadow: 0px -1px 0px 0px #d4d4d5;\n}\n\n.ui.celled.grid > .column:not(.row),\n.ui.celled.grid > .row > .column {\n  -webkit-box-shadow: -1px 0px 0px 0px #d4d4d5;\n  box-shadow: -1px 0px 0px 0px #d4d4d5;\n}\n\n.ui.celled.grid > .column:first-child,\n.ui.celled.grid > .row > .column:first-child {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.celled.grid > .column:not(.row),\n.ui.celled.grid > .row > .column {\n  padding: 1em;\n}\n\n.ui.relaxed.celled.grid > .column:not(.row),\n.ui.relaxed.celled.grid > .row > .column {\n  padding: 1.5em;\n}\n\n.ui[class*=\"very relaxed\"].celled.grid > .column:not(.row),\n.ui[class*=\"very relaxed\"].celled.grid > .row > .column {\n  padding: 2em;\n}\n\n/* Internally Celled */\n\n.ui[class*=\"internally celled\"].grid {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  margin: 0em;\n}\n\n.ui[class*=\"internally celled\"].grid > .row:first-child {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui[class*=\"internally celled\"].grid > .row > .column:first-child {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/*----------------------\n  Vertically Aligned\n-----------------------*/\n\n/* Top Aligned */\n\n.ui[class*=\"top aligned\"].grid > .column:not(.row),\n.ui[class*=\"top aligned\"].grid > .row > .column,\n.ui.grid > [class*=\"top aligned\"].row > .column,\n.ui.grid > [class*=\"top aligned\"].column:not(.row),\n.ui.grid > .row > [class*=\"top aligned\"].column {\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  vertical-align: top;\n  -ms-flex-item-align: start !important;\n  align-self: flex-start !important;\n}\n\n/* Middle Aligned */\n\n.ui[class*=\"middle aligned\"].grid > .column:not(.row),\n.ui[class*=\"middle aligned\"].grid > .row > .column,\n.ui.grid > [class*=\"middle aligned\"].row > .column,\n.ui.grid > [class*=\"middle aligned\"].column:not(.row),\n.ui.grid > .row > [class*=\"middle aligned\"].column {\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  vertical-align: middle;\n  -ms-flex-item-align: center !important;\n  align-self: center !important;\n}\n\n/* Bottom Aligned */\n\n.ui[class*=\"bottom aligned\"].grid > .column:not(.row),\n.ui[class*=\"bottom aligned\"].grid > .row > .column,\n.ui.grid > [class*=\"bottom aligned\"].row > .column,\n.ui.grid > [class*=\"bottom aligned\"].column:not(.row),\n.ui.grid > .row > [class*=\"bottom aligned\"].column {\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  vertical-align: bottom;\n  -ms-flex-item-align: end !important;\n  align-self: flex-end !important;\n}\n\n/* Stretched */\n\n.ui.stretched.grid > .row > .column,\n.ui.stretched.grid > .column,\n.ui.grid > .stretched.row > .column,\n.ui.grid > .stretched.column:not(.row),\n.ui.grid > .row > .stretched.column {\n  display: -webkit-inline-box !important;\n  display: -ms-inline-flexbox !important;\n  display: inline-flex !important;\n  -ms-flex-item-align: stretch;\n  align-self: stretch;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n}\n\n.ui.stretched.grid > .row > .column > *,\n.ui.stretched.grid > .column > *,\n.ui.grid > .stretched.row > .column > *,\n.ui.grid > .stretched.column:not(.row) > *,\n.ui.grid > .row > .stretched.column > * {\n  -webkit-box-flex: 1;\n  -ms-flex-positive: 1;\n  flex-grow: 1;\n}\n\n/*----------------------\n  Horizontally Centered\n-----------------------*/\n\n/* Left Aligned */\n\n.ui[class*=\"left aligned\"].grid > .column,\n.ui[class*=\"left aligned\"].grid > .row > .column,\n.ui.grid > [class*=\"left aligned\"].row > .column,\n.ui.grid > [class*=\"left aligned\"].column.column,\n.ui.grid > .row > [class*=\"left aligned\"].column.column {\n  text-align: left;\n  -ms-flex-item-align: inherit;\n  align-self: inherit;\n}\n\n/* Center Aligned */\n\n.ui[class*=\"center aligned\"].grid > .column,\n.ui[class*=\"center aligned\"].grid > .row > .column,\n.ui.grid > [class*=\"center aligned\"].row > .column,\n.ui.grid > [class*=\"center aligned\"].column.column,\n.ui.grid > .row > [class*=\"center aligned\"].column.column {\n  text-align: center;\n  -ms-flex-item-align: inherit;\n  align-self: inherit;\n}\n\n.ui[class*=\"center aligned\"].grid {\n  -webkit-box-pack: center;\n  -ms-flex-pack: center;\n  justify-content: center;\n}\n\n/* Right Aligned */\n\n.ui[class*=\"right aligned\"].grid > .column,\n.ui[class*=\"right aligned\"].grid > .row > .column,\n.ui.grid > [class*=\"right aligned\"].row > .column,\n.ui.grid > [class*=\"right aligned\"].column.column,\n.ui.grid > .row > [class*=\"right aligned\"].column.column {\n  text-align: right;\n  -ms-flex-item-align: inherit;\n  align-self: inherit;\n}\n\n/* Justified */\n\n.ui.justified.grid > .column,\n.ui.justified.grid > .row > .column,\n.ui.grid > .justified.row > .column,\n.ui.grid > .justified.column.column,\n.ui.grid > .row > .justified.column.column {\n  text-align: justify;\n  -webkit-hyphens: auto;\n  -ms-hyphens: auto;\n  hyphens: auto;\n}\n\n/*----------------------\n        Colored\n-----------------------*/\n\n.ui.grid > .row > .red.column,\n.ui.grid > .row > .orange.column,\n.ui.grid > .row > .yellow.column,\n.ui.grid > .row > .olive.column,\n.ui.grid > .row > .green.column,\n.ui.grid > .row > .teal.column,\n.ui.grid > .row > .blue.column,\n.ui.grid > .row > .violet.column,\n.ui.grid > .row > .purple.column,\n.ui.grid > .row > .pink.column,\n.ui.grid > .row > .brown.column,\n.ui.grid > .row > .grey.column,\n.ui.grid > .row > .black.column {\n  margin-top: -1rem;\n  margin-bottom: -1rem;\n  padding-top: 1rem;\n  padding-bottom: 1rem;\n}\n\n/* Red */\n\n.ui.grid > .red.row,\n.ui.grid > .red.column,\n.ui.grid > .row > .red.column {\n  background-color: #db2828 !important;\n  color: #ffffff;\n}\n\n/* Orange */\n\n.ui.grid > .orange.row,\n.ui.grid > .orange.column,\n.ui.grid > .row > .orange.column {\n  background-color: #f2711c !important;\n  color: #ffffff;\n}\n\n/* Yellow */\n\n.ui.grid > .yellow.row,\n.ui.grid > .yellow.column,\n.ui.grid > .row > .yellow.column {\n  background-color: #fbbd08 !important;\n  color: #ffffff;\n}\n\n/* Olive */\n\n.ui.grid > .olive.row,\n.ui.grid > .olive.column,\n.ui.grid > .row > .olive.column {\n  background-color: #b5cc18 !important;\n  color: #ffffff;\n}\n\n/* Green */\n\n.ui.grid > .green.row,\n.ui.grid > .green.column,\n.ui.grid > .row > .green.column {\n  background-color: #21ba45 !important;\n  color: #ffffff;\n}\n\n/* Teal */\n\n.ui.grid > .teal.row,\n.ui.grid > .teal.column,\n.ui.grid > .row > .teal.column {\n  background-color: #00b5ad !important;\n  color: #ffffff;\n}\n\n/* Blue */\n\n.ui.grid > .blue.row,\n.ui.grid > .blue.column,\n.ui.grid > .row > .blue.column {\n  background-color: #2185d0 !important;\n  color: #ffffff;\n}\n\n/* Violet */\n\n.ui.grid > .violet.row,\n.ui.grid > .violet.column,\n.ui.grid > .row > .violet.column {\n  background-color: #6435c9 !important;\n  color: #ffffff;\n}\n\n/* Purple */\n\n.ui.grid > .purple.row,\n.ui.grid > .purple.column,\n.ui.grid > .row > .purple.column {\n  background-color: #a333c8 !important;\n  color: #ffffff;\n}\n\n/* Pink */\n\n.ui.grid > .pink.row,\n.ui.grid > .pink.column,\n.ui.grid > .row > .pink.column {\n  background-color: #e03997 !important;\n  color: #ffffff;\n}\n\n/* Brown */\n\n.ui.grid > .brown.row,\n.ui.grid > .brown.column,\n.ui.grid > .row > .brown.column {\n  background-color: #a5673f !important;\n  color: #ffffff;\n}\n\n/* Grey */\n\n.ui.grid > .grey.row,\n.ui.grid > .grey.column,\n.ui.grid > .row > .grey.column {\n  background-color: #767676 !important;\n  color: #ffffff;\n}\n\n/* Black */\n\n.ui.grid > .black.row,\n.ui.grid > .black.column,\n.ui.grid > .row > .black.column {\n  background-color: #1b1c1d !important;\n  color: #ffffff;\n}\n\n/*----------------------\n      Equal Width\n-----------------------*/\n\n.ui[class*=\"equal width\"].grid > .column:not(.row),\n.ui[class*=\"equal width\"].grid > .row > .column,\n.ui.grid > [class*=\"equal width\"].row > .column {\n  display: inline-block;\n  -webkit-box-flex: 1;\n  -ms-flex-positive: 1;\n  flex-grow: 1;\n}\n\n.ui[class*=\"equal width\"].grid > .wide.column,\n.ui[class*=\"equal width\"].grid > .row > .wide.column,\n.ui.grid > [class*=\"equal width\"].row > .wide.column {\n  -webkit-box-flex: 0;\n  -ms-flex-positive: 0;\n  flex-grow: 0;\n}\n\n/*----------------------\n        Reverse\n-----------------------*/\n\n/* Mobile */\n\n@media only screen and (width < 768px) {\n\n  .ui[class*=\"mobile reversed\"].grid,\n  .ui[class*=\"mobile reversed\"].grid > .row,\n  .ui.grid > [class*=\"mobile reversed\"].row {\n    -webkit-box-orient: horizontal;\n    -webkit-box-direction: reverse;\n    -ms-flex-direction: row-reverse;\n    flex-direction: row-reverse;\n  }\n\n  .ui[class*=\"mobile vertically reversed\"].grid,\n  .ui.stackable[class*=\"mobile reversed\"] {\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: reverse;\n    -ms-flex-direction: column-reverse;\n    flex-direction: column-reverse;\n  }\n\n  /* Divided Reversed */\n\n  .ui[class*=\"mobile reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .column:first-child,\n  .ui[class*=\"mobile reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .row > .column:first-child {\n    -webkit-box-shadow: -1px 0px 0px 0px rgba(34, 36, 38, 0.15);\n    box-shadow: -1px 0px 0px 0px rgba(34, 36, 38, 0.15);\n  }\n\n  .ui[class*=\"mobile reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .column:last-child,\n  .ui[class*=\"mobile reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .row > .column:last-child {\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n\n  /* Vertically Divided Reversed */\n\n  .ui.grid[class*=\"vertically divided\"][class*=\"mobile vertically reversed\"] > .row:first-child:before {\n    -webkit-box-shadow: 0px -1px 0px 0px rgba(34, 36, 38, 0.15);\n    box-shadow: 0px -1px 0px 0px rgba(34, 36, 38, 0.15);\n  }\n\n  .ui.grid[class*=\"vertically divided\"][class*=\"mobile vertically reversed\"] > .row:last-child:before {\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n\n  /* Celled Reversed */\n\n  .ui[class*=\"mobile reversed\"].celled.grid > .row > .column:first-child {\n    -webkit-box-shadow: -1px 0px 0px 0px #d4d4d5;\n    box-shadow: -1px 0px 0px 0px #d4d4d5;\n  }\n\n  .ui[class*=\"mobile reversed\"].celled.grid > .row > .column:last-child {\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n}\n\n/* Tablet */\n\n@media only screen and (768px <= width < 992px) {\n\n  .ui[class*=\"tablet reversed\"].grid,\n  .ui[class*=\"tablet reversed\"].grid > .row,\n  .ui.grid > [class*=\"tablet reversed\"].row {\n    -webkit-box-orient: horizontal;\n    -webkit-box-direction: reverse;\n    -ms-flex-direction: row-reverse;\n    flex-direction: row-reverse;\n  }\n\n  .ui[class*=\"tablet vertically reversed\"].grid {\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: reverse;\n    -ms-flex-direction: column-reverse;\n    flex-direction: column-reverse;\n  }\n\n  /* Divided Reversed */\n\n  .ui[class*=\"tablet reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .column:first-child,\n  .ui[class*=\"tablet reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .row > .column:first-child {\n    -webkit-box-shadow: -1px 0px 0px 0px rgba(34, 36, 38, 0.15);\n    box-shadow: -1px 0px 0px 0px rgba(34, 36, 38, 0.15);\n  }\n\n  .ui[class*=\"tablet reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .column:last-child,\n  .ui[class*=\"tablet reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .row > .column:last-child {\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n\n  /* Vertically Divided Reversed */\n\n  .ui.grid[class*=\"vertically divided\"][class*=\"tablet vertically reversed\"] > .row:first-child:before {\n    -webkit-box-shadow: 0px -1px 0px 0px rgba(34, 36, 38, 0.15);\n    box-shadow: 0px -1px 0px 0px rgba(34, 36, 38, 0.15);\n  }\n\n  .ui.grid[class*=\"vertically divided\"][class*=\"tablet vertically reversed\"] > .row:last-child:before {\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n\n  /* Celled Reversed */\n\n  .ui[class*=\"tablet reversed\"].celled.grid > .row > .column:first-child {\n    -webkit-box-shadow: -1px 0px 0px 0px #d4d4d5;\n    box-shadow: -1px 0px 0px 0px #d4d4d5;\n  }\n\n  .ui[class*=\"tablet reversed\"].celled.grid > .row > .column:last-child {\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n}\n\n/* Computer */\n\n@media only screen and (width >= 992px) {\n\n  .ui[class*=\"computer reversed\"].grid,\n  .ui[class*=\"computer reversed\"].grid > .row,\n  .ui.grid > [class*=\"computer reversed\"].row {\n    -webkit-box-orient: horizontal;\n    -webkit-box-direction: reverse;\n    -ms-flex-direction: row-reverse;\n    flex-direction: row-reverse;\n  }\n\n  .ui[class*=\"computer vertically reversed\"].grid {\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: reverse;\n    -ms-flex-direction: column-reverse;\n    flex-direction: column-reverse;\n  }\n\n  /* Divided Reversed */\n\n  .ui[class*=\"computer reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .column:first-child,\n  .ui[class*=\"computer reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .row > .column:first-child {\n    -webkit-box-shadow: -1px 0px 0px 0px rgba(34, 36, 38, 0.15);\n    box-shadow: -1px 0px 0px 0px rgba(34, 36, 38, 0.15);\n  }\n\n  .ui[class*=\"computer reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .column:last-child,\n  .ui[class*=\"computer reversed\"].divided.grid:not([class*=\"vertically divided\"]) > .row > .column:last-child {\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n\n  /* Vertically Divided Reversed */\n\n  .ui.grid[class*=\"vertically divided\"][class*=\"computer vertically reversed\"] > .row:first-child:before {\n    -webkit-box-shadow: 0px -1px 0px 0px rgba(34, 36, 38, 0.15);\n    box-shadow: 0px -1px 0px 0px rgba(34, 36, 38, 0.15);\n  }\n\n  .ui.grid[class*=\"vertically divided\"][class*=\"computer vertically reversed\"] > .row:last-child:before {\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n\n  /* Celled Reversed */\n\n  .ui[class*=\"computer reversed\"].celled.grid > .row > .column:first-child {\n    -webkit-box-shadow: -1px 0px 0px 0px #d4d4d5;\n    box-shadow: -1px 0px 0px 0px #d4d4d5;\n  }\n\n  .ui[class*=\"computer reversed\"].celled.grid > .row > .column:last-child {\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n}\n\n/*-------------------\n      Doubling\n--------------------*/\n\n/* Tablet Only */\n\n@media only screen and (768px <= width < 992px) {\n  .ui.doubling.grid {\n    width: auto;\n  }\n\n  .ui.grid > .doubling.row,\n  .ui.doubling.grid > .row {\n    margin: 0em !important;\n    padding: 0em !important;\n  }\n\n  .ui.grid > .doubling.row > .column,\n  .ui.doubling.grid > .row > .column {\n    display: inline-block !important;\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n    margin: 0em;\n  }\n\n  .ui[class*=\"two column\"].doubling.grid > .row > .column,\n  .ui[class*=\"two column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"two column\"].doubling.row.row > .column {\n    width: 100% !important;\n  }\n\n  .ui[class*=\"three column\"].doubling.grid > .row > .column,\n  .ui[class*=\"three column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"three column\"].doubling.row.row > .column {\n    width: 50% !important;\n  }\n\n  .ui[class*=\"four column\"].doubling.grid > .row > .column,\n  .ui[class*=\"four column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"four column\"].doubling.row.row > .column {\n    width: 50% !important;\n  }\n\n  .ui[class*=\"five column\"].doubling.grid > .row > .column,\n  .ui[class*=\"five column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"five column\"].doubling.row.row > .column {\n    width: 33.33333333% !important;\n  }\n\n  .ui[class*=\"six column\"].doubling.grid > .row > .column,\n  .ui[class*=\"six column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"six column\"].doubling.row.row > .column {\n    width: 33.33333333% !important;\n  }\n\n  .ui[class*=\"seven column\"].doubling.grid > .row > .column,\n  .ui[class*=\"seven column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"seven column\"].doubling.row.row > .column {\n    width: 33.33333333% !important;\n  }\n\n  .ui[class*=\"eight column\"].doubling.grid > .row > .column,\n  .ui[class*=\"eight column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"eight column\"].doubling.row.row > .column {\n    width: 25% !important;\n  }\n\n  .ui[class*=\"nine column\"].doubling.grid > .row > .column,\n  .ui[class*=\"nine column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"nine column\"].doubling.row.row > .column {\n    width: 25% !important;\n  }\n\n  .ui[class*=\"ten column\"].doubling.grid > .row > .column,\n  .ui[class*=\"ten column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"ten column\"].doubling.row.row > .column {\n    width: 20% !important;\n  }\n\n  .ui[class*=\"eleven column\"].doubling.grid > .row > .column,\n  .ui[class*=\"eleven column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"eleven column\"].doubling.row.row > .column {\n    width: 20% !important;\n  }\n\n  .ui[class*=\"twelve column\"].doubling.grid > .row > .column,\n  .ui[class*=\"twelve column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"twelve column\"].doubling.row.row > .column {\n    width: 16.66666667% !important;\n  }\n\n  .ui[class*=\"thirteen column\"].doubling.grid > .row > .column,\n  .ui[class*=\"thirteen column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"thirteen column\"].doubling.row.row > .column {\n    width: 16.66666667% !important;\n  }\n\n  .ui[class*=\"fourteen column\"].doubling.grid > .row > .column,\n  .ui[class*=\"fourteen column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"fourteen column\"].doubling.row.row > .column {\n    width: 14.28571429% !important;\n  }\n\n  .ui[class*=\"fifteen column\"].doubling.grid > .row > .column,\n  .ui[class*=\"fifteen column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"fifteen column\"].doubling.row.row > .column {\n    width: 14.28571429% !important;\n  }\n\n  .ui[class*=\"sixteen column\"].doubling.grid > .row > .column,\n  .ui[class*=\"sixteen column\"].doubling.grid > .column:not(.row),\n  .ui.grid > [class*=\"sixteen column\"].doubling.row.row > .column {\n    width: 12.5% !important;\n  }\n}\n\n/* Mobile Only */\n\n@media only screen and (width < 768px) {\n\n  .ui.grid > .doubling.row,\n  .ui.doubling.grid > .row {\n    margin: 0em !important;\n    padding: 0em !important;\n  }\n\n  .ui.grid > .doubling.row > .column,\n  .ui.doubling.grid > .row > .column {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n    margin: 0em !important;\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n  }\n\n  .ui[class*=\"two column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"two column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"two column\"].doubling:not(.stackable).row.row > .column {\n    width: 100% !important;\n  }\n\n  .ui[class*=\"three column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"three column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"three column\"].doubling:not(.stackable).row.row > .column {\n    width: 50% !important;\n  }\n\n  .ui[class*=\"four column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"four column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"four column\"].doubling:not(.stackable).row.row > .column {\n    width: 50% !important;\n  }\n\n  .ui[class*=\"five column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"five column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"five column\"].doubling:not(.stackable).row.row > .column {\n    width: 50% !important;\n  }\n\n  .ui[class*=\"six column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"six column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"six column\"].doubling:not(.stackable).row.row > .column {\n    width: 50% !important;\n  }\n\n  .ui[class*=\"seven column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"seven column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"seven column\"].doubling:not(.stackable).row.row > .column {\n    width: 50% !important;\n  }\n\n  .ui[class*=\"eight column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"eight column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"eight column\"].doubling:not(.stackable).row.row > .column {\n    width: 50% !important;\n  }\n\n  .ui[class*=\"nine column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"nine column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"nine column\"].doubling:not(.stackable).row.row > .column {\n    width: 33.33333333% !important;\n  }\n\n  .ui[class*=\"ten column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"ten column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"ten column\"].doubling:not(.stackable).row.row > .column {\n    width: 33.33333333% !important;\n  }\n\n  .ui[class*=\"eleven column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"eleven column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"eleven column\"].doubling:not(.stackable).row.row > .column {\n    width: 33.33333333% !important;\n  }\n\n  .ui[class*=\"twelve column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"twelve column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"twelve column\"].doubling:not(.stackable).row.row > .column {\n    width: 33.33333333% !important;\n  }\n\n  .ui[class*=\"thirteen column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"thirteen column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"thirteen column\"].doubling:not(.stackable).row.row > .column {\n    width: 33.33333333% !important;\n  }\n\n  .ui[class*=\"fourteen column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"fourteen column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"fourteen column\"].doubling:not(.stackable).row.row > .column {\n    width: 25% !important;\n  }\n\n  .ui[class*=\"fifteen column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"fifteen column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"fifteen column\"].doubling:not(.stackable).row.row > .column {\n    width: 25% !important;\n  }\n\n  .ui[class*=\"sixteen column\"].doubling:not(.stackable).grid > .row > .column,\n  .ui[class*=\"sixteen column\"].doubling:not(.stackable).grid > .column:not(.row),\n  .ui.grid > [class*=\"sixteen column\"].doubling:not(.stackable).row.row > .column {\n    width: 25% !important;\n  }\n}\n\n/*-------------------\n      Stackable\n--------------------*/\n\n@media only screen and (width < 768px) {\n  .ui.stackable.grid {\n    width: auto;\n    margin-left: 0em !important;\n    margin-right: 0em !important;\n  }\n\n  .ui.stackable.grid > .row > .wide.column,\n  .ui.stackable.grid > .wide.column,\n  .ui.stackable.grid > .column.grid > .column,\n  .ui.stackable.grid > .column.row > .column,\n  .ui.stackable.grid > .row > .column,\n  .ui.stackable.grid > .column:not(.row),\n  .ui.grid > .stackable.stackable.row > .column {\n    width: 100% !important;\n    margin: 0em 0em !important;\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n    padding: 1rem 1rem !important;\n  }\n\n  .ui.stackable.grid:not(.vertically) > .row {\n    margin: 0em;\n    padding: 0em;\n  }\n\n  /* Coupling */\n\n  .ui.container > .ui.stackable.grid > .column,\n  .ui.container > .ui.stackable.grid > .row > .column {\n    padding-left: 0em !important;\n    padding-right: 0em !important;\n  }\n\n  /* Don't pad inside segment or nested grid */\n\n  .ui.grid .ui.stackable.grid,\n  .ui.segment:not(.vertical) .ui.stackable.page.grid {\n    margin-left: -1rem !important;\n    margin-right: -1rem !important;\n  }\n\n  /* Divided Stackable */\n\n  .ui.stackable.divided.grid > .row:first-child > .column:first-child,\n  .ui.stackable.celled.grid > .row:first-child > .column:first-child,\n  .ui.stackable.divided.grid > .column:not(.row):first-child,\n  .ui.stackable.celled.grid > .column:not(.row):first-child {\n    border-top: none !important;\n  }\n\n  .ui.inverted.stackable.celled.grid > .column:not(.row),\n  .ui.inverted.stackable.divided.grid > .column:not(.row),\n  .ui.inverted.stackable.celled.grid > .row > .column,\n  .ui.inverted.stackable.divided.grid > .row > .column {\n    border-top: 1px solid rgba(255, 255, 255, 0.1);\n  }\n\n  .ui.stackable.celled.grid > .column:not(.row),\n  .ui.stackable.divided:not(.vertically).grid > .column:not(.row),\n  .ui.stackable.celled.grid > .row > .column,\n  .ui.stackable.divided:not(.vertically).grid > .row > .column {\n    border-top: 1px solid rgba(34, 36, 38, 0.15);\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n    padding-top: 2rem !important;\n    padding-bottom: 2rem !important;\n  }\n\n  .ui.stackable.celled.grid > .row {\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n  }\n\n  .ui.stackable.divided:not(.vertically).grid > .column:not(.row),\n  .ui.stackable.divided:not(.vertically).grid > .row > .column {\n    padding-left: 0em !important;\n    padding-right: 0em !important;\n  }\n}\n\n/*----------------------\n    Only (Device)\n-----------------------*/\n\n/* These include arbitrary class repetitions for forced specificity */\n\n/* Mobile Only Hide */\n\n@media only screen and (width < 768px) {\n\n  .ui[class*=\"tablet only\"].grid.grid.grid:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"tablet only\"].row:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"tablet only\"].column:not(.mobile),\n  .ui.grid.grid.grid > .row > [class*=\"tablet only\"].column:not(.mobile) {\n    display: none !important;\n  }\n\n  .ui[class*=\"computer only\"].grid.grid.grid:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"computer only\"].row:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"computer only\"].column:not(.mobile),\n  .ui.grid.grid.grid > .row > [class*=\"computer only\"].column:not(.mobile) {\n    display: none !important;\n  }\n\n  .ui[class*=\"large screen only\"].grid.grid.grid:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"large screen only\"].row:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"large screen only\"].column:not(.mobile),\n  .ui.grid.grid.grid > .row > [class*=\"large screen only\"].column:not(.mobile) {\n    display: none !important;\n  }\n\n  .ui[class*=\"widescreen only\"].grid.grid.grid:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"widescreen only\"].row:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"widescreen only\"].column:not(.mobile),\n  .ui.grid.grid.grid > .row > [class*=\"widescreen only\"].column:not(.mobile) {\n    display: none !important;\n  }\n}\n\n/* Tablet Only Hide */\n\n@media only screen and (768px <= width < 992px) {\n\n  .ui[class*=\"mobile only\"].grid.grid.grid:not(.tablet),\n  .ui.grid.grid.grid > [class*=\"mobile only\"].row:not(.tablet),\n  .ui.grid.grid.grid > [class*=\"mobile only\"].column:not(.tablet),\n  .ui.grid.grid.grid > .row > [class*=\"mobile only\"].column:not(.tablet) {\n    display: none !important;\n  }\n\n  .ui[class*=\"computer only\"].grid.grid.grid:not(.tablet),\n  .ui.grid.grid.grid > [class*=\"computer only\"].row:not(.tablet),\n  .ui.grid.grid.grid > [class*=\"computer only\"].column:not(.tablet),\n  .ui.grid.grid.grid > .row > [class*=\"computer only\"].column:not(.tablet) {\n    display: none !important;\n  }\n\n  .ui[class*=\"large screen only\"].grid.grid.grid:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"large screen only\"].row:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"large screen only\"].column:not(.mobile),\n  .ui.grid.grid.grid > .row > [class*=\"large screen only\"].column:not(.mobile) {\n    display: none !important;\n  }\n\n  .ui[class*=\"widescreen only\"].grid.grid.grid:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"widescreen only\"].row:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"widescreen only\"].column:not(.mobile),\n  .ui.grid.grid.grid > .row > [class*=\"widescreen only\"].column:not(.mobile) {\n    display: none !important;\n  }\n}\n\n/* Computer Only Hide */\n\n@media only screen and (992px <= width < 1200px) {\n\n  .ui[class*=\"mobile only\"].grid.grid.grid:not(.computer),\n  .ui.grid.grid.grid > [class*=\"mobile only\"].row:not(.computer),\n  .ui.grid.grid.grid > [class*=\"mobile only\"].column:not(.computer),\n  .ui.grid.grid.grid > .row > [class*=\"mobile only\"].column:not(.computer) {\n    display: none !important;\n  }\n\n  .ui[class*=\"tablet only\"].grid.grid.grid:not(.computer),\n  .ui.grid.grid.grid > [class*=\"tablet only\"].row:not(.computer),\n  .ui.grid.grid.grid > [class*=\"tablet only\"].column:not(.computer),\n  .ui.grid.grid.grid > .row > [class*=\"tablet only\"].column:not(.computer) {\n    display: none !important;\n  }\n\n  .ui[class*=\"large screen only\"].grid.grid.grid:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"large screen only\"].row:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"large screen only\"].column:not(.mobile),\n  .ui.grid.grid.grid > .row > [class*=\"large screen only\"].column:not(.mobile) {\n    display: none !important;\n  }\n\n  .ui[class*=\"widescreen only\"].grid.grid.grid:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"widescreen only\"].row:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"widescreen only\"].column:not(.mobile),\n  .ui.grid.grid.grid > .row > [class*=\"widescreen only\"].column:not(.mobile) {\n    display: none !important;\n  }\n}\n\n/* Large Screen Only Hide */\n\n@media only screen and (1200px <= width <= 1920px) {\n\n  .ui[class*=\"mobile only\"].grid.grid.grid:not(.computer),\n  .ui.grid.grid.grid > [class*=\"mobile only\"].row:not(.computer),\n  .ui.grid.grid.grid > [class*=\"mobile only\"].column:not(.computer),\n  .ui.grid.grid.grid > .row > [class*=\"mobile only\"].column:not(.computer) {\n    display: none !important;\n  }\n\n  .ui[class*=\"tablet only\"].grid.grid.grid:not(.computer),\n  .ui.grid.grid.grid > [class*=\"tablet only\"].row:not(.computer),\n  .ui.grid.grid.grid > [class*=\"tablet only\"].column:not(.computer),\n  .ui.grid.grid.grid > .row > [class*=\"tablet only\"].column:not(.computer) {\n    display: none !important;\n  }\n\n  .ui[class*=\"widescreen only\"].grid.grid.grid:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"widescreen only\"].row:not(.mobile),\n  .ui.grid.grid.grid > [class*=\"widescreen only\"].column:not(.mobile),\n  .ui.grid.grid.grid > .row > [class*=\"widescreen only\"].column:not(.mobile) {\n    display: none !important;\n  }\n}\n\n/* Widescreen Only Hide */\n\n@media only screen and (width >= 1920px) {\n\n  .ui[class*=\"mobile only\"].grid.grid.grid:not(.computer),\n  .ui.grid.grid.grid > [class*=\"mobile only\"].row:not(.computer),\n  .ui.grid.grid.grid > [class*=\"mobile only\"].column:not(.computer),\n  .ui.grid.grid.grid > .row > [class*=\"mobile only\"].column:not(.computer) {\n    display: none !important;\n  }\n\n  .ui[class*=\"tablet only\"].grid.grid.grid:not(.computer),\n  .ui.grid.grid.grid > [class*=\"tablet only\"].row:not(.computer),\n  .ui.grid.grid.grid > [class*=\"tablet only\"].column:not(.computer),\n  .ui.grid.grid.grid > .row > [class*=\"tablet only\"].column:not(.computer) {\n    display: none !important;\n  }\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*\n* # Semantic - Menu\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Copyright 2015 Contributor\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Standard\n*******************************/\n\n/*--------------\n      Menu\n---------------*/\n\n.ui.menu {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  margin: 1rem 0em;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  background: #ffffff;\n  font-weight: normal;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n  border-radius: 0.28571429rem;\n  min-height: 2.85714286em;\n}\n\n.ui.menu:after {\n  content: \"\";\n  display: block;\n  height: 0px;\n  clear: both;\n  visibility: hidden;\n}\n\n.ui.menu:first-child {\n  margin-top: 0rem;\n}\n\n.ui.menu:last-child {\n  margin-bottom: 0rem;\n}\n\n/*--------------\n    Sub-Menu\n---------------*/\n\n.ui.menu .menu {\n  margin: 0em;\n}\n\n.ui.menu:not(.vertical) > .menu {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n}\n\n/*--------------\n      Item\n---------------*/\n\n.ui.menu:not(.vertical) .item {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n}\n\n.ui.menu .item {\n  position: relative;\n  vertical-align: middle;\n  line-height: 1;\n  text-decoration: none;\n  -webkit-tap-highlight-color: transparent;\n  -webkit-box-flex: 0;\n  -ms-flex: 0 0 auto;\n  flex: 0 0 auto;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  background: none;\n  padding: 0.92857143em 1.14285714em;\n  text-transform: none;\n  color: rgba(0, 0, 0, 0.87);\n  font-weight: normal;\n  -webkit-transition: background 0.1s ease, color 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n  transition: background 0.1s ease, color 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n  transition: background 0.1s ease, box-shadow 0.1s ease, color 0.1s ease;\n  transition: background 0.1s ease, box-shadow 0.1s ease, color 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n}\n\n.ui.menu > .item:first-child {\n  border-radius: 0.28571429rem 0px 0px 0.28571429rem;\n}\n\n/* Border */\n\n.ui.menu .item:before {\n  position: absolute;\n  content: \"\";\n  top: 0%;\n  right: 0px;\n  height: 100%;\n  width: 1px;\n  background: rgba(34, 36, 38, 0.1);\n}\n\n/*--------------\n  Text Content\n---------------*/\n\n.ui.menu .text.item > *,\n.ui.menu .item > a:not(.ui),\n.ui.menu .item > p:only-child {\n  -webkit-user-select: text;\n  -moz-user-select: text;\n  -ms-user-select: text;\n  user-select: text;\n  line-height: 1.3;\n}\n\n.ui.menu .item > p:first-child {\n  margin-top: 0;\n}\n\n.ui.menu .item > p:last-child {\n  margin-bottom: 0;\n}\n\n/*--------------\n      Icons\n---------------*/\n\n.ui.menu .item > i.icon {\n  opacity: 0.9;\n  float: none;\n  margin: 0em 0.35714286em 0em 0em;\n}\n\n/*--------------\n    Button\n---------------*/\n\n.ui.menu:not(.vertical) .item > .button {\n  position: relative;\n  top: 0em;\n  margin: -0.5em 0em;\n  padding-bottom: 0.78571429em;\n  padding-top: 0.78571429em;\n  font-size: 1em;\n}\n\n/*----------------\nGrid / Container\n-----------------*/\n\n.ui.menu > .grid,\n.ui.menu > .container {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: inherit;\n  -ms-flex-align: inherit;\n  align-items: inherit;\n  -webkit-box-orient: inherit;\n  -webkit-box-direction: inherit;\n  -ms-flex-direction: inherit;\n  flex-direction: inherit;\n}\n\n/*--------------\n    Inputs\n---------------*/\n\n.ui.menu .item > .input {\n  width: 100%;\n}\n\n.ui.menu:not(.vertical) .item > .input {\n  position: relative;\n  top: 0em;\n  margin: -0.5em 0em;\n}\n\n.ui.menu .item > .input input {\n  font-size: 1em;\n  padding-top: 0.57142857em;\n  padding-bottom: 0.57142857em;\n}\n\n/*--------------\n    Header\n---------------*/\n\n.ui.menu .header.item,\n.ui.vertical.menu .header.item {\n  margin: 0em;\n  background: \"\";\n  text-transform: normal;\n  font-weight: bold;\n}\n\n.ui.vertical.menu .item > .header:not(.ui) {\n  margin: 0em 0em 0.5em;\n  font-size: 1em;\n  font-weight: bold;\n}\n\n/*--------------\n    Dropdowns\n---------------*/\n\n/* Dropdown Icon */\n\n.ui.menu .item > i.dropdown.icon {\n  padding: 0em;\n  float: right;\n  margin: 0em 0em 0em 1em;\n}\n\n/* Menu */\n\n.ui.menu .dropdown.item .menu {\n  min-width: calc(100% - 1px);\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n  background: #ffffff;\n  margin: 0em 0px 0px;\n  -webkit-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.08);\n  box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.08);\n  -webkit-box-orient: vertical !important;\n  -webkit-box-direction: normal !important;\n  -ms-flex-direction: column !important;\n  flex-direction: column !important;\n}\n\n/* Menu Items */\n\n.ui.menu .ui.dropdown .menu > .item {\n  margin: 0;\n  text-align: left;\n  font-size: 1em !important;\n  padding: 0.78571429em 1.14285714em !important;\n  background: transparent !important;\n  color: rgba(0, 0, 0, 0.87) !important;\n  text-transform: none !important;\n  font-weight: normal !important;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  -webkit-transition: none !important;\n  transition: none !important;\n}\n\n.ui.menu .ui.dropdown .menu > .item:hover {\n  background: rgba(0, 0, 0, 0.05) !important;\n  color: rgba(0, 0, 0, 0.95) !important;\n}\n\n.ui.menu .ui.dropdown .menu > .selected.item {\n  background: rgba(0, 0, 0, 0.05) !important;\n  color: rgba(0, 0, 0, 0.95) !important;\n}\n\n.ui.menu .ui.dropdown .menu > .active.item {\n  background: rgba(0, 0, 0, 0.03) !important;\n  font-weight: bold !important;\n  color: rgba(0, 0, 0, 0.95) !important;\n}\n\n.ui.menu .ui.dropdown.item .menu .item:not(.filtered) {\n  display: block;\n}\n\n.ui.menu .ui.dropdown .menu > .item .icon:not(.dropdown) {\n  display: inline-block;\n  font-size: 1em !important;\n  float: none;\n  margin: 0em 0.75em 0em 0em !important;\n}\n\n/* Secondary */\n\n.ui.secondary.menu .dropdown.item > .menu,\n.ui.text.menu .dropdown.item > .menu {\n  border-radius: 0.28571429rem;\n  margin-top: 0.35714286em;\n}\n\n/* Pointing */\n\n.ui.menu .pointing.dropdown.item .menu {\n  margin-top: 0.75em;\n}\n\n/* Inverted */\n\n.ui.inverted.menu .search.dropdown.item > .search,\n.ui.inverted.menu .search.dropdown.item > .text {\n  color: rgba(255, 255, 255, 0.9);\n}\n\n/* Vertical */\n\n.ui.vertical.menu .dropdown.item > .icon {\n  float: right;\n  content: \"\\f0da\";\n  margin-left: 1em;\n}\n\n.ui.vertical.menu .dropdown.item .menu {\n  left: 100%;\n  min-width: 0;\n  margin: 0em 0em 0em 0em;\n  -webkit-box-shadow: 0 1px 3px 0px rgba(0, 0, 0, 0.08);\n  box-shadow: 0 1px 3px 0px rgba(0, 0, 0, 0.08);\n  border-radius: 0em 0.28571429rem 0.28571429rem 0.28571429rem;\n}\n\n.ui.vertical.menu .dropdown.item.upward .menu {\n  bottom: 0;\n}\n\n.ui.vertical.menu .dropdown.item:not(.upward) .menu {\n  top: 0;\n}\n\n.ui.vertical.menu .active.dropdown.item {\n  border-top-right-radius: 0em;\n  border-bottom-right-radius: 0em;\n}\n\n.ui.vertical.menu .dropdown.active.item {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Evenly Divided */\n\n.ui.item.menu .dropdown .menu .item {\n  width: 100%;\n}\n\n/*--------------\n    Labels\n---------------*/\n\n.ui.menu .item > .label {\n  background: #999999;\n  color: #ffffff;\n  margin-left: 1em;\n  padding: 0.3em 0.78571429em;\n}\n\n.ui.vertical.menu .item > .label {\n  background: #999999;\n  color: #ffffff;\n  margin-top: -0.15em;\n  margin-bottom: -0.15em;\n  padding: 0.3em 0.78571429em;\n}\n\n.ui.menu .item > .floating.label {\n  padding: 0.3em 0.78571429em;\n}\n\n/*--------------\n    Images\n---------------*/\n\n.ui.menu .item > img:not(.ui) {\n  display: inline-block;\n  vertical-align: middle;\n  margin: -0.3em 0em;\n  width: 2.5em;\n}\n\n.ui.vertical.menu .item > img:not(.ui):only-child {\n  display: block;\n  max-width: 100%;\n  width: auto;\n}\n\n/*******************************\n          Coupling\n*******************************/\n\n/*--------------\n    List\n---------------*/\n\n/* Menu divider shouldnt apply */\n\n.ui.menu .list .item:before {\n  background: none !important;\n}\n\n/*--------------\n    Sidebar\n---------------*/\n\n/* Show vertical dividers below last */\n\n.ui.vertical.sidebar.menu > .item:first-child:before {\n  display: block !important;\n}\n\n.ui.vertical.sidebar.menu > .item::before {\n  top: auto;\n  bottom: 0px;\n}\n\n/*--------------\n    Container\n---------------*/\n\n@media only screen and (width < 768px) {\n  .ui.menu > .ui.container {\n    width: 100% !important;\n    margin-left: 0em !important;\n    margin-right: 0em !important;\n  }\n}\n\n@media only screen and (width >= 768px) {\n  .ui.menu:not(.secondary):not(.text):not(.tabular):not(.borderless) > .container > .item:not(.right):not(.borderless):first-child {\n    border-left: 1px solid rgba(34, 36, 38, 0.1);\n  }\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------\n      Hover\n---------------*/\n\n.ui.link.menu .item:hover,\n.ui.menu .dropdown.item:hover,\n.ui.menu .link.item:hover,\n.ui.menu a.item:hover {\n  cursor: pointer;\n  background: rgba(0, 0, 0, 0.03);\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------\n    Pressed\n---------------*/\n\n.ui.link.menu .item:active,\n.ui.menu .link.item:active,\n.ui.menu a.item:active {\n  background: rgba(0, 0, 0, 0.03);\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------\n    Active\n---------------*/\n\n.ui.menu .active.item {\n  background: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.95);\n  font-weight: normal;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.menu .active.item > i.icon {\n  opacity: 1;\n}\n\n/*--------------\n  Active Hover\n---------------*/\n\n.ui.menu .active.item:hover,\n.ui.vertical.menu .active.item:hover {\n  background-color: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------\n    Disabled\n---------------*/\n\n.ui.menu .item.disabled,\n.ui.menu .item.disabled:hover {\n  cursor: default !important;\n  background-color: transparent !important;\n  color: rgba(40, 40, 40, 0.3) !important;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*------------------\nFloated Menu / Item\n-------------------*/\n\n/* Left Floated */\n\n.ui.menu:not(.vertical) .left.item,\n.ui.menu:not(.vertical) :not(.dropdown) > .left.menu {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  margin-right: auto !important;\n}\n\n/* Right Floated */\n\n.ui.menu:not(.vertical) .right.item,\n.ui.menu:not(.vertical) .right.menu {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  margin-left: auto !important;\n}\n\n/* Swapped Borders */\n\n.ui.menu .right.item::before,\n.ui.menu .right.menu > .item::before {\n  right: auto;\n  left: 0;\n}\n\n/*--------------\n    Vertical\n---------------*/\n\n.ui.vertical.menu {\n  display: block;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  background: #ffffff;\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n}\n\n/*--- Item ---*/\n\n.ui.vertical.menu .item {\n  display: block;\n  background: none;\n  border-top: none;\n  border-right: none;\n}\n\n.ui.vertical.menu > .item:first-child {\n  border-radius: 0.28571429rem 0.28571429rem 0px 0px;\n}\n\n.ui.vertical.menu > .item:last-child {\n  border-radius: 0px 0px 0.28571429rem 0.28571429rem;\n}\n\n/*--- Label ---*/\n\n.ui.vertical.menu .item > .label {\n  float: right;\n  text-align: center;\n}\n\n/*--- Icon ---*/\n\n.ui.vertical.menu .item > i.icon {\n  width: 1.18em;\n  float: right;\n  margin: 0em 0em 0em 0.5em;\n}\n\n.ui.vertical.menu .item > .label + i.icon {\n  float: none;\n  margin: 0em 0.5em 0em 0em;\n}\n\n/*--- Border ---*/\n\n.ui.vertical.menu .item:before {\n  position: absolute;\n  content: \"\";\n  top: 0%;\n  left: 0px;\n  width: 100%;\n  height: 1px;\n  background: rgba(34, 36, 38, 0.1);\n}\n\n.ui.vertical.menu .item:first-child:before {\n  display: none !important;\n}\n\n/*--- Sub Menu ---*/\n\n.ui.vertical.menu .item > .menu {\n  margin: 0.5em -1.14285714em 0em;\n}\n\n.ui.vertical.menu .menu .item {\n  background: none;\n  padding: 0.5em 1.33333333em;\n  font-size: 0.85714286em;\n  color: rgba(0, 0, 0, 0.5);\n}\n\n.ui.vertical.menu .item .menu a.item:hover,\n.ui.vertical.menu .item .menu .link.item:hover {\n  color: rgba(0, 0, 0, 0.85);\n}\n\n.ui.vertical.menu .menu .item:before {\n  display: none;\n}\n\n/* Vertical Active */\n\n.ui.vertical.menu .active.item {\n  background: rgba(0, 0, 0, 0.05);\n  border-radius: 0em;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.vertical.menu > .active.item:first-child {\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n.ui.vertical.menu > .active.item:last-child {\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n.ui.vertical.menu > .active.item:only-child {\n  border-radius: 0.28571429rem;\n}\n\n.ui.vertical.menu .active.item .menu .active.item {\n  border-left: none;\n}\n\n.ui.vertical.menu .item .menu .active.item {\n  background-color: transparent;\n  font-weight: bold;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------\n    Tabular\n---------------*/\n\n.ui.tabular.menu {\n  border-radius: 0em;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  border: none;\n  background: none transparent;\n  border-bottom: 1px solid #d4d4d5;\n}\n\n.ui.tabular.fluid.menu {\n  width: calc(100% + 2px) !important;\n}\n\n.ui.tabular.menu .item {\n  background: transparent;\n  border-bottom: none;\n  border-left: 1px solid transparent;\n  border-right: 1px solid transparent;\n  border-top: 2px solid transparent;\n  padding: 0.92857143em 1.42857143em;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.tabular.menu .item:before {\n  display: none;\n}\n\n/* Hover */\n\n.ui.tabular.menu .item:hover {\n  background-color: transparent;\n  color: rgba(0, 0, 0, 0.8);\n}\n\n/* Active */\n\n.ui.tabular.menu .active.item {\n  background: none #ffffff;\n  color: rgba(0, 0, 0, 0.95);\n  border-top-width: 1px;\n  border-color: #d4d4d5;\n  font-weight: bold;\n  margin-bottom: -1px;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border-radius: 0.28571429rem 0.28571429rem 0px 0px !important;\n}\n\n/* Coupling with segment for attachment */\n\n.ui.tabular.menu + .attached:not(.top).segment,\n.ui.tabular.menu + .attached:not(.top).segment + .attached:not(.top).segment {\n  border-top: none;\n  margin-left: 0px;\n  margin-top: 0px;\n  margin-right: 0px;\n  width: 100%;\n}\n\n.top.attached.segment + .ui.bottom.tabular.menu {\n  position: relative;\n  width: calc(100% + 2px);\n  left: -1px;\n}\n\n/* Bottom Vertical Tabular */\n\n.ui.bottom.tabular.menu {\n  background: none transparent;\n  border-radius: 0em;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  border-bottom: none;\n  border-top: 1px solid #d4d4d5;\n}\n\n.ui.bottom.tabular.menu .item {\n  background: none;\n  border-left: 1px solid transparent;\n  border-right: 1px solid transparent;\n  border-bottom: 1px solid transparent;\n  border-top: none;\n}\n\n.ui.bottom.tabular.menu .active.item {\n  background: none #ffffff;\n  color: rgba(0, 0, 0, 0.95);\n  border-color: #d4d4d5;\n  margin: -1px 0px 0px 0px;\n  border-radius: 0px 0px 0.28571429rem 0.28571429rem !important;\n}\n\n/* Vertical Tabular (Left) */\n\n.ui.vertical.tabular.menu {\n  background: none transparent;\n  border-radius: 0em;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  border-bottom: none;\n  border-right: 1px solid #d4d4d5;\n}\n\n.ui.vertical.tabular.menu .item {\n  background: none;\n  border-left: 1px solid transparent;\n  border-bottom: 1px solid transparent;\n  border-top: 1px solid transparent;\n  border-right: none;\n}\n\n.ui.vertical.tabular.menu .active.item {\n  background: none #ffffff;\n  color: rgba(0, 0, 0, 0.95);\n  border-color: #d4d4d5;\n  margin: 0px -1px 0px 0px;\n  border-radius: 0.28571429rem 0px 0px 0.28571429rem !important;\n}\n\n/* Vertical Right Tabular */\n\n.ui.vertical.right.tabular.menu {\n  background: none transparent;\n  border-radius: 0em;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  border-bottom: none;\n  border-right: none;\n  border-left: 1px solid #d4d4d5;\n}\n\n.ui.vertical.right.tabular.menu .item {\n  background: none;\n  border-right: 1px solid transparent;\n  border-bottom: 1px solid transparent;\n  border-top: 1px solid transparent;\n  border-left: none;\n}\n\n.ui.vertical.right.tabular.menu .active.item {\n  background: none #ffffff;\n  color: rgba(0, 0, 0, 0.95);\n  border-color: #d4d4d5;\n  margin: 0px 0px 0px -1px;\n  border-radius: 0px 0.28571429rem 0.28571429rem 0px !important;\n}\n\n/* Dropdown */\n\n.ui.tabular.menu .active.dropdown.item {\n  margin-bottom: 0px;\n  border-left: 1px solid transparent;\n  border-right: 1px solid transparent;\n  border-top: 2px solid transparent;\n  border-bottom: none;\n}\n\n/*--------------\n  Pagination\n---------------*/\n\n.ui.pagination.menu {\n  margin: 0em;\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  vertical-align: middle;\n}\n\n.ui.pagination.menu .item:last-child {\n  border-radius: 0em 0.28571429rem 0.28571429rem 0em;\n}\n\n.ui.compact.menu .item:last-child {\n  border-radius: 0em 0.28571429rem 0.28571429rem 0em;\n}\n\n.ui.pagination.menu .item:last-child:before {\n  display: none;\n}\n\n.ui.pagination.menu .item {\n  min-width: 3em;\n  text-align: center;\n}\n\n.ui.pagination.menu .icon.item i.icon {\n  vertical-align: top;\n}\n\n/* Active */\n\n.ui.pagination.menu .active.item {\n  border-top: none;\n  padding-top: 0.92857143em;\n  background-color: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.95);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/*--------------\n  Secondary\n---------------*/\n\n.ui.secondary.menu {\n  background: none;\n  margin-left: -0.35714286em;\n  margin-right: -0.35714286em;\n  border-radius: 0em;\n  border: none;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Item */\n\n.ui.secondary.menu .item {\n  -ms-flex-item-align: center;\n  align-self: center;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border: none;\n  padding: 0.78571429em 0.92857143em;\n  margin: 0em 0.35714286em;\n  background: none;\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n  border-radius: 0.28571429rem;\n}\n\n/* No Divider */\n\n.ui.secondary.menu .item:before {\n  display: none !important;\n}\n\n/* Header */\n\n.ui.secondary.menu .header.item {\n  border-radius: 0em;\n  border-right: none;\n  background: none transparent;\n}\n\n/* Image */\n\n.ui.secondary.menu .item > img:not(.ui) {\n  margin: 0em;\n}\n\n/* Hover */\n\n.ui.secondary.menu .dropdown.item:hover,\n.ui.secondary.menu .link.item:hover,\n.ui.secondary.menu a.item:hover {\n  background: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/* Active */\n\n.ui.secondary.menu .active.item {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  background: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.95);\n  border-radius: 0.28571429rem;\n}\n\n/* Active Hover */\n\n.ui.secondary.menu .active.item:hover {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  background: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/* Inverted */\n\n.ui.secondary.inverted.menu .link.item,\n.ui.secondary.inverted.menu a.item {\n  color: rgba(255, 255, 255, 0.7) !important;\n}\n\n.ui.secondary.inverted.menu .dropdown.item:hover,\n.ui.secondary.inverted.menu .link.item:hover,\n.ui.secondary.inverted.menu a.item:hover {\n  background: rgba(255, 255, 255, 0.08);\n  color: #ffffff !important;\n}\n\n.ui.secondary.inverted.menu .active.item {\n  background: rgba(255, 255, 255, 0.15);\n  color: #ffffff !important;\n}\n\n/* Fix item margins */\n\n.ui.secondary.item.menu {\n  margin-left: 0em;\n  margin-right: 0em;\n}\n\n.ui.secondary.item.menu .item:last-child {\n  margin-right: 0em;\n}\n\n.ui.secondary.attached.menu {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Sub Menu */\n\n.ui.vertical.secondary.menu .item:not(.dropdown) > .menu {\n  margin: 0em -0.92857143em;\n}\n\n.ui.vertical.secondary.menu .item:not(.dropdown) > .menu > .item {\n  margin: 0em;\n  padding: 0.5em 1.33333333em;\n}\n\n/*---------------------\n  Secondary Vertical\n-----------------------*/\n\n.ui.secondary.vertical.menu > .item {\n  border: none;\n  margin: 0em 0em 0.35714286em;\n  border-radius: 0.28571429rem !important;\n}\n\n.ui.secondary.vertical.menu > .header.item {\n  border-radius: 0em;\n}\n\n/* Sub Menu */\n\n.ui.vertical.secondary.menu .item > .menu .item {\n  background-color: transparent;\n}\n\n/* Inverted */\n\n.ui.secondary.inverted.menu {\n  background-color: transparent;\n}\n\n/*---------------------\n  Secondary Pointing\n-----------------------*/\n\n.ui.secondary.pointing.menu {\n  margin-left: 0em;\n  margin-right: 0em;\n  border-bottom: 2px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.secondary.pointing.menu .item {\n  border-bottom-color: transparent;\n  border-bottom-style: solid;\n  border-radius: 0em;\n  -ms-flex-item-align: end;\n  align-self: flex-end;\n  margin: 0em 0em -2px;\n  padding: 0.85714286em 1.14285714em;\n  border-bottom-width: 2px;\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n}\n\n/* Item Types */\n\n.ui.secondary.pointing.menu .header.item {\n  color: rgba(0, 0, 0, 0.85) !important;\n}\n\n.ui.secondary.pointing.menu .text.item {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n}\n\n.ui.secondary.pointing.menu .item:after {\n  display: none;\n}\n\n/* Hover */\n\n.ui.secondary.pointing.menu .dropdown.item:hover,\n.ui.secondary.pointing.menu .link.item:hover,\n.ui.secondary.pointing.menu a.item:hover {\n  background-color: transparent;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* Pressed */\n\n.ui.secondary.pointing.menu .dropdown.item:active,\n.ui.secondary.pointing.menu .link.item:active,\n.ui.secondary.pointing.menu a.item:active {\n  background-color: transparent;\n  border-color: rgba(34, 36, 38, 0.15);\n}\n\n/* Active */\n\n.ui.secondary.pointing.menu .active.item {\n  background-color: transparent;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border-color: #1b1c1d;\n  font-weight: bold;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/* Active Hover */\n\n.ui.secondary.pointing.menu .active.item:hover {\n  border-color: #1b1c1d;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/* Active Dropdown */\n\n.ui.secondary.pointing.menu .active.dropdown.item {\n  border-color: transparent;\n}\n\n/* Vertical Pointing */\n\n.ui.secondary.vertical.pointing.menu {\n  border-bottom-width: 0px;\n  border-right-width: 2px;\n  border-right-style: solid;\n  border-right-color: rgba(34, 36, 38, 0.15);\n}\n\n.ui.secondary.vertical.pointing.menu .item {\n  border-bottom: none;\n  border-right-style: solid;\n  border-right-color: transparent;\n  border-radius: 0em !important;\n  margin: 0em -2px 0em 0em;\n  border-right-width: 2px;\n}\n\n/* Vertical Active */\n\n.ui.secondary.vertical.pointing.menu .active.item {\n  border-color: #1b1c1d;\n}\n\n/* Inverted */\n\n.ui.secondary.inverted.pointing.menu {\n  border-color: rgba(255, 255, 255, 0.1);\n}\n\n.ui.secondary.inverted.pointing.menu {\n  border-width: 2px;\n  border-color: rgba(34, 36, 38, 0.15);\n}\n\n.ui.secondary.inverted.pointing.menu .item {\n  color: rgba(255, 255, 255, 0.9);\n}\n\n.ui.secondary.inverted.pointing.menu .header.item {\n  color: #ffffff !important;\n}\n\n/* Hover */\n\n.ui.secondary.inverted.pointing.menu .link.item:hover,\n.ui.secondary.inverted.pointing.menu a.item:hover {\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/* Active */\n\n.ui.secondary.inverted.pointing.menu .active.item {\n  border-color: #ffffff;\n  color: #ffffff;\n}\n\n/*--------------\n    Text Menu\n---------------*/\n\n.ui.text.menu {\n  background: none transparent;\n  border-radius: 0px;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border: none;\n  margin: 1em -0.5em;\n}\n\n.ui.text.menu .item {\n  border-radius: 0px;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  -ms-flex-item-align: center;\n  align-self: center;\n  margin: 0em 0em;\n  padding: 0.35714286em 0.5em;\n  font-weight: normal;\n  color: rgba(0, 0, 0, 0.6);\n  -webkit-transition: opacity 0.1s ease;\n  transition: opacity 0.1s ease;\n}\n\n/* Border */\n\n.ui.text.menu .item:before,\n.ui.text.menu .menu .item:before {\n  display: none !important;\n}\n\n/* Header */\n\n.ui.text.menu .header.item {\n  background-color: transparent;\n  opacity: 1;\n  color: rgba(0, 0, 0, 0.85);\n  font-size: 0.92857143em;\n  text-transform: uppercase;\n  font-weight: bold;\n}\n\n/* Image */\n\n.ui.text.menu .item > img:not(.ui) {\n  margin: 0em;\n}\n\n/*--- fluid text ---*/\n\n.ui.text.item.menu .item {\n  margin: 0em;\n}\n\n/*--- vertical text ---*/\n\n.ui.vertical.text.menu {\n  margin: 1em 0em;\n}\n\n.ui.vertical.text.menu:first-child {\n  margin-top: 0rem;\n}\n\n.ui.vertical.text.menu:last-child {\n  margin-bottom: 0rem;\n}\n\n.ui.vertical.text.menu .item {\n  margin: 0.57142857em 0em;\n  padding-left: 0em;\n  padding-right: 0em;\n}\n\n.ui.vertical.text.menu .item > i.icon {\n  float: none;\n  margin: 0em 0.35714286em 0em 0em;\n}\n\n.ui.vertical.text.menu .header.item {\n  margin: 0.57142857em 0em 0.71428571em;\n}\n\n/* Vertical Sub Menu */\n\n.ui.vertical.text.menu .item:not(.dropdown) > .menu {\n  margin: 0em;\n}\n\n.ui.vertical.text.menu .item:not(.dropdown) > .menu > .item {\n  margin: 0em;\n  padding: 0.5em 0em;\n}\n\n/*--- hover ---*/\n\n.ui.text.menu .item:hover {\n  opacity: 1;\n  background-color: transparent;\n}\n\n/*--- active ---*/\n\n.ui.text.menu .active.item {\n  background-color: transparent;\n  border: none;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  font-weight: normal;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--- active hover ---*/\n\n.ui.text.menu .active.item:hover {\n  background-color: transparent;\n}\n\n/* Disable Bariations */\n\n.ui.text.pointing.menu .active.item:after {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.text.attached.menu {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Inverted */\n\n.ui.inverted.text.menu,\n.ui.inverted.text.menu .item,\n.ui.inverted.text.menu .item:hover,\n.ui.inverted.text.menu .active.item {\n  background-color: transparent !important;\n}\n\n/* Fluid */\n\n.ui.fluid.text.menu {\n  margin-left: 0em;\n  margin-right: 0em;\n}\n\n/*--------------\n    Icon Only\n---------------*/\n\n/* Vertical Menu */\n\n.ui.vertical.icon.menu {\n  display: inline-block;\n  width: auto;\n}\n\n/* Item */\n\n.ui.icon.menu .item {\n  height: auto;\n  text-align: center;\n  color: #1b1c1d;\n}\n\n/* Icon */\n\n.ui.icon.menu .item > .icon:not(.dropdown) {\n  margin: 0;\n  opacity: 1;\n}\n\n/* Icon Gylph */\n\n.ui.icon.menu .icon:before {\n  opacity: 1;\n}\n\n/* (x) Item Icon */\n\n.ui.menu .icon.item > .icon {\n  width: auto;\n  margin: 0em auto;\n}\n\n/* Vertical Icon */\n\n.ui.vertical.icon.menu .item > .icon:not(.dropdown) {\n  display: block;\n  opacity: 1;\n  margin: 0em auto;\n  float: none;\n}\n\n/* Inverted */\n\n.ui.inverted.icon.menu .item {\n  color: #ffffff;\n}\n\n/*--------------\n  Labeled Icon\n---------------*/\n\n/* Menu */\n\n.ui.labeled.icon.menu {\n  text-align: center;\n}\n\n/* Item */\n\n.ui.labeled.icon.menu .item {\n  min-width: 6em;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n}\n\n/* Icon */\n\n.ui.labeled.icon.menu .item > .icon:not(.dropdown) {\n  height: 1em;\n  display: block;\n  font-size: 1.71428571em !important;\n  margin: 0em auto 0.5rem !important;\n}\n\n/* Fluid */\n\n.ui.fluid.labeled.icon.menu > .item {\n  min-width: 0em;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Stackable\n---------------*/\n\n@media only screen and (width < 768px) {\n  .ui.stackable.menu {\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    -ms-flex-direction: column;\n    flex-direction: column;\n  }\n\n  .ui.stackable.menu .item {\n    width: 100% !important;\n  }\n\n  .ui.stackable.menu .item:before {\n    position: absolute;\n    content: \"\";\n    top: auto;\n    bottom: 0px;\n    left: 0px;\n    width: 100%;\n    height: 1px;\n    background: rgba(34, 36, 38, 0.1);\n  }\n\n  .ui.stackable.menu .left.menu,\n  .ui.stackable.menu .left.item {\n    margin-right: 0 !important;\n  }\n\n  .ui.stackable.menu .right.menu,\n  .ui.stackable.menu .right.item {\n    margin-left: 0 !important;\n  }\n\n  .ui.stackable.menu .right.menu,\n  .ui.stackable.menu .left.menu {\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    -ms-flex-direction: column;\n    flex-direction: column;\n  }\n}\n\n/*--------------\n    Colors\n---------------*/\n\n/*--- Standard Colors  ---*/\n\n.ui.menu .red.active.item,\n.ui.red.menu .active.item {\n  border-color: #db2828 !important;\n  color: #db2828 !important;\n}\n\n.ui.menu .orange.active.item,\n.ui.orange.menu .active.item {\n  border-color: #f2711c !important;\n  color: #f2711c !important;\n}\n\n.ui.menu .yellow.active.item,\n.ui.yellow.menu .active.item {\n  border-color: #fbbd08 !important;\n  color: #fbbd08 !important;\n}\n\n.ui.menu .olive.active.item,\n.ui.olive.menu .active.item {\n  border-color: #b5cc18 !important;\n  color: #b5cc18 !important;\n}\n\n.ui.menu .green.active.item,\n.ui.green.menu .active.item {\n  border-color: #21ba45 !important;\n  color: #21ba45 !important;\n}\n\n.ui.menu .teal.active.item,\n.ui.teal.menu .active.item {\n  border-color: #00b5ad !important;\n  color: #00b5ad !important;\n}\n\n.ui.menu .blue.active.item,\n.ui.blue.menu .active.item {\n  border-color: #2185d0 !important;\n  color: #2185d0 !important;\n}\n\n.ui.menu .violet.active.item,\n.ui.violet.menu .active.item {\n  border-color: #6435c9 !important;\n  color: #6435c9 !important;\n}\n\n.ui.menu .purple.active.item,\n.ui.purple.menu .active.item {\n  border-color: #a333c8 !important;\n  color: #a333c8 !important;\n}\n\n.ui.menu .pink.active.item,\n.ui.pink.menu .active.item {\n  border-color: #e03997 !important;\n  color: #e03997 !important;\n}\n\n.ui.menu .brown.active.item,\n.ui.brown.menu .active.item {\n  border-color: #a5673f !important;\n  color: #a5673f !important;\n}\n\n.ui.menu .grey.active.item,\n.ui.grey.menu .active.item {\n  border-color: #767676 !important;\n  color: #767676 !important;\n}\n\n/*--------------\n    Inverted\n---------------*/\n\n.ui.inverted.menu {\n  border: 0px solid transparent;\n  background: #1b1c1d;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Menu Item */\n\n.ui.inverted.menu .item,\n.ui.inverted.menu .item > a:not(.ui) {\n  background: transparent;\n  color: rgba(255, 255, 255, 0.9);\n}\n\n.ui.inverted.menu .item.menu {\n  background: transparent;\n}\n\n/*--- Border ---*/\n\n.ui.inverted.menu .item:before {\n  background: rgba(255, 255, 255, 0.08);\n}\n\n.ui.vertical.inverted.menu .item:before {\n  background: rgba(255, 255, 255, 0.08);\n}\n\n/* Sub Menu */\n\n.ui.vertical.inverted.menu .menu .item,\n.ui.vertical.inverted.menu .menu .item a:not(.ui) {\n  color: rgba(255, 255, 255, 0.5);\n}\n\n/* Header */\n\n.ui.inverted.menu .header.item {\n  margin: 0em;\n  background: transparent;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Disabled */\n\n.ui.inverted.menu .item.disabled,\n.ui.inverted.menu .item.disabled:hover {\n  color: rgba(225, 225, 225, 0.3);\n}\n\n/*--- Hover ---*/\n\n.ui.link.inverted.menu .item:hover,\n.ui.inverted.menu .dropdown.item:hover,\n.ui.inverted.menu .link.item:hover,\n.ui.inverted.menu a.item:hover {\n  background: rgba(255, 255, 255, 0.08);\n  color: #ffffff;\n}\n\n.ui.vertical.inverted.menu .item .menu a.item:hover,\n.ui.vertical.inverted.menu .item .menu .link.item:hover {\n  background: transparent;\n  color: #ffffff;\n}\n\n/*--- Pressed ---*/\n\n.ui.inverted.menu a.item:active,\n.ui.inverted.menu .link.item:active {\n  background: rgba(255, 255, 255, 0.08);\n  color: #ffffff;\n}\n\n/*--- Active ---*/\n\n.ui.inverted.menu .active.item {\n  background: rgba(255, 255, 255, 0.15);\n  color: #ffffff !important;\n}\n\n.ui.inverted.vertical.menu .item .menu .active.item {\n  background: transparent;\n  color: #ffffff;\n}\n\n.ui.inverted.pointing.menu .active.item:after {\n  background: #3d3e3f !important;\n  margin: 0em !important;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  border: none !important;\n}\n\n/*--- Active Hover ---*/\n\n.ui.inverted.menu .active.item:hover {\n  background: rgba(255, 255, 255, 0.15);\n  color: #ffffff !important;\n}\n\n.ui.inverted.pointing.menu .active.item:hover:after {\n  background: #3d3e3f !important;\n}\n\n/*--------------\n    Floated\n---------------*/\n\n.ui.floated.menu {\n  float: left;\n  margin: 0rem 0.5rem 0rem 0rem;\n}\n\n.ui.floated.menu .item:last-child:before {\n  display: none;\n}\n\n.ui.right.floated.menu {\n  float: right;\n  margin: 0rem 0rem 0rem 0.5rem;\n}\n\n/*--------------\n    Inverted\n---------------*/\n\n/* Red */\n\n.ui.inverted.menu .red.active.item,\n.ui.inverted.red.menu {\n  background-color: #db2828;\n}\n\n.ui.inverted.red.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.red.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/* Orange */\n\n.ui.inverted.menu .orange.active.item,\n.ui.inverted.orange.menu {\n  background-color: #f2711c;\n}\n\n.ui.inverted.orange.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.orange.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/* Yellow */\n\n.ui.inverted.menu .yellow.active.item,\n.ui.inverted.yellow.menu {\n  background-color: #fbbd08;\n}\n\n.ui.inverted.yellow.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.yellow.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/* Olive */\n\n.ui.inverted.menu .olive.active.item,\n.ui.inverted.olive.menu {\n  background-color: #b5cc18;\n}\n\n.ui.inverted.olive.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.olive.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/* Green */\n\n.ui.inverted.menu .green.active.item,\n.ui.inverted.green.menu {\n  background-color: #21ba45;\n}\n\n.ui.inverted.green.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.green.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/* Teal */\n\n.ui.inverted.menu .teal.active.item,\n.ui.inverted.teal.menu {\n  background-color: #00b5ad;\n}\n\n.ui.inverted.teal.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.teal.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/* Blue */\n\n.ui.inverted.menu .blue.active.item,\n.ui.inverted.blue.menu {\n  background-color: #2185d0;\n}\n\n.ui.inverted.blue.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.blue.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/* Violet */\n\n.ui.inverted.menu .violet.active.item,\n.ui.inverted.violet.menu {\n  background-color: #6435c9;\n}\n\n.ui.inverted.violet.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.violet.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/* Purple */\n\n.ui.inverted.menu .purple.active.item,\n.ui.inverted.purple.menu {\n  background-color: #a333c8;\n}\n\n.ui.inverted.purple.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.purple.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/* Pink */\n\n.ui.inverted.menu .pink.active.item,\n.ui.inverted.pink.menu {\n  background-color: #e03997;\n}\n\n.ui.inverted.pink.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.pink.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/* Brown */\n\n.ui.inverted.menu .brown.active.item,\n.ui.inverted.brown.menu {\n  background-color: #a5673f;\n}\n\n.ui.inverted.brown.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.brown.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/* Grey */\n\n.ui.inverted.menu .grey.active.item,\n.ui.inverted.grey.menu {\n  background-color: #767676;\n}\n\n.ui.inverted.grey.menu .item:before {\n  background-color: rgba(34, 36, 38, 0.1);\n}\n\n.ui.inverted.grey.menu .active.item {\n  background-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n/*--------------\n    Fitted\n---------------*/\n\n.ui.fitted.menu .item,\n.ui.fitted.menu .item .menu .item,\n.ui.menu .fitted.item {\n  padding: 0em;\n}\n\n.ui.horizontally.fitted.menu .item,\n.ui.horizontally.fitted.menu .item .menu .item,\n.ui.menu .horizontally.fitted.item {\n  padding-top: 0.92857143em;\n  padding-bottom: 0.92857143em;\n}\n\n.ui.vertically.fitted.menu .item,\n.ui.vertically.fitted.menu .item .menu .item,\n.ui.menu .vertically.fitted.item {\n  padding-left: 1.14285714em;\n  padding-right: 1.14285714em;\n}\n\n/*--------------\n  Borderless\n---------------*/\n\n.ui.borderless.menu .item:before,\n.ui.borderless.menu .item .menu .item:before,\n.ui.menu .borderless.item:before {\n  background: none !important;\n}\n\n/*-------------------\n      Compact\n--------------------*/\n\n.ui.compact.menu {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  margin: 0em;\n  vertical-align: middle;\n}\n\n.ui.compact.vertical.menu {\n  display: inline-block;\n}\n\n.ui.compact.menu .item:last-child {\n  border-radius: 0em 0.28571429rem 0.28571429rem 0em;\n}\n\n.ui.compact.menu .item:last-child:before {\n  display: none;\n}\n\n.ui.compact.vertical.menu {\n  width: auto !important;\n}\n\n.ui.compact.vertical.menu .item:last-child::before {\n  display: block;\n}\n\n/*-------------------\n        Fluid\n--------------------*/\n\n.ui.menu.fluid,\n.ui.vertical.menu.fluid {\n  width: 100% !important;\n}\n\n/*-------------------\n      Evenly Sized\n--------------------*/\n\n.ui.item.menu,\n.ui.item.menu .item {\n  width: 100%;\n  padding-left: 0em !important;\n  padding-right: 0em !important;\n  margin-left: 0em !important;\n  margin-right: 0em !important;\n  text-align: center;\n  -webkit-box-pack: center;\n  -ms-flex-pack: center;\n  justify-content: center;\n}\n\n.ui.attached.item.menu {\n  margin: 0em -1px !important;\n}\n\n.ui.item.menu .item:last-child:before {\n  display: none;\n}\n\n.ui.menu.two.item .item {\n  width: 50%;\n}\n\n.ui.menu.three.item .item {\n  width: 33.333%;\n}\n\n.ui.menu.four.item .item {\n  width: 25%;\n}\n\n.ui.menu.five.item .item {\n  width: 20%;\n}\n\n.ui.menu.six.item .item {\n  width: 16.666%;\n}\n\n.ui.menu.seven.item .item {\n  width: 14.285%;\n}\n\n.ui.menu.eight.item .item {\n  width: 12.5%;\n}\n\n.ui.menu.nine.item .item {\n  width: 11.11%;\n}\n\n.ui.menu.ten.item .item {\n  width: 10%;\n}\n\n.ui.menu.eleven.item .item {\n  width: 9.09%;\n}\n\n.ui.menu.twelve.item .item {\n  width: 8.333%;\n}\n\n/*--------------\n    Fixed\n---------------*/\n\n.ui.menu.fixed {\n  position: fixed;\n  z-index: 101;\n  margin: 0em;\n  width: 100%;\n}\n\n.ui.menu.fixed,\n.ui.menu.fixed .item:first-child,\n.ui.menu.fixed .item:last-child {\n  border-radius: 0px !important;\n}\n\n.ui.fixed.menu,\n.ui[class*=\"top fixed\"].menu {\n  top: 0px;\n  left: 0px;\n  right: auto;\n  bottom: auto;\n}\n\n.ui[class*=\"top fixed\"].menu {\n  border-top: none;\n  border-left: none;\n  border-right: none;\n}\n\n.ui[class*=\"right fixed\"].menu {\n  border-top: none;\n  border-bottom: none;\n  border-right: none;\n  top: 0px;\n  right: 0px;\n  left: auto;\n  bottom: auto;\n  width: auto;\n  height: 100%;\n}\n\n.ui[class*=\"bottom fixed\"].menu {\n  border-bottom: none;\n  border-left: none;\n  border-right: none;\n  bottom: 0px;\n  left: 0px;\n  top: auto;\n  right: auto;\n}\n\n.ui[class*=\"left fixed\"].menu {\n  border-top: none;\n  border-bottom: none;\n  border-left: none;\n  top: 0px;\n  left: 0px;\n  right: auto;\n  bottom: auto;\n  width: auto;\n  height: 100%;\n}\n\n/* Coupling with Grid */\n\n.ui.fixed.menu + .ui.grid {\n  padding-top: 2.75rem;\n}\n\n/*-------------------\n      Pointing\n--------------------*/\n\n.ui.pointing.menu .item:after {\n  visibility: hidden;\n  position: absolute;\n  content: \"\";\n  top: 100%;\n  left: 50%;\n  -webkit-transform: translateX(-50%) translateY(-50%) rotate(45deg);\n  transform: translateX(-50%) translateY(-50%) rotate(45deg);\n  background: none;\n  margin: 0.5px 0em 0em;\n  width: 0.57142857em;\n  height: 0.57142857em;\n  border: none;\n  border-bottom: 1px solid #d4d4d5;\n  border-right: 1px solid #d4d4d5;\n  z-index: 2;\n  -webkit-transition: background 0.1s ease;\n  transition: background 0.1s ease;\n}\n\n.ui.vertical.pointing.menu .item:after {\n  position: absolute;\n  top: 50%;\n  right: 0%;\n  bottom: auto;\n  left: auto;\n  -webkit-transform: translateX(50%) translateY(-50%) rotate(45deg);\n  transform: translateX(50%) translateY(-50%) rotate(45deg);\n  margin: 0em -0.5px 0em 0em;\n  border: none;\n  border-top: 1px solid #d4d4d5;\n  border-right: 1px solid #d4d4d5;\n}\n\n/* Active */\n\n.ui.pointing.menu .active.item:after {\n  visibility: visible;\n}\n\n.ui.pointing.menu .active.dropdown.item:after {\n  visibility: hidden;\n}\n\n/* Don't double up pointers */\n\n.ui.pointing.menu .dropdown.active.item:after,\n.ui.pointing.menu .active.item .menu .active.item:after {\n  display: none;\n}\n\n/* Colors */\n\n.ui.pointing.menu .active.item:hover:after {\n  background-color: #f2f2f2;\n}\n\n.ui.pointing.menu .active.item:after {\n  background-color: #f2f2f2;\n}\n\n.ui.pointing.menu .active.item:hover:after {\n  background-color: #f2f2f2;\n}\n\n.ui.vertical.pointing.menu .active.item:hover:after {\n  background-color: #f2f2f2;\n}\n\n.ui.vertical.pointing.menu .active.item:after {\n  background-color: #f2f2f2;\n}\n\n.ui.vertical.pointing.menu .menu .active.item:after {\n  background-color: #ffffff;\n}\n\n/*--------------\n    Attached\n---------------*/\n\n/* Middle */\n\n.ui.attached.menu {\n  top: 0px;\n  bottom: 0px;\n  border-radius: 0px;\n  margin: 0em -1px;\n  width: calc(100% + 2px);\n  max-width: calc(100% + 2px);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.attached + .ui.attached.menu:not(.top) {\n  border-top: none;\n}\n\n/* Top */\n\n.ui[class*=\"top attached\"].menu {\n  bottom: 0px;\n  margin-bottom: 0em;\n  top: 0px;\n  margin-top: 1rem;\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n.ui.menu[class*=\"top attached\"]:first-child {\n  margin-top: 0em;\n}\n\n/* Bottom */\n\n.ui[class*=\"bottom attached\"].menu {\n  bottom: 0px;\n  margin-top: 0em;\n  top: 0px;\n  margin-bottom: 1rem;\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), none;\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), none;\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n.ui[class*=\"bottom attached\"].menu:last-child {\n  margin-bottom: 0em;\n}\n\n/* Attached Menu Item */\n\n.ui.top.attached.menu > .item:first-child {\n  border-radius: 0.28571429rem 0em 0em 0em;\n}\n\n.ui.bottom.attached.menu > .item:first-child {\n  border-radius: 0em 0em 0em 0.28571429rem;\n}\n\n/* Tabular Attached */\n\n.ui.attached.menu:not(.tabular) {\n  border: 1px solid #d4d4d5;\n}\n\n.ui.attached.inverted.menu {\n  border: none;\n}\n\n.ui.attached.tabular.menu {\n  margin-left: 0;\n  margin-right: 0;\n  width: 100%;\n}\n\n/*--------------\n    Sizes\n---------------*/\n\n/* Mini */\n\n.ui.mini.menu {\n  font-size: 0.78571429rem;\n}\n\n.ui.mini.vertical.menu {\n  width: 9rem;\n}\n\n/* Tiny */\n\n.ui.tiny.menu {\n  font-size: 0.85714286rem;\n}\n\n.ui.tiny.vertical.menu {\n  width: 11rem;\n}\n\n/* Small */\n\n.ui.small.menu {\n  font-size: 0.92857143rem;\n}\n\n.ui.small.vertical.menu {\n  width: 13rem;\n}\n\n/* Medium */\n\n.ui.menu {\n  font-size: 1rem;\n}\n\n.ui.vertical.menu {\n  width: 15rem;\n}\n\n/* Large */\n\n.ui.large.menu {\n  font-size: 1.07142857rem;\n}\n\n.ui.large.vertical.menu {\n  width: 18rem;\n}\n\n/* Huge */\n\n.ui.huge.menu {\n  font-size: 1.21428571rem;\n}\n\n.ui.huge.vertical.menu {\n  width: 22rem;\n}\n\n/* Big */\n\n.ui.big.menu {\n  font-size: 1.14285714rem;\n}\n\n.ui.big.vertical.menu {\n  width: 20rem;\n}\n\n/* Massive */\n\n.ui.massive.menu {\n  font-size: 1.28571429rem;\n}\n\n.ui.massive.vertical.menu {\n  width: 25rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Message\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Message\n*******************************/\n\n.ui.message {\n  position: relative;\n  min-height: 1em;\n  margin: 1em 0em;\n  background: #f8f8f9;\n  padding: 1em 1.5em;\n  line-height: 1.4285em;\n  color: rgba(0, 0, 0, 0.87);\n  -webkit-transition: opacity 0.1s ease, color 0.1s ease, background 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n  transition: opacity 0.1s ease, color 0.1s ease, background 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n  transition: opacity 0.1s ease, color 0.1s ease, background 0.1s ease,\n    box-shadow 0.1s ease;\n  transition: opacity 0.1s ease, color 0.1s ease, background 0.1s ease,\n    box-shadow 0.1s ease, -webkit-box-shadow 0.1s ease;\n  border-radius: 0.28571429rem;\n  -webkit-box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.22) inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.22) inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.message:first-child {\n  margin-top: 0em;\n}\n\n.ui.message:last-child {\n  margin-bottom: 0em;\n}\n\n/*--------------\n    Content\n---------------*/\n\n/* Header */\n\n.ui.message .header {\n  display: block;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-weight: bold;\n  margin: -0.14285714em 0em 0rem 0em;\n}\n\n/* Default font size */\n\n.ui.message .header:not(.ui) {\n  font-size: 1.14285714em;\n}\n\n/* Paragraph */\n\n.ui.message p {\n  opacity: 0.85;\n  margin: 0.75em 0em;\n}\n\n.ui.message p:first-child {\n  margin-top: 0em;\n}\n\n.ui.message p:last-child {\n  margin-bottom: 0em;\n}\n\n.ui.message .header + p {\n  margin-top: 0.25em;\n}\n\n/* List */\n\n.ui.message .list:not(.ui) {\n  text-align: left;\n  padding: 0em;\n  opacity: 0.85;\n  list-style-position: inside;\n  margin: 0.5em 0em 0em;\n}\n\n.ui.message .list:not(.ui):first-child {\n  margin-top: 0em;\n}\n\n.ui.message .list:not(.ui):last-child {\n  margin-bottom: 0em;\n}\n\n.ui.message .list:not(.ui) li {\n  position: relative;\n  list-style-type: none;\n  margin: 0em 0em 0.3em 1em;\n  padding: 0em;\n}\n\n.ui.message .list:not(.ui) li:before {\n  position: absolute;\n  content: \"•\";\n  left: -1em;\n  height: 100%;\n  vertical-align: baseline;\n}\n\n.ui.message .list:not(.ui) li:last-child {\n  margin-bottom: 0em;\n}\n\n/* Icon */\n\n.ui.message > .icon {\n  margin-right: 0.6em;\n}\n\n/* Close Icon */\n\n.ui.message > .close.icon {\n  cursor: pointer;\n  position: absolute;\n  margin: 0em;\n  top: 0.78575em;\n  right: 0.5em;\n  opacity: 0.7;\n  -webkit-transition: opacity 0.1s ease;\n  transition: opacity 0.1s ease;\n}\n\n.ui.message > .close.icon:hover {\n  opacity: 1;\n}\n\n/* First / Last Element */\n\n.ui.message > :first-child {\n  margin-top: 0em;\n}\n\n.ui.message > :last-child {\n  margin-bottom: 0em;\n}\n\n/*******************************\n            Coupling\n*******************************/\n\n.ui.dropdown .menu > .message {\n  margin: 0px -1px;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------\n    Visible\n---------------*/\n\n.ui.visible.visible.visible.visible.message {\n  display: block;\n}\n\n.ui.icon.visible.visible.visible.visible.message {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n}\n\n/*--------------\n    Hidden\n---------------*/\n\n.ui.hidden.hidden.hidden.hidden.message {\n  display: none;\n}\n\n/*******************************\n            Variations\n*******************************/\n\n/*--------------\n    Compact\n---------------*/\n\n.ui.compact.message {\n  display: inline-block;\n}\n\n.ui.compact.icon.message {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n}\n\n/*--------------\n    Attached\n---------------*/\n\n.ui.attached.message {\n  margin-bottom: -1px;\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n  -webkit-box-shadow: 0em 0em 0em 1px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0em 0em 0em 1px rgba(34, 36, 38, 0.15) inset;\n  margin-left: -1px;\n  margin-right: -1px;\n}\n\n.ui.attached + .ui.attached.message:not(.top):not(.bottom) {\n  margin-top: -1px;\n  border-radius: 0em;\n}\n\n.ui.bottom.attached.message {\n  margin-top: -1px;\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n  -webkit-box-shadow: 0em 0em 0em 1px rgba(34, 36, 38, 0.15) inset,\n    0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n  box-shadow: 0em 0em 0em 1px rgba(34, 36, 38, 0.15) inset,\n    0px 1px 2px 0 rgba(34, 36, 38, 0.15);\n}\n\n.ui.bottom.attached.message:not(:last-child) {\n  margin-bottom: 1em;\n}\n\n.ui.attached.icon.message {\n  width: auto;\n}\n\n/*--------------\n      Icon\n---------------*/\n\n.ui.icon.message {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  width: 100%;\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n}\n\n.ui.icon.message > .icon:not(.close) {\n  display: block;\n  -webkit-box-flex: 0;\n  -ms-flex: 0 0 auto;\n  flex: 0 0 auto;\n  width: auto;\n  line-height: 1;\n  vertical-align: middle;\n  font-size: 3em;\n  opacity: 0.8;\n}\n\n.ui.icon.message > .content {\n  display: block;\n  -webkit-box-flex: 1;\n  -ms-flex: 1 1 auto;\n  flex: 1 1 auto;\n  vertical-align: middle;\n}\n\n.ui.icon.message .icon:not(.close) + .content {\n  padding-left: 0rem;\n}\n\n.ui.icon.message .circular.icon {\n  width: 1em;\n}\n\n/*--------------\n    Floating\n---------------*/\n\n.ui.floating.message {\n  -webkit-box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.22) inset,\n    0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.22) inset,\n    0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n}\n\n/*--------------\n    Colors\n---------------*/\n\n.ui.black.message {\n  background-color: #1b1c1d;\n  color: rgba(255, 255, 255, 0.9);\n}\n\n/*--------------\n    Types\n---------------*/\n\n/* Positive */\n\n.ui.positive.message {\n  background-color: #fcfff5;\n  color: #2c662d;\n}\n\n.ui.positive.message,\n.ui.attached.positive.message {\n  -webkit-box-shadow: 0px 0px 0px 1px #a3c293 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #a3c293 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.positive.message .header {\n  color: #1a531b;\n}\n\n/* Negative */\n\n.ui.negative.message {\n  background-color: #fff6f6;\n  color: #9f3a38;\n}\n\n.ui.negative.message,\n.ui.attached.negative.message {\n  -webkit-box-shadow: 0px 0px 0px 1px #e0b4b4 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #e0b4b4 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.negative.message .header {\n  color: #912d2b;\n}\n\n/* Info */\n\n.ui.info.message {\n  background-color: #f8ffff;\n  color: #276f86;\n}\n\n.ui.info.message,\n.ui.attached.info.message {\n  -webkit-box-shadow: 0px 0px 0px 1px #a9d5de inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #a9d5de inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.info.message .header {\n  color: #0e566c;\n}\n\n/* Warning */\n\n.ui.warning.message {\n  background-color: #fffaf3;\n  color: #573a08;\n}\n\n.ui.warning.message,\n.ui.attached.warning.message {\n  -webkit-box-shadow: 0px 0px 0px 1px #c9ba9b inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #c9ba9b inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.warning.message .header {\n  color: #794b02;\n}\n\n/* Error */\n\n.ui.error.message {\n  background-color: #fff6f6;\n  color: #9f3a38;\n}\n\n.ui.error.message,\n.ui.attached.error.message {\n  -webkit-box-shadow: 0px 0px 0px 1px #e0b4b4 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #e0b4b4 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.error.message .header {\n  color: #912d2b;\n}\n\n/* Success */\n\n.ui.success.message {\n  background-color: #fcfff5;\n  color: #2c662d;\n}\n\n.ui.success.message,\n.ui.attached.success.message {\n  -webkit-box-shadow: 0px 0px 0px 1px #a3c293 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #a3c293 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.success.message .header {\n  color: #1a531b;\n}\n\n/* Colors */\n\n.ui.inverted.message,\n.ui.black.message {\n  background-color: #1b1c1d;\n  color: rgba(255, 255, 255, 0.9);\n}\n\n.ui.red.message {\n  background-color: #ffe8e6;\n  color: #db2828;\n  -webkit-box-shadow: 0px 0px 0px 1px #db2828 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #db2828 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.red.message .header {\n  color: #c82121;\n}\n\n.ui.orange.message {\n  background-color: #ffedde;\n  color: #f2711c;\n  -webkit-box-shadow: 0px 0px 0px 1px #f2711c inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #f2711c inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.orange.message .header {\n  color: #e7640d;\n}\n\n.ui.yellow.message {\n  background-color: #fff8db;\n  color: #b58105;\n  -webkit-box-shadow: 0px 0px 0px 1px #b58105 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #b58105 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.yellow.message .header {\n  color: #9c6f04;\n}\n\n.ui.olive.message {\n  background-color: #fbfdef;\n  color: #8abc1e;\n  -webkit-box-shadow: 0px 0px 0px 1px #8abc1e inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #8abc1e inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.olive.message .header {\n  color: #7aa61a;\n}\n\n.ui.green.message {\n  background-color: #e5f9e7;\n  color: #1ebc30;\n  -webkit-box-shadow: 0px 0px 0px 1px #1ebc30 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #1ebc30 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.green.message .header {\n  color: #1aa62a;\n}\n\n.ui.teal.message {\n  background-color: #e1f7f7;\n  color: #10a3a3;\n  -webkit-box-shadow: 0px 0px 0px 1px #10a3a3 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #10a3a3 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.teal.message .header {\n  color: #0e8c8c;\n}\n\n.ui.blue.message {\n  background-color: #dff0ff;\n  color: #2185d0;\n  -webkit-box-shadow: 0px 0px 0px 1px #2185d0 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #2185d0 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.blue.message .header {\n  color: #1e77ba;\n}\n\n.ui.violet.message {\n  background-color: #eae7ff;\n  color: #6435c9;\n  -webkit-box-shadow: 0px 0px 0px 1px #6435c9 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #6435c9 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.violet.message .header {\n  color: #5a30b5;\n}\n\n.ui.purple.message {\n  background-color: #f6e7ff;\n  color: #a333c8;\n  -webkit-box-shadow: 0px 0px 0px 1px #a333c8 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #a333c8 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.purple.message .header {\n  color: #922eb4;\n}\n\n.ui.pink.message {\n  background-color: #ffe3fb;\n  color: #e03997;\n  -webkit-box-shadow: 0px 0px 0px 1px #e03997 inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #e03997 inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.pink.message .header {\n  color: #dd238b;\n}\n\n.ui.brown.message {\n  background-color: #f1e2d3;\n  color: #a5673f;\n  -webkit-box-shadow: 0px 0px 0px 1px #a5673f inset,\n    0px 0px 0px 0px rgba(0, 0, 0, 0);\n  box-shadow: 0px 0px 0px 1px #a5673f inset, 0px 0px 0px 0px rgba(0, 0, 0, 0);\n}\n\n.ui.brown.message .header {\n  color: #935b38;\n}\n\n/*--------------\n    Sizes\n---------------*/\n\n.ui.mini.message {\n  font-size: 0.78571429em;\n}\n\n.ui.tiny.message {\n  font-size: 0.85714286em;\n}\n\n.ui.small.message {\n  font-size: 0.92857143em;\n}\n\n.ui.message {\n  font-size: 1em;\n}\n\n.ui.large.message {\n  font-size: 1.14285714em;\n}\n\n.ui.big.message {\n  font-size: 1.28571429em;\n}\n\n.ui.huge.message {\n  font-size: 1.42857143em;\n}\n\n.ui.massive.message {\n  font-size: 1.71428571em;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Table\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Table\n*******************************/\n\n/* Prototype */\n\n.ui.table {\n  width: 100%;\n  background: #ffffff;\n  margin: 1em 0em;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border-radius: 0.28571429rem;\n  text-align: left;\n  color: rgba(0, 0, 0, 0.87);\n  border-collapse: separate;\n  border-spacing: 0px;\n}\n\n.ui.table:first-child {\n  margin-top: 0em;\n}\n\n.ui.table:last-child {\n  margin-bottom: 0em;\n}\n\n/*******************************\n            Parts\n*******************************/\n\n/* Table Content */\n\n.ui.table th,\n.ui.table td {\n  -webkit-transition: background 0.1s ease, color 0.1s ease;\n  transition: background 0.1s ease, color 0.1s ease;\n}\n\n/* Headers */\n\n.ui.table thead {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.table thead th {\n  cursor: auto;\n  background: #f9fafb;\n  text-align: inherit;\n  color: rgba(0, 0, 0, 0.87);\n  padding: 0.92857143em 0.78571429em;\n  vertical-align: inherit;\n  font-style: none;\n  font-weight: bold;\n  text-transform: none;\n  border-bottom: 1px solid rgba(34, 36, 38, 0.1);\n  border-left: none;\n}\n\n.ui.table thead tr > th:first-child {\n  border-left: none;\n}\n\n.ui.table thead tr:first-child > th:first-child {\n  border-radius: 0.28571429rem 0em 0em 0em;\n}\n\n.ui.table thead tr:first-child > th:last-child {\n  border-radius: 0em 0.28571429rem 0em 0em;\n}\n\n.ui.table thead tr:first-child > th:only-child {\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n/* Footer */\n\n.ui.table tfoot {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.table tfoot th {\n  cursor: auto;\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n  background: #f9fafb;\n  text-align: inherit;\n  color: rgba(0, 0, 0, 0.87);\n  padding: 0.78571429em 0.78571429em;\n  vertical-align: middle;\n  font-style: normal;\n  font-weight: normal;\n  text-transform: none;\n}\n\n.ui.table tfoot tr > th:first-child {\n  border-left: none;\n}\n\n.ui.table tfoot tr:first-child > th:first-child {\n  border-radius: 0em 0em 0em 0.28571429rem;\n}\n\n.ui.table tfoot tr:first-child > th:last-child {\n  border-radius: 0em 0em 0.28571429rem 0em;\n}\n\n.ui.table tfoot tr:first-child > th:only-child {\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n/* Table Row */\n\n.ui.table tr td {\n  border-top: 1px solid rgba(34, 36, 38, 0.1);\n}\n\n.ui.table tr:first-child td {\n  border-top: none;\n}\n\n/* Repeated tbody */\n\n.ui.table tbody + tbody tr:first-child td {\n  border-top: 1px solid rgba(34, 36, 38, 0.1);\n}\n\n/* Table Cells */\n\n.ui.table td {\n  padding: 0.78571429em 0.78571429em;\n  text-align: inherit;\n}\n\n/* Icons */\n\n.ui.table > .icon {\n  vertical-align: baseline;\n}\n\n.ui.table > .icon:only-child {\n  margin: 0em;\n}\n\n/* Table Segment */\n\n.ui.table.segment {\n  padding: 0em;\n}\n\n.ui.table.segment:after {\n  display: none;\n}\n\n.ui.table.segment.stacked:after {\n  display: block;\n}\n\n/* Responsive */\n\n@media only screen and (width < 768px) {\n  .ui.table:not(.unstackable) {\n    width: 100%;\n  }\n\n  .ui.table:not(.unstackable) tbody,\n  .ui.table:not(.unstackable) tr,\n  .ui.table:not(.unstackable) tr > th,\n  .ui.table:not(.unstackable) tr > td {\n    width: auto !important;\n    display: block !important;\n  }\n\n  .ui.table:not(.unstackable) {\n    padding: 0em;\n  }\n\n  .ui.table:not(.unstackable) thead {\n    display: block;\n  }\n\n  .ui.table:not(.unstackable) tfoot {\n    display: block;\n  }\n\n  .ui.table:not(.unstackable) tr {\n    padding-top: 1em;\n    padding-bottom: 1em;\n    -webkit-box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.1) inset !important;\n    box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.1) inset !important;\n  }\n\n  .ui.table:not(.unstackable) tr > th,\n  .ui.table:not(.unstackable) tr > td {\n    background: none;\n    border: none !important;\n    padding: 0.25em 0.75em !important;\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n  }\n\n  .ui.table:not(.unstackable) th:first-child,\n  .ui.table:not(.unstackable) td:first-child {\n    font-weight: bold;\n  }\n\n  /* Definition Table */\n\n  .ui.definition.table:not(.unstackable) thead th:first-child {\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n  }\n}\n\n/*******************************\n            Coupling\n*******************************/\n\n/* UI Image */\n\n.ui.table th .image,\n.ui.table th .image img,\n.ui.table td .image,\n.ui.table td .image img {\n  max-width: none;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*--------------\n    Complex\n---------------*/\n\n.ui.structured.table {\n  border-collapse: collapse;\n}\n\n.ui.structured.table thead th {\n  border-left: none;\n  border-right: none;\n}\n\n.ui.structured.sortable.table thead th {\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n  border-right: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.structured.basic.table th {\n  border-left: none;\n  border-right: none;\n}\n\n.ui.structured.celled.table tr th,\n.ui.structured.celled.table tr td {\n  border-left: 1px solid rgba(34, 36, 38, 0.1);\n  border-right: 1px solid rgba(34, 36, 38, 0.1);\n}\n\n/*--------------\n  Definition\n---------------*/\n\n.ui.definition.table thead:not(.full-width) th:first-child {\n  pointer-events: none;\n  background: transparent;\n  font-weight: normal;\n  color: rgba(0, 0, 0, 0.4);\n  -webkit-box-shadow: -1px -1px 0px 1px #ffffff;\n  box-shadow: -1px -1px 0px 1px #ffffff;\n}\n\n.ui.definition.table tfoot:not(.full-width) th:first-child {\n  pointer-events: none;\n  background: transparent;\n  font-weight: rgba(0, 0, 0, 0.4);\n  color: normal;\n  -webkit-box-shadow: 1px 1px 0px 1px #ffffff;\n  box-shadow: 1px 1px 0px 1px #ffffff;\n}\n\n/* Remove Border */\n\n.ui.celled.definition.table thead:not(.full-width) th:first-child {\n  -webkit-box-shadow: 0px -1px 0px 1px #ffffff;\n  box-shadow: 0px -1px 0px 1px #ffffff;\n}\n\n.ui.celled.definition.table tfoot:not(.full-width) th:first-child {\n  -webkit-box-shadow: 0px 1px 0px 1px #ffffff;\n  box-shadow: 0px 1px 0px 1px #ffffff;\n}\n\n/* Highlight Defining Column */\n\n.ui.definition.table tr td:first-child:not(.ignored),\n.ui.definition.table tr td.definition {\n  background: rgba(0, 0, 0, 0.03);\n  font-weight: bold;\n  color: rgba(0, 0, 0, 0.95);\n  text-transform: \"\";\n  -webkit-box-shadow: \"\";\n  box-shadow: \"\";\n  text-align: \"\";\n  font-size: 1em;\n  padding-left: \"\";\n  padding-right: \"\";\n}\n\n/* Fix 2nd Column */\n\n.ui.definition.table thead:not(.full-width) th:nth-child(2) {\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.definition.table tfoot:not(.full-width) th:nth-child(2) {\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.definition.table td:nth-child(2) {\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------\n    Positive\n---------------*/\n\n.ui.table tr.positive,\n.ui.table td.positive {\n  -webkit-box-shadow: 0px 0px 0px #a3c293 inset;\n  box-shadow: 0px 0px 0px #a3c293 inset;\n}\n\n.ui.table tr.positive,\n.ui.table td.positive {\n  background: #fcfff5 !important;\n  color: #2c662d !important;\n}\n\n/*--------------\n    Negative\n---------------*/\n\n.ui.table tr.negative,\n.ui.table td.negative {\n  -webkit-box-shadow: 0px 0px 0px #e0b4b4 inset;\n  box-shadow: 0px 0px 0px #e0b4b4 inset;\n}\n\n.ui.table tr.negative,\n.ui.table td.negative {\n  background: #fff6f6 !important;\n  color: #9f3a38 !important;\n}\n\n/*--------------\n      Error\n---------------*/\n\n.ui.table tr.error,\n.ui.table td.error {\n  -webkit-box-shadow: 0px 0px 0px #e0b4b4 inset;\n  box-shadow: 0px 0px 0px #e0b4b4 inset;\n}\n\n.ui.table tr.error,\n.ui.table td.error {\n  background: #fff6f6 !important;\n  color: #9f3a38 !important;\n}\n\n/*--------------\n    Warning\n---------------*/\n\n.ui.table tr.warning,\n.ui.table td.warning {\n  -webkit-box-shadow: 0px 0px 0px #c9ba9b inset;\n  box-shadow: 0px 0px 0px #c9ba9b inset;\n}\n\n.ui.table tr.warning,\n.ui.table td.warning {\n  background: #fffaf3 !important;\n  color: #573a08 !important;\n}\n\n/*--------------\n    Active\n---------------*/\n\n.ui.table tr.active,\n.ui.table td.active {\n  -webkit-box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.87) inset;\n  box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.87) inset;\n}\n\n.ui.table tr.active,\n.ui.table td.active {\n  background: #e0e0e0 !important;\n  color: rgba(0, 0, 0, 0.87) !important;\n}\n\n/*--------------\n    Disabled\n---------------*/\n\n.ui.table tr.disabled td,\n.ui.table tr td.disabled,\n.ui.table tr.disabled:hover,\n.ui.table tr:hover td.disabled {\n  pointer-events: none;\n  color: rgba(40, 40, 40, 0.3);\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Stackable\n---------------*/\n\n@media only screen and (width < 992px) {\n\n  .ui[class*=\"tablet stackable\"].table,\n  .ui[class*=\"tablet stackable\"].table tbody,\n  .ui[class*=\"tablet stackable\"].table tr,\n  .ui[class*=\"tablet stackable\"].table tr > th,\n  .ui[class*=\"tablet stackable\"].table tr > td {\n    width: 100% !important;\n    display: block !important;\n  }\n\n  .ui[class*=\"tablet stackable\"].table {\n    padding: 0em;\n  }\n\n  .ui[class*=\"tablet stackable\"].table thead {\n    display: block;\n  }\n\n  .ui[class*=\"tablet stackable\"].table tfoot {\n    display: block;\n  }\n\n  .ui[class*=\"tablet stackable\"].table tr {\n    padding-top: 1em;\n    padding-bottom: 1em;\n    -webkit-box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.1) inset !important;\n    box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.1) inset !important;\n  }\n\n  .ui[class*=\"tablet stackable\"].table tr > th,\n  .ui[class*=\"tablet stackable\"].table tr > td {\n    background: none;\n    border: none !important;\n    padding: 0.25em 0.75em;\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n  }\n\n  /* Definition Table */\n\n  .ui.definition[class*=\"tablet stackable\"].table thead th:first-child {\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n  }\n}\n\n/*--------------\nText Alignment\n---------------*/\n\n.ui.table[class*=\"left aligned\"],\n.ui.table [class*=\"left aligned\"] {\n  text-align: left;\n}\n\n.ui.table[class*=\"center aligned\"],\n.ui.table [class*=\"center aligned\"] {\n  text-align: center;\n}\n\n.ui.table[class*=\"right aligned\"],\n.ui.table [class*=\"right aligned\"] {\n  text-align: right;\n}\n\n/*------------------\nVertical Alignment\n------------------*/\n\n.ui.table[class*=\"top aligned\"],\n.ui.table [class*=\"top aligned\"] {\n  vertical-align: top;\n}\n\n.ui.table[class*=\"middle aligned\"],\n.ui.table [class*=\"middle aligned\"] {\n  vertical-align: middle;\n}\n\n.ui.table[class*=\"bottom aligned\"],\n.ui.table [class*=\"bottom aligned\"] {\n  vertical-align: bottom;\n}\n\n/*--------------\n    Collapsing\n---------------*/\n\n.ui.table th.collapsing,\n.ui.table td.collapsing {\n  width: 1px;\n  white-space: nowrap;\n}\n\n/*--------------\n    Fixed\n---------------*/\n\n.ui.fixed.table {\n  table-layout: fixed;\n}\n\n.ui.fixed.table th,\n.ui.fixed.table td {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n/*--------------\n  Selectable\n---------------*/\n\n.ui.selectable.table tbody tr:hover,\n.ui.table tbody tr td.selectable:hover {\n  background: rgba(0, 0, 0, 0.05) !important;\n  color: rgba(0, 0, 0, 0.95) !important;\n}\n\n.ui.selectable.inverted.table tbody tr:hover,\n.ui.inverted.table tbody tr td.selectable:hover {\n  background: rgba(255, 255, 255, 0.08) !important;\n  color: #ffffff !important;\n}\n\n/* Selectable Cell Link */\n\n.ui.table tbody tr td.selectable {\n  padding: 0em;\n}\n\n.ui.table tbody tr td.selectable > a:not(.ui) {\n  display: block;\n  color: inherit;\n  padding: 0.78571429em 0.78571429em;\n}\n\n/* Other States */\n\n.ui.selectable.table tr.error:hover,\n.ui.table tr td.selectable.error:hover,\n.ui.selectable.table tr:hover td.error {\n  background: #ffe7e7 !important;\n  color: #943634 !important;\n}\n\n.ui.selectable.table tr.warning:hover,\n.ui.table tr td.selectable.warning:hover,\n.ui.selectable.table tr:hover td.warning {\n  background: #fff4e4 !important;\n  color: #493107 !important;\n}\n\n.ui.selectable.table tr.active:hover,\n.ui.table tr td.selectable.active:hover,\n.ui.selectable.table tr:hover td.active {\n  background: #e0e0e0 !important;\n  color: rgba(0, 0, 0, 0.87) !important;\n}\n\n.ui.selectable.table tr.positive:hover,\n.ui.table tr td.selectable.positive:hover,\n.ui.selectable.table tr:hover td.positive {\n  background: #f7ffe6 !important;\n  color: #275b28 !important;\n}\n\n.ui.selectable.table tr.negative:hover,\n.ui.table tr td.selectable.negative:hover,\n.ui.selectable.table tr:hover td.negative {\n  background: #ffe7e7 !important;\n  color: #943634 !important;\n}\n\n/*-------------------\n      Attached\n--------------------*/\n\n/* Middle */\n\n.ui.attached.table {\n  top: 0px;\n  bottom: 0px;\n  border-radius: 0px;\n  margin: 0em -1px;\n  width: calc(100% + 2px);\n  max-width: calc(100% + 2px);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border: 1px solid #d4d4d5;\n}\n\n.ui.attached + .ui.attached.table:not(.top) {\n  border-top: none;\n}\n\n/* Top */\n\n.ui[class*=\"top attached\"].table {\n  bottom: 0px;\n  margin-bottom: 0em;\n  top: 0px;\n  margin-top: 1em;\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n.ui.table[class*=\"top attached\"]:first-child {\n  margin-top: 0em;\n}\n\n/* Bottom */\n\n.ui[class*=\"bottom attached\"].table {\n  bottom: 0px;\n  margin-top: 0em;\n  top: 0px;\n  margin-bottom: 1em;\n  -webkit-box-shadow: none, none;\n  box-shadow: none, none;\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n.ui[class*=\"bottom attached\"].table:last-child {\n  margin-bottom: 0em;\n}\n\n/*--------------\n    Striped\n---------------*/\n\n/* Table Striping */\n\n.ui.striped.table > tr:nth-child(2n),\n.ui.striped.table tbody tr:nth-child(2n) {\n  background-color: rgba(0, 0, 50, 0.02);\n}\n\n/* Stripes */\n\n.ui.inverted.striped.table > tr:nth-child(2n),\n.ui.inverted.striped.table tbody tr:nth-child(2n) {\n  background-color: rgba(255, 255, 255, 0.05);\n}\n\n/* Allow striped active hover */\n\n.ui.striped.selectable.selectable.selectable.table tbody tr.active:hover {\n  background: #efefef !important;\n  color: rgba(0, 0, 0, 0.95) !important;\n}\n\n/*--------------\n  Single Line\n---------------*/\n\n.ui.table[class*=\"single line\"],\n.ui.table [class*=\"single line\"] {\n  white-space: nowrap;\n}\n\n.ui.table[class*=\"single line\"],\n.ui.table [class*=\"single line\"] {\n  white-space: nowrap;\n}\n\n/*-------------------\n      Colors\n--------------------*/\n\n/* Red */\n\n.ui.red.table {\n  border-top: 0.2em solid #db2828;\n}\n\n.ui.inverted.red.table {\n  background-color: #db2828 !important;\n  color: #ffffff !important;\n}\n\n/* Orange */\n\n.ui.orange.table {\n  border-top: 0.2em solid #f2711c;\n}\n\n.ui.inverted.orange.table {\n  background-color: #f2711c !important;\n  color: #ffffff !important;\n}\n\n/* Yellow */\n\n.ui.yellow.table {\n  border-top: 0.2em solid #fbbd08;\n}\n\n.ui.inverted.yellow.table {\n  background-color: #fbbd08 !important;\n  color: #ffffff !important;\n}\n\n/* Olive */\n\n.ui.olive.table {\n  border-top: 0.2em solid #b5cc18;\n}\n\n.ui.inverted.olive.table {\n  background-color: #b5cc18 !important;\n  color: #ffffff !important;\n}\n\n/* Green */\n\n.ui.green.table {\n  border-top: 0.2em solid #21ba45;\n}\n\n.ui.inverted.green.table {\n  background-color: #21ba45 !important;\n  color: #ffffff !important;\n}\n\n/* Teal */\n\n.ui.teal.table {\n  border-top: 0.2em solid #00b5ad;\n}\n\n.ui.inverted.teal.table {\n  background-color: #00b5ad !important;\n  color: #ffffff !important;\n}\n\n/* Blue */\n\n.ui.blue.table {\n  border-top: 0.2em solid #2185d0;\n}\n\n.ui.inverted.blue.table {\n  background-color: #2185d0 !important;\n  color: #ffffff !important;\n}\n\n/* Violet */\n\n.ui.violet.table {\n  border-top: 0.2em solid #6435c9;\n}\n\n.ui.inverted.violet.table {\n  background-color: #6435c9 !important;\n  color: #ffffff !important;\n}\n\n/* Purple */\n\n.ui.purple.table {\n  border-top: 0.2em solid #a333c8;\n}\n\n.ui.inverted.purple.table {\n  background-color: #a333c8 !important;\n  color: #ffffff !important;\n}\n\n/* Pink */\n\n.ui.pink.table {\n  border-top: 0.2em solid #e03997;\n}\n\n.ui.inverted.pink.table {\n  background-color: #e03997 !important;\n  color: #ffffff !important;\n}\n\n/* Brown */\n\n.ui.brown.table {\n  border-top: 0.2em solid #a5673f;\n}\n\n.ui.inverted.brown.table {\n  background-color: #a5673f !important;\n  color: #ffffff !important;\n}\n\n/* Grey */\n\n.ui.grey.table {\n  border-top: 0.2em solid #767676;\n}\n\n.ui.inverted.grey.table {\n  background-color: #767676 !important;\n  color: #ffffff !important;\n}\n\n/* Black */\n\n.ui.black.table {\n  border-top: 0.2em solid #1b1c1d;\n}\n\n.ui.inverted.black.table {\n  background-color: #1b1c1d !important;\n  color: #ffffff !important;\n}\n\n/*--------------\n  Column Count\n---------------*/\n\n/* Grid Based */\n\n.ui.one.column.table td {\n  width: 100%;\n}\n\n.ui.two.column.table td {\n  width: 50%;\n}\n\n.ui.three.column.table td {\n  width: 33.33333333%;\n}\n\n.ui.four.column.table td {\n  width: 25%;\n}\n\n.ui.five.column.table td {\n  width: 20%;\n}\n\n.ui.six.column.table td {\n  width: 16.66666667%;\n}\n\n.ui.seven.column.table td {\n  width: 14.28571429%;\n}\n\n.ui.eight.column.table td {\n  width: 12.5%;\n}\n\n.ui.nine.column.table td {\n  width: 11.11111111%;\n}\n\n.ui.ten.column.table td {\n  width: 10%;\n}\n\n.ui.eleven.column.table td {\n  width: 9.09090909%;\n}\n\n.ui.twelve.column.table td {\n  width: 8.33333333%;\n}\n\n.ui.thirteen.column.table td {\n  width: 7.69230769%;\n}\n\n.ui.fourteen.column.table td {\n  width: 7.14285714%;\n}\n\n.ui.fifteen.column.table td {\n  width: 6.66666667%;\n}\n\n.ui.sixteen.column.table td {\n  width: 6.25%;\n}\n\n/* Column Width */\n\n.ui.table th.one.wide,\n.ui.table td.one.wide {\n  width: 6.25%;\n}\n\n.ui.table th.two.wide,\n.ui.table td.two.wide {\n  width: 12.5%;\n}\n\n.ui.table th.three.wide,\n.ui.table td.three.wide {\n  width: 18.75%;\n}\n\n.ui.table th.four.wide,\n.ui.table td.four.wide {\n  width: 25%;\n}\n\n.ui.table th.five.wide,\n.ui.table td.five.wide {\n  width: 31.25%;\n}\n\n.ui.table th.six.wide,\n.ui.table td.six.wide {\n  width: 37.5%;\n}\n\n.ui.table th.seven.wide,\n.ui.table td.seven.wide {\n  width: 43.75%;\n}\n\n.ui.table th.eight.wide,\n.ui.table td.eight.wide {\n  width: 50%;\n}\n\n.ui.table th.nine.wide,\n.ui.table td.nine.wide {\n  width: 56.25%;\n}\n\n.ui.table th.ten.wide,\n.ui.table td.ten.wide {\n  width: 62.5%;\n}\n\n.ui.table th.eleven.wide,\n.ui.table td.eleven.wide {\n  width: 68.75%;\n}\n\n.ui.table th.twelve.wide,\n.ui.table td.twelve.wide {\n  width: 75%;\n}\n\n.ui.table th.thirteen.wide,\n.ui.table td.thirteen.wide {\n  width: 81.25%;\n}\n\n.ui.table th.fourteen.wide,\n.ui.table td.fourteen.wide {\n  width: 87.5%;\n}\n\n.ui.table th.fifteen.wide,\n.ui.table td.fifteen.wide {\n  width: 93.75%;\n}\n\n.ui.table th.sixteen.wide,\n.ui.table td.sixteen.wide {\n  width: 100%;\n}\n\n/*--------------\n    Sortable\n---------------*/\n\n.ui.sortable.table thead th {\n  cursor: pointer;\n  white-space: nowrap;\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.sortable.table thead th:first-child {\n  border-left: none;\n}\n\n.ui.sortable.table thead th.sorted,\n.ui.sortable.table thead th.sorted:hover {\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n\n.ui.sortable.table thead th:after {\n  display: none;\n  font-style: normal;\n  font-weight: normal;\n  text-decoration: inherit;\n  content: \"\";\n  height: 1em;\n  width: auto;\n  opacity: 0.8;\n  margin: 0em 0em 0em 0.5em;\n  font-family: \"Icons\";\n}\n\n.ui.sortable.table thead th.ascending:after {\n  content: \"\\f0d8\";\n}\n\n.ui.sortable.table thead th.descending:after {\n  content: \"\\f0d7\";\n}\n\n/* Hover */\n\n.ui.sortable.table th.disabled:hover {\n  cursor: auto;\n  color: rgba(40, 40, 40, 0.3);\n}\n\n.ui.sortable.table thead th:hover {\n  background: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.8);\n}\n\n/* Sorted */\n\n.ui.sortable.table thead th.sorted {\n  background: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.95);\n}\n\n.ui.sortable.table thead th.sorted:after {\n  display: inline-block;\n}\n\n/* Sorted Hover */\n\n.ui.sortable.table thead th.sorted:hover {\n  background: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/* Inverted */\n\n.ui.inverted.sortable.table thead th.sorted {\n  background: rgba(255, 255, 255, 0.15) -webkit-gradient(linear, left top, left bottom, from(transparent), to(rgba(0, 0, 0, 0.05)));\n  background: rgba(255, 255, 255, 0.15) -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  background: rgba(255, 255, 255, 0.15) linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  color: #ffffff;\n}\n\n.ui.inverted.sortable.table thead th:hover {\n  background: rgba(255, 255, 255, 0.08) -webkit-gradient(linear, left top, left bottom, from(transparent), to(rgba(0, 0, 0, 0.05)));\n  background: rgba(255, 255, 255, 0.08) -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  background: rgba(255, 255, 255, 0.08) linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  color: #ffffff;\n}\n\n.ui.inverted.sortable.table thead th {\n  border-left-color: transparent;\n  border-right-color: transparent;\n}\n\n/*--------------\n    Inverted\n---------------*/\n\n/* Text Color */\n\n.ui.inverted.table {\n  background: #333333;\n  color: rgba(255, 255, 255, 0.9);\n  border: none;\n}\n\n.ui.inverted.table th {\n  background-color: rgba(0, 0, 0, 0.15);\n  border-color: rgba(255, 255, 255, 0.1) !important;\n  color: rgba(255, 255, 255, 0.9) !important;\n}\n\n.ui.inverted.table tr td {\n  border-color: rgba(255, 255, 255, 0.1) !important;\n}\n\n.ui.inverted.table tr.disabled td,\n.ui.inverted.table tr td.disabled,\n.ui.inverted.table tr.disabled:hover td,\n.ui.inverted.table tr:hover td.disabled {\n  pointer-events: none;\n  color: rgba(225, 225, 225, 0.3);\n}\n\n/* Definition */\n\n.ui.inverted.definition.table tfoot:not(.full-width) th:first-child,\n.ui.inverted.definition.table thead:not(.full-width) th:first-child {\n  background: #ffffff;\n}\n\n.ui.inverted.definition.table tr td:first-child {\n  background: rgba(255, 255, 255, 0.02);\n  color: #ffffff;\n}\n\n/*--------------\n  Collapsing\n---------------*/\n\n.ui.collapsing.table {\n  width: auto;\n}\n\n/*--------------\n      Basic\n---------------*/\n\n.ui.basic.table {\n  background: transparent;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.basic.table thead,\n.ui.basic.table tfoot {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.basic.table th {\n  background: transparent;\n  border-left: none;\n}\n\n.ui.basic.table tbody tr {\n  border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n}\n\n.ui.basic.table td {\n  background: transparent;\n}\n\n.ui.basic.striped.table tbody tr:nth-child(2n) {\n  background-color: rgba(0, 0, 0, 0.05) !important;\n}\n\n/* Very Basic */\n\n.ui[class*=\"very basic\"].table {\n  border: none;\n}\n\n.ui[class*=\"very basic\"].table:not(.sortable):not(.striped) th,\n.ui[class*=\"very basic\"].table:not(.sortable):not(.striped) td {\n  padding: \"\";\n}\n\n.ui[class*=\"very basic\"].table:not(.sortable):not(.striped) th:first-child,\n.ui[class*=\"very basic\"].table:not(.sortable):not(.striped) td:first-child {\n  padding-left: 0em;\n}\n\n.ui[class*=\"very basic\"].table:not(.sortable):not(.striped) th:last-child,\n.ui[class*=\"very basic\"].table:not(.sortable):not(.striped) td:last-child {\n  padding-right: 0em;\n}\n\n.ui[class*=\"very basic\"].table:not(.sortable):not(.striped) thead tr:first-child th {\n  padding-top: 0em;\n}\n\n/*--------------\n    Celled\n---------------*/\n\n.ui.celled.table tr th,\n.ui.celled.table tr td {\n  border-left: 1px solid rgba(34, 36, 38, 0.1);\n}\n\n.ui.celled.table tr th:first-child,\n.ui.celled.table tr td:first-child {\n  border-left: none;\n}\n\n/*--------------\n    Padded\n---------------*/\n\n.ui.padded.table th {\n  padding-left: 1em;\n  padding-right: 1em;\n}\n\n.ui.padded.table th,\n.ui.padded.table td {\n  padding: 1em 1em;\n}\n\n/* Very */\n\n.ui[class*=\"very padded\"].table th {\n  padding-left: 1.5em;\n  padding-right: 1.5em;\n}\n\n.ui[class*=\"very padded\"].table td {\n  padding: 1.5em 1.5em;\n}\n\n/*--------------\n    Compact\n---------------*/\n\n.ui.compact.table th {\n  padding-left: 0.7em;\n  padding-right: 0.7em;\n}\n\n.ui.compact.table td {\n  padding: 0.5em 0.7em;\n}\n\n/* Very */\n\n.ui[class*=\"very compact\"].table th {\n  padding-left: 0.6em;\n  padding-right: 0.6em;\n}\n\n.ui[class*=\"very compact\"].table td {\n  padding: 0.4em 0.6em;\n}\n\n/*--------------\n      Sizes\n---------------*/\n\n/* Small */\n\n.ui.small.table {\n  font-size: 0.9em;\n}\n\n/* Standard */\n\n.ui.table {\n  font-size: 1em;\n}\n\n/* Large */\n\n.ui.large.table {\n  font-size: 1.1em;\n}\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Ad\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Copyright 2013 Contributors\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n        Advertisement\n*******************************/\n\n.ui.ad {\n  display: block;\n  overflow: hidden;\n  margin: 1em 0em;\n}\n\n.ui.ad:first-child {\n  margin: 0em;\n}\n\n.ui.ad:last-child {\n  margin: 0em;\n}\n\n.ui.ad iframe {\n  margin: 0em;\n  padding: 0em;\n  border: none;\n  overflow: hidden;\n}\n\n/*--------------\n    Common\n---------------*/\n\n/* Leaderboard */\n\n.ui.leaderboard.ad {\n  width: 728px;\n  height: 90px;\n}\n\n/* Medium Rectangle */\n\n.ui[class*=\"medium rectangle\"].ad {\n  width: 300px;\n  height: 250px;\n}\n\n/* Large Rectangle */\n\n.ui[class*=\"large rectangle\"].ad {\n  width: 336px;\n  height: 280px;\n}\n\n/* Half Page */\n\n.ui[class*=\"half page\"].ad {\n  width: 300px;\n  height: 600px;\n}\n\n/*--------------\n    Square\n---------------*/\n\n/* Square */\n\n.ui.square.ad {\n  width: 250px;\n  height: 250px;\n}\n\n/* Small Square */\n\n.ui[class*=\"small square\"].ad {\n  width: 200px;\n  height: 200px;\n}\n\n/*--------------\n    Rectangle\n---------------*/\n\n/* Small Rectangle */\n\n.ui[class*=\"small rectangle\"].ad {\n  width: 180px;\n  height: 150px;\n}\n\n/* Vertical Rectangle */\n\n.ui[class*=\"vertical rectangle\"].ad {\n  width: 240px;\n  height: 400px;\n}\n\n/*--------------\n    Button\n---------------*/\n\n.ui.button.ad {\n  width: 120px;\n  height: 90px;\n}\n\n.ui[class*=\"square button\"].ad {\n  width: 125px;\n  height: 125px;\n}\n\n.ui[class*=\"small button\"].ad {\n  width: 120px;\n  height: 60px;\n}\n\n/*--------------\n  Skyscrapers\n---------------*/\n\n/* Skyscraper */\n\n.ui.skyscraper.ad {\n  width: 120px;\n  height: 600px;\n}\n\n/* Wide Skyscraper */\n\n.ui[class*=\"wide skyscraper\"].ad {\n  width: 160px;\n}\n\n/*--------------\n    Banners\n---------------*/\n\n/* Banner */\n\n.ui.banner.ad {\n  width: 468px;\n  height: 60px;\n}\n\n/* Vertical Banner */\n\n.ui[class*=\"vertical banner\"].ad {\n  width: 120px;\n  height: 240px;\n}\n\n/* Top Banner */\n\n.ui[class*=\"top banner\"].ad {\n  width: 930px;\n  height: 180px;\n}\n\n/* Half Banner */\n\n.ui[class*=\"half banner\"].ad {\n  width: 234px;\n  height: 60px;\n}\n\n/*--------------\n    Boards\n---------------*/\n\n/* Leaderboard */\n\n.ui[class*=\"large leaderboard\"].ad {\n  width: 970px;\n  height: 90px;\n}\n\n/* Billboard */\n\n.ui.billboard.ad {\n  width: 970px;\n  height: 250px;\n}\n\n/*--------------\n    Panorama\n---------------*/\n\n/* Panorama */\n\n.ui.panorama.ad {\n  width: 980px;\n  height: 120px;\n}\n\n/*--------------\n    Netboard\n---------------*/\n\n/* Netboard */\n\n.ui.netboard.ad {\n  width: 580px;\n  height: 400px;\n}\n\n/*--------------\n    Mobile\n---------------*/\n\n/* Large Mobile Banner */\n\n.ui[class*=\"large mobile banner\"].ad {\n  width: 320px;\n  height: 100px;\n}\n\n/* Mobile Leaderboard */\n\n.ui[class*=\"mobile leaderboard\"].ad {\n  width: 320px;\n  height: 50px;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/* Mobile Sizes */\n\n.ui.mobile.ad {\n  display: none;\n}\n\n@media only screen and (width < 768px) {\n  .ui.mobile.ad {\n    display: block;\n  }\n}\n\n/*******************************\n          Variations\n*******************************/\n\n.ui.centered.ad {\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.ui.test.ad {\n  position: relative;\n  background: #545454;\n}\n\n.ui.test.ad:after {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  width: 100%;\n  text-align: center;\n  -webkit-transform: translateX(-50%) translateY(-50%);\n  transform: translateX(-50%) translateY(-50%);\n  content: \"Ad\";\n  color: #ffffff;\n  font-size: 1em;\n  font-weight: bold;\n}\n\n.ui.mobile.test.ad:after {\n  font-size: 0.85714286em;\n}\n\n.ui.test.ad[data-text]:after {\n  content: attr(data-text);\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n    User Variable Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Item\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Standard\n*******************************/\n\n/*--------------\n      Card\n---------------*/\n\n.ui.cards > .card,\n.ui.card {\n  max-width: 100%;\n  position: relative;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  width: 290px;\n  min-height: 0px;\n  background: #ffffff;\n  padding: 0em;\n  border: none;\n  border-radius: 0.28571429rem;\n  -webkit-box-shadow: 0px 1px 3px 0px #d4d4d5, 0px 0px 0px 1px #d4d4d5;\n  box-shadow: 0px 1px 3px 0px #d4d4d5, 0px 0px 0px 1px #d4d4d5;\n  -webkit-transition: -webkit-box-shadow 0.1s ease, -webkit-transform 0.1s ease;\n  transition: -webkit-box-shadow 0.1s ease, -webkit-transform 0.1s ease;\n  transition: box-shadow 0.1s ease, transform 0.1s ease;\n  transition: box-shadow 0.1s ease, transform 0.1s ease,\n    -webkit-box-shadow 0.1s ease, -webkit-transform 0.1s ease;\n  z-index: \"\";\n}\n\n.ui.card {\n  margin: 1em 0em;\n}\n\n.ui.cards > .card a,\n.ui.card a {\n  cursor: pointer;\n}\n\n.ui.card:first-child {\n  margin-top: 0em;\n}\n\n.ui.card:last-child {\n  margin-bottom: 0em;\n}\n\n/*--------------\n      Cards\n---------------*/\n\n.ui.cards {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  margin: -0.875em -0.5em;\n  -ms-flex-wrap: wrap;\n  flex-wrap: wrap;\n}\n\n.ui.cards > .card {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  margin: 0.875em 0.5em;\n  float: none;\n}\n\n/* Clearing */\n\n.ui.cards:after,\n.ui.card:after {\n  display: block;\n  content: \" \";\n  height: 0px;\n  clear: both;\n  overflow: hidden;\n  visibility: hidden;\n}\n\n/* Consecutive Card Groups Preserve Row Spacing */\n\n.ui.cards ~ .ui.cards {\n  margin-top: 0.875em;\n}\n\n/*--------------\n  Rounded Edges\n---------------*/\n\n.ui.cards > .card > :first-child,\n.ui.card > :first-child {\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em !important;\n  border-top: none !important;\n}\n\n.ui.cards > .card > :last-child,\n.ui.card > :last-child {\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem !important;\n}\n\n.ui.cards > .card > :only-child,\n.ui.card > :only-child {\n  border-radius: 0.28571429rem !important;\n}\n\n/*--------------\n    Images\n---------------*/\n\n.ui.cards > .card > .image,\n.ui.card > .image {\n  position: relative;\n  display: block;\n  -webkit-box-flex: 0;\n  -ms-flex: 0 0 auto;\n  flex: 0 0 auto;\n  padding: 0em;\n  background: rgba(0, 0, 0, 0.05);\n}\n\n.ui.cards > .card > .image > img,\n.ui.card > .image > img {\n  display: block;\n  width: 100%;\n  height: auto;\n  border-radius: inherit;\n}\n\n.ui.cards > .card > .image:not(.ui) > img,\n.ui.card > .image:not(.ui) > img {\n  border: none;\n}\n\n/*--------------\n    Content\n---------------*/\n\n.ui.cards > .card > .content,\n.ui.card > .content {\n  -webkit-box-flex: 1;\n  -ms-flex-positive: 1;\n  flex-grow: 1;\n  border: none;\n  border-top: 1px solid rgba(34, 36, 38, 0.1);\n  background: none;\n  margin: 0em;\n  padding: 1em 1em;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  font-size: 1em;\n  border-radius: 0em;\n}\n\n.ui.cards > .card > .content:after,\n.ui.card > .content:after {\n  display: block;\n  content: \" \";\n  height: 0px;\n  clear: both;\n  overflow: hidden;\n  visibility: hidden;\n}\n\n.ui.cards > .card > .content > .header,\n.ui.card > .content > .header {\n  display: block;\n  margin: \"\";\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  color: rgba(0, 0, 0, 0.85);\n}\n\n/* Default Header Size */\n\n.ui.cards > .card > .content > .header:not(.ui),\n.ui.card > .content > .header:not(.ui) {\n  font-weight: bold;\n  font-size: 1.28571429em;\n  margin-top: -0.21425em;\n  line-height: 1.28571429em;\n}\n\n.ui.cards > .card > .content > .meta + .description,\n.ui.cards > .card > .content > .header + .description,\n.ui.card > .content > .meta + .description,\n.ui.card > .content > .header + .description {\n  margin-top: 0.5em;\n}\n\n/*----------------\nFloated Content\n-----------------*/\n\n.ui.cards > .card [class*=\"left floated\"],\n.ui.card [class*=\"left floated\"] {\n  float: left;\n}\n\n.ui.cards > .card [class*=\"right floated\"],\n.ui.card [class*=\"right floated\"] {\n  float: right;\n}\n\n/*--------------\n    Aligned\n---------------*/\n\n.ui.cards > .card [class*=\"left aligned\"],\n.ui.card [class*=\"left aligned\"] {\n  text-align: left;\n}\n\n.ui.cards > .card [class*=\"center aligned\"],\n.ui.card [class*=\"center aligned\"] {\n  text-align: center;\n}\n\n.ui.cards > .card [class*=\"right aligned\"],\n.ui.card [class*=\"right aligned\"] {\n  text-align: right;\n}\n\n/*--------------\n  Content Image\n---------------*/\n\n.ui.cards > .card .content img,\n.ui.card .content img {\n  display: inline-block;\n  vertical-align: middle;\n  width: \"\";\n}\n\n.ui.cards > .card img.avatar,\n.ui.cards > .card .avatar img,\n.ui.card img.avatar,\n.ui.card .avatar img {\n  width: 2em;\n  height: 2em;\n  border-radius: 500rem;\n}\n\n/*--------------\n  Description\n---------------*/\n\n.ui.cards > .card > .content > .description,\n.ui.card > .content > .description {\n  clear: both;\n  color: rgba(0, 0, 0, 0.68);\n}\n\n/*--------------\n    Paragraph\n---------------*/\n\n.ui.cards > .card > .content p,\n.ui.card > .content p {\n  margin: 0em 0em 0.5em;\n}\n\n.ui.cards > .card > .content p:last-child,\n.ui.card > .content p:last-child {\n  margin-bottom: 0em;\n}\n\n/*--------------\n      Meta\n---------------*/\n\n.ui.cards > .card .meta,\n.ui.card .meta {\n  font-size: 1em;\n  color: rgba(0, 0, 0, 0.4);\n}\n\n.ui.cards > .card .meta *,\n.ui.card .meta * {\n  margin-right: 0.3em;\n}\n\n.ui.cards > .card .meta :last-child,\n.ui.card .meta :last-child {\n  margin-right: 0em;\n}\n\n.ui.cards > .card .meta [class*=\"right floated\"],\n.ui.card .meta [class*=\"right floated\"] {\n  margin-right: 0em;\n  margin-left: 0.3em;\n}\n\n/*--------------\n      Links\n---------------*/\n\n/* Generic */\n\n.ui.cards > .card > .content a:not(.ui),\n.ui.card > .content a:not(.ui) {\n  color: \"\";\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n}\n\n.ui.cards > .card > .content a:not(.ui):hover,\n.ui.card > .content a:not(.ui):hover {\n  color: \"\";\n}\n\n/* Header */\n\n.ui.cards > .card > .content > a.header,\n.ui.card > .content > a.header {\n  color: rgba(0, 0, 0, 0.85);\n}\n\n.ui.cards > .card > .content > a.header:hover,\n.ui.card > .content > a.header:hover {\n  color: #1e70bf;\n}\n\n/* Meta */\n\n.ui.cards > .card .meta > a:not(.ui),\n.ui.card .meta > a:not(.ui) {\n  color: rgba(0, 0, 0, 0.4);\n}\n\n.ui.cards > .card .meta > a:not(.ui):hover,\n.ui.card .meta > a:not(.ui):hover {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/*--------------\n    Buttons\n---------------*/\n\n.ui.cards > .card > .buttons,\n.ui.card > .buttons,\n.ui.cards > .card > .button,\n.ui.card > .button {\n  margin: 0px -1px;\n  width: calc(100% + 2px);\n}\n\n/*--------------\n      Dimmer\n---------------*/\n\n.ui.cards > .card .dimmer,\n.ui.card .dimmer {\n  background-color: \"\";\n  z-index: 10;\n}\n\n/*--------------\n    Labels\n---------------*/\n\n/*-----Star----- */\n\n/* Icon */\n\n.ui.cards > .card > .content .star.icon,\n.ui.card > .content .star.icon {\n  cursor: pointer;\n  opacity: 0.75;\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n}\n\n.ui.cards > .card > .content .star.icon:hover,\n.ui.card > .content .star.icon:hover {\n  opacity: 1;\n  color: #ffb70a;\n}\n\n.ui.cards > .card > .content .active.star.icon,\n.ui.card > .content .active.star.icon {\n  color: #ffe623;\n}\n\n/*-----Like----- */\n\n/* Icon */\n\n.ui.cards > .card > .content .like.icon,\n.ui.card > .content .like.icon {\n  cursor: pointer;\n  opacity: 0.75;\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n}\n\n.ui.cards > .card > .content .like.icon:hover,\n.ui.card > .content .like.icon:hover {\n  opacity: 1;\n  color: #ff2733;\n}\n\n.ui.cards > .card > .content .active.like.icon,\n.ui.card > .content .active.like.icon {\n  color: #ff2733;\n}\n\n/*----------------\n  Extra Content\n-----------------*/\n\n.ui.cards > .card > .extra,\n.ui.card > .extra {\n  max-width: 100%;\n  min-height: 0em !important;\n  -webkit-box-flex: 0;\n  -ms-flex-positive: 0;\n  flex-grow: 0;\n  border-top: 1px solid rgba(0, 0, 0, 0.05) !important;\n  position: static;\n  background: none;\n  width: auto;\n  margin: 0em 0em;\n  padding: 0.75em 1em;\n  top: 0em;\n  left: 0em;\n  color: rgba(0, 0, 0, 0.4);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n}\n\n.ui.cards > .card > .extra a:not(.ui),\n.ui.card > .extra a:not(.ui) {\n  color: rgba(0, 0, 0, 0.4);\n}\n\n.ui.cards > .card > .extra a:not(.ui):hover,\n.ui.card > .extra a:not(.ui):hover {\n  color: #1e70bf;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*-------------------\n      Raised\n--------------------*/\n\n.ui.raised.cards > .card,\n.ui.raised.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5,\n    0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n}\n\n.ui.raised.cards a.card:hover,\n.ui.link.cards .raised.card:hover,\na.ui.raised.card:hover,\n.ui.link.raised.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5,\n    0px 2px 4px 0px rgba(34, 36, 38, 0.15),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.25);\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 4px 0px rgba(34, 36, 38, 0.15),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.25);\n}\n\n.ui.raised.cards > .card,\n.ui.raised.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5,\n    0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n}\n\n/*-------------------\n      Centered\n--------------------*/\n\n.ui.centered.cards {\n  -webkit-box-pack: center;\n  -ms-flex-pack: center;\n  justify-content: center;\n}\n\n.ui.centered.card {\n  margin-left: auto;\n  margin-right: auto;\n}\n\n/*-------------------\n        Fluid\n--------------------*/\n\n.ui.fluid.card {\n  width: 100%;\n  max-width: 9999px;\n}\n\n/*-------------------\n        Link\n--------------------*/\n\n.ui.cards a.card,\n.ui.link.cards .card,\na.ui.card,\n.ui.link.card {\n  -webkit-transform: none;\n  transform: none;\n}\n\n.ui.cards a.card:hover,\n.ui.link.cards .card:hover,\na.ui.card:hover,\n.ui.link.card:hover {\n  cursor: pointer;\n  z-index: 5;\n  background: #ffffff;\n  border: none;\n  -webkit-box-shadow: 0px 1px 3px 0px #bcbdbd, 0px 0px 0px 1px #d4d4d5;\n  box-shadow: 0px 1px 3px 0px #bcbdbd, 0px 0px 0px 1px #d4d4d5;\n  -webkit-transform: translateY(-3px);\n  transform: translateY(-3px);\n}\n\n/*-------------------\n      Colors\n--------------------*/\n\n/* Red */\n\n.ui.red.cards > .card,\n.ui.cards > .red.card,\n.ui.red.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #db2828,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #db2828,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.red.cards > .card:hover,\n.ui.cards > .red.card:hover,\n.ui.red.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #d01919,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #d01919,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Orange */\n\n.ui.orange.cards > .card,\n.ui.cards > .orange.card,\n.ui.orange.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #f2711c,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #f2711c,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.orange.cards > .card:hover,\n.ui.cards > .orange.card:hover,\n.ui.orange.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #f26202,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #f26202,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Yellow */\n\n.ui.yellow.cards > .card,\n.ui.cards > .yellow.card,\n.ui.yellow.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #fbbd08,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #fbbd08,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.yellow.cards > .card:hover,\n.ui.cards > .yellow.card:hover,\n.ui.yellow.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #eaae00,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #eaae00,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Olive */\n\n.ui.olive.cards > .card,\n.ui.cards > .olive.card,\n.ui.olive.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #b5cc18,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #b5cc18,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.olive.cards > .card:hover,\n.ui.cards > .olive.card:hover,\n.ui.olive.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #a7bd0d,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #a7bd0d,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Green */\n\n.ui.green.cards > .card,\n.ui.cards > .green.card,\n.ui.green.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #21ba45,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #21ba45,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.green.cards > .card:hover,\n.ui.cards > .green.card:hover,\n.ui.green.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #16ab39,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #16ab39,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Teal */\n\n.ui.teal.cards > .card,\n.ui.cards > .teal.card,\n.ui.teal.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #00b5ad,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #00b5ad,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.teal.cards > .card:hover,\n.ui.cards > .teal.card:hover,\n.ui.teal.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #009c95,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #009c95,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Blue */\n\n.ui.blue.cards > .card,\n.ui.cards > .blue.card,\n.ui.blue.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #2185d0,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #2185d0,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.blue.cards > .card:hover,\n.ui.cards > .blue.card:hover,\n.ui.blue.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #1678c2,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #1678c2,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Violet */\n\n.ui.violet.cards > .card,\n.ui.cards > .violet.card,\n.ui.violet.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #6435c9,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #6435c9,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.violet.cards > .card:hover,\n.ui.cards > .violet.card:hover,\n.ui.violet.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #5829bb,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #5829bb,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Purple */\n\n.ui.purple.cards > .card,\n.ui.cards > .purple.card,\n.ui.purple.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #a333c8,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #a333c8,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.purple.cards > .card:hover,\n.ui.cards > .purple.card:hover,\n.ui.purple.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #9627ba,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #9627ba,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Pink */\n\n.ui.pink.cards > .card,\n.ui.cards > .pink.card,\n.ui.pink.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #e03997,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #e03997,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.pink.cards > .card:hover,\n.ui.cards > .pink.card:hover,\n.ui.pink.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #e61a8d,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #e61a8d,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Brown */\n\n.ui.brown.cards > .card,\n.ui.cards > .brown.card,\n.ui.brown.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #a5673f,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #a5673f,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.brown.cards > .card:hover,\n.ui.cards > .brown.card:hover,\n.ui.brown.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #975b33,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #975b33,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Grey */\n\n.ui.grey.cards > .card,\n.ui.cards > .grey.card,\n.ui.grey.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #767676,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #767676,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.grey.cards > .card:hover,\n.ui.cards > .grey.card:hover,\n.ui.grey.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #838383,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #838383,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/* Black */\n\n.ui.black.cards > .card,\n.ui.cards > .black.card,\n.ui.black.card {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #1b1c1d,\n    0px 1px 3px 0px #d4d4d5;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #1b1c1d,\n    0px 1px 3px 0px #d4d4d5;\n}\n\n.ui.black.cards > .card:hover,\n.ui.cards > .black.card:hover,\n.ui.black.card:hover {\n  -webkit-box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #27292a,\n    0px 1px 3px 0px #bcbdbd;\n  box-shadow: 0px 0px 0px 1px #d4d4d5, 0px 2px 0px 0px #27292a,\n    0px 1px 3px 0px #bcbdbd;\n}\n\n/*--------------\n  Card Count\n---------------*/\n\n.ui.one.cards {\n  margin-left: 0em;\n  margin-right: 0em;\n}\n\n.ui.one.cards > .card {\n  width: 100%;\n}\n\n.ui.two.cards {\n  margin-left: -1em;\n  margin-right: -1em;\n}\n\n.ui.two.cards > .card {\n  width: calc(50% - 2em);\n  margin-left: 1em;\n  margin-right: 1em;\n}\n\n.ui.three.cards {\n  margin-left: -1em;\n  margin-right: -1em;\n}\n\n.ui.three.cards > .card {\n  width: calc(33.33333333% - 2em);\n  margin-left: 1em;\n  margin-right: 1em;\n}\n\n.ui.four.cards {\n  margin-left: -0.75em;\n  margin-right: -0.75em;\n}\n\n.ui.four.cards > .card {\n  width: calc(25% - 1.5em);\n  margin-left: 0.75em;\n  margin-right: 0.75em;\n}\n\n.ui.five.cards {\n  margin-left: -0.75em;\n  margin-right: -0.75em;\n}\n\n.ui.five.cards > .card {\n  width: calc(20% - 1.5em);\n  margin-left: 0.75em;\n  margin-right: 0.75em;\n}\n\n.ui.six.cards {\n  margin-left: -0.75em;\n  margin-right: -0.75em;\n}\n\n.ui.six.cards > .card {\n  width: calc(16.66666667% - 1.5em);\n  margin-left: 0.75em;\n  margin-right: 0.75em;\n}\n\n.ui.seven.cards {\n  margin-left: -0.5em;\n  margin-right: -0.5em;\n}\n\n.ui.seven.cards > .card {\n  width: calc(14.28571429% - 1em);\n  margin-left: 0.5em;\n  margin-right: 0.5em;\n}\n\n.ui.eight.cards {\n  margin-left: -0.5em;\n  margin-right: -0.5em;\n}\n\n.ui.eight.cards > .card {\n  width: calc(12.5% - 1em);\n  margin-left: 0.5em;\n  margin-right: 0.5em;\n  font-size: 11px;\n}\n\n.ui.nine.cards {\n  margin-left: -0.5em;\n  margin-right: -0.5em;\n}\n\n.ui.nine.cards > .card {\n  width: calc(11.11111111% - 1em);\n  margin-left: 0.5em;\n  margin-right: 0.5em;\n  font-size: 10px;\n}\n\n.ui.ten.cards {\n  margin-left: -0.5em;\n  margin-right: -0.5em;\n}\n\n.ui.ten.cards > .card {\n  width: calc(10% - 1em);\n  margin-left: 0.5em;\n  margin-right: 0.5em;\n}\n\n/*-------------------\n      Doubling\n--------------------*/\n\n/* Mobile Only */\n\n@media only screen and (width < 768px) {\n  .ui.two.doubling.cards {\n    margin-left: 0em;\n    margin-right: 0em;\n  }\n\n  .ui.two.doubling.cards > .card {\n    width: 100%;\n    margin-left: 0em;\n    margin-right: 0em;\n  }\n\n  .ui.three.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.three.doubling.cards > .card {\n    width: calc(50% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.four.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.four.doubling.cards > .card {\n    width: calc(50% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.five.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.five.doubling.cards > .card {\n    width: calc(50% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.six.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.six.doubling.cards > .card {\n    width: calc(50% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.seven.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.seven.doubling.cards > .card {\n    width: calc(33.33333333% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.eight.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.eight.doubling.cards > .card {\n    width: calc(33.33333333% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.nine.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.nine.doubling.cards > .card {\n    width: calc(33.33333333% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.ten.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.ten.doubling.cards > .card {\n    width: calc(33.33333333% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n}\n\n/* Tablet Only */\n\n@media only screen and (768px <= width < 992px) {\n  .ui.two.doubling.cards {\n    margin-left: 0em;\n    margin-right: 0em;\n  }\n\n  .ui.two.doubling.cards > .card {\n    width: 100%;\n    margin-left: 0em;\n    margin-right: 0em;\n  }\n\n  .ui.three.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.three.doubling.cards > .card {\n    width: calc(50% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.four.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.four.doubling.cards > .card {\n    width: calc(50% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.five.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.five.doubling.cards > .card {\n    width: calc(33.33333333% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.six.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.six.doubling.cards > .card {\n    width: calc(33.33333333% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.eight.doubling.cards {\n    margin-left: -1em;\n    margin-right: -1em;\n  }\n\n  .ui.eight.doubling.cards > .card {\n    width: calc(33.33333333% - 2em);\n    margin-left: 1em;\n    margin-right: 1em;\n  }\n\n  .ui.eight.doubling.cards {\n    margin-left: -0.75em;\n    margin-right: -0.75em;\n  }\n\n  .ui.eight.doubling.cards > .card {\n    width: calc(25% - 1.5em);\n    margin-left: 0.75em;\n    margin-right: 0.75em;\n  }\n\n  .ui.nine.doubling.cards {\n    margin-left: -0.75em;\n    margin-right: -0.75em;\n  }\n\n  .ui.nine.doubling.cards > .card {\n    width: calc(25% - 1.5em);\n    margin-left: 0.75em;\n    margin-right: 0.75em;\n  }\n\n  .ui.ten.doubling.cards {\n    margin-left: -0.75em;\n    margin-right: -0.75em;\n  }\n\n  .ui.ten.doubling.cards > .card {\n    width: calc(20% - 1.5em);\n    margin-left: 0.75em;\n    margin-right: 0.75em;\n  }\n}\n\n/*-------------------\n      Stackable\n--------------------*/\n\n@media only screen and (width < 768px) {\n  .ui.stackable.cards {\n    display: block !important;\n  }\n\n  .ui.stackable.cards .card:first-child {\n    margin-top: 0em !important;\n  }\n\n  .ui.stackable.cards > .card {\n    display: block !important;\n    height: auto !important;\n    margin: 1em 1em;\n    padding: 0 !important;\n    width: calc(100% - 2em) !important;\n  }\n}\n\n/*--------------\n      Size\n---------------*/\n\n.ui.cards > .card {\n  font-size: 1em;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n    User Variable Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Comment\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Standard\n*******************************/\n\n/*--------------\n    Comments\n---------------*/\n\n.ui.comments {\n  margin: 1.5em 0em;\n  max-width: 650px;\n}\n\n.ui.comments:first-child {\n  margin-top: 0em;\n}\n\n.ui.comments:last-child {\n  margin-bottom: 0em;\n}\n\n/*--------------\n    Comment\n---------------*/\n\n.ui.comments .comment {\n  position: relative;\n  background: none;\n  margin: 0.5em 0em 0em;\n  padding: 0.5em 0em 0em;\n  border: none;\n  border-top: none;\n  line-height: 1.2;\n}\n\n.ui.comments .comment:first-child {\n  margin-top: 0em;\n  padding-top: 0em;\n}\n\n/*--------------------\n    Nested Comments\n---------------------*/\n\n.ui.comments .comment .comments {\n  margin: 0em 0em 0.5em 0.5em;\n  padding: 1em 0em 1em 1em;\n}\n\n.ui.comments .comment .comments:before {\n  position: absolute;\n  top: 0px;\n  left: 0px;\n}\n\n.ui.comments .comment .comments .comment {\n  border: none;\n  border-top: none;\n  background: none;\n}\n\n/*--------------\n    Avatar\n---------------*/\n\n.ui.comments .comment .avatar {\n  display: block;\n  width: 2.5em;\n  height: auto;\n  float: left;\n  margin: 0.2em 0em 0em;\n}\n\n.ui.comments .comment img.avatar,\n.ui.comments .comment .avatar img {\n  display: block;\n  margin: 0em auto;\n  width: 100%;\n  height: 100%;\n  border-radius: 0.25rem;\n}\n\n/*--------------\n    Content\n---------------*/\n\n.ui.comments .comment > .content {\n  display: block;\n}\n\n/* If there is an avatar move content over */\n\n.ui.comments .comment > .avatar ~ .content {\n  margin-left: 3.5em;\n}\n\n/*--------------\n    Author\n---------------*/\n\n.ui.comments .comment .author {\n  font-size: 1em;\n  color: rgba(0, 0, 0, 0.87);\n  font-weight: bold;\n}\n\n.ui.comments .comment a.author {\n  cursor: pointer;\n}\n\n.ui.comments .comment a.author:hover {\n  color: #1e70bf;\n}\n\n/*--------------\n    Metadata\n---------------*/\n\n.ui.comments .comment .metadata {\n  display: inline-block;\n  margin-left: 0.5em;\n  color: rgba(0, 0, 0, 0.4);\n  font-size: 0.875em;\n}\n\n.ui.comments .comment .metadata > * {\n  display: inline-block;\n  margin: 0em 0.5em 0em 0em;\n}\n\n.ui.comments .comment .metadata > :last-child {\n  margin-right: 0em;\n}\n\n/*--------------------\n    Comment Text\n---------------------*/\n\n.ui.comments .comment .text {\n  margin: 0.25em 0em 0.5em;\n  font-size: 1em;\n  word-wrap: break-word;\n  color: rgba(0, 0, 0, 0.87);\n  line-height: 1.3;\n}\n\n/*--------------------\n    User Actions\n---------------------*/\n\n.ui.comments .comment .actions {\n  font-size: 0.875em;\n}\n\n.ui.comments .comment .actions a {\n  cursor: pointer;\n  display: inline-block;\n  margin: 0em 0.75em 0em 0em;\n  color: rgba(0, 0, 0, 0.4);\n}\n\n.ui.comments .comment .actions a:last-child {\n  margin-right: 0em;\n}\n\n.ui.comments .comment .actions a.active,\n.ui.comments .comment .actions a:hover {\n  color: rgba(0, 0, 0, 0.8);\n}\n\n/*--------------------\n      Reply Form\n---------------------*/\n\n.ui.comments > .reply.form {\n  margin-top: 1em;\n}\n\n.ui.comments .comment .reply.form {\n  width: 100%;\n  margin-top: 1em;\n}\n\n.ui.comments .reply.form textarea {\n  font-size: 1em;\n  height: 12em;\n}\n\n/*******************************\n            State\n*******************************/\n\n.ui.collapsed.comments,\n.ui.comments .collapsed.comments,\n.ui.comments .collapsed.comment {\n  display: none;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------------\n        Threaded\n---------------------*/\n\n.ui.threaded.comments .comment .comments {\n  margin: -1.5em 0 -1em 1.25em;\n  padding: 3em 0em 2em 2.25em;\n  -webkit-box-shadow: -1px 0px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: -1px 0px 0px rgba(34, 36, 38, 0.15);\n}\n\n/*--------------------\n        Minimal\n---------------------*/\n\n.ui.minimal.comments .comment .actions {\n  opacity: 0;\n  position: absolute;\n  top: 0px;\n  right: 0px;\n  left: auto;\n  -webkit-transition: opacity 0.2s ease;\n  transition: opacity 0.2s ease;\n  -webkit-transition-delay: 0.1s;\n  transition-delay: 0.1s;\n}\n\n.ui.minimal.comments .comment > .content:hover > .actions {\n  opacity: 1;\n}\n\n/*-------------------\n        Sizes\n--------------------*/\n\n.ui.mini.comments {\n  font-size: 0.78571429rem;\n}\n\n.ui.tiny.comments {\n  font-size: 0.85714286rem;\n}\n\n.ui.small.comments {\n  font-size: 0.92857143rem;\n}\n\n.ui.comments {\n  font-size: 1rem;\n}\n\n.ui.large.comments {\n  font-size: 1.14285714rem;\n}\n\n.ui.big.comments {\n  font-size: 1.28571429rem;\n}\n\n.ui.huge.comments {\n  font-size: 1.42857143rem;\n}\n\n.ui.massive.comments {\n  font-size: 1.71428571rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n    User Variable Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Feed\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n        Activity Feed\n*******************************/\n\n.ui.feed {\n  margin: 1em 0em;\n}\n\n.ui.feed:first-child {\n  margin-top: 0em;\n}\n\n.ui.feed:last-child {\n  margin-bottom: 0em;\n}\n\n/*******************************\n            Content\n*******************************/\n\n/* Event */\n\n.ui.feed > .event {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n  width: 100%;\n  padding: 0.21428571rem 0em;\n  margin: 0em;\n  background: none;\n  border-top: none;\n}\n\n.ui.feed > .event:first-child {\n  border-top: 0px;\n  padding-top: 0em;\n}\n\n.ui.feed > .event:last-child {\n  padding-bottom: 0em;\n}\n\n/* Event Label */\n\n.ui.feed > .event > .label {\n  display: block;\n  -webkit-box-flex: 0;\n  -ms-flex: 0 0 auto;\n  flex: 0 0 auto;\n  width: 2.5em;\n  height: auto;\n  -ms-flex-item-align: stretch;\n  align-self: stretch;\n  text-align: left;\n}\n\n.ui.feed > .event > .label .icon {\n  opacity: 1;\n  font-size: 1.5em;\n  width: 100%;\n  padding: 0.25em;\n  background: none;\n  border: none;\n  border-radius: none;\n  color: rgba(0, 0, 0, 0.6);\n}\n\n.ui.feed > .event > .label img {\n  width: 100%;\n  height: auto;\n  border-radius: 500rem;\n}\n\n.ui.feed > .event > .label + .content {\n  margin: 0.5em 0em 0.35714286em 1.14285714em;\n}\n\n/*--------------\n    Content\n---------------*/\n\n/* Content */\n\n.ui.feed > .event > .content {\n  display: block;\n  -webkit-box-flex: 1;\n  -ms-flex: 1 1 auto;\n  flex: 1 1 auto;\n  -ms-flex-item-align: stretch;\n  align-self: stretch;\n  text-align: left;\n  word-wrap: break-word;\n}\n\n.ui.feed > .event:last-child > .content {\n  padding-bottom: 0em;\n}\n\n/* Link */\n\n.ui.feed > .event > .content a {\n  cursor: pointer;\n}\n\n/*--------------\n      Date\n---------------*/\n\n.ui.feed > .event > .content .date {\n  margin: -0.5rem 0em 0em;\n  padding: 0em;\n  font-weight: normal;\n  font-size: 1em;\n  font-style: normal;\n  color: rgba(0, 0, 0, 0.4);\n}\n\n/*--------------\n    Summary\n---------------*/\n\n.ui.feed > .event > .content .summary {\n  margin: 0em;\n  font-size: 1em;\n  font-weight: bold;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* Summary Image */\n\n.ui.feed > .event > .content .summary img {\n  display: inline-block;\n  width: auto;\n  height: 10em;\n  margin: -0.25em 0.25em 0em 0em;\n  border-radius: 0.25em;\n  vertical-align: middle;\n}\n\n/*--------------\n      User\n---------------*/\n\n.ui.feed > .event > .content .user {\n  display: inline-block;\n  font-weight: bold;\n  margin-right: 0em;\n  vertical-align: baseline;\n}\n\n.ui.feed > .event > .content .user img {\n  margin: -0.25em 0.25em 0em 0em;\n  width: auto;\n  height: 10em;\n  vertical-align: middle;\n}\n\n/*--------------\n  Inline Date\n---------------*/\n\n/* Date inside Summary */\n\n.ui.feed > .event > .content .summary > .date {\n  display: inline-block;\n  float: none;\n  font-weight: normal;\n  font-size: 0.85714286em;\n  font-style: normal;\n  margin: 0em 0em 0em 0.5em;\n  padding: 0em;\n  color: rgba(0, 0, 0, 0.4);\n}\n\n/*--------------\n  Extra Summary\n---------------*/\n\n.ui.feed > .event > .content .extra {\n  margin: 0.5em 0em 0em;\n  background: none;\n  padding: 0em;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* Images */\n\n.ui.feed > .event > .content .extra.images img {\n  display: inline-block;\n  margin: 0em 0.25em 0em 0em;\n  width: 6em;\n}\n\n/* Text */\n\n.ui.feed > .event > .content .extra.text {\n  padding: 0em;\n  border-left: none;\n  font-size: 1em;\n  max-width: 500px;\n  line-height: 1.4285em;\n}\n\n/*--------------\n      Meta\n---------------*/\n\n.ui.feed > .event > .content .meta {\n  display: inline-block;\n  font-size: 0.85714286em;\n  margin: 0.5em 0em 0em;\n  background: none;\n  border: none;\n  border-radius: 0;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  padding: 0em;\n  color: rgba(0, 0, 0, 0.6);\n}\n\n.ui.feed > .event > .content .meta > * {\n  position: relative;\n  margin-left: 0.75em;\n}\n\n.ui.feed > .event > .content .meta > *:after {\n  content: \"\";\n  color: rgba(0, 0, 0, 0.2);\n  top: 0em;\n  left: -1em;\n  opacity: 1;\n  position: absolute;\n  vertical-align: top;\n}\n\n.ui.feed > .event > .content .meta .like {\n  color: \"\";\n  -webkit-transition: 0.2s color ease;\n  transition: 0.2s color ease;\n}\n\n.ui.feed > .event > .content .meta .like:hover .icon {\n  color: #ff2733;\n}\n\n.ui.feed > .event > .content .meta .active.like .icon {\n  color: #ef404a;\n}\n\n/* First element */\n\n.ui.feed > .event > .content .meta > :first-child {\n  margin-left: 0em;\n}\n\n.ui.feed > .event > .content .meta > :first-child::after {\n  display: none;\n}\n\n/* Action */\n\n.ui.feed > .event > .content .meta a,\n.ui.feed > .event > .content .meta > .icon {\n  cursor: pointer;\n  opacity: 1;\n  color: rgba(0, 0, 0, 0.5);\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n}\n\n.ui.feed > .event > .content .meta a:hover,\n.ui.feed > .event > .content .meta a:hover .icon,\n.ui.feed > .event > .content .meta > .icon:hover {\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*******************************\n            Variations\n*******************************/\n\n.ui.small.feed {\n  font-size: 0.92857143rem;\n}\n\n.ui.feed {\n  font-size: 1rem;\n}\n\n.ui.large.feed {\n  font-size: 1.14285714rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n    User Variable Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Item\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Standard\n*******************************/\n\n/*--------------\n      Item\n---------------*/\n\n.ui.items > .item {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  margin: 1em 0em;\n  width: 100%;\n  min-height: 0px;\n  background: transparent;\n  padding: 0em;\n  border: none;\n  border-radius: 0rem;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  -webkit-transition: -webkit-box-shadow 0.1s ease;\n  transition: -webkit-box-shadow 0.1s ease;\n  transition: box-shadow 0.1s ease;\n  transition: box-shadow 0.1s ease, -webkit-box-shadow 0.1s ease;\n  z-index: \"\";\n}\n\n.ui.items > .item a {\n  cursor: pointer;\n}\n\n/*--------------\n      Items\n---------------*/\n\n.ui.items {\n  margin: 1.5em 0em;\n}\n\n.ui.items:first-child {\n  margin-top: 0em !important;\n}\n\n.ui.items:last-child {\n  margin-bottom: 0em !important;\n}\n\n/*--------------\n      Item\n---------------*/\n\n.ui.items > .item:after {\n  display: block;\n  content: \" \";\n  height: 0px;\n  clear: both;\n  overflow: hidden;\n  visibility: hidden;\n}\n\n.ui.items > .item:first-child {\n  margin-top: 0em;\n}\n\n.ui.items > .item:last-child {\n  margin-bottom: 0em;\n}\n\n/*--------------\n    Images\n---------------*/\n\n.ui.items > .item > .image {\n  position: relative;\n  -webkit-box-flex: 0;\n  -ms-flex: 0 0 auto;\n  flex: 0 0 auto;\n  display: block;\n  float: none;\n  margin: 0em;\n  padding: 0em;\n  max-height: \"\";\n  -ms-flex-item-align: top;\n  align-self: top;\n}\n\n.ui.items > .item > .image > img {\n  display: block;\n  width: 100%;\n  height: auto;\n  border-radius: 0.125rem;\n  border: none;\n}\n\n.ui.items > .item > .image:only-child > img {\n  border-radius: 0rem;\n}\n\n/*--------------\n    Content\n---------------*/\n\n.ui.items > .item > .content {\n  display: block;\n  -webkit-box-flex: 1;\n  -ms-flex: 1 1 auto;\n  flex: 1 1 auto;\n  background: none;\n  margin: 0em;\n  padding: 0em;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  font-size: 1em;\n  border: none;\n  border-radius: 0em;\n}\n\n.ui.items > .item > .content:after {\n  display: block;\n  content: \" \";\n  height: 0px;\n  clear: both;\n  overflow: hidden;\n  visibility: hidden;\n}\n\n.ui.items > .item > .image + .content {\n  min-width: 0;\n  width: auto;\n  display: block;\n  margin-left: 0em;\n  -ms-flex-item-align: top;\n  align-self: top;\n  padding-left: 1.5em;\n}\n\n.ui.items > .item > .content > .header {\n  display: inline-block;\n  margin: -0.21425em 0em 0em;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-weight: bold;\n  color: rgba(0, 0, 0, 0.85);\n}\n\n/* Default Header Size */\n\n.ui.items > .item > .content > .header:not(.ui) {\n  font-size: 1.28571429em;\n}\n\n/*--------------\n    Floated\n---------------*/\n\n.ui.items > .item [class*=\"left floated\"] {\n  float: left;\n}\n\n.ui.items > .item [class*=\"right floated\"] {\n  float: right;\n}\n\n/*--------------\n  Content Image\n---------------*/\n\n.ui.items > .item .content img {\n  -ms-flex-item-align: middle;\n  align-self: middle;\n  width: \"\";\n}\n\n.ui.items > .item img.avatar,\n.ui.items > .item .avatar img {\n  width: \"\";\n  height: \"\";\n  border-radius: 500rem;\n}\n\n/*--------------\n  Description\n---------------*/\n\n.ui.items > .item > .content > .description {\n  margin-top: 0.6em;\n  max-width: auto;\n  font-size: 1em;\n  line-height: 1.4285em;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/*--------------\n    Paragraph\n---------------*/\n\n.ui.items > .item > .content p {\n  margin: 0em 0em 0.5em;\n}\n\n.ui.items > .item > .content p:last-child {\n  margin-bottom: 0em;\n}\n\n/*--------------\n      Meta\n---------------*/\n\n.ui.items > .item .meta {\n  margin: 0.5em 0em 0.5em;\n  font-size: 1em;\n  line-height: 1em;\n  color: rgba(0, 0, 0, 0.6);\n}\n\n.ui.items > .item .meta * {\n  margin-right: 0.3em;\n}\n\n.ui.items > .item .meta :last-child {\n  margin-right: 0em;\n}\n\n.ui.items > .item .meta [class*=\"right floated\"] {\n  margin-right: 0em;\n  margin-left: 0.3em;\n}\n\n/*--------------\n      Links\n---------------*/\n\n/* Generic */\n\n.ui.items > .item > .content a:not(.ui) {\n  color: \"\";\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n}\n\n.ui.items > .item > .content a:not(.ui):hover {\n  color: \"\";\n}\n\n/* Header */\n\n.ui.items > .item > .content > a.header {\n  color: rgba(0, 0, 0, 0.85);\n}\n\n.ui.items > .item > .content > a.header:hover {\n  color: #1e70bf;\n}\n\n/* Meta */\n\n.ui.items > .item .meta > a:not(.ui) {\n  color: rgba(0, 0, 0, 0.4);\n}\n\n.ui.items > .item .meta > a:not(.ui):hover {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/*--------------\n    Labels\n---------------*/\n\n/*-----Star----- */\n\n/* Icon */\n\n.ui.items > .item > .content .favorite.icon {\n  cursor: pointer;\n  opacity: 0.75;\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n}\n\n.ui.items > .item > .content .favorite.icon:hover {\n  opacity: 1;\n  color: #ffb70a;\n}\n\n.ui.items > .item > .content .active.favorite.icon {\n  color: #ffe623;\n}\n\n/*-----Like----- */\n\n/* Icon */\n\n.ui.items > .item > .content .like.icon {\n  cursor: pointer;\n  opacity: 0.75;\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n}\n\n.ui.items > .item > .content .like.icon:hover {\n  opacity: 1;\n  color: #ff2733;\n}\n\n.ui.items > .item > .content .active.like.icon {\n  color: #ff2733;\n}\n\n/*----------------\n  Extra Content\n-----------------*/\n\n.ui.items > .item .extra {\n  display: block;\n  position: relative;\n  background: none;\n  margin: 0.5rem 0em 0em;\n  width: 100%;\n  padding: 0em 0em 0em;\n  top: 0em;\n  left: 0em;\n  color: rgba(0, 0, 0, 0.4);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n  border-top: none;\n}\n\n.ui.items > .item .extra > * {\n  margin: 0.25rem 0.5rem 0.25rem 0em;\n}\n\n.ui.items > .item .extra > [class*=\"right floated\"] {\n  margin: 0.25rem 0em 0.25rem 0.5rem;\n}\n\n.ui.items > .item .extra:after {\n  display: block;\n  content: \" \";\n  height: 0px;\n  clear: both;\n  overflow: hidden;\n  visibility: hidden;\n}\n\n/*******************************\n          Responsive\n*******************************/\n\n/* Default Image Width */\n\n.ui.items > .item > .image:not(.ui) {\n  width: 175px;\n}\n\n/* Tablet Only */\n\n@media only screen and (768px <= width < 992px) {\n  .ui.items > .item {\n    margin: 1em 0em;\n  }\n\n  .ui.items > .item > .image:not(.ui) {\n    width: 150px;\n  }\n\n  .ui.items > .item > .image + .content {\n    display: block;\n    padding: 0em 0em 0em 1em;\n  }\n}\n\n/* Mobile Only */\n\n@media only screen and (width < 768px) {\n  .ui.items:not(.unstackable) > .item {\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    -ms-flex-direction: column;\n    flex-direction: column;\n    margin: 2em 0em;\n  }\n\n  .ui.items:not(.unstackable) > .item > .image {\n    display: block;\n    margin-left: auto;\n    margin-right: auto;\n  }\n\n  .ui.items:not(.unstackable) > .item > .image,\n  .ui.items:not(.unstackable) > .item > .image > img {\n    max-width: 100% !important;\n    width: auto !important;\n    max-height: 250px !important;\n  }\n\n  .ui.items:not(.unstackable) > .item > .image + .content {\n    display: block;\n    padding: 1.5em 0em 0em;\n  }\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*-------------------\n      Aligned\n--------------------*/\n\n.ui.items > .item > .image + [class*=\"top aligned\"].content {\n  -ms-flex-item-align: start;\n  align-self: flex-start;\n}\n\n.ui.items > .item > .image + [class*=\"middle aligned\"].content {\n  -ms-flex-item-align: center;\n  align-self: center;\n}\n\n.ui.items > .item > .image + [class*=\"bottom aligned\"].content {\n  -ms-flex-item-align: end;\n  align-self: flex-end;\n}\n\n/*--------------\n    Relaxed\n---------------*/\n\n.ui.relaxed.items > .item {\n  margin: 1.5em 0em;\n}\n\n.ui[class*=\"very relaxed\"].items > .item {\n  margin: 2em 0em;\n}\n\n/*-------------------\n      Divided\n--------------------*/\n\n.ui.divided.items > .item {\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n  margin: 0em;\n  padding: 1em 0em;\n}\n\n.ui.divided.items > .item:first-child {\n  border-top: none;\n  margin-top: 0em !important;\n  padding-top: 0em !important;\n}\n\n.ui.divided.items > .item:last-child {\n  margin-bottom: 0em !important;\n  padding-bottom: 0em !important;\n}\n\n/* Relaxed Divided */\n\n.ui.relaxed.divided.items > .item {\n  margin: 0em;\n  padding: 1.5em 0em;\n}\n\n.ui[class*=\"very relaxed\"].divided.items > .item {\n  margin: 0em;\n  padding: 2em 0em;\n}\n\n/*-------------------\n        Link\n--------------------*/\n\n.ui.items a.item:hover,\n.ui.link.items > .item:hover {\n  cursor: pointer;\n}\n\n.ui.items a.item:hover .content .header,\n.ui.link.items > .item:hover .content .header {\n  color: #1e70bf;\n}\n\n/*--------------\n      Size\n---------------*/\n\n.ui.items > .item {\n  font-size: 1em;\n}\n\n/*---------------\n  Unstackable\n----------------*/\n\n@media only screen and (width < 768px) {\n\n  .ui.unstackable.items > .item > .image,\n  .ui.unstackable.items > .item > .image > img {\n    width: 125px !important;\n  }\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n    User Variable Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Statistic\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n          Statistic\n*******************************/\n\n/* Standalone */\n\n.ui.statistic {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  margin: 1em 0em;\n  max-width: auto;\n}\n\n.ui.statistic + .ui.statistic {\n  margin: 0em 0em 0em 1.5em;\n}\n\n.ui.statistic:first-child {\n  margin-top: 0em;\n}\n\n.ui.statistic:last-child {\n  margin-bottom: 0em;\n}\n\n/*******************************\n            Group\n*******************************/\n\n/* Grouped */\n\n.ui.statistics {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: start;\n  -ms-flex-align: start;\n  align-items: flex-start;\n  -ms-flex-wrap: wrap;\n  flex-wrap: wrap;\n}\n\n.ui.statistics > .statistic {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  -webkit-box-flex: 0;\n  -ms-flex: 0 1 auto;\n  flex: 0 1 auto;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  margin: 0em 1.5em 1em;\n  max-width: auto;\n}\n\n.ui.statistics {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  margin: 1em -1.5em -1em;\n}\n\n/* Clearing */\n\n.ui.statistics:after {\n  display: block;\n  content: \" \";\n  height: 0px;\n  clear: both;\n  overflow: hidden;\n  visibility: hidden;\n}\n\n.ui.statistics:first-child {\n  margin-top: 0em;\n}\n\n/*******************************\n            Content\n*******************************/\n\n/*--------------\n      Value\n---------------*/\n\n.ui.statistics .statistic > .value,\n.ui.statistic > .value {\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-size: 4rem;\n  font-weight: normal;\n  line-height: 1em;\n  color: #1b1c1d;\n  text-transform: uppercase;\n  text-align: center;\n}\n\n/*--------------\n    Label\n---------------*/\n\n.ui.statistics .statistic > .label,\n.ui.statistic > .label {\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-size: 1em;\n  font-weight: bold;\n  color: rgba(0, 0, 0, 0.87);\n  text-transform: uppercase;\n  text-align: center;\n}\n\n/* Top Label */\n\n.ui.statistics .statistic > .label ~ .value,\n.ui.statistic > .label ~ .value {\n  margin-top: 0rem;\n}\n\n/* Bottom Label */\n\n.ui.statistics .statistic > .value ~ .label,\n.ui.statistic > .value ~ .label {\n  margin-top: 0rem;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*--------------\n  Icon Value\n---------------*/\n\n.ui.statistics .statistic > .value .icon,\n.ui.statistic > .value .icon {\n  opacity: 1;\n  width: auto;\n  margin: 0em;\n}\n\n/*--------------\n  Text Value\n---------------*/\n\n.ui.statistics .statistic > .text.value,\n.ui.statistic > .text.value {\n  line-height: 1em;\n  min-height: 2em;\n  font-weight: bold;\n  text-align: center;\n}\n\n.ui.statistics .statistic > .text.value + .label,\n.ui.statistic > .text.value + .label {\n  text-align: center;\n}\n\n/*--------------\n  Image Value\n---------------*/\n\n.ui.statistics .statistic > .value img,\n.ui.statistic > .value img {\n  max-height: 3rem;\n  vertical-align: baseline;\n}\n\n/*******************************\n            Variations\n*******************************/\n\n/*--------------\n      Count\n---------------*/\n\n.ui.ten.statistics {\n  margin: 0em 0em -1em;\n}\n\n.ui.ten.statistics .statistic {\n  min-width: 10%;\n  margin: 0em 0em 1em;\n}\n\n.ui.nine.statistics {\n  margin: 0em 0em -1em;\n}\n\n.ui.nine.statistics .statistic {\n  min-width: 11.11111111%;\n  margin: 0em 0em 1em;\n}\n\n.ui.eight.statistics {\n  margin: 0em 0em -1em;\n}\n\n.ui.eight.statistics .statistic {\n  min-width: 12.5%;\n  margin: 0em 0em 1em;\n}\n\n.ui.seven.statistics {\n  margin: 0em 0em -1em;\n}\n\n.ui.seven.statistics .statistic {\n  min-width: 14.28571429%;\n  margin: 0em 0em 1em;\n}\n\n.ui.six.statistics {\n  margin: 0em 0em -1em;\n}\n\n.ui.six.statistics .statistic {\n  min-width: 16.66666667%;\n  margin: 0em 0em 1em;\n}\n\n.ui.five.statistics {\n  margin: 0em 0em -1em;\n}\n\n.ui.five.statistics .statistic {\n  min-width: 20%;\n  margin: 0em 0em 1em;\n}\n\n.ui.four.statistics {\n  margin: 0em 0em -1em;\n}\n\n.ui.four.statistics .statistic {\n  min-width: 25%;\n  margin: 0em 0em 1em;\n}\n\n.ui.three.statistics {\n  margin: 0em 0em -1em;\n}\n\n.ui.three.statistics .statistic {\n  min-width: 33.33333333%;\n  margin: 0em 0em 1em;\n}\n\n.ui.two.statistics {\n  margin: 0em 0em -1em;\n}\n\n.ui.two.statistics .statistic {\n  min-width: 50%;\n  margin: 0em 0em 1em;\n}\n\n.ui.one.statistics {\n  margin: 0em 0em -1em;\n}\n\n.ui.one.statistics .statistic {\n  min-width: 100%;\n  margin: 0em 0em 1em;\n}\n\n/*--------------\n  Horizontal\n---------------*/\n\n.ui.horizontal.statistic {\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n}\n\n.ui.horizontal.statistics {\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  margin: 0em;\n  max-width: none;\n}\n\n.ui.horizontal.statistics .statistic {\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n  max-width: none;\n  margin: 1em 0em;\n}\n\n.ui.horizontal.statistic > .text.value,\n.ui.horizontal.statistics > .statistic > .text.value {\n  min-height: 0em !important;\n}\n\n.ui.horizontal.statistics .statistic > .value .icon,\n.ui.horizontal.statistic > .value .icon {\n  width: 1.18em;\n}\n\n.ui.horizontal.statistics .statistic > .value,\n.ui.horizontal.statistic > .value {\n  display: inline-block;\n  vertical-align: middle;\n}\n\n.ui.horizontal.statistics .statistic > .label,\n.ui.horizontal.statistic > .label {\n  display: inline-block;\n  vertical-align: middle;\n  margin: 0em 0em 0em 0.75em;\n}\n\n/*--------------\n    Colors\n---------------*/\n\n.ui.red.statistics .statistic > .value,\n.ui.statistics .red.statistic > .value,\n.ui.red.statistic > .value {\n  color: #db2828;\n}\n\n.ui.orange.statistics .statistic > .value,\n.ui.statistics .orange.statistic > .value,\n.ui.orange.statistic > .value {\n  color: #f2711c;\n}\n\n.ui.yellow.statistics .statistic > .value,\n.ui.statistics .yellow.statistic > .value,\n.ui.yellow.statistic > .value {\n  color: #fbbd08;\n}\n\n.ui.olive.statistics .statistic > .value,\n.ui.statistics .olive.statistic > .value,\n.ui.olive.statistic > .value {\n  color: #b5cc18;\n}\n\n.ui.green.statistics .statistic > .value,\n.ui.statistics .green.statistic > .value,\n.ui.green.statistic > .value {\n  color: #21ba45;\n}\n\n.ui.teal.statistics .statistic > .value,\n.ui.statistics .teal.statistic > .value,\n.ui.teal.statistic > .value {\n  color: #00b5ad;\n}\n\n.ui.blue.statistics .statistic > .value,\n.ui.statistics .blue.statistic > .value,\n.ui.blue.statistic > .value {\n  color: #2185d0;\n}\n\n.ui.violet.statistics .statistic > .value,\n.ui.statistics .violet.statistic > .value,\n.ui.violet.statistic > .value {\n  color: #6435c9;\n}\n\n.ui.purple.statistics .statistic > .value,\n.ui.statistics .purple.statistic > .value,\n.ui.purple.statistic > .value {\n  color: #a333c8;\n}\n\n.ui.pink.statistics .statistic > .value,\n.ui.statistics .pink.statistic > .value,\n.ui.pink.statistic > .value {\n  color: #e03997;\n}\n\n.ui.brown.statistics .statistic > .value,\n.ui.statistics .brown.statistic > .value,\n.ui.brown.statistic > .value {\n  color: #a5673f;\n}\n\n.ui.grey.statistics .statistic > .value,\n.ui.statistics .grey.statistic > .value,\n.ui.grey.statistic > .value {\n  color: #767676;\n}\n\n/*--------------\n    Inverted\n---------------*/\n\n.ui.inverted.statistics .statistic > .value,\n.ui.inverted.statistic .value {\n  color: #ffffff;\n}\n\n.ui.inverted.statistics .statistic > .label,\n.ui.inverted.statistic .label {\n  color: rgba(255, 255, 255, 0.9);\n}\n\n.ui.inverted.red.statistics .statistic > .value,\n.ui.statistics .inverted.red.statistic > .value,\n.ui.inverted.red.statistic > .value {\n  color: #ff695e;\n}\n\n.ui.inverted.orange.statistics .statistic > .value,\n.ui.statistics .inverted.orange.statistic > .value,\n.ui.inverted.orange.statistic > .value {\n  color: #ff851b;\n}\n\n.ui.inverted.yellow.statistics .statistic > .value,\n.ui.statistics .inverted.yellow.statistic > .value,\n.ui.inverted.yellow.statistic > .value {\n  color: #ffe21f;\n}\n\n.ui.inverted.olive.statistics .statistic > .value,\n.ui.statistics .inverted.olive.statistic > .value,\n.ui.inverted.olive.statistic > .value {\n  color: #d9e778;\n}\n\n.ui.inverted.green.statistics .statistic > .value,\n.ui.statistics .inverted.green.statistic > .value,\n.ui.inverted.green.statistic > .value {\n  color: #2ecc40;\n}\n\n.ui.inverted.teal.statistics .statistic > .value,\n.ui.statistics .inverted.teal.statistic > .value,\n.ui.inverted.teal.statistic > .value {\n  color: #6dffff;\n}\n\n.ui.inverted.blue.statistics .statistic > .value,\n.ui.statistics .inverted.blue.statistic > .value,\n.ui.inverted.blue.statistic > .value {\n  color: #54c8ff;\n}\n\n.ui.inverted.violet.statistics .statistic > .value,\n.ui.statistics .inverted.violet.statistic > .value,\n.ui.inverted.violet.statistic > .value {\n  color: #a291fb;\n}\n\n.ui.inverted.purple.statistics .statistic > .value,\n.ui.statistics .inverted.purple.statistic > .value,\n.ui.inverted.purple.statistic > .value {\n  color: #dc73ff;\n}\n\n.ui.inverted.pink.statistics .statistic > .value,\n.ui.statistics .inverted.pink.statistic > .value,\n.ui.inverted.pink.statistic > .value {\n  color: #ff8edf;\n}\n\n.ui.inverted.brown.statistics .statistic > .value,\n.ui.statistics .inverted.brown.statistic > .value,\n.ui.inverted.brown.statistic > .value {\n  color: #d67c1c;\n}\n\n.ui.inverted.grey.statistics .statistic > .value,\n.ui.statistics .inverted.grey.statistic > .value,\n.ui.inverted.grey.statistic > .value {\n  color: #dcddde;\n}\n\n/*--------------\n    Floated\n---------------*/\n\n.ui[class*=\"left floated\"].statistic {\n  float: left;\n  margin: 0em 2em 1em 0em;\n}\n\n.ui[class*=\"right floated\"].statistic {\n  float: right;\n  margin: 0em 0em 1em 2em;\n}\n\n.ui.floated.statistic:last-child {\n  margin-bottom: 0em;\n}\n\n/*--------------\n    Sizes\n---------------*/\n\n/* Mini */\n\n.ui.mini.statistics .statistic > .value,\n.ui.mini.statistic > .value {\n  font-size: 1.5rem !important;\n}\n\n.ui.mini.horizontal.statistics .statistic > .value,\n.ui.mini.horizontal.statistic > .value {\n  font-size: 1.5rem !important;\n}\n\n.ui.mini.statistics .statistic > .text.value,\n.ui.mini.statistic > .text.value {\n  font-size: 1rem !important;\n}\n\n/* Tiny */\n\n.ui.tiny.statistics .statistic > .value,\n.ui.tiny.statistic > .value {\n  font-size: 2rem !important;\n}\n\n.ui.tiny.horizontal.statistics .statistic > .value,\n.ui.tiny.horizontal.statistic > .value {\n  font-size: 2rem !important;\n}\n\n.ui.tiny.statistics .statistic > .text.value,\n.ui.tiny.statistic > .text.value {\n  font-size: 1rem !important;\n}\n\n/* Small */\n\n.ui.small.statistics .statistic > .value,\n.ui.small.statistic > .value {\n  font-size: 3rem !important;\n}\n\n.ui.small.horizontal.statistics .statistic > .value,\n.ui.small.horizontal.statistic > .value {\n  font-size: 2rem !important;\n}\n\n.ui.small.statistics .statistic > .text.value,\n.ui.small.statistic > .text.value {\n  font-size: 1rem !important;\n}\n\n/* Medium */\n\n.ui.statistics .statistic > .value,\n.ui.statistic > .value {\n  font-size: 4rem !important;\n}\n\n.ui.horizontal.statistics .statistic > .value,\n.ui.horizontal.statistic > .value {\n  font-size: 3rem !important;\n}\n\n.ui.statistics .statistic > .text.value,\n.ui.statistic > .text.value {\n  font-size: 2rem !important;\n}\n\n/* Large */\n\n.ui.large.statistics .statistic > .value,\n.ui.large.statistic > .value {\n  font-size: 5rem !important;\n}\n\n.ui.large.horizontal.statistics .statistic > .value,\n.ui.large.horizontal.statistic > .value {\n  font-size: 4rem !important;\n}\n\n.ui.large.statistics .statistic > .text.value,\n.ui.large.statistic > .text.value {\n  font-size: 2.5rem !important;\n}\n\n/* Huge */\n\n.ui.huge.statistics .statistic > .value,\n.ui.huge.statistic > .value {\n  font-size: 6rem !important;\n}\n\n.ui.huge.horizontal.statistics .statistic > .value,\n.ui.huge.horizontal.statistic > .value {\n  font-size: 5rem !important;\n}\n\n.ui.huge.statistics .statistic > .text.value,\n.ui.huge.statistic > .text.value {\n  font-size: 2.5rem !important;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n    User Variable Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Accordion\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Accordion\n*******************************/\n\n.ui.accordion,\n.ui.accordion .accordion {\n  max-width: 100%;\n}\n\n.ui.accordion .accordion {\n  margin: 1em 0em 0em;\n  padding: 0em;\n}\n\n/* Title */\n\n.ui.accordion .title,\n.ui.accordion .accordion .title {\n  cursor: pointer;\n}\n\n/* Default Styling */\n\n.ui.accordion .title:not(.ui) {\n  padding: 0.5em 0em;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-size: 1em;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* Content */\n\n.ui.accordion .title ~ .content,\n.ui.accordion .accordion .title ~ .content {\n  display: none;\n}\n\n/* Default Styling */\n\n.ui.accordion:not(.styled) .title ~ .content:not(.ui),\n.ui.accordion:not(.styled) .accordion .title ~ .content:not(.ui) {\n  margin: \"\";\n  padding: 0.5em 0em 1em;\n}\n\n.ui.accordion:not(.styled) .title ~ .content:not(.ui):last-child {\n  padding-bottom: 0em;\n}\n\n/* Arrow */\n\n.ui.accordion .title .dropdown.icon,\n.ui.accordion .accordion .title .dropdown.icon {\n  display: inline-block;\n  float: none;\n  opacity: 1;\n  width: 1.25em;\n  height: 1em;\n  margin: 0em 0.25rem 0em 0rem;\n  padding: 0em;\n  font-size: 1em;\n  -webkit-transition: opacity 0.1s ease, -webkit-transform 0.1s ease;\n  transition: opacity 0.1s ease, -webkit-transform 0.1s ease;\n  transition: transform 0.1s ease, opacity 0.1s ease;\n  transition: transform 0.1s ease, opacity 0.1s ease,\n    -webkit-transform 0.1s ease;\n  vertical-align: baseline;\n  -webkit-transform: none;\n  transform: none;\n}\n\n/*--------------\n    Coupling\n---------------*/\n\n/* Menu */\n\n.ui.accordion.menu .item .title {\n  display: block;\n  padding: 0em;\n}\n\n.ui.accordion.menu .item .title > .dropdown.icon {\n  float: right;\n  margin: 0.21425em 0em 0em 1em;\n  -webkit-transform: rotate(180deg);\n  transform: rotate(180deg);\n}\n\n/* Header */\n\n.ui.accordion .ui.header .dropdown.icon {\n  font-size: 1em;\n  margin: 0em 0.25rem 0em 0rem;\n}\n\n/*******************************\n            States\n*******************************/\n\n.ui.accordion .active.title .dropdown.icon,\n.ui.accordion .accordion .active.title .dropdown.icon {\n  -webkit-transform: rotate(90deg);\n  transform: rotate(90deg);\n}\n\n.ui.accordion.menu .item .active.title > .dropdown.icon {\n  -webkit-transform: rotate(90deg);\n  transform: rotate(90deg);\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*--------------\n    Styled\n---------------*/\n\n.ui.styled.accordion {\n  width: 600px;\n}\n\n.ui.styled.accordion,\n.ui.styled.accordion .accordion {\n  border-radius: 0.28571429rem;\n  background: #ffffff;\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15),\n    0px 0px 0px 1px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15),\n    0px 0px 0px 1px rgba(34, 36, 38, 0.15);\n}\n\n.ui.styled.accordion .title,\n.ui.styled.accordion .accordion .title {\n  margin: 0em;\n  padding: 0.75em 1em;\n  color: rgba(0, 0, 0, 0.4);\n  font-weight: bold;\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n  -webkit-transition: background 0.1s ease, color 0.1s ease;\n  transition: background 0.1s ease, color 0.1s ease;\n}\n\n.ui.styled.accordion > .title:first-child,\n.ui.styled.accordion .accordion .title:first-child {\n  border-top: none;\n}\n\n/* Content */\n\n.ui.styled.accordion .content,\n.ui.styled.accordion .accordion .content {\n  margin: 0em;\n  padding: 0.5em 1em 1.5em;\n}\n\n.ui.styled.accordion .accordion .content {\n  padding: 0em;\n  padding: 0.5em 1em 1.5em;\n}\n\n/* Hover */\n\n.ui.styled.accordion .title:hover,\n.ui.styled.accordion .active.title,\n.ui.styled.accordion .accordion .title:hover,\n.ui.styled.accordion .accordion .active.title {\n  background: transparent;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.styled.accordion .accordion .title:hover,\n.ui.styled.accordion .accordion .active.title {\n  background: transparent;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* Active */\n\n.ui.styled.accordion .active.title {\n  background: transparent;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n.ui.styled.accordion .accordion .active.title {\n  background: transparent;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------\n    Active\n---------------*/\n\n.ui.accordion .active.content,\n.ui.accordion .accordion .active.content {\n  display: block;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Fluid\n---------------*/\n\n.ui.fluid.accordion,\n.ui.fluid.accordion .accordion {\n  width: 100%;\n}\n\n/*--------------\n    Inverted\n---------------*/\n\n.ui.inverted.accordion .title:not(.ui) {\n  color: rgba(255, 255, 255, 0.9);\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n@font-face {\n  font-family: \"Accordion\";\n  src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMggjB5AAAAC8AAAAYGNtYXAPfOIKAAABHAAAAExnYXNwAAAAEAAAAWgAAAAIZ2x5Zryj6HgAAAFwAAAAyGhlYWT/0IhHAAACOAAAADZoaGVhApkB5wAAAnAAAAAkaG10eAJuABIAAAKUAAAAGGxvY2EAjABWAAACrAAAAA5tYXhwAAgAFgAAArwAAAAgbmFtZfC1n04AAALcAAABPHBvc3QAAwAAAAAEGAAAACAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDZ//3//wAB/+MPKwADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQASAEkAtwFuABMAADc0PwE2FzYXFh0BFAcGJwYvASY1EgaABQgHBQYGBQcIBYAG2wcGfwcBAQcECf8IBAcBAQd/BgYAAAAAAQAAAEkApQFuABMAADcRNDc2MzIfARYVFA8BBiMiJyY1AAUGBwgFgAYGgAUIBwYFWwEACAUGBoAFCAcFgAYGBQcAAAABAAAAAQAAqWYls18PPPUACwIAAAAAAM/9o+4AAAAAz/2j7gAAAAAAtwFuAAAACAACAAAAAAAAAAEAAAHg/+AAAAIAAAAAAAC3AAEAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAQAAAAC3ABIAtwAAAAAAAAAKABQAHgBCAGQAAAABAAAABgAUAAEAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEADAAAAAEAAAAAAAIADgBAAAEAAAAAAAMADAAiAAEAAAAAAAQADABOAAEAAAAAAAUAFgAMAAEAAAAAAAYABgAuAAEAAAAAAAoANABaAAMAAQQJAAEADAAAAAMAAQQJAAIADgBAAAMAAQQJAAMADAAiAAMAAQQJAAQADABOAAMAAQQJAAUAFgAMAAMAAQQJAAYADAA0AAMAAQQJAAoANABaAHIAYQB0AGkAbgBnAFYAZQByAHMAaQBvAG4AIAAxAC4AMAByAGEAdABpAG4AZ3JhdGluZwByAGEAdABpAG4AZwBSAGUAZwB1AGwAYQByAHIAYQB0AGkAbgBnAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format(\"truetype\"),\n    url(data:application/font-woff;charset=utf-8;base64,d09GRk9UVE8AAASwAAoAAAAABGgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAA9AAAAS0AAAEtFpovuE9TLzIAAAIkAAAAYAAAAGAIIweQY21hcAAAAoQAAABMAAAATA984gpnYXNwAAAC0AAAAAgAAAAIAAAAEGhlYWQAAALYAAAANgAAADb/0IhHaGhlYQAAAxAAAAAkAAAAJAKZAedobXR4AAADNAAAABgAAAAYAm4AEm1heHAAAANMAAAABgAAAAYABlAAbmFtZQAAA1QAAAE8AAABPPC1n05wb3N0AAAEkAAAACAAAAAgAAMAAAEABAQAAQEBB3JhdGluZwABAgABADr4HAL4GwP4GAQeCgAZU/+Lix4KABlT/4uLDAeLa/iU+HQFHQAAAHkPHQAAAH4RHQAAAAkdAAABJBIABwEBBw0PERQZHnJhdGluZ3JhdGluZ3UwdTF1MjB1RjBEOXVGMERBAAACAYkABAAGAQEEBwoNVp38lA78lA78lA77lA773Z33bxWLkI2Qj44I9xT3FAWOj5CNkIuQi4+JjoePiI2Gi4YIi/uUBYuGiYeHiIiHh4mGi4aLho2Ijwj7FPcUBYeOiY+LkAgO+92L5hWL95QFi5CNkI6Oj4+PjZCLkIuQiY6HCPcU+xQFj4iNhouGi4aJh4eICPsU+xQFiIeGiYaLhouHjYePiI6Jj4uQCA74lBT4lBWLDAoAAAAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDZ//3//wAB/+MPKwADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAEAADfYOJZfDzz1AAsCAAAAAADP/aPuAAAAAM/9o+4AAAAAALcBbgAAAAgAAgAAAAAAAAABAAAB4P/gAAACAAAAAAAAtwABAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAEAAAAAtwASALcAAAAAUAAABgAAAAAADgCuAAEAAAAAAAEADAAAAAEAAAAAAAIADgBAAAEAAAAAAAMADAAiAAEAAAAAAAQADABOAAEAAAAAAAUAFgAMAAEAAAAAAAYABgAuAAEAAAAAAAoANABaAAMAAQQJAAEADAAAAAMAAQQJAAIADgBAAAMAAQQJAAMADAAiAAMAAQQJAAQADABOAAMAAQQJAAUAFgAMAAMAAQQJAAYADAA0AAMAAQQJAAoANABaAHIAYQB0AGkAbgBnAFYAZQByAHMAaQBvAG4AIAAxAC4AMAByAGEAdABpAG4AZ3JhdGluZwByAGEAdABpAG4AZwBSAGUAZwB1AGwAYQByAHIAYQB0AGkAbgBnAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format(\"woff\");\n  font-weight: normal;\n  font-style: normal;\n}\n\n/* Dropdown Icon */\n\n.ui.accordion .title .dropdown.icon,\n.ui.accordion .accordion .title .dropdown.icon {\n  font-family: Accordion;\n  line-height: 1;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  font-weight: normal;\n  font-style: normal;\n  text-align: center;\n}\n\n.ui.accordion .title .dropdown.icon:before,\n.ui.accordion .accordion .title .dropdown.icon:before {\n  content: \"\\f0da\";\n}\n\n/*******************************\n        User Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Checkbox\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n          Checkbox\n*******************************/\n\n/*--------------\n    Content\n---------------*/\n\n.ui.checkbox {\n  position: relative;\n  display: inline-block;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  outline: none;\n  vertical-align: baseline;\n  font-style: normal;\n  min-height: 17px;\n  font-size: 1rem;\n  line-height: 17px;\n  min-width: 17px;\n}\n\n/* HTML Checkbox */\n\n.ui.checkbox input[type=\"checkbox\"],\n.ui.checkbox input[type=\"radio\"] {\n  cursor: pointer;\n  position: absolute;\n  top: 0px;\n  left: 0px;\n  opacity: 0 !important;\n  outline: none;\n  z-index: 3;\n  width: 17px;\n  height: 17px;\n}\n\n/*--------------\n      Box\n---------------*/\n\n.ui.checkbox .box,\n.ui.checkbox label {\n  cursor: auto;\n  position: relative;\n  display: block;\n  padding-left: 1.85714em;\n  outline: none;\n  font-size: 1em;\n}\n\n.ui.checkbox .box:before,\n.ui.checkbox label:before {\n  position: absolute;\n  top: 0px;\n  left: 0px;\n  width: 17px;\n  height: 17px;\n  content: \"\";\n  background: #ffffff;\n  border-radius: 0.21428571rem;\n  -webkit-transition: border 0.1s ease, opacity 0.1s ease,\n    -webkit-transform 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: border 0.1s ease, opacity 0.1s ease, -webkit-transform 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n  transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease,\n    box-shadow 0.1s ease;\n  transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease,\n    box-shadow 0.1s ease, -webkit-transform 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n  border: 1px solid #d4d4d5;\n}\n\n/*--------------\n    Checkmark\n---------------*/\n\n.ui.checkbox .box:after,\n.ui.checkbox label:after {\n  position: absolute;\n  font-size: 14px;\n  top: 0px;\n  left: 0px;\n  width: 17px;\n  height: 17px;\n  text-align: center;\n  opacity: 0;\n  color: rgba(0, 0, 0, 0.87);\n  -webkit-transition: border 0.1s ease, opacity 0.1s ease,\n    -webkit-transform 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: border 0.1s ease, opacity 0.1s ease, -webkit-transform 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n  transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease,\n    box-shadow 0.1s ease;\n  transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease,\n    box-shadow 0.1s ease, -webkit-transform 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n}\n\n/*--------------\n      Label\n---------------*/\n\n/* Inside */\n\n.ui.checkbox label,\n.ui.checkbox + label {\n  color: rgba(0, 0, 0, 0.87);\n  -webkit-transition: color 0.1s ease;\n  transition: color 0.1s ease;\n}\n\n/* Outside */\n\n.ui.checkbox + label {\n  vertical-align: middle;\n}\n\n/*******************************\n          States\n*******************************/\n\n/*--------------\n      Hover\n---------------*/\n\n.ui.checkbox .box:hover::before,\n.ui.checkbox label:hover::before {\n  background: #ffffff;\n  border-color: rgba(34, 36, 38, 0.35);\n}\n\n.ui.checkbox label:hover,\n.ui.checkbox + label:hover {\n  color: rgba(0, 0, 0, 0.8);\n}\n\n/*--------------\n      Down\n---------------*/\n\n.ui.checkbox .box:active::before,\n.ui.checkbox label:active::before {\n  background: #f9fafb;\n  border-color: rgba(34, 36, 38, 0.35);\n}\n\n.ui.checkbox .box:active::after,\n.ui.checkbox label:active::after {\n  color: rgba(0, 0, 0, 0.95);\n}\n\n.ui.checkbox input:active ~ label {\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------\n    Focus\n---------------*/\n\n.ui.checkbox input:focus ~ .box:before,\n.ui.checkbox input:focus ~ label:before {\n  background: #ffffff;\n  border-color: #96c8da;\n}\n\n.ui.checkbox input:focus ~ .box:after,\n.ui.checkbox input:focus ~ label:after {\n  color: rgba(0, 0, 0, 0.95);\n}\n\n.ui.checkbox input:focus ~ label {\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------\n    Active\n---------------*/\n\n.ui.checkbox input:checked ~ .box:before,\n.ui.checkbox input:checked ~ label:before {\n  background: #ffffff;\n  border-color: rgba(34, 36, 38, 0.35);\n}\n\n.ui.checkbox input:checked ~ .box:after,\n.ui.checkbox input:checked ~ label:after {\n  opacity: 1;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------\n  Indeterminate\n---------------*/\n\n.ui.checkbox input:not([type=\"radio\"]):indeterminate ~ .box:before,\n.ui.checkbox input:not([type=\"radio\"]):indeterminate ~ label:before {\n  background: #ffffff;\n  border-color: rgba(34, 36, 38, 0.35);\n}\n\n.ui.checkbox input:not([type=\"radio\"]):indeterminate ~ .box:after,\n.ui.checkbox input:not([type=\"radio\"]):indeterminate ~ label:after {\n  opacity: 1;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------\n  Active Focus\n---------------*/\n\n.ui.checkbox input:not([type=\"radio\"]):indeterminate:focus ~ .box:before,\n.ui.checkbox input:not([type=\"radio\"]):indeterminate:focus ~ label:before,\n.ui.checkbox input:checked:focus ~ .box:before,\n.ui.checkbox input:checked:focus ~ label:before {\n  background: #ffffff;\n  border-color: #96c8da;\n}\n\n.ui.checkbox input:not([type=\"radio\"]):indeterminate:focus ~ .box:after,\n.ui.checkbox input:not([type=\"radio\"]):indeterminate:focus ~ label:after,\n.ui.checkbox input:checked:focus ~ .box:after,\n.ui.checkbox input:checked:focus ~ label:after {\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------\n    Read-Only\n---------------*/\n\n.ui.read-only.checkbox,\n.ui.read-only.checkbox label {\n  cursor: default;\n}\n\n/*--------------\n    Disabled\n---------------*/\n\n.ui.disabled.checkbox .box:after,\n.ui.disabled.checkbox label,\n.ui.checkbox input[disabled] ~ .box:after,\n.ui.checkbox input[disabled] ~ label {\n  cursor: default !important;\n  opacity: 0.5;\n  color: #000000;\n}\n\n/*--------------\n    Hidden\n---------------*/\n\n/* Initialized checkbox moves input below element\nto prevent manually triggering */\n\n.ui.checkbox input.hidden {\n  z-index: -1;\n}\n\n/* Selectable Label */\n\n.ui.checkbox input.hidden + label {\n  cursor: pointer;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*--------------\n    Radio\n---------------*/\n\n.ui.radio.checkbox {\n  min-height: 15px;\n}\n\n.ui.radio.checkbox .box,\n.ui.radio.checkbox label {\n  padding-left: 1.85714em;\n}\n\n/* Box */\n\n.ui.radio.checkbox .box:before,\n.ui.radio.checkbox label:before {\n  content: \"\";\n  -webkit-transform: none;\n  transform: none;\n  width: 15px;\n  height: 15px;\n  border-radius: 500rem;\n  top: 1px;\n  left: 0px;\n}\n\n/* Bullet */\n\n.ui.radio.checkbox .box:after,\n.ui.radio.checkbox label:after {\n  border: none;\n  content: \"\" !important;\n  width: 15px;\n  height: 15px;\n  line-height: 15px;\n}\n\n/* Radio Checkbox */\n\n.ui.radio.checkbox .box:after,\n.ui.radio.checkbox label:after {\n  top: 1px;\n  left: 0px;\n  width: 15px;\n  height: 15px;\n  border-radius: 500rem;\n  -webkit-transform: scale(0.46666667);\n  transform: scale(0.46666667);\n  background-color: rgba(0, 0, 0, 0.87);\n}\n\n/* Focus */\n\n.ui.radio.checkbox input:focus ~ .box:before,\n.ui.radio.checkbox input:focus ~ label:before {\n  background-color: #ffffff;\n}\n\n.ui.radio.checkbox input:focus ~ .box:after,\n.ui.radio.checkbox input:focus ~ label:after {\n  background-color: rgba(0, 0, 0, 0.95);\n}\n\n/* Indeterminate */\n\n.ui.radio.checkbox input:indeterminate ~ .box:after,\n.ui.radio.checkbox input:indeterminate ~ label:after {\n  opacity: 0;\n}\n\n/* Active */\n\n.ui.radio.checkbox input:checked ~ .box:before,\n.ui.radio.checkbox input:checked ~ label:before {\n  background-color: #ffffff;\n}\n\n.ui.radio.checkbox input:checked ~ .box:after,\n.ui.radio.checkbox input:checked ~ label:after {\n  background-color: rgba(0, 0, 0, 0.95);\n}\n\n/* Active Focus */\n\n.ui.radio.checkbox input:focus:checked ~ .box:before,\n.ui.radio.checkbox input:focus:checked ~ label:before {\n  background-color: #ffffff;\n}\n\n.ui.radio.checkbox input:focus:checked ~ .box:after,\n.ui.radio.checkbox input:focus:checked ~ label:after {\n  background-color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------\n    Slider\n---------------*/\n\n.ui.slider.checkbox {\n  min-height: 1.25rem;\n}\n\n/* Input */\n\n.ui.slider.checkbox input {\n  width: 3.5rem;\n  height: 1.25rem;\n}\n\n/* Label */\n\n.ui.slider.checkbox .box,\n.ui.slider.checkbox label {\n  padding-left: 4.5rem;\n  line-height: 1rem;\n  color: rgba(0, 0, 0, 0.4);\n}\n\n/* Line */\n\n.ui.slider.checkbox .box:before,\n.ui.slider.checkbox label:before {\n  display: block;\n  position: absolute;\n  content: \"\";\n  border: none !important;\n  left: 0em;\n  z-index: 1;\n  top: 0.4rem;\n  background-color: rgba(0, 0, 0, 0.05);\n  width: 3.5rem;\n  height: 0.21428571rem;\n  -webkit-transform: none;\n  transform: none;\n  border-radius: 500rem;\n  -webkit-transition: background 0.3s ease;\n  transition: background 0.3s ease;\n}\n\n/* Handle */\n\n.ui.slider.checkbox .box:after,\n.ui.slider.checkbox label:after {\n  background: #ffffff -webkit-gradient(linear, left top, left bottom, from(transparent), to(rgba(0, 0, 0, 0.05)));\n  background: #ffffff -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  background: #ffffff linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  position: absolute;\n  content: \"\" !important;\n  opacity: 1;\n  z-index: 2;\n  border: none;\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15),\n    0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15),\n    0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n  width: 1.5rem;\n  height: 1.5rem;\n  top: -0.25rem;\n  left: 0em;\n  -webkit-transform: none;\n  transform: none;\n  border-radius: 500rem;\n  -webkit-transition: left 0.3s ease;\n  transition: left 0.3s ease;\n}\n\n/* Focus */\n\n.ui.slider.checkbox input:focus ~ .box:before,\n.ui.slider.checkbox input:focus ~ label:before {\n  background-color: rgba(0, 0, 0, 0.15);\n  border: none;\n}\n\n/* Hover */\n\n.ui.slider.checkbox .box:hover,\n.ui.slider.checkbox label:hover {\n  color: rgba(0, 0, 0, 0.8);\n}\n\n.ui.slider.checkbox .box:hover::before,\n.ui.slider.checkbox label:hover::before {\n  background: rgba(0, 0, 0, 0.15);\n}\n\n/* Active */\n\n.ui.slider.checkbox input:checked ~ .box,\n.ui.slider.checkbox input:checked ~ label {\n  color: rgba(0, 0, 0, 0.95) !important;\n}\n\n.ui.slider.checkbox input:checked ~ .box:before,\n.ui.slider.checkbox input:checked ~ label:before {\n  background-color: #545454 !important;\n}\n\n.ui.slider.checkbox input:checked ~ .box:after,\n.ui.slider.checkbox input:checked ~ label:after {\n  left: 2rem;\n}\n\n/* Active Focus */\n\n.ui.slider.checkbox input:focus:checked ~ .box,\n.ui.slider.checkbox input:focus:checked ~ label {\n  color: rgba(0, 0, 0, 0.95) !important;\n}\n\n.ui.slider.checkbox input:focus:checked ~ .box:before,\n.ui.slider.checkbox input:focus:checked ~ label:before {\n  background-color: #000000 !important;\n}\n\n/*--------------\n    Toggle\n---------------*/\n\n.ui.toggle.checkbox {\n  min-height: 1.5rem;\n}\n\n/* Input */\n\n.ui.toggle.checkbox input {\n  width: 3.5rem;\n  height: 1.5rem;\n}\n\n/* Label */\n\n.ui.toggle.checkbox .box,\n.ui.toggle.checkbox label {\n  min-height: 1.5rem;\n  padding-left: 4.5rem;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.toggle.checkbox label {\n  padding-top: 0.15em;\n}\n\n/* Switch */\n\n.ui.toggle.checkbox .box:before,\n.ui.toggle.checkbox label:before {\n  display: block;\n  position: absolute;\n  content: \"\";\n  z-index: 1;\n  -webkit-transform: none;\n  transform: none;\n  border: none;\n  top: 0rem;\n  background: rgba(0, 0, 0, 0.05);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  width: 3.5rem;\n  height: 1.5rem;\n  border-radius: 500rem;\n}\n\n/* Handle */\n\n.ui.toggle.checkbox .box:after,\n.ui.toggle.checkbox label:after {\n  background: #ffffff -webkit-gradient(linear, left top, left bottom, from(transparent), to(rgba(0, 0, 0, 0.05)));\n  background: #ffffff -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  background: #ffffff linear-gradient(transparent, rgba(0, 0, 0, 0.05));\n  position: absolute;\n  content: \"\" !important;\n  opacity: 1;\n  z-index: 2;\n  border: none;\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15),\n    0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15),\n    0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n  width: 1.5rem;\n  height: 1.5rem;\n  top: 0rem;\n  left: 0em;\n  border-radius: 500rem;\n  -webkit-transition: background 0.3s ease, left 0.3s ease;\n  transition: background 0.3s ease, left 0.3s ease;\n}\n\n.ui.toggle.checkbox input ~ .box:after,\n.ui.toggle.checkbox input ~ label:after {\n  left: -0.05rem;\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15),\n    0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15),\n    0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n}\n\n/* Focus */\n\n.ui.toggle.checkbox input:focus ~ .box:before,\n.ui.toggle.checkbox input:focus ~ label:before {\n  background-color: rgba(0, 0, 0, 0.15);\n  border: none;\n}\n\n/* Hover */\n\n.ui.toggle.checkbox .box:hover::before,\n.ui.toggle.checkbox label:hover::before {\n  background-color: rgba(0, 0, 0, 0.15);\n  border: none;\n}\n\n/* Active */\n\n.ui.toggle.checkbox input:checked ~ .box,\n.ui.toggle.checkbox input:checked ~ label {\n  color: rgba(0, 0, 0, 0.95) !important;\n}\n\n.ui.toggle.checkbox input:checked ~ .box:before,\n.ui.toggle.checkbox input:checked ~ label:before {\n  background-color: #2185d0 !important;\n}\n\n.ui.toggle.checkbox input:checked ~ .box:after,\n.ui.toggle.checkbox input:checked ~ label:after {\n  left: 2.15rem;\n  -webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15),\n    0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15),\n    0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n}\n\n/* Active Focus */\n\n.ui.toggle.checkbox input:focus:checked ~ .box,\n.ui.toggle.checkbox input:focus:checked ~ label {\n  color: rgba(0, 0, 0, 0.95) !important;\n}\n\n.ui.toggle.checkbox input:focus:checked ~ .box:before,\n.ui.toggle.checkbox input:focus:checked ~ label:before {\n  background-color: #0d71bb !important;\n}\n\n/*******************************\n            Variations\n*******************************/\n\n/*--------------\n    Fitted\n---------------*/\n\n.ui.fitted.checkbox .box,\n.ui.fitted.checkbox label {\n  padding-left: 0em !important;\n}\n\n.ui.fitted.toggle.checkbox,\n.ui.fitted.toggle.checkbox {\n  width: 3.5rem;\n}\n\n.ui.fitted.slider.checkbox,\n.ui.fitted.slider.checkbox {\n  width: 3.5rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n@font-face {\n  font-family: \"Checkbox\";\n  src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBD8AAAC8AAAAYGNtYXAYVtCJAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5Zn4huwUAAAF4AAABYGhlYWQGPe1ZAAAC2AAAADZoaGVhB30DyAAAAxAAAAAkaG10eBBKAEUAAAM0AAAAHGxvY2EAmgESAAADUAAAABBtYXhwAAkALwAAA2AAAAAgbmFtZSC8IugAAAOAAAABknBvc3QAAwAAAAAFFAAAACAAAwMTAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADoAgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6AL//f//AAAAAAAg6AD//f//AAH/4xgEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAEUAUQO7AvgAGgAAARQHAQYjIicBJjU0PwE2MzIfAQE2MzIfARYVA7sQ/hQQFhcQ/uMQEE4QFxcQqAF2EBcXEE4QAnMWEP4UEBABHRAXFhBOEBCoAXcQEE4QFwAAAAABAAABbgMlAkkAFAAAARUUBwYjISInJj0BNDc2MyEyFxYVAyUQEBf9SRcQEBAQFwK3FxAQAhJtFxAQEBAXbRcQEBAQFwAAAAABAAAASQMlA24ALAAAARUUBwYrARUUBwYrASInJj0BIyInJj0BNDc2OwE1NDc2OwEyFxYdATMyFxYVAyUQEBfuEBAXbhYQEO4XEBAQEBfuEBAWbhcQEO4XEBACEm0XEBDuFxAQEBAX7hAQF20XEBDuFxAQEBAX7hAQFwAAAQAAAAIAAHRSzT9fDzz1AAsEAAAAAADRsdR3AAAAANGx1HcAAAAAA7sDbgAAAAgAAgAAAAAAAAABAAADwP/AAAAEAAAAAAADuwABAAAAAAAAAAAAAAAAAAAABwQAAAAAAAAAAAAAAAIAAAAEAABFAyUAAAMlAAAAAAAAAAoAFAAeAE4AcgCwAAEAAAAHAC0AAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAIAAAAAQAAAAAAAgAHAGkAAQAAAAAAAwAIADkAAQAAAAAABAAIAH4AAQAAAAAABQALABgAAQAAAAAABgAIAFEAAQAAAAAACgAaAJYAAwABBAkAAQAQAAgAAwABBAkAAgAOAHAAAwABBAkAAwAQAEEAAwABBAkABAAQAIYAAwABBAkABQAWACMAAwABBAkABgAQAFkAAwABBAkACgA0ALBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhWZXJzaW9uIDIuMABWAGUAcgBzAGkAbwBuACAAMgAuADBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhDaGVja2JveABDAGgAZQBjAGsAYgBvAHhSZWd1bGFyAFIAZQBnAHUAbABhAHJDaGVja2JveABDAGgAZQBjAGsAYgBvAHhGb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format(\"truetype\");\n}\n\n/* Checkmark */\n\n.ui.checkbox label:after,\n.ui.checkbox .box:after {\n  font-family: \"Checkbox\";\n}\n\n/* Checked */\n\n.ui.checkbox input:checked ~ .box:after,\n.ui.checkbox input:checked ~ label:after {\n  content: \"\\e800\";\n}\n\n/* Indeterminate */\n\n.ui.checkbox input:indeterminate ~ .box:after,\n.ui.checkbox input:indeterminate ~ label:after {\n  font-size: 12px;\n  content: \"\\e801\";\n}\n\n/*  UTF Reference\n.check:before { content: '\\e800'; }\n.dash:before  { content: '\\e801'; }\n.plus:before { content: '\\e802'; }\n*/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Dimmer\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Dimmer\n*******************************/\n\n.dimmable:not(body) {\n  position: relative;\n}\n\n.ui.dimmer {\n  display: none;\n  position: absolute;\n  top: 0em !important;\n  left: 0em !important;\n  width: 100%;\n  height: 100%;\n  text-align: center;\n  vertical-align: middle;\n  padding: 1em;\n  background-color: rgba(0, 0, 0, 0.85);\n  opacity: 0;\n  line-height: 1;\n  -webkit-animation-fill-mode: both;\n  animation-fill-mode: both;\n  -webkit-animation-duration: 0.5s;\n  animation-duration: 0.5s;\n  -webkit-transition: background-color 0.5s linear;\n  transition: background-color 0.5s linear;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n  -webkit-box-pack: center;\n  -ms-flex-pack: center;\n  justify-content: center;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  will-change: opacity;\n  z-index: 1000;\n}\n\n/* Dimmer Content */\n\n.ui.dimmer > .content {\n  -webkit-user-select: text;\n  -moz-user-select: text;\n  -ms-user-select: text;\n  user-select: text;\n  color: #ffffff;\n}\n\n/* Loose Coupling */\n\n.ui.segment > .ui.dimmer {\n  border-radius: inherit !important;\n}\n\n/* Scrollbars */\n\n.ui.dimmer:not(.inverted)::-webkit-scrollbar-track {\n  background: rgba(255, 255, 255, 0.1);\n}\n\n.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb {\n  background: rgba(255, 255, 255, 0.25);\n}\n\n.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb:window-inactive {\n  background: rgba(255, 255, 255, 0.15);\n}\n\n.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb:hover {\n  background: rgba(255, 255, 255, 0.35);\n}\n\n/*******************************\n            States\n*******************************/\n\n/* Animating */\n\n.animating.dimmable:not(body),\n.dimmed.dimmable:not(body) {\n  overflow: hidden;\n}\n\n/* Animating / Active / Visible */\n\n.dimmed.dimmable > .ui.animating.dimmer,\n.dimmed.dimmable > .ui.visible.dimmer,\n.ui.active.dimmer {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  opacity: 1;\n}\n\n/* Disabled */\n\n.ui.disabled.dimmer {\n  width: 0 !important;\n  height: 0 !important;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Legacy\n---------------*/\n\n/* Animating / Active / Visible */\n\n.dimmed.dimmable > .ui.animating.legacy.dimmer,\n.dimmed.dimmable > .ui.visible.legacy.dimmer,\n.ui.active.legacy.dimmer {\n  display: block;\n}\n\n/*--------------\n    Alignment\n---------------*/\n\n.ui[class*=\"top aligned\"].dimmer {\n  -webkit-box-pack: start;\n  -ms-flex-pack: start;\n  justify-content: flex-start;\n}\n\n.ui[class*=\"bottom aligned\"].dimmer {\n  -webkit-box-pack: end;\n  -ms-flex-pack: end;\n  justify-content: flex-end;\n}\n\n/*--------------\n      Page\n---------------*/\n\n.ui.page.dimmer {\n  position: fixed;\n  -webkit-transform-style: \"\";\n  transform-style: \"\";\n  -webkit-perspective: 2000px;\n  perspective: 2000px;\n  -webkit-transform-origin: center center;\n  transform-origin: center center;\n}\n\nbody.animating.in.dimmable,\nbody.dimmed.dimmable {\n  overflow: hidden;\n}\n\nbody.dimmable > .dimmer {\n  position: fixed;\n}\n\n/*--------------\n    Blurring\n---------------*/\n\n.blurring.dimmable > :not(.dimmer) {\n  -webkit-filter: blur(0px) grayscale(0);\n  filter: blur(0px) grayscale(0);\n  -webkit-transition: 800ms -webkit-filter ease;\n  transition: 800ms -webkit-filter ease;\n  transition: 800ms filter ease;\n  transition: 800ms filter ease, 800ms -webkit-filter ease;\n}\n\n.blurring.dimmed.dimmable > :not(.dimmer) {\n  -webkit-filter: blur(5px) grayscale(0.7);\n  filter: blur(5px) grayscale(0.7);\n}\n\n/* Dimmer Color */\n\n.blurring.dimmable > .dimmer {\n  background-color: rgba(0, 0, 0, 0.6);\n}\n\n.blurring.dimmable > .inverted.dimmer {\n  background-color: rgba(255, 255, 255, 0.6);\n}\n\n/*--------------\n    Aligned\n---------------*/\n\n.ui.dimmer > .top.aligned.content > * {\n  vertical-align: top;\n}\n\n.ui.dimmer > .bottom.aligned.content > * {\n  vertical-align: bottom;\n}\n\n/*--------------\n    Inverted\n---------------*/\n\n.ui.inverted.dimmer {\n  background-color: rgba(255, 255, 255, 0.85);\n}\n\n.ui.inverted.dimmer > .content > * {\n  color: #ffffff;\n}\n\n/*--------------\n    Simple\n---------------*/\n\n/* Displays without javascript */\n\n.ui.simple.dimmer {\n  display: block;\n  overflow: hidden;\n  opacity: 1;\n  width: 0%;\n  height: 0%;\n  z-index: -100;\n  background-color: rgba(0, 0, 0, 0);\n}\n\n.dimmed.dimmable > .ui.simple.dimmer {\n  overflow: visible;\n  opacity: 1;\n  width: 100%;\n  height: 100%;\n  background-color: rgba(0, 0, 0, 0.85);\n  z-index: 1;\n}\n\n.ui.simple.inverted.dimmer {\n  background-color: rgba(255, 255, 255, 0);\n}\n\n.dimmed.dimmable > .ui.simple.inverted.dimmer {\n  background-color: rgba(255, 255, 255, 0.85);\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        User Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Dropdown\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Dropdown\n*******************************/\n\n.ui.dropdown {\n  cursor: pointer;\n  position: relative;\n  display: inline-block;\n  outline: none;\n  text-align: left;\n  -webkit-transition: width 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: width 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: box-shadow 0.1s ease, width 0.1s ease;\n  transition: box-shadow 0.1s ease, width 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\n/*******************************\n            Content\n*******************************/\n\n/*--------------\n      Menu\n---------------*/\n\n.ui.dropdown .menu {\n  cursor: auto;\n  position: absolute;\n  display: none;\n  outline: none;\n  top: 100%;\n  min-width: -webkit-max-content;\n  min-width: -moz-max-content;\n  min-width: max-content;\n  margin: 0em;\n  padding: 0em 0em;\n  background: #ffffff;\n  font-size: 1em;\n  text-shadow: none;\n  text-align: left;\n  -webkit-box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  border-radius: 0.28571429rem;\n  -webkit-transition: opacity 0.1s ease;\n  transition: opacity 0.1s ease;\n  z-index: 11;\n  will-change: transform, opacity;\n}\n\n.ui.dropdown .menu > * {\n  white-space: nowrap;\n}\n\n/*--------------\n  Hidden Input\n---------------*/\n\n.ui.dropdown > input:not(.search):first-child,\n.ui.dropdown > select {\n  display: none !important;\n}\n\n/*--------------\nDropdown Icon\n---------------*/\n\n.ui.dropdown > .dropdown.icon {\n  position: relative;\n  width: auto;\n  font-size: 0.85714286em;\n  margin: 0em 0em 0em 1em;\n}\n\n.ui.dropdown .menu > .item .dropdown.icon {\n  width: auto;\n  float: right;\n  margin: 0em 0em 0em 1em;\n}\n\n.ui.dropdown .menu > .item .dropdown.icon + .text {\n  margin-right: 1em;\n}\n\n/*--------------\n      Text\n---------------*/\n\n.ui.dropdown > .text {\n  display: inline-block;\n  -webkit-transition: none;\n  transition: none;\n}\n\n/*--------------\n    Menu Item\n---------------*/\n\n.ui.dropdown .menu > .item {\n  position: relative;\n  cursor: pointer;\n  display: block;\n  border: none;\n  height: auto;\n  text-align: left;\n  border-top: none;\n  line-height: 1em;\n  color: rgba(0, 0, 0, 0.87);\n  padding: 0.78571429rem 1.14285714rem !important;\n  font-size: 1rem;\n  text-transform: none;\n  font-weight: normal;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  -webkit-touch-callout: none;\n}\n\n.ui.dropdown .menu > .item:first-child {\n  border-top-width: 0px;\n}\n\n/*--------------\n  Floated Content\n---------------*/\n\n.ui.dropdown > .text > [class*=\"right floated\"],\n.ui.dropdown .menu .item > [class*=\"right floated\"] {\n  float: right !important;\n  margin-right: 0em !important;\n  margin-left: 1em !important;\n}\n\n.ui.dropdown > .text > [class*=\"left floated\"],\n.ui.dropdown .menu .item > [class*=\"left floated\"] {\n  float: left !important;\n  margin-left: 0em !important;\n  margin-right: 1em !important;\n}\n\n.ui.dropdown .menu .item > .icon.floated,\n.ui.dropdown .menu .item > .flag.floated,\n.ui.dropdown .menu .item > .image.floated,\n.ui.dropdown .menu .item > img.floated {\n  margin-top: 0em;\n}\n\n/*--------------\n  Menu Divider\n---------------*/\n\n.ui.dropdown .menu > .header {\n  margin: 1rem 0rem 0.75rem;\n  padding: 0em 1.14285714rem;\n  color: rgba(0, 0, 0, 0.85);\n  font-size: 0.78571429em;\n  font-weight: bold;\n  text-transform: uppercase;\n}\n\n.ui.dropdown .menu > .divider {\n  border-top: 1px solid rgba(34, 36, 38, 0.1);\n  height: 0em;\n  margin: 0.5em 0em;\n}\n\n.ui.dropdown.dropdown .menu > .input {\n  width: auto;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  margin: 1.14285714rem 0.78571429rem;\n  min-width: 10rem;\n}\n\n.ui.dropdown .menu > .header + .input {\n  margin-top: 0em;\n}\n\n.ui.dropdown .menu > .input:not(.transparent) input {\n  padding: 0.5em 1em;\n}\n\n.ui.dropdown .menu > .input:not(.transparent) .button,\n.ui.dropdown .menu > .input:not(.transparent) .icon,\n.ui.dropdown .menu > .input:not(.transparent) .label {\n  padding-top: 0.5em;\n  padding-bottom: 0.5em;\n}\n\n/*-----------------\n  Item Description\n-------------------*/\n\n.ui.dropdown > .text > .description,\n.ui.dropdown .menu > .item > .description {\n  float: right;\n  margin: 0em 0em 0em 1em;\n  color: rgba(0, 0, 0, 0.4);\n}\n\n/*-----------------\n      Message\n-------------------*/\n\n.ui.dropdown .menu > .message {\n  padding: 0.78571429rem 1.14285714rem;\n  font-weight: normal;\n}\n\n.ui.dropdown .menu > .message:not(.ui) {\n  color: rgba(0, 0, 0, 0.4);\n}\n\n/*--------------\n    Sub Menu\n---------------*/\n\n.ui.dropdown .menu .menu {\n  top: 0% !important;\n  left: 100%;\n  right: auto;\n  margin: 0em 0em 0em -0.5em !important;\n  border-radius: 0.28571429rem !important;\n  z-index: 21 !important;\n}\n\n/* Hide Arrow */\n\n.ui.dropdown .menu .menu:after {\n  display: none;\n}\n\n/*--------------\n  Sub Elements\n---------------*/\n\n/* Icons / Flags / Labels / Image */\n\n.ui.dropdown > .text > .icon,\n.ui.dropdown > .text > .label,\n.ui.dropdown > .text > .flag,\n.ui.dropdown > .text > img,\n.ui.dropdown > .text > .image {\n  margin-top: 0em;\n}\n\n.ui.dropdown .menu > .item > .icon,\n.ui.dropdown .menu > .item > .label,\n.ui.dropdown .menu > .item > .flag,\n.ui.dropdown .menu > .item > .image,\n.ui.dropdown .menu > .item > img {\n  margin-top: 0em;\n}\n\n.ui.dropdown > .text > .icon,\n.ui.dropdown > .text > .label,\n.ui.dropdown > .text > .flag,\n.ui.dropdown > .text > img,\n.ui.dropdown > .text > .image,\n.ui.dropdown .menu > .item > .icon,\n.ui.dropdown .menu > .item > .label,\n.ui.dropdown .menu > .item > .flag,\n.ui.dropdown .menu > .item > .image,\n.ui.dropdown .menu > .item > img {\n  margin-left: 0em;\n  float: none;\n  margin-right: 0.78571429rem;\n}\n\n/*--------------\n    Image\n---------------*/\n\n.ui.dropdown > .text > img,\n.ui.dropdown > .text > .image,\n.ui.dropdown .menu > .item > .image,\n.ui.dropdown .menu > .item > img {\n  display: inline-block;\n  vertical-align: top;\n  width: auto;\n  margin-top: -0.5em;\n  margin-bottom: -0.5em;\n  max-height: 2em;\n}\n\n/*******************************\n            Coupling\n*******************************/\n\n/*--------------\n      Menu\n---------------*/\n\n/* Remove Menu Item Divider */\n\n.ui.dropdown .ui.menu > .item:before,\n.ui.menu .ui.dropdown .menu > .item:before {\n  display: none;\n}\n\n/* Prevent Menu Item Border */\n\n.ui.menu .ui.dropdown .menu .active.item {\n  border-left: none;\n}\n\n/* Automatically float dropdown menu right on last menu item */\n\n.ui.menu .right.menu .dropdown:last-child .menu,\n.ui.menu .right.dropdown.item .menu,\n.ui.buttons > .ui.dropdown:last-child .menu {\n  left: auto;\n  right: 0em;\n}\n\n/*--------------\n      Label\n---------------*/\n\n/* Dropdown Menu */\n\n.ui.label.dropdown .menu {\n  min-width: 100%;\n}\n\n/*--------------\n    Button\n---------------*/\n\n/* No Margin On Icon Button */\n\n.ui.dropdown.icon.button > .dropdown.icon {\n  margin: 0em;\n}\n\n.ui.button.dropdown .menu {\n  min-width: 100%;\n}\n\n/*******************************\n              Types\n*******************************/\n\n/*--------------\n    Selection\n---------------*/\n\n/* Displays like a select box */\n\n.ui.selection.dropdown {\n  cursor: pointer;\n  word-wrap: break-word;\n  line-height: 1em;\n  white-space: normal;\n  outline: 0;\n  -webkit-transform: rotateZ(0deg);\n  transform: rotateZ(0deg);\n  min-width: 14em;\n  min-height: 2.71428571em;\n  background: #ffffff;\n  display: inline-block;\n  padding: 0.78571429em 2.1em 0.78571429em 1em;\n  color: rgba(0, 0, 0, 0.87);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  border-radius: 0.28571429rem;\n  -webkit-transition: width 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: width 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: box-shadow 0.1s ease, width 0.1s ease;\n  transition: box-shadow 0.1s ease, width 0.1s ease,\n    -webkit-box-shadow 0.1s ease;\n}\n\n.ui.selection.dropdown.visible,\n.ui.selection.dropdown.active {\n  z-index: 10;\n}\n\nselect.ui.dropdown {\n  height: 38px;\n  padding: 0.5em;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  visibility: visible;\n}\n\n.ui.selection.dropdown > .search.icon,\n.ui.selection.dropdown > .delete.icon,\n.ui.selection.dropdown > .dropdown.icon {\n  cursor: pointer;\n  position: absolute;\n  width: auto;\n  height: auto;\n  line-height: 1.21428571em;\n  top: 0.78571429em;\n  right: 1em;\n  z-index: 3;\n  margin: -0.78571429em;\n  padding: 0.91666667em;\n  opacity: 0.8;\n  -webkit-transition: opacity 0.1s ease;\n  transition: opacity 0.1s ease;\n}\n\n/* Compact */\n\n.ui.compact.selection.dropdown {\n  min-width: 0px;\n}\n\n/*  Selection Menu */\n\n.ui.selection.dropdown .menu {\n  overflow-x: hidden;\n  overflow-y: auto;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  -webkit-overflow-scrolling: touch;\n  border-top-width: 0px !important;\n  width: auto;\n  outline: none;\n  margin: 0px -1px;\n  min-width: calc(100% + 2px);\n  width: calc(100% + 2px);\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n  -webkit-box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n  -webkit-transition: opacity 0.1s ease;\n  transition: opacity 0.1s ease;\n}\n\n.ui.selection.dropdown .menu:after,\n.ui.selection.dropdown .menu:before {\n  display: none;\n}\n\n/*--------------\n    Message\n---------------*/\n\n.ui.selection.dropdown .menu > .message {\n  padding: 0.78571429rem 1.14285714rem;\n}\n\n@media only screen and (width < 768px) {\n  .ui.selection.dropdown .menu {\n    max-height: 8.01428571rem;\n  }\n}\n\n@media only screen and (width >= 768px) {\n  .ui.selection.dropdown .menu {\n    max-height: 10.68571429rem;\n  }\n}\n\n@media only screen and (width >= 992px) {\n  .ui.selection.dropdown .menu {\n    max-height: 16.02857143rem;\n  }\n}\n\n@media only screen and (width >= 1920px) {\n  .ui.selection.dropdown .menu {\n    max-height: 21.37142857rem;\n  }\n}\n\n/* Menu Item */\n\n.ui.selection.dropdown .menu > .item {\n  border-top: 1px solid #fafafa;\n  padding: 0.78571429rem 1.14285714rem !important;\n  white-space: normal;\n  word-wrap: normal;\n}\n\n/* User Item */\n\n.ui.selection.dropdown .menu > .hidden.addition.item {\n  display: none;\n}\n\n/* Hover */\n\n.ui.selection.dropdown:hover {\n  border-color: rgba(34, 36, 38, 0.35);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n/* Active */\n\n.ui.selection.active.dropdown {\n  border-color: #96c8da;\n  -webkit-box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n}\n\n.ui.selection.active.dropdown .menu {\n  border-color: #96c8da;\n  -webkit-box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n}\n\n/* Focus */\n\n.ui.selection.dropdown:focus {\n  border-color: #96c8da;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.selection.dropdown:focus .menu {\n  border-color: #96c8da;\n  -webkit-box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n}\n\n/* Visible */\n\n.ui.selection.visible.dropdown > .text:not(.default) {\n  font-weight: normal;\n  color: rgba(0, 0, 0, 0.8);\n}\n\n/* Visible Hover */\n\n.ui.selection.active.dropdown:hover {\n  border-color: #96c8da;\n  -webkit-box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n}\n\n.ui.selection.active.dropdown:hover .menu {\n  border-color: #96c8da;\n  -webkit-box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);\n}\n\n/* Dropdown Icon */\n\n.ui.active.selection.dropdown > .dropdown.icon,\n.ui.visible.selection.dropdown > .dropdown.icon {\n  opacity: \"\";\n  z-index: 3;\n}\n\n/* Connecting Border */\n\n.ui.active.selection.dropdown {\n  border-bottom-left-radius: 0em !important;\n  border-bottom-right-radius: 0em !important;\n}\n\n/* Empty Connecting Border */\n\n.ui.active.empty.selection.dropdown {\n  border-radius: 0.28571429rem !important;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n}\n\n.ui.active.empty.selection.dropdown .menu {\n  border: none !important;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n}\n\n/*--------------\n  Searchable\n---------------*/\n\n/* Search Selection */\n\n.ui.search.dropdown {\n  min-width: \"\";\n}\n\n/* Search Dropdown */\n\n.ui.search.dropdown > input.search {\n  background: none transparent !important;\n  border: none !important;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  cursor: text;\n  top: 0em;\n  left: 1px;\n  width: 100%;\n  outline: none;\n  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);\n  padding: inherit;\n}\n\n/* Text Layering */\n\n.ui.search.dropdown > input.search {\n  position: absolute;\n  z-index: 2;\n}\n\n.ui.search.dropdown > .text {\n  cursor: text;\n  position: relative;\n  left: 1px;\n  z-index: 3;\n}\n\n/* Search Selection */\n\n.ui.search.selection.dropdown > input.search {\n  line-height: 1.21428571em;\n  padding: 0.67857143em 2.1em 0.67857143em 1em;\n}\n\n/* Used to size multi select input to character width */\n\n.ui.search.selection.dropdown > span.sizer {\n  line-height: 1.21428571em;\n  padding: 0.67857143em 2.1em 0.67857143em 1em;\n  display: none;\n  white-space: pre;\n}\n\n/* Active/Visible Search */\n\n.ui.search.dropdown.active > input.search,\n.ui.search.dropdown.visible > input.search {\n  cursor: auto;\n}\n\n.ui.search.dropdown.active > .text,\n.ui.search.dropdown.visible > .text {\n  pointer-events: none;\n}\n\n/* Filtered Text */\n\n.ui.active.search.dropdown input.search:focus + .text .icon,\n.ui.active.search.dropdown input.search:focus + .text .flag {\n  opacity: 0.45;\n}\n\n.ui.active.search.dropdown input.search:focus + .text {\n  color: rgba(115, 115, 115, 0.87) !important;\n}\n\n/* Search Menu */\n\n.ui.search.dropdown .menu {\n  overflow-x: hidden;\n  overflow-y: auto;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  -webkit-overflow-scrolling: touch;\n}\n\n@media only screen and (width < 768px) {\n  .ui.search.dropdown .menu {\n    max-height: 8.01428571rem;\n  }\n}\n\n@media only screen and (width >= 768px) {\n  .ui.search.dropdown .menu {\n    max-height: 10.68571429rem;\n  }\n}\n\n@media only screen and (width >= 992px) {\n  .ui.search.dropdown .menu {\n    max-height: 16.02857143rem;\n  }\n}\n\n@media only screen and (width >= 1920px) {\n  .ui.search.dropdown .menu {\n    max-height: 21.37142857rem;\n  }\n}\n\n/*--------------\n    Multiple\n---------------*/\n\n/* Multiple Selection */\n\n.ui.multiple.dropdown {\n  padding: 0.22619048em 2.1em 0.22619048em 0.35714286em;\n}\n\n.ui.multiple.dropdown .menu {\n  cursor: auto;\n}\n\n/* Multiple Search Selection */\n\n.ui.multiple.search.dropdown,\n.ui.multiple.search.dropdown > input.search {\n  cursor: text;\n}\n\n/* Selection Label */\n\n.ui.multiple.dropdown > .label {\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  display: inline-block;\n  vertical-align: top;\n  white-space: normal;\n  font-size: 1em;\n  padding: 0.35714286em 0.78571429em;\n  margin: 0.14285714rem 0.28571429rem 0.14285714rem 0em;\n  -webkit-box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n  box-shadow: 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;\n}\n\n/* Dropdown Icon */\n\n.ui.multiple.dropdown .dropdown.icon {\n  margin: \"\";\n  padding: \"\";\n}\n\n/* Text */\n\n.ui.multiple.dropdown > .text {\n  position: static;\n  padding: 0;\n  max-width: 100%;\n  margin: 0.45238095em 0em 0.45238095em 0.64285714em;\n  line-height: 1.21428571em;\n}\n\n.ui.multiple.dropdown > .label ~ input.search {\n  margin-left: 0.14285714em !important;\n}\n\n.ui.multiple.dropdown > .label ~ .text {\n  display: none;\n}\n\n/*-----------------\n  Multiple Search\n-----------------*/\n\n/* Prompt Text */\n\n.ui.multiple.search.dropdown > .text {\n  display: inline-block;\n  position: absolute;\n  top: 0;\n  left: 0;\n  padding: inherit;\n  margin: 0.45238095em 0em 0.45238095em 0.64285714em;\n  line-height: 1.21428571em;\n}\n\n.ui.multiple.search.dropdown > .label ~ .text {\n  display: none;\n}\n\n/* Search */\n\n.ui.multiple.search.dropdown > input.search {\n  position: static;\n  padding: 0;\n  max-width: 100%;\n  margin: 0.45238095em 0em 0.45238095em 0.64285714em;\n  width: 2.2em;\n  line-height: 1.21428571em;\n}\n\n/*--------------\n    Inline\n---------------*/\n\n.ui.inline.dropdown {\n  cursor: pointer;\n  display: inline-block;\n  color: inherit;\n}\n\n.ui.inline.dropdown .dropdown.icon {\n  margin: 0em 0.21428571em 0em 0.21428571em;\n  vertical-align: baseline;\n}\n\n.ui.inline.dropdown > .text {\n  font-weight: bold;\n}\n\n.ui.inline.dropdown .menu {\n  cursor: auto;\n  margin-top: 0.21428571em;\n  border-radius: 0.28571429rem;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------------\n        Active\n----------------------*/\n\n/* Menu Item Active */\n\n.ui.dropdown .menu .active.item {\n  background: transparent;\n  font-weight: bold;\n  color: rgba(0, 0, 0, 0.95);\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  z-index: 12;\n}\n\n/*--------------------\n        Hover\n----------------------*/\n\n/* Menu Item Hover */\n\n.ui.dropdown .menu > .item:hover {\n  background: rgba(0, 0, 0, 0.05);\n  color: rgba(0, 0, 0, 0.95);\n  z-index: 13;\n}\n\n/*--------------------\n      Loading\n---------------------*/\n\n.ui.loading.dropdown > i.icon {\n  height: 1em !important;\n}\n\n.ui.loading.selection.dropdown > i.icon {\n  padding: 1.5em 1.28571429em !important;\n}\n\n.ui.loading.dropdown > i.icon:before {\n  position: absolute;\n  content: \"\";\n  top: 50%;\n  left: 50%;\n  margin: -0.64285714em 0em 0em -0.64285714em;\n  width: 1.28571429em;\n  height: 1.28571429em;\n  border-radius: 500rem;\n  border: 0.2em solid rgba(0, 0, 0, 0.1);\n}\n\n.ui.loading.dropdown > i.icon:after {\n  position: absolute;\n  content: \"\";\n  top: 50%;\n  left: 50%;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent;\n  box-shadow: 0px 0px 0px 1px transparent;\n  margin: -0.64285714em 0em 0em -0.64285714em;\n  width: 1.28571429em;\n  height: 1.28571429em;\n  -webkit-animation: dropdown-spin 0.6s linear;\n  animation: dropdown-spin 0.6s linear;\n  -webkit-animation-iteration-count: infinite;\n  animation-iteration-count: infinite;\n  border-radius: 500rem;\n  border-color: #767676 transparent transparent;\n  border-style: solid;\n  border-width: 0.2em;\n}\n\n/* Coupling */\n\n.ui.loading.dropdown.button > i.icon:before,\n.ui.loading.dropdown.button > i.icon:after {\n  display: none;\n}\n\n@-webkit-keyframes dropdown-spin {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes dropdown-spin {\n  from {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  to {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n/*--------------------\n    Default Text\n----------------------*/\n\n.ui.dropdown:not(.button) > .default.text,\n.ui.default.dropdown:not(.button) > .text {\n  color: rgba(191, 191, 191, 0.87);\n}\n\n.ui.dropdown:not(.button) > input:focus ~ .default.text,\n.ui.default.dropdown:not(.button) > input:focus ~ .text {\n  color: rgba(115, 115, 115, 0.87);\n}\n\n/*--------------------\n        Loading\n----------------------*/\n\n.ui.loading.dropdown > .text {\n  -webkit-transition: none;\n  transition: none;\n}\n\n/* Used To Check Position */\n\n.ui.dropdown .loading.menu {\n  display: block;\n  visibility: hidden;\n  z-index: -1;\n}\n\n.ui.dropdown > .loading.menu {\n  left: 0px !important;\n  right: auto !important;\n}\n\n.ui.dropdown > .menu .loading.menu {\n  left: 100% !important;\n  right: auto !important;\n}\n\n/*--------------------\n    Keyboard Select\n----------------------*/\n\n/* Selected Item */\n\n.ui.dropdown.selected,\n.ui.dropdown .menu .selected.item {\n  background: rgba(0, 0, 0, 0.03);\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------------\n    Search Filtered\n----------------------*/\n\n/* Filtered Item */\n\n.ui.dropdown > .filtered.text {\n  visibility: hidden;\n}\n\n.ui.dropdown .filtered.item {\n  display: none !important;\n}\n\n/*--------------------\n        Error\n----------------------*/\n\n.ui.dropdown.error,\n.ui.dropdown.error > .text,\n.ui.dropdown.error > .default.text {\n  color: #9f3a38;\n}\n\n.ui.selection.dropdown.error {\n  background: #fff6f6;\n  border-color: #e0b4b4;\n}\n\n.ui.selection.dropdown.error:hover {\n  border-color: #e0b4b4;\n}\n\n.ui.dropdown.error > .menu,\n.ui.dropdown.error > .menu .menu {\n  border-color: #e0b4b4;\n}\n\n.ui.dropdown.error > .menu > .item {\n  color: #9f3a38;\n}\n\n.ui.multiple.selection.error.dropdown > .label {\n  border-color: #e0b4b4;\n}\n\n/* Item Hover */\n\n.ui.dropdown.error > .menu > .item:hover {\n  background-color: #fff2f2;\n}\n\n/* Item Active */\n\n.ui.dropdown.error > .menu .active.item {\n  background-color: #fdcfcf;\n}\n\n/*--------------------\n        Clear\n----------------------*/\n\n.ui.dropdown > .clear.dropdown.icon {\n  opacity: 0.8;\n  -webkit-transition: opacity 0.1s ease;\n  transition: opacity 0.1s ease;\n}\n\n.ui.dropdown > .clear.dropdown.icon:hover {\n  opacity: 1;\n}\n\n/*--------------------\n        Disabled\n----------------------*/\n\n/* Disabled */\n\n.ui.disabled.dropdown,\n.ui.dropdown .menu > .disabled.item {\n  cursor: default;\n  pointer-events: none;\n  opacity: 0.45;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Direction\n---------------*/\n\n/* Flyout Direction */\n\n.ui.dropdown .menu {\n  left: 0px;\n}\n\n/* Default Side (Right) */\n\n.ui.dropdown .right.menu > .menu,\n.ui.dropdown .menu .right.menu {\n  left: 100% !important;\n  right: auto !important;\n  border-radius: 0.28571429rem !important;\n}\n\n/* Leftward Opening Menu */\n\n.ui.dropdown > .left.menu {\n  left: auto !important;\n  right: 0px !important;\n}\n\n.ui.dropdown > .left.menu .menu,\n.ui.dropdown .menu .left.menu {\n  left: auto;\n  right: 100%;\n  margin: 0em -0.5em 0em 0em !important;\n  border-radius: 0.28571429rem !important;\n}\n\n.ui.dropdown .item .left.dropdown.icon,\n.ui.dropdown .left.menu .item .dropdown.icon {\n  width: auto;\n  float: left;\n  margin: 0em 0em 0em 0em;\n}\n\n.ui.dropdown .item .left.dropdown.icon,\n.ui.dropdown .left.menu .item .dropdown.icon {\n  width: auto;\n  float: left;\n  margin: 0em 0em 0em 0em;\n}\n\n.ui.dropdown .item .left.dropdown.icon + .text,\n.ui.dropdown .left.menu .item .dropdown.icon + .text {\n  margin-left: 1em;\n  margin-right: 0em;\n}\n\n/*--------------\n    Upward\n---------------*/\n\n/* Upward Main Menu */\n\n.ui.upward.dropdown > .menu {\n  top: auto;\n  bottom: 100%;\n  -webkit-box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.08);\n  box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.08);\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n/* Upward Sub Menu */\n\n.ui.dropdown .upward.menu {\n  top: auto !important;\n  bottom: 0 !important;\n}\n\n/* Active Upward */\n\n.ui.simple.upward.active.dropdown,\n.ui.simple.upward.dropdown:hover {\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em !important;\n}\n\n.ui.upward.dropdown.button:not(.pointing):not(.floating).active {\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n/* Selection */\n\n.ui.upward.selection.dropdown .menu {\n  border-top-width: 1px !important;\n  border-bottom-width: 0px !important;\n  -webkit-box-shadow: 0px -2px 3px 0px rgba(0, 0, 0, 0.08);\n  box-shadow: 0px -2px 3px 0px rgba(0, 0, 0, 0.08);\n}\n\n.ui.upward.selection.dropdown:hover {\n  -webkit-box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.05);\n  box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.05);\n}\n\n/* Active Upward */\n\n.ui.active.upward.selection.dropdown {\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem !important;\n}\n\n/* Visible Upward */\n\n.ui.upward.selection.dropdown.visible {\n  -webkit-box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.08);\n  box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.08);\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem !important;\n}\n\n/* Visible Hover Upward */\n\n.ui.upward.active.selection.dropdown:hover {\n  -webkit-box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.05);\n  box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.05);\n}\n\n.ui.upward.active.selection.dropdown:hover .menu {\n  -webkit-box-shadow: 0px -2px 3px 0px rgba(0, 0, 0, 0.08);\n  box-shadow: 0px -2px 3px 0px rgba(0, 0, 0, 0.08);\n}\n\n/*--------------\n    Simple\n---------------*/\n\n/*  Selection Menu */\n\n.ui.scrolling.dropdown .menu,\n.ui.dropdown .scrolling.menu {\n  overflow-x: hidden;\n  overflow-y: auto;\n}\n\n.ui.scrolling.dropdown .menu {\n  overflow-x: hidden;\n  overflow-y: auto;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  -webkit-overflow-scrolling: touch;\n  min-width: 100% !important;\n  width: auto !important;\n}\n\n.ui.dropdown .scrolling.menu {\n  position: static;\n  overflow-y: auto;\n  border: none;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  border-radius: 0 !important;\n  margin: 0 !important;\n  min-width: 100% !important;\n  width: auto !important;\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.scrolling.dropdown .menu .item.item.item,\n.ui.dropdown .scrolling.menu > .item.item.item {\n  border-top: none;\n}\n\n.ui.scrolling.dropdown .menu .item:first-child,\n.ui.dropdown .scrolling.menu .item:first-child {\n  border-top: none;\n}\n\n.ui.dropdown > .animating.menu .scrolling.menu,\n.ui.dropdown > .visible.menu .scrolling.menu {\n  display: block;\n}\n\n/* Scrollbar in IE */\n\n@media all and (-ms-high-contrast: none) {\n\n  .ui.scrolling.dropdown .menu,\n  .ui.dropdown .scrolling.menu {\n    min-width: calc(100% - 17px);\n  }\n}\n\n@media only screen and (width < 768px) {\n\n  .ui.scrolling.dropdown .menu,\n  .ui.dropdown .scrolling.menu {\n    max-height: 10.28571429rem;\n  }\n}\n\n@media only screen and (width >= 768px) {\n\n  .ui.scrolling.dropdown .menu,\n  .ui.dropdown .scrolling.menu {\n    max-height: 15.42857143rem;\n  }\n}\n\n@media only screen and (width >= 992px) {\n\n  .ui.scrolling.dropdown .menu,\n  .ui.dropdown .scrolling.menu {\n    max-height: 20.57142857rem;\n  }\n}\n\n@media only screen and (width >= 1920px) {\n\n  .ui.scrolling.dropdown .menu,\n  .ui.dropdown .scrolling.menu {\n    max-height: 20.57142857rem;\n  }\n}\n\n/*--------------\n    Simple\n---------------*/\n\n/* Displays without javascript */\n\n.ui.simple.dropdown .menu:before,\n.ui.simple.dropdown .menu:after {\n  display: none;\n}\n\n.ui.simple.dropdown .menu {\n  position: absolute;\n  display: block;\n  overflow: hidden;\n  top: -9999px !important;\n  opacity: 0;\n  width: 0;\n  height: 0;\n  -webkit-transition: opacity 0.1s ease;\n  transition: opacity 0.1s ease;\n}\n\n.ui.simple.active.dropdown,\n.ui.simple.dropdown:hover {\n  border-bottom-left-radius: 0em !important;\n  border-bottom-right-radius: 0em !important;\n}\n\n.ui.simple.active.dropdown > .menu,\n.ui.simple.dropdown:hover > .menu {\n  overflow: visible;\n  width: auto;\n  height: auto;\n  top: 100% !important;\n  opacity: 1;\n}\n\n.ui.simple.dropdown > .menu > .item:active > .menu,\n.ui.simple.dropdown:hover > .menu > .item:hover > .menu {\n  overflow: visible;\n  width: auto;\n  height: auto;\n  top: 0% !important;\n  left: 100% !important;\n  opacity: 1;\n}\n\n.ui.simple.disabled.dropdown:hover .menu {\n  display: none;\n  height: 0px;\n  width: 0px;\n  overflow: hidden;\n}\n\n/* Visible */\n\n.ui.simple.visible.dropdown > .menu {\n  display: block;\n}\n\n/*--------------\n      Fluid\n---------------*/\n\n.ui.fluid.dropdown {\n  display: block;\n  width: 100%;\n  min-width: 0em;\n}\n\n.ui.fluid.dropdown > .dropdown.icon {\n  float: right;\n}\n\n/*--------------\n    Floating\n---------------*/\n\n.ui.floating.dropdown .menu {\n  left: 0;\n  right: auto;\n  -webkit-box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15) !important;\n  box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15) !important;\n  border-radius: 0.28571429rem !important;\n}\n\n.ui.floating.dropdown > .menu {\n  margin-top: 0.5em !important;\n  border-radius: 0.28571429rem !important;\n}\n\n/*--------------\n    Pointing\n---------------*/\n\n.ui.pointing.dropdown > .menu {\n  top: 100%;\n  margin-top: 0.78571429rem;\n  border-radius: 0.28571429rem;\n}\n\n.ui.pointing.dropdown > .menu:after {\n  display: block;\n  position: absolute;\n  pointer-events: none;\n  content: \"\";\n  visibility: visible;\n  -webkit-transform: rotate(45deg);\n  transform: rotate(45deg);\n  width: 0.5em;\n  height: 0.5em;\n  -webkit-box-shadow: -1px -1px 0px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: -1px -1px 0px 0px rgba(34, 36, 38, 0.15);\n  background: #ffffff;\n  z-index: 2;\n}\n\n.ui.pointing.dropdown > .menu:after {\n  top: -0.25em;\n  left: 50%;\n  margin: 0em 0em 0em -0.25em;\n}\n\n/* Top Left Pointing */\n\n.ui.top.left.pointing.dropdown > .menu {\n  top: 100%;\n  bottom: auto;\n  left: 0%;\n  right: auto;\n  margin: 1em 0em 0em;\n}\n\n.ui.top.left.pointing.dropdown > .menu {\n  top: 100%;\n  bottom: auto;\n  left: 0%;\n  right: auto;\n  margin: 1em 0em 0em;\n}\n\n.ui.top.left.pointing.dropdown > .menu:after {\n  top: -0.25em;\n  left: 1em;\n  right: auto;\n  margin: 0em;\n  -webkit-transform: rotate(45deg);\n  transform: rotate(45deg);\n}\n\n/* Top Right Pointing */\n\n.ui.top.right.pointing.dropdown > .menu {\n  top: 100%;\n  bottom: auto;\n  right: 0%;\n  left: auto;\n  margin: 1em 0em 0em;\n}\n\n.ui.top.pointing.dropdown > .left.menu:after,\n.ui.top.right.pointing.dropdown > .menu:after {\n  top: -0.25em;\n  left: auto !important;\n  right: 1em !important;\n  margin: 0em;\n  -webkit-transform: rotate(45deg);\n  transform: rotate(45deg);\n}\n\n/* Left Pointing */\n\n.ui.left.pointing.dropdown > .menu {\n  top: 0%;\n  left: 100%;\n  right: auto;\n  margin: 0em 0em 0em 1em;\n}\n\n.ui.left.pointing.dropdown > .menu:after {\n  top: 1em;\n  left: -0.25em;\n  margin: 0em 0em 0em 0em;\n  -webkit-transform: rotate(-45deg);\n  transform: rotate(-45deg);\n}\n\n.ui.left:not(.top):not(.bottom).pointing.dropdown > .left.menu {\n  left: auto !important;\n  right: 100% !important;\n  margin: 0em 1em 0em 0em;\n}\n\n.ui.left:not(.top):not(.bottom).pointing.dropdown > .left.menu:after {\n  top: 1em;\n  left: auto;\n  right: -0.25em;\n  margin: 0em 0em 0em 0em;\n  -webkit-transform: rotate(135deg);\n  transform: rotate(135deg);\n}\n\n/* Right Pointing */\n\n.ui.right.pointing.dropdown > .menu {\n  top: 0%;\n  left: auto;\n  right: 100%;\n  margin: 0em 1em 0em 0em;\n}\n\n.ui.right.pointing.dropdown > .menu:after {\n  top: 1em;\n  left: auto;\n  right: -0.25em;\n  margin: 0em 0em 0em 0em;\n  -webkit-transform: rotate(135deg);\n  transform: rotate(135deg);\n}\n\n/* Bottom Pointing */\n\n.ui.bottom.pointing.dropdown > .menu {\n  top: auto;\n  bottom: 100%;\n  left: 0%;\n  right: auto;\n  margin: 0em 0em 1em;\n}\n\n.ui.bottom.pointing.dropdown > .menu:after {\n  top: auto;\n  bottom: -0.25em;\n  right: auto;\n  margin: 0em;\n  -webkit-transform: rotate(-135deg);\n  transform: rotate(-135deg);\n}\n\n/* Reverse Sub-Menu Direction */\n\n.ui.bottom.pointing.dropdown > .menu .menu {\n  top: auto !important;\n  bottom: 0px !important;\n}\n\n/* Bottom Left */\n\n.ui.bottom.left.pointing.dropdown > .menu {\n  left: 0%;\n  right: auto;\n}\n\n.ui.bottom.left.pointing.dropdown > .menu:after {\n  left: 1em;\n  right: auto;\n}\n\n/* Bottom Right */\n\n.ui.bottom.right.pointing.dropdown > .menu {\n  right: 0%;\n  left: auto;\n}\n\n.ui.bottom.right.pointing.dropdown > .menu:after {\n  left: auto;\n  right: 1em;\n}\n\n/* Upward pointing */\n\n.ui.pointing.upward.dropdown .menu,\n.ui.top.pointing.upward.dropdown .menu {\n  top: auto !important;\n  bottom: 100% !important;\n  margin: 0em 0em 0.78571429rem;\n  border-radius: 0.28571429rem;\n}\n\n.ui.pointing.upward.dropdown .menu:after,\n.ui.top.pointing.upward.dropdown .menu:after {\n  top: 100% !important;\n  bottom: auto !important;\n  -webkit-box-shadow: 1px 1px 0px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 1px 1px 0px 0px rgba(34, 36, 38, 0.15);\n  margin: -0.25em 0em 0em;\n}\n\n/* Right Pointing Upward */\n\n.ui.right.pointing.upward.dropdown:not(.top):not(.bottom) .menu {\n  top: auto !important;\n  bottom: 0 !important;\n  margin: 0em 1em 0em 0em;\n}\n\n.ui.right.pointing.upward.dropdown:not(.top):not(.bottom) .menu:after {\n  top: auto !important;\n  bottom: 0 !important;\n  margin: 0em 0em 1em 0em;\n  -webkit-box-shadow: -1px -1px 0px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: -1px -1px 0px 0px rgba(34, 36, 38, 0.15);\n}\n\n/* Left Pointing Upward */\n\n.ui.left.pointing.upward.dropdown:not(.top):not(.bottom) .menu {\n  top: auto !important;\n  bottom: 0 !important;\n  margin: 0em 0em 0em 1em;\n}\n\n.ui.left.pointing.upward.dropdown:not(.top):not(.bottom) .menu:after {\n  top: auto !important;\n  bottom: 0 !important;\n  margin: 0em 0em 1em 0em;\n  -webkit-box-shadow: -1px -1px 0px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: -1px -1px 0px 0px rgba(34, 36, 38, 0.15);\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/* Dropdown Carets */\n\n@font-face {\n  font-family: \"Dropdown\";\n  src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAVgAA8AAAAACFAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAABWAAAABwAAAAchGgaq0dERUYAAAF0AAAAHAAAAB4AJwAPT1MvMgAAAZAAAABDAAAAVnW4TJdjbWFwAAAB1AAAAEsAAAFS8CcaqmN2dCAAAAIgAAAABAAAAAQAEQFEZ2FzcAAAAiQAAAAIAAAACP//AANnbHlmAAACLAAAAQoAAAGkrRHP9WhlYWQAAAM4AAAAMAAAADYPK8YyaGhlYQAAA2gAAAAdAAAAJANCAb1obXR4AAADiAAAACIAAAAiCBkAOGxvY2EAAAOsAAAAFAAAABQBnAIybWF4cAAAA8AAAAAfAAAAIAEVAF5uYW1lAAAD4AAAATAAAAKMFGlj5HBvc3QAAAUQAAAARgAAAHJoedjqd2ViZgAABVgAAAAGAAAABrO7W5UAAAABAAAAANXulPUAAAAA1r4hgAAAAADXu2Q1eNpjYGRgYOABYjEgZmJgBEIOIGYB8xgAA/YAN3jaY2BktGOcwMDKwMI4jTGNgYHBHUp/ZZBkaGFgYGJgZWbACgLSXFMYHFT/fLjFeOD/AQY9xjMMbkBhRpAcAN48DQYAeNpjYGBgZoBgGQZGBhDwAfIYwXwWBgMgzQGETAwMqn8+8H649f8/lHX9//9b7Pzf+fWgusCAkY0BzmUE6gHpQwGMDMMeAACbxg7SAAARAUQAAAAB//8AAnjadZBPSsNAGMXfS+yMqYgOhpSuSlKadmUhiVEhEMQzFF22m17BbbvzCh5BXCUn6EG8gjeQ4DepwYo4i+/ffL95j4EDA+CFC7jQuKyIeVHrI3wkleq9F7XrSInKteOeHdda8bOoaeepSc00NWPz/LRec9G8GabyGtEdF7h19z033GAMTK7zbM42xNEZpzYof0RtQ5CUHAQJ73OtVyutc+3b7Ou//b8XNlsPx3jgjUifABdhEohKJJL5iM5p39uqc7X1+sRQSqmGrUVhlsJ4lpmEUVwyT8SUYtg0P9DyNzPADDs+tjrGV6KRCRfsui3eHcL4/p8ZXvfMlcnEU+CLv7hDykOP+AKTPTxbAAB42mNgZGBgAGKuf5KP4vltvjLIMzGAwLV9ig0g+vruFFMQzdjACOJzMIClARh0CTJ42mNgZGBgPPD/AJD8wgAEjA0MjAyogAMAbOQEAQAAAAC7ABEAAAAAAKoAAAH0AAABgAAAAUAACAFAAAgAwAAXAAAAAAAAACoAKgAqADIAbACGAKAAugDSeNpjYGRgYOBkUGFgYgABEMkFhAwM/xn0QAIADdUBdAB42qWQvUoDQRSFv3GjaISUQaymSmGxJoGAsRC0iPYLsU50Y6IxrvlRtPCJJKUPIBb+PIHv4EN4djKuKAqCDHfmu+feOdwZoMCUAJNbAlYUMzaUlM14jjxbngOq7HnOia89z1Pk1vMCa9x7ztPkzfMyJbPj+ZGi6Xp+omxuPD+zaD7meaFg7mb8GrBqHmhwxoAxlm0uiRkpP9X5m26pKRoMxTGR1D49Dv/Yb/91o6l8qL6eu5n2hZQzn68utR9m3FU2cB4t9cdSLG2utI+44Eh/P9bqKO+oJ/WxmXssj77YkrjasZQD6SFddythk3Wtzrf+UF2p076Udla1VNzsERP3kkjVRKel7mp1udXYcHtZSlV7RfmJe1GiFWveluaeKD5/MuJcSk8Tpm/vvwPIbmJleNpjYGKAAFYG7ICTgYGRiZGZkYWRlZGNkZ2Rg5GTLT2nsiDDEEIZsZfmZRqZujmDaDcDAxcI7WIOpS2gtCWUdgQAZkcSmQAAAAFblbO6AAA=) format(\"woff\");\n  font-weight: normal;\n  font-style: normal;\n}\n\n.ui.dropdown > .dropdown.icon {\n  font-family: \"Dropdown\";\n  line-height: 1;\n  height: 1em;\n  width: 1.23em;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  font-weight: normal;\n  font-style: normal;\n  text-align: center;\n}\n\n.ui.dropdown > .dropdown.icon {\n  width: auto;\n}\n\n.ui.dropdown > .dropdown.icon:before {\n  content: \"\\f0d7\";\n}\n\n/* Sub Menu */\n\n.ui.dropdown .menu .item .dropdown.icon:before {\n  content: \"\\f0da\";\n}\n\n.ui.dropdown .item .left.dropdown.icon:before,\n.ui.dropdown .left.menu .item .dropdown.icon:before {\n  content: \"\\f0d9\";\n}\n\n/* Vertical Menu Dropdown */\n\n.ui.vertical.menu .dropdown.item > .dropdown.icon:before {\n  content: \"\\f0da\";\n}\n\n.ui.dropdown > .clear.icon:before {\n  content: \"\\f00d\";\n}\n\n/* Icons for Reference (Subsetted in 2.4.0)\n  .dropdown.down:before { content: \"\\f0d7\"; }\n  .dropdown.up:before { content: \"\\f0d8\"; }\n  .dropdown.left:before { content: \"\\f0d9\"; }\n  .dropdown.right:before { content: \"\\f0da\"; }\n  .dropdown.close:before { content: \"\\f00d\"; }\n*/\n\n/*******************************\n        User Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Video\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Types\n*******************************/\n\n.ui.embed {\n  position: relative;\n  max-width: 100%;\n  height: 0px;\n  overflow: hidden;\n  background: #dcddde;\n  padding-bottom: 56.25%;\n}\n\n/*-----------------\n  Embedded Content\n------------------*/\n\n.ui.embed iframe,\n.ui.embed embed,\n.ui.embed object {\n  position: absolute;\n  border: none;\n  width: 100%;\n  height: 100%;\n  top: 0px;\n  left: 0px;\n  margin: 0em;\n  padding: 0em;\n}\n\n/*-----------------\n      Embed\n------------------*/\n\n.ui.embed > .embed {\n  display: none;\n}\n\n/*--------------\n  Placeholder\n---------------*/\n\n.ui.embed > .placeholder {\n  position: absolute;\n  cursor: pointer;\n  top: 0px;\n  left: 0px;\n  display: block;\n  width: 100%;\n  height: 100%;\n  background-color: radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3));\n}\n\n/*--------------\n      Icon\n---------------*/\n\n.ui.embed > .icon {\n  cursor: pointer;\n  position: absolute;\n  top: 0px;\n  left: 0px;\n  width: 100%;\n  height: 100%;\n  z-index: 2;\n}\n\n.ui.embed > .icon:after {\n  position: absolute;\n  top: 0%;\n  left: 0%;\n  width: 100%;\n  height: 100%;\n  z-index: 3;\n  content: \"\";\n  background: -webkit-radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3));\n  background: radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3));\n  opacity: 0.5;\n  -webkit-transition: opacity 0.5s ease;\n  transition: opacity 0.5s ease;\n}\n\n.ui.embed > .icon:before {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  z-index: 4;\n  -webkit-transform: translateX(-50%) translateY(-50%);\n  transform: translateX(-50%) translateY(-50%);\n  color: #ffffff;\n  font-size: 6rem;\n  text-shadow: 0px 2px 10px rgba(34, 36, 38, 0.2);\n  -webkit-transition: opacity 0.5s ease, color 0.5s ease;\n  transition: opacity 0.5s ease, color 0.5s ease;\n  z-index: 10;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------\n    Hover\n---------------*/\n\n.ui.embed .icon:hover:after {\n  background: -webkit-radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3));\n  background: radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3));\n  opacity: 1;\n}\n\n.ui.embed .icon:hover:before {\n  color: #ffffff;\n}\n\n/*--------------\n    Active\n---------------*/\n\n.ui.active.embed > .icon,\n.ui.active.embed > .placeholder {\n  display: none;\n}\n\n.ui.active.embed > .embed {\n  display: block;\n}\n\n/*******************************\n        Video Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n\n/*******************************\n          Variations\n*******************************/\n\n.ui.square.embed {\n  padding-bottom: 100%;\n}\n\n.ui[class*=\"4:3\"].embed {\n  padding-bottom: 75%;\n}\n\n.ui[class*=\"16:9\"].embed {\n  padding-bottom: 56.25%;\n}\n\n.ui[class*=\"21:9\"].embed {\n  padding-bottom: 42.85714286%;\n}\n\n/*!\n* # Semantic UI 2.4.0 - Modal\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Modal\n*******************************/\n\n.ui.modal {\n  position: absolute;\n  display: none;\n  z-index: 1001;\n  text-align: left;\n  background: #ffffff;\n  border: none;\n  -webkit-box-shadow: 1px 3px 3px 0px rgba(0, 0, 0, 0.2),\n    1px 3px 15px 2px rgba(0, 0, 0, 0.2);\n  box-shadow: 1px 3px 3px 0px rgba(0, 0, 0, 0.2),\n    1px 3px 15px 2px rgba(0, 0, 0, 0.2);\n  -webkit-transform-origin: 50% 25%;\n  transform-origin: 50% 25%;\n  -webkit-box-flex: 0;\n  -ms-flex: 0 0 auto;\n  flex: 0 0 auto;\n  border-radius: 0.28571429rem;\n  -webkit-user-select: text;\n  -moz-user-select: text;\n  -ms-user-select: text;\n  user-select: text;\n  will-change: top, left, margin, transform, opacity;\n}\n\n.ui.modal > :first-child:not(.icon),\n.ui.modal > .icon:first-child + * {\n  border-top-left-radius: 0.28571429rem;\n  border-top-right-radius: 0.28571429rem;\n}\n\n.ui.modal > :last-child {\n  border-bottom-left-radius: 0.28571429rem;\n  border-bottom-right-radius: 0.28571429rem;\n}\n\n/*******************************\n            Content\n*******************************/\n\n/*--------------\n    Close\n---------------*/\n\n.ui.modal > .close {\n  cursor: pointer;\n  position: absolute;\n  top: -2.5rem;\n  right: -2.5rem;\n  z-index: 1;\n  opacity: 0.8;\n  font-size: 1.25em;\n  color: #ffffff;\n  width: 2.25rem;\n  height: 2.25rem;\n  padding: 0.625rem 0rem 0rem 0rem;\n}\n\n.ui.modal > .close:hover {\n  opacity: 1;\n}\n\n/*--------------\n    Header\n---------------*/\n\n.ui.modal > .header {\n  display: block;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  background: #ffffff;\n  margin: 0em;\n  padding: 1.25rem 1.5rem;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  color: rgba(0, 0, 0, 0.85);\n  border-bottom: 1px solid rgba(34, 36, 38, 0.15);\n}\n\n.ui.modal > .header:not(.ui) {\n  font-size: 1.42857143rem;\n  line-height: 1.28571429em;\n  font-weight: bold;\n}\n\n/*--------------\n    Content\n---------------*/\n\n.ui.modal > .content {\n  display: block;\n  width: 100%;\n  font-size: 1em;\n  line-height: 1.4;\n  padding: 1.5rem;\n  background: #ffffff;\n}\n\n.ui.modal > .image.content {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: row;\n  flex-direction: row;\n}\n\n/* Image */\n\n.ui.modal > .content > .image {\n  display: block;\n  -webkit-box-flex: 0;\n  -ms-flex: 0 1 auto;\n  flex: 0 1 auto;\n  width: \"\";\n  -ms-flex-item-align: top;\n  align-self: top;\n}\n\n.ui.modal > [class*=\"top aligned\"] {\n  -ms-flex-item-align: top;\n  align-self: top;\n}\n\n.ui.modal > [class*=\"middle aligned\"] {\n  -ms-flex-item-align: middle;\n  align-self: middle;\n}\n\n.ui.modal > [class*=\"stretched\"] {\n  -ms-flex-item-align: stretch;\n  align-self: stretch;\n}\n\n/* Description */\n\n.ui.modal > .content > .description {\n  display: block;\n  -webkit-box-flex: 1;\n  -ms-flex: 1 0 auto;\n  flex: 1 0 auto;\n  min-width: 0px;\n  -ms-flex-item-align: top;\n  align-self: top;\n}\n\n.ui.modal > .content > .icon + .description,\n.ui.modal > .content > .image + .description {\n  -webkit-box-flex: 0;\n  -ms-flex: 0 1 auto;\n  flex: 0 1 auto;\n  min-width: \"\";\n  width: auto;\n  padding-left: 2em;\n}\n\n/*rtl:ignore*/\n\n.ui.modal > .content > .image > i.icon {\n  margin: 0em;\n  opacity: 1;\n  width: auto;\n  line-height: 1;\n  font-size: 8rem;\n}\n\n/*--------------\n    Actions\n---------------*/\n\n.ui.modal > .actions {\n  background: #f9fafb;\n  padding: 1rem 1rem;\n  border-top: 1px solid rgba(34, 36, 38, 0.15);\n  text-align: right;\n}\n\n.ui.modal .actions > .button {\n  margin-left: 0.75em;\n}\n\n/*-------------------\n      Responsive\n--------------------*/\n\n/* Modal Width */\n\n@media only screen and (width < 768px) {\n  .ui.modal {\n    width: 95%;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 768px) {\n  .ui.modal {\n    width: 88%;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 992px) {\n  .ui.modal {\n    width: 850px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 1200px) {\n  .ui.modal {\n    width: 900px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 1920px) {\n  .ui.modal {\n    width: 950px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n/* Tablet and Mobile */\n\n@media only screen and (width < 992px) {\n  .ui.modal > .header {\n    padding-right: 2.25rem;\n  }\n\n  .ui.modal > .close {\n    top: 1.0535rem;\n    right: 1rem;\n    color: rgba(0, 0, 0, 0.87);\n  }\n}\n\n/* Mobile */\n\n@media only screen and (width < 768px) {\n  .ui.modal > .header {\n    padding: 0.75rem 1rem !important;\n    padding-right: 2.25rem !important;\n  }\n\n  .ui.modal > .content {\n    display: block;\n    padding: 1rem !important;\n  }\n\n  .ui.modal > .close {\n    top: 0.5rem !important;\n    right: 0.5rem !important;\n  }\n\n  /*rtl:ignore*/\n\n  .ui.modal .image.content {\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    -ms-flex-direction: column;\n    flex-direction: column;\n  }\n\n  .ui.modal .content > .image {\n    display: block;\n    max-width: 100%;\n    margin: 0em auto !important;\n    text-align: center;\n    padding: 0rem 0rem 1rem !important;\n  }\n\n  .ui.modal > .content > .image > i.icon {\n    font-size: 5rem;\n    text-align: center;\n  }\n\n  /*rtl:ignore*/\n\n  .ui.modal .content > .description {\n    display: block;\n    width: 100% !important;\n    margin: 0em !important;\n    padding: 1rem 0rem !important;\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n\n  /* Let Buttons Stack */\n\n  .ui.modal > .actions {\n    padding: 1rem 1rem 0rem !important;\n  }\n\n  .ui.modal .actions > .buttons,\n  .ui.modal .actions > .button {\n    margin-bottom: 1rem;\n  }\n}\n\n/*--------------\n    Coupling\n---------------*/\n\n.ui.inverted.dimmer > .ui.modal {\n  -webkit-box-shadow: 1px 3px 10px 2px rgba(0, 0, 0, 0.2);\n  box-shadow: 1px 3px 10px 2px rgba(0, 0, 0, 0.2);\n}\n\n/*******************************\n            Types\n*******************************/\n\n.ui.basic.modal {\n  background-color: transparent;\n  border: none;\n  border-radius: 0em;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n  color: #ffffff;\n}\n\n.ui.basic.modal > .header,\n.ui.basic.modal > .content,\n.ui.basic.modal > .actions {\n  background-color: transparent;\n}\n\n.ui.basic.modal > .header {\n  color: #ffffff;\n}\n\n.ui.basic.modal > .close {\n  top: 1rem;\n  right: 1.5rem;\n}\n\n.ui.inverted.dimmer > .basic.modal {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.inverted.dimmer > .ui.basic.modal > .header {\n  color: rgba(0, 0, 0, 0.85);\n}\n\n/* Resort to margin positioning if legacy */\n\n.ui.legacy.modal,\n.ui.legacy.page.dimmer > .ui.modal {\n  top: 50%;\n  left: 50%;\n}\n\n.ui.legacy.page.dimmer > .ui.scrolling.modal,\n.ui.page.dimmer > .ui.scrolling.legacy.modal,\n.ui.top.aligned.legacy.page.dimmer > .ui.modal,\n.ui.top.aligned.dimmer > .ui.legacy.modal {\n  top: auto;\n}\n\n/* Tablet and Mobile */\n\n@media only screen and (width < 992px) {\n  .ui.basic.modal > .close {\n    color: #ffffff;\n  }\n}\n\n/*******************************\n            States\n*******************************/\n\n.ui.loading.modal {\n  display: block;\n  visibility: hidden;\n  z-index: -1;\n}\n\n.ui.active.modal {\n  display: block;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n  Top Aligned\n---------------*/\n\n/* Top Aligned Modal */\n\n.modals.dimmer[class*=\"top aligned\"] .modal {\n  margin: 5vh auto;\n}\n\n@media only screen and (width < 768px) {\n  .modals.dimmer[class*=\"top aligned\"] .modal {\n    margin: 1rem auto;\n  }\n}\n\n/* Legacy Top Aligned */\n\n.legacy.modals.dimmer[class*=\"top aligned\"] {\n  padding-top: 5vh;\n}\n\n@media only screen and (width < 768px) {\n  .legacy.modals.dimmer[class*=\"top aligned\"] {\n    padding-top: 1rem;\n  }\n}\n\n/*--------------\n    Scrolling\n---------------*/\n\n/* Scrolling Dimmer */\n\n.scrolling.dimmable.dimmed {\n  overflow: hidden;\n}\n\n/* .scrolling.dimmable > .dimmer {\n  -webkit-box-pack: start;\n  -ms-flex-pack: start;\n  justify-content: flex-start;\n} */\n\n.scrolling.dimmable.dimmed > .dimmer {\n  overflow: auto;\n  -webkit-overflow-scrolling: touch;\n}\n\n.scrolling.dimmable > .dimmer {\n  position: fixed;\n}\n\n.modals.dimmer .ui.scrolling.modal {\n  margin: 1rem auto;\n}\n\n/* Undetached Scrolling */\n\n.scrolling.undetached.dimmable.dimmed {\n  overflow: auto;\n  -webkit-overflow-scrolling: touch;\n}\n\n.scrolling.undetached.dimmable.dimmed > .dimmer {\n  overflow: hidden;\n}\n\n.scrolling.undetached.dimmable .ui.scrolling.modal {\n  position: absolute;\n  left: 50%;\n  margin-top: 1rem !important;\n}\n\n/* Scrolling Content */\n\n.ui.modal .scrolling.content {\n  max-height: calc(70vh);\n  overflow: auto;\n}\n\n/*--------------\n  Full Screen\n---------------*/\n\n.ui.fullscreen.modal {\n  width: 95% !important;\n  left: 0em !important;\n  margin: 1em auto;\n}\n\n.ui.fullscreen.scrolling.modal {\n  left: 0em !important;\n}\n\n.ui.fullscreen.modal > .header {\n  padding-right: 2.25rem;\n}\n\n.ui.fullscreen.modal > .close {\n  top: 1.0535rem;\n  right: 1rem;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/*--------------\n      Size\n---------------*/\n\n.ui.modal {\n  font-size: 1rem;\n}\n\n/* Mini */\n\n.ui.mini.modal > .header:not(.ui) {\n  font-size: 1.3em;\n}\n\n/* Mini Modal Width */\n\n@media only screen and (width < 768px) {\n  .ui.mini.modal {\n    width: 95%;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 768px) {\n  .ui.mini.modal {\n    width: 35.2%;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 992px) {\n  .ui.mini.modal {\n    width: 340px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 1200px) {\n  .ui.mini.modal {\n    width: 360px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 1920px) {\n  .ui.mini.modal {\n    width: 380px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n/* mini */\n\n.ui.small.modal > .header:not(.ui) {\n  font-size: 1.3em;\n}\n\n/* Tiny Modal Width */\n\n@media only screen and (width < 768px) {\n  .ui.tiny.modal {\n    width: 95%;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 768px) {\n  .ui.tiny.modal {\n    width: 52.8%;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 992px) {\n  .ui.tiny.modal {\n    width: 510px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 1200px) {\n  .ui.tiny.modal {\n    width: 540px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 1920px) {\n  .ui.tiny.modal {\n    width: 570px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n/* Small */\n\n.ui.small.modal > .header:not(.ui) {\n  font-size: 1.3em;\n}\n\n/* Small Modal Width */\n\n@media only screen and (width < 768px) {\n  .ui.small.modal {\n    width: 95%;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 768px) {\n  .ui.small.modal {\n    width: 70.4%;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 992px) {\n  .ui.small.modal {\n    width: 680px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 1200px) {\n  .ui.small.modal {\n    width: 720px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 1920px) {\n  .ui.small.modal {\n    width: 760px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n/* Large Modal Width */\n\n.ui.large.modal > .header {\n  font-size: 1.6em;\n}\n\n@media only screen and (width < 768px) {\n  .ui.large.modal {\n    width: 95%;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 768px) {\n  .ui.large.modal {\n    width: 88%;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 992px) {\n  .ui.large.modal {\n    width: 1020px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 1200px) {\n  .ui.large.modal {\n    width: 1080px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n@media only screen and (width >= 1920px) {\n  .ui.large.modal {\n    width: 1140px;\n    margin: 0em 0em 0em 0em;\n  }\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Nag\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Nag\n*******************************/\n\n.ui.nag {\n  display: none;\n  opacity: 0.95;\n  position: relative;\n  top: 0em;\n  left: 0px;\n  z-index: 999;\n  min-height: 0em;\n  width: 100%;\n  margin: 0em;\n  padding: 0.75em 1em;\n  background: #555555;\n  -webkit-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.2);\n  box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.2);\n  font-size: 1rem;\n  text-align: center;\n  color: rgba(0, 0, 0, 0.87);\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n  -webkit-transition: 0.2s background ease;\n  transition: 0.2s background ease;\n}\n\na.ui.nag {\n  cursor: pointer;\n}\n\n.ui.nag > .title {\n  display: inline-block;\n  margin: 0em 0.5em;\n  color: #ffffff;\n}\n\n.ui.nag > .close.icon {\n  cursor: pointer;\n  opacity: 0.4;\n  position: absolute;\n  top: 50%;\n  right: 1em;\n  font-size: 1em;\n  margin: -0.5em 0em 0em;\n  color: #ffffff;\n  -webkit-transition: opacity 0.2s ease;\n  transition: opacity 0.2s ease;\n}\n\n/*******************************\n            States\n*******************************/\n\n/* Hover */\n\n.ui.nag:hover {\n  background: #555555;\n  opacity: 1;\n}\n\n.ui.nag .close:hover {\n  opacity: 1;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Static\n---------------*/\n\n.ui.overlay.nag {\n  position: absolute;\n  display: block;\n}\n\n/*--------------\n    Fixed\n---------------*/\n\n.ui.fixed.nag {\n  position: fixed;\n}\n\n/*--------------\n    Bottom\n---------------*/\n\n.ui.bottom.nags,\n.ui.bottom.nag {\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n  top: auto;\n  bottom: 0em;\n}\n\n/*--------------\n    White\n---------------*/\n\n.ui.inverted.nags .nag,\n.ui.inverted.nag {\n  background-color: #f3f4f5;\n  color: rgba(0, 0, 0, 0.85);\n}\n\n.ui.inverted.nags .nag .close,\n.ui.inverted.nags .nag .title,\n.ui.inverted.nag .close,\n.ui.inverted.nag .title {\n  color: rgba(0, 0, 0, 0.4);\n}\n\n/*******************************\n          Groups\n*******************************/\n\n.ui.nags .nag {\n  border-radius: 0em !important;\n}\n\n.ui.nags .nag:last-child {\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n.ui.bottom.nags .nag:last-child {\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        User Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Popup\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Popup\n*******************************/\n\n.ui.popup {\n  display: none;\n  position: absolute;\n  top: 0px;\n  right: 0px;\n  /* Fixes content being squished when inline (moz only) */\n  min-width: -webkit-min-content;\n  min-width: -moz-min-content;\n  min-width: min-content;\n  z-index: 1900;\n  border: 1px solid #d4d4d5;\n  line-height: 1.4285em;\n  max-width: 250px;\n  background: #ffffff;\n  padding: 0.833em 1em;\n  font-weight: normal;\n  font-style: normal;\n  color: rgba(0, 0, 0, 0.87);\n  border-radius: 0.28571429rem;\n  -webkit-box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n}\n\n.ui.popup > .header {\n  padding: 0em;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-size: 1.14285714em;\n  line-height: 1.2;\n  font-weight: bold;\n}\n\n.ui.popup > .header + .content {\n  padding-top: 0.5em;\n}\n\n.ui.popup:before {\n  position: absolute;\n  content: \"\";\n  width: 0.71428571em;\n  height: 0.71428571em;\n  background: #ffffff;\n  -webkit-transform: rotate(45deg);\n  transform: rotate(45deg);\n  z-index: 2;\n  -webkit-box-shadow: 1px 1px 0px 0px #bababc;\n  box-shadow: 1px 1px 0px 0px #bababc;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*--------------\n    Tooltip\n---------------*/\n\n/* Content */\n\n[data-tooltip] {\n  position: relative;\n}\n\n/* Arrow */\n\n[data-tooltip]:before {\n  pointer-events: none;\n  position: absolute;\n  content: \"\";\n  font-size: 1rem;\n  width: 0.71428571em;\n  height: 0.71428571em;\n  background: #ffffff;\n  -webkit-transform: rotate(45deg);\n  transform: rotate(45deg);\n  z-index: 2;\n  -webkit-box-shadow: 1px 1px 0px 0px #bababc;\n  box-shadow: 1px 1px 0px 0px #bababc;\n}\n\n/* Popup */\n\n[data-tooltip]:after {\n  pointer-events: none;\n  content: attr(data-tooltip);\n  position: absolute;\n  text-transform: none;\n  text-align: left;\n  white-space: nowrap;\n  font-size: 1rem;\n  border: 1px solid #d4d4d5;\n  line-height: 1.4285em;\n  max-width: none;\n  background: #ffffff;\n  padding: 0.833em 1em;\n  font-weight: normal;\n  font-style: normal;\n  color: rgba(0, 0, 0, 0.87);\n  border-radius: 0.28571429rem;\n  -webkit-box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n  z-index: 1;\n}\n\n/* Default Position (Top Center) */\n\n[data-tooltip]:not([data-position]):before {\n  top: auto;\n  right: auto;\n  bottom: 100%;\n  left: 50%;\n  background: #ffffff;\n  margin-left: -0.07142857rem;\n  margin-bottom: 0.14285714rem;\n}\n\n[data-tooltip]:not([data-position]):after {\n  left: 50%;\n  -webkit-transform: translateX(-50%);\n  transform: translateX(-50%);\n  bottom: 100%;\n  margin-bottom: 0.5em;\n}\n\n/* Animation */\n\n[data-tooltip]:before,\n[data-tooltip]:after {\n  pointer-events: none;\n  visibility: hidden;\n}\n\n[data-tooltip]:before {\n  opacity: 0;\n  -webkit-transform: rotate(45deg) scale(0) !important;\n  transform: rotate(45deg) scale(0) !important;\n  -webkit-transform-origin: center top;\n  transform-origin: center top;\n  -webkit-transition: all 0.1s ease;\n  transition: all 0.1s ease;\n}\n\n[data-tooltip]:after {\n  opacity: 1;\n  -webkit-transform-origin: center bottom;\n  transform-origin: center bottom;\n  -webkit-transition: all 0.1s ease;\n  transition: all 0.1s ease;\n}\n\n[data-tooltip]:hover:before,\n[data-tooltip]:hover:after {\n  visibility: visible;\n  pointer-events: auto;\n}\n\n[data-tooltip]:hover:before {\n  -webkit-transform: rotate(45deg) scale(1) !important;\n  transform: rotate(45deg) scale(1) !important;\n  opacity: 1;\n}\n\n/* Animation Position */\n\n[data-tooltip]:after,\n[data-tooltip][data-position=\"top center\"]:after,\n[data-tooltip][data-position=\"bottom center\"]:after {\n  -webkit-transform: translateX(-50%) scale(0) !important;\n  transform: translateX(-50%) scale(0) !important;\n}\n\n[data-tooltip]:hover:after,\n[data-tooltip][data-position=\"bottom center\"]:hover:after {\n  -webkit-transform: translateX(-50%) scale(1) !important;\n  transform: translateX(-50%) scale(1) !important;\n}\n\n[data-tooltip][data-position=\"left center\"]:after,\n[data-tooltip][data-position=\"right center\"]:after {\n  -webkit-transform: translateY(-50%) scale(0) !important;\n  transform: translateY(-50%) scale(0) !important;\n}\n\n[data-tooltip][data-position=\"left center\"]:hover:after,\n[data-tooltip][data-position=\"right center\"]:hover:after {\n  -webkit-transform: translateY(-50%) scale(1) !important;\n  transform: translateY(-50%) scale(1) !important;\n}\n\n[data-tooltip][data-position=\"top left\"]:after,\n[data-tooltip][data-position=\"top right\"]:after,\n[data-tooltip][data-position=\"bottom left\"]:after,\n[data-tooltip][data-position=\"bottom right\"]:after {\n  -webkit-transform: scale(0) !important;\n  transform: scale(0) !important;\n}\n\n[data-tooltip][data-position=\"top left\"]:hover:after,\n[data-tooltip][data-position=\"top right\"]:hover:after,\n[data-tooltip][data-position=\"bottom left\"]:hover:after,\n[data-tooltip][data-position=\"bottom right\"]:hover:after {\n  -webkit-transform: scale(1) !important;\n  transform: scale(1) !important;\n}\n\n/*--------------\n    Inverted\n---------------*/\n\n/* Arrow */\n\n[data-tooltip][data-inverted]:before {\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n}\n\n/* Arrow Position */\n\n[data-tooltip][data-inverted]:before {\n  background: #1b1c1d;\n}\n\n/* Popup  */\n\n[data-tooltip][data-inverted]:after {\n  background: #1b1c1d;\n  color: #ffffff;\n  border: none;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n[data-tooltip][data-inverted]:after .header {\n  background-color: none;\n  color: #ffffff;\n}\n\n/*--------------\n    Position\n---------------*/\n\n/* Top Center */\n\n[data-position=\"top center\"][data-tooltip]:after {\n  top: auto;\n  right: auto;\n  left: 50%;\n  bottom: 100%;\n  -webkit-transform: translateX(-50%);\n  transform: translateX(-50%);\n  margin-bottom: 0.5em;\n}\n\n[data-position=\"top center\"][data-tooltip]:before {\n  top: auto;\n  right: auto;\n  bottom: 100%;\n  left: 50%;\n  background: #ffffff;\n  margin-left: -0.07142857rem;\n  margin-bottom: 0.14285714rem;\n}\n\n/* Top Left */\n\n[data-position=\"top left\"][data-tooltip]:after {\n  top: auto;\n  right: auto;\n  left: 0;\n  bottom: 100%;\n  margin-bottom: 0.5em;\n}\n\n[data-position=\"top left\"][data-tooltip]:before {\n  top: auto;\n  right: auto;\n  bottom: 100%;\n  left: 1em;\n  margin-left: -0.07142857rem;\n  margin-bottom: 0.14285714rem;\n}\n\n/* Top Right */\n\n[data-position=\"top right\"][data-tooltip]:after {\n  top: auto;\n  left: auto;\n  right: 0;\n  bottom: 100%;\n  margin-bottom: 0.5em;\n}\n\n[data-position=\"top right\"][data-tooltip]:before {\n  top: auto;\n  left: auto;\n  bottom: 100%;\n  right: 1em;\n  margin-left: -0.07142857rem;\n  margin-bottom: 0.14285714rem;\n}\n\n/* Bottom Center */\n\n[data-position=\"bottom center\"][data-tooltip]:after {\n  bottom: auto;\n  right: auto;\n  left: 50%;\n  top: 100%;\n  -webkit-transform: translateX(-50%);\n  transform: translateX(-50%);\n  margin-top: 0.5em;\n}\n\n[data-position=\"bottom center\"][data-tooltip]:before {\n  bottom: auto;\n  right: auto;\n  top: 100%;\n  left: 50%;\n  margin-left: -0.07142857rem;\n  margin-top: 0.14285714rem;\n}\n\n/* Bottom Left */\n\n[data-position=\"bottom left\"][data-tooltip]:after {\n  left: 0;\n  top: 100%;\n  margin-top: 0.5em;\n}\n\n[data-position=\"bottom left\"][data-tooltip]:before {\n  bottom: auto;\n  right: auto;\n  top: 100%;\n  left: 1em;\n  margin-left: -0.07142857rem;\n  margin-top: 0.14285714rem;\n}\n\n/* Bottom Right */\n\n[data-position=\"bottom right\"][data-tooltip]:after {\n  right: 0;\n  top: 100%;\n  margin-top: 0.5em;\n}\n\n[data-position=\"bottom right\"][data-tooltip]:before {\n  bottom: auto;\n  left: auto;\n  top: 100%;\n  right: 1em;\n  margin-left: -0.14285714rem;\n  margin-top: 0.07142857rem;\n}\n\n/* Left Center */\n\n[data-position=\"left center\"][data-tooltip]:after {\n  right: 100%;\n  top: 50%;\n  margin-right: 0.5em;\n  -webkit-transform: translateY(-50%);\n  transform: translateY(-50%);\n}\n\n[data-position=\"left center\"][data-tooltip]:before {\n  right: 100%;\n  top: 50%;\n  margin-top: -0.14285714rem;\n  margin-right: -0.07142857rem;\n}\n\n/* Right Center */\n\n[data-position=\"right center\"][data-tooltip]:after {\n  left: 100%;\n  top: 50%;\n  margin-left: 0.5em;\n  -webkit-transform: translateY(-50%);\n  transform: translateY(-50%);\n}\n\n[data-position=\"right center\"][data-tooltip]:before {\n  left: 100%;\n  top: 50%;\n  margin-top: -0.07142857rem;\n  margin-left: 0.14285714rem;\n}\n\n/* Arrow */\n\n[data-position ~ =\"bottom\"][data-tooltip]:before {\n  background: #ffffff;\n  -webkit-box-shadow: -1px -1px 0px 0px #bababc;\n  box-shadow: -1px -1px 0px 0px #bababc;\n}\n\n[data-position=\"left center\"][data-tooltip]:before {\n  background: #ffffff;\n  -webkit-box-shadow: 1px -1px 0px 0px #bababc;\n  box-shadow: 1px -1px 0px 0px #bababc;\n}\n\n[data-position=\"right center\"][data-tooltip]:before {\n  background: #ffffff;\n  -webkit-box-shadow: -1px 1px 0px 0px #bababc;\n  box-shadow: -1px 1px 0px 0px #bababc;\n}\n\n[data-position ~ =\"top\"][data-tooltip]:before {\n  background: #ffffff;\n}\n\n/* Inverted Arrow Color */\n\n[data-inverted][data-position ~ =\"bottom\"][data-tooltip]:before {\n  background: #1b1c1d;\n  -webkit-box-shadow: -1px -1px 0px 0px #bababc;\n  box-shadow: -1px -1px 0px 0px #bababc;\n}\n\n[data-inverted][data-position=\"left center\"][data-tooltip]:before {\n  background: #1b1c1d;\n  -webkit-box-shadow: 1px -1px 0px 0px #bababc;\n  box-shadow: 1px -1px 0px 0px #bababc;\n}\n\n[data-inverted][data-position=\"right center\"][data-tooltip]:before {\n  background: #1b1c1d;\n  -webkit-box-shadow: -1px 1px 0px 0px #bababc;\n  box-shadow: -1px 1px 0px 0px #bababc;\n}\n\n[data-inverted][data-position ~ =\"top\"][data-tooltip]:before {\n  background: #1b1c1d;\n}\n\n[data-position ~ =\"bottom\"][data-tooltip]:before {\n  -webkit-transform-origin: center bottom;\n  transform-origin: center bottom;\n}\n\n[data-position ~ =\"bottom\"][data-tooltip]:after {\n  -webkit-transform-origin: center top;\n  transform-origin: center top;\n}\n\n[data-position=\"left center\"][data-tooltip]:before {\n  -webkit-transform-origin: top center;\n  transform-origin: top center;\n}\n\n[data-position=\"left center\"][data-tooltip]:after {\n  -webkit-transform-origin: right center;\n  transform-origin: right center;\n}\n\n[data-position=\"right center\"][data-tooltip]:before {\n  -webkit-transform-origin: right center;\n  transform-origin: right center;\n}\n\n[data-position=\"right center\"][data-tooltip]:after {\n  -webkit-transform-origin: left center;\n  transform-origin: left center;\n}\n\n/*--------------\n    Spacing\n---------------*/\n\n.ui.popup {\n  margin: 0em;\n}\n\n/* Extending from Top */\n\n.ui.top.popup {\n  margin: 0em 0em 0.71428571em;\n}\n\n.ui.top.left.popup {\n  -webkit-transform-origin: left bottom;\n  transform-origin: left bottom;\n}\n\n.ui.top.center.popup {\n  -webkit-transform-origin: center bottom;\n  transform-origin: center bottom;\n}\n\n.ui.top.right.popup {\n  -webkit-transform-origin: right bottom;\n  transform-origin: right bottom;\n}\n\n/* Extending from Vertical Center */\n\n.ui.left.center.popup {\n  margin: 0em 0.71428571em 0em 0em;\n  -webkit-transform-origin: right 50%;\n  transform-origin: right 50%;\n}\n\n.ui.right.center.popup {\n  margin: 0em 0em 0em 0.71428571em;\n  -webkit-transform-origin: left 50%;\n  transform-origin: left 50%;\n}\n\n/* Extending from Bottom */\n\n.ui.bottom.popup {\n  margin: 0.71428571em 0em 0em;\n}\n\n.ui.bottom.left.popup {\n  -webkit-transform-origin: left top;\n  transform-origin: left top;\n}\n\n.ui.bottom.center.popup {\n  -webkit-transform-origin: center top;\n  transform-origin: center top;\n}\n\n.ui.bottom.right.popup {\n  -webkit-transform-origin: right top;\n  transform-origin: right top;\n}\n\n/*--------------\n    Pointer\n---------------*/\n\n/*--- Below ---*/\n\n.ui.bottom.center.popup:before {\n  margin-left: -0.30714286em;\n  top: -0.30714286em;\n  left: 50%;\n  right: auto;\n  bottom: auto;\n  -webkit-box-shadow: -1px -1px 0px 0px #bababc;\n  box-shadow: -1px -1px 0px 0px #bababc;\n}\n\n.ui.bottom.left.popup {\n  margin-left: 0em;\n}\n\n/*rtl:rename*/\n\n.ui.bottom.left.popup:before {\n  top: -0.30714286em;\n  left: 1em;\n  right: auto;\n  bottom: auto;\n  margin-left: 0em;\n  -webkit-box-shadow: -1px -1px 0px 0px #bababc;\n  box-shadow: -1px -1px 0px 0px #bababc;\n}\n\n.ui.bottom.right.popup {\n  margin-right: 0em;\n}\n\n/*rtl:rename*/\n\n.ui.bottom.right.popup:before {\n  top: -0.30714286em;\n  right: 1em;\n  bottom: auto;\n  left: auto;\n  margin-left: 0em;\n  -webkit-box-shadow: -1px -1px 0px 0px #bababc;\n  box-shadow: -1px -1px 0px 0px #bababc;\n}\n\n/*--- Above ---*/\n\n.ui.top.center.popup:before {\n  top: auto;\n  right: auto;\n  bottom: -0.30714286em;\n  left: 50%;\n  margin-left: -0.30714286em;\n}\n\n.ui.top.left.popup {\n  margin-left: 0em;\n}\n\n/*rtl:rename*/\n\n.ui.top.left.popup:before {\n  bottom: -0.30714286em;\n  left: 1em;\n  top: auto;\n  right: auto;\n  margin-left: 0em;\n}\n\n.ui.top.right.popup {\n  margin-right: 0em;\n}\n\n/*rtl:rename*/\n\n.ui.top.right.popup:before {\n  bottom: -0.30714286em;\n  right: 1em;\n  top: auto;\n  left: auto;\n  margin-left: 0em;\n}\n\n/*--- Left Center ---*/\n\n/*rtl:rename*/\n\n.ui.left.center.popup:before {\n  top: 50%;\n  right: -0.30714286em;\n  bottom: auto;\n  left: auto;\n  margin-top: -0.30714286em;\n  -webkit-box-shadow: 1px -1px 0px 0px #bababc;\n  box-shadow: 1px -1px 0px 0px #bababc;\n}\n\n/*--- Right Center  ---*/\n\n/*rtl:rename*/\n\n.ui.right.center.popup:before {\n  top: 50%;\n  left: -0.30714286em;\n  bottom: auto;\n  right: auto;\n  margin-top: -0.30714286em;\n  -webkit-box-shadow: -1px 1px 0px 0px #bababc;\n  box-shadow: -1px 1px 0px 0px #bababc;\n}\n\n/* Arrow Color By Location */\n\n.ui.bottom.popup:before {\n  background: #ffffff;\n}\n\n.ui.right.center.popup:before,\n.ui.left.center.popup:before {\n  background: #ffffff;\n}\n\n.ui.top.popup:before {\n  background: #ffffff;\n}\n\n/* Inverted Arrow Color */\n\n.ui.inverted.bottom.popup:before {\n  background: #1b1c1d;\n}\n\n.ui.inverted.right.center.popup:before,\n.ui.inverted.left.center.popup:before {\n  background: #1b1c1d;\n}\n\n.ui.inverted.top.popup:before {\n  background: #1b1c1d;\n}\n\n/*******************************\n            Coupling\n*******************************/\n\n/* Immediate Nested Grid */\n\n.ui.popup > .ui.grid:not(.padded) {\n  width: calc(100% + 1.75rem);\n  margin: -0.7rem -0.875rem;\n}\n\n/*******************************\n            States\n*******************************/\n\n.ui.loading.popup {\n  display: block;\n  visibility: hidden;\n  z-index: -1;\n}\n\n.ui.animating.popup,\n.ui.visible.popup {\n  display: block;\n}\n\n.ui.visible.popup {\n  -webkit-transform: translateZ(0px);\n  transform: translateZ(0px);\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n}\n\n/*******************************\n            Variations\n*******************************/\n\n/*--------------\n    Basic\n---------------*/\n\n.ui.basic.popup:before {\n  display: none;\n}\n\n/*--------------\n    Wide\n---------------*/\n\n.ui.wide.popup {\n  max-width: 350px;\n}\n\n.ui[class*=\"very wide\"].popup {\n  max-width: 550px;\n}\n\n@media only screen and (width < 768px) {\n\n  .ui.wide.popup,\n  .ui[class*=\"very wide\"].popup {\n    max-width: 250px;\n  }\n}\n\n/*--------------\n    Fluid\n---------------*/\n\n.ui.fluid.popup {\n  width: 100%;\n  max-width: none;\n}\n\n/*--------------\n    Colors\n---------------*/\n\n/* Inverted colors  */\n\n.ui.inverted.popup {\n  background: #1b1c1d;\n  color: #ffffff;\n  border: none;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.inverted.popup .header {\n  background-color: none;\n  color: #ffffff;\n}\n\n.ui.inverted.popup:before {\n  background-color: #1b1c1d;\n  -webkit-box-shadow: none !important;\n  box-shadow: none !important;\n}\n\n/*--------------\n    Flowing\n---------------*/\n\n.ui.flowing.popup {\n  max-width: none;\n}\n\n/*--------------\n    Sizes\n---------------*/\n\n.ui.mini.popup {\n  font-size: 0.78571429rem;\n}\n\n.ui.tiny.popup {\n  font-size: 0.85714286rem;\n}\n\n.ui.small.popup {\n  font-size: 0.92857143rem;\n}\n\n.ui.popup {\n  font-size: 1rem;\n}\n\n.ui.large.popup {\n  font-size: 1.14285714rem;\n}\n\n.ui.huge.popup {\n  font-size: 1.42857143rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        User Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Progress Bar\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Progress\n*******************************/\n\n.ui.progress {\n  position: relative;\n  display: block;\n  max-width: 100%;\n  border: none;\n  margin: 1em 0em 2.5em;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  background: rgba(0, 0, 0, 0.1);\n  padding: 0em;\n  border-radius: 0.28571429rem;\n}\n\n.ui.progress:first-child {\n  margin: 0em 0em 2.5em;\n}\n\n.ui.progress:last-child {\n  margin: 0em 0em 1.5em;\n}\n\n/*******************************\n            Content\n*******************************/\n\n/* Activity Bar */\n\n.ui.progress .bar {\n  display: block;\n  line-height: 1;\n  position: relative;\n  width: 0%;\n  min-width: 2em;\n  background: #888888;\n  border-radius: 0.28571429rem;\n  -webkit-transition: width 0.1s ease, background-color 0.1s ease;\n  transition: width 0.1s ease, background-color 0.1s ease;\n}\n\n/* Percent Complete */\n\n.ui.progress .bar > .progress {\n  white-space: nowrap;\n  position: absolute;\n  width: auto;\n  font-size: 0.92857143em;\n  top: 50%;\n  right: 0.5em;\n  left: auto;\n  bottom: auto;\n  color: rgba(255, 255, 255, 0.7);\n  text-shadow: none;\n  margin-top: -0.5em;\n  font-weight: bold;\n  text-align: left;\n}\n\n/* Label */\n\n.ui.progress > .label {\n  position: absolute;\n  width: 100%;\n  font-size: 1em;\n  top: 100%;\n  right: auto;\n  left: 0%;\n  bottom: auto;\n  color: rgba(0, 0, 0, 0.87);\n  font-weight: bold;\n  text-shadow: none;\n  margin-top: 0.2em;\n  text-align: center;\n  -webkit-transition: color 0.4s ease;\n  transition: color 0.4s ease;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/* Indicating */\n\n.ui.indicating.progress[data-percent^=\"1\"] .bar,\n.ui.indicating.progress[data-percent^=\"2\"] .bar {\n  background-color: #d95c5c;\n}\n\n.ui.indicating.progress[data-percent^=\"3\"] .bar {\n  background-color: #efbc72;\n}\n\n.ui.indicating.progress[data-percent^=\"4\"] .bar,\n.ui.indicating.progress[data-percent^=\"5\"] .bar {\n  background-color: #e6bb48;\n}\n\n.ui.indicating.progress[data-percent^=\"6\"] .bar {\n  background-color: #ddc928;\n}\n\n.ui.indicating.progress[data-percent^=\"7\"] .bar,\n.ui.indicating.progress[data-percent^=\"8\"] .bar {\n  background-color: #b4d95c;\n}\n\n.ui.indicating.progress[data-percent^=\"9\"] .bar,\n.ui.indicating.progress[data-percent^=\"100\"] .bar {\n  background-color: #66da81;\n}\n\n/* Indicating Label */\n\n.ui.indicating.progress[data-percent^=\"1\"] .label,\n.ui.indicating.progress[data-percent^=\"2\"] .label {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.indicating.progress[data-percent^=\"3\"] .label {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.indicating.progress[data-percent^=\"4\"] .label,\n.ui.indicating.progress[data-percent^=\"5\"] .label {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.indicating.progress[data-percent^=\"6\"] .label {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.indicating.progress[data-percent^=\"7\"] .label,\n.ui.indicating.progress[data-percent^=\"8\"] .label {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.indicating.progress[data-percent^=\"9\"] .label,\n.ui.indicating.progress[data-percent^=\"100\"] .label {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* Single Digits */\n\n.ui.indicating.progress[data-percent=\"1\"] .bar,\n.ui.indicating.progress[data-percent=\"2\"] .bar,\n.ui.indicating.progress[data-percent=\"3\"] .bar,\n.ui.indicating.progress[data-percent=\"4\"] .bar,\n.ui.indicating.progress[data-percent=\"5\"] .bar,\n.ui.indicating.progress[data-percent=\"6\"] .bar,\n.ui.indicating.progress[data-percent=\"7\"] .bar,\n.ui.indicating.progress[data-percent=\"8\"] .bar,\n.ui.indicating.progress[data-percent=\"9\"] .bar {\n  background-color: #d95c5c;\n}\n\n.ui.indicating.progress[data-percent=\"1\"] .label,\n.ui.indicating.progress[data-percent=\"2\"] .label,\n.ui.indicating.progress[data-percent=\"3\"] .label,\n.ui.indicating.progress[data-percent=\"4\"] .label,\n.ui.indicating.progress[data-percent=\"5\"] .label,\n.ui.indicating.progress[data-percent=\"6\"] .label,\n.ui.indicating.progress[data-percent=\"7\"] .label,\n.ui.indicating.progress[data-percent=\"8\"] .label,\n.ui.indicating.progress[data-percent=\"9\"] .label {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* Indicating Success */\n\n.ui.indicating.progress.success .label {\n  color: #1a531b;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------\n    Success\n---------------*/\n\n.ui.progress.success .bar {\n  background-color: #21ba45 !important;\n}\n\n.ui.progress.success .bar,\n.ui.progress.success .bar::after {\n  -webkit-animation: none !important;\n  animation: none !important;\n}\n\n.ui.progress.success > .label {\n  color: #1a531b;\n}\n\n/*--------------\n    Warning\n---------------*/\n\n.ui.progress.warning .bar {\n  background-color: #f2c037 !important;\n}\n\n.ui.progress.warning .bar,\n.ui.progress.warning .bar::after {\n  -webkit-animation: none !important;\n  animation: none !important;\n}\n\n.ui.progress.warning > .label {\n  color: #794b02;\n}\n\n/*--------------\n    Error\n---------------*/\n\n.ui.progress.error .bar {\n  background-color: #db2828 !important;\n}\n\n.ui.progress.error .bar,\n.ui.progress.error .bar::after {\n  -webkit-animation: none !important;\n  animation: none !important;\n}\n\n.ui.progress.error > .label {\n  color: #912d2b;\n}\n\n/*--------------\n    Active\n---------------*/\n\n.ui.active.progress .bar {\n  position: relative;\n  min-width: 2em;\n}\n\n.ui.active.progress .bar::after {\n  content: \"\";\n  opacity: 0;\n  position: absolute;\n  top: 0px;\n  left: 0px;\n  right: 0px;\n  bottom: 0px;\n  background: #ffffff;\n  border-radius: 0.28571429rem;\n  -webkit-animation: progress-active 2s ease infinite;\n  animation: progress-active 2s ease infinite;\n}\n\n@-webkit-keyframes progress-active {\n  0% {\n    opacity: 0.3;\n    width: 0;\n  }\n\n  100% {\n    opacity: 0;\n    width: 100%;\n  }\n}\n\n@keyframes progress-active {\n  0% {\n    opacity: 0.3;\n    width: 0;\n  }\n\n  100% {\n    opacity: 0;\n    width: 100%;\n  }\n}\n\n/*--------------\n    Disabled\n---------------*/\n\n.ui.disabled.progress {\n  opacity: 0.35;\n}\n\n.ui.disabled.progress .bar,\n.ui.disabled.progress .bar::after {\n  -webkit-animation: none !important;\n  animation: none !important;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Inverted\n---------------*/\n\n.ui.inverted.progress {\n  background: rgba(255, 255, 255, 0.08);\n  border: none;\n}\n\n.ui.inverted.progress .bar {\n  background: #888888;\n}\n\n.ui.inverted.progress .bar > .progress {\n  color: #f9fafb;\n}\n\n.ui.inverted.progress > .label {\n  color: #ffffff;\n}\n\n.ui.inverted.progress.success > .label {\n  color: #21ba45;\n}\n\n.ui.inverted.progress.warning > .label {\n  color: #f2c037;\n}\n\n.ui.inverted.progress.error > .label {\n  color: #db2828;\n}\n\n/*--------------\n    Attached\n---------------*/\n\n/* bottom attached */\n\n.ui.progress.attached {\n  background: transparent;\n  position: relative;\n  border: none;\n  margin: 0em;\n}\n\n.ui.progress.attached,\n.ui.progress.attached .bar {\n  display: block;\n  height: 0.2rem;\n  padding: 0px;\n  overflow: hidden;\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n.ui.progress.attached .bar {\n  border-radius: 0em;\n}\n\n/* top attached */\n\n.ui.progress.top.attached,\n.ui.progress.top.attached .bar {\n  top: 0px;\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n.ui.progress.top.attached .bar {\n  border-radius: 0em;\n}\n\n/* Coupling */\n\n.ui.segment > .ui.attached.progress,\n.ui.card > .ui.attached.progress {\n  position: absolute;\n  top: auto;\n  left: 0;\n  bottom: 100%;\n  width: 100%;\n}\n\n.ui.segment > .ui.bottom.attached.progress,\n.ui.card > .ui.bottom.attached.progress {\n  top: 100%;\n  bottom: auto;\n}\n\n/*--------------\n    Colors\n---------------*/\n\n/* Red */\n\n.ui.red.progress .bar {\n  background-color: #db2828;\n}\n\n.ui.red.inverted.progress .bar {\n  background-color: #ff695e;\n}\n\n/* Orange */\n\n.ui.orange.progress .bar {\n  background-color: #f2711c;\n}\n\n.ui.orange.inverted.progress .bar {\n  background-color: #ff851b;\n}\n\n/* Yellow */\n\n.ui.yellow.progress .bar {\n  background-color: #fbbd08;\n}\n\n.ui.yellow.inverted.progress .bar {\n  background-color: #ffe21f;\n}\n\n/* Olive */\n\n.ui.olive.progress .bar {\n  background-color: #b5cc18;\n}\n\n.ui.olive.inverted.progress .bar {\n  background-color: #d9e778;\n}\n\n/* Green */\n\n.ui.green.progress .bar {\n  background-color: #21ba45;\n}\n\n.ui.green.inverted.progress .bar {\n  background-color: #2ecc40;\n}\n\n/* Teal */\n\n.ui.teal.progress .bar {\n  background-color: #00b5ad;\n}\n\n.ui.teal.inverted.progress .bar {\n  background-color: #6dffff;\n}\n\n/* Blue */\n\n.ui.blue.progress .bar {\n  background-color: #2185d0;\n}\n\n.ui.blue.inverted.progress .bar {\n  background-color: #54c8ff;\n}\n\n/* Violet */\n\n.ui.violet.progress .bar {\n  background-color: #6435c9;\n}\n\n.ui.violet.inverted.progress .bar {\n  background-color: #a291fb;\n}\n\n/* Purple */\n\n.ui.purple.progress .bar {\n  background-color: #a333c8;\n}\n\n.ui.purple.inverted.progress .bar {\n  background-color: #dc73ff;\n}\n\n/* Pink */\n\n.ui.pink.progress .bar {\n  background-color: #e03997;\n}\n\n.ui.pink.inverted.progress .bar {\n  background-color: #ff8edf;\n}\n\n/* Brown */\n\n.ui.brown.progress .bar {\n  background-color: #a5673f;\n}\n\n.ui.brown.inverted.progress .bar {\n  background-color: #d67c1c;\n}\n\n/* Grey */\n\n.ui.grey.progress .bar {\n  background-color: #767676;\n}\n\n.ui.grey.inverted.progress .bar {\n  background-color: #dcddde;\n}\n\n/* Black */\n\n.ui.black.progress .bar {\n  background-color: #1b1c1d;\n}\n\n.ui.black.inverted.progress .bar {\n  background-color: #545454;\n}\n\n/*--------------\n    Sizes\n---------------*/\n\n.ui.tiny.progress {\n  font-size: 0.85714286rem;\n}\n\n.ui.tiny.progress .bar {\n  height: 0.5em;\n}\n\n.ui.small.progress {\n  font-size: 0.92857143rem;\n}\n\n.ui.small.progress .bar {\n  height: 1em;\n}\n\n.ui.progress {\n  font-size: 1rem;\n}\n\n.ui.progress .bar {\n  height: 1.75em;\n}\n\n.ui.large.progress {\n  font-size: 1.14285714rem;\n}\n\n.ui.large.progress .bar {\n  height: 2.5em;\n}\n\n.ui.big.progress {\n  font-size: 1.28571429rem;\n}\n\n.ui.big.progress .bar {\n  height: 3.5em;\n}\n\n/*******************************\n            Progress\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Rating\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n          Rating\n*******************************/\n\n.ui.rating {\n  display: -webkit-inline-box;\n  display: -ms-inline-flexbox;\n  display: inline-flex;\n  white-space: nowrap;\n  vertical-align: baseline;\n}\n\n.ui.rating:last-child {\n  margin-right: 0em;\n}\n\n/* Icon */\n\n.ui.rating .icon {\n  padding: 0em;\n  margin: 0em;\n  text-align: center;\n  font-weight: normal;\n  font-style: normal;\n  -webkit-box-flex: 1;\n  -ms-flex: 1 0 auto;\n  flex: 1 0 auto;\n  cursor: pointer;\n  width: 1.25em;\n  height: auto;\n  -webkit-transition: opacity 0.1s ease, background 0.1s ease,\n    text-shadow 0.1s ease, color 0.1s ease;\n  transition: opacity 0.1s ease, background 0.1s ease, text-shadow 0.1s ease,\n    color 0.1s ease;\n}\n\n/*******************************\n            Types\n*******************************/\n\n/*-------------------\n      Standard\n--------------------*/\n\n/* Inactive Icon */\n\n.ui.rating .icon {\n  background: transparent;\n  color: rgba(0, 0, 0, 0.15);\n}\n\n/* Active Icon */\n\n.ui.rating .active.icon {\n  background: transparent;\n  color: rgba(0, 0, 0, 0.85);\n}\n\n/* Selected Icon */\n\n.ui.rating .icon.selected,\n.ui.rating .icon.selected.active {\n  background: transparent;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/*-------------------\n        Star\n--------------------*/\n\n/* Inactive */\n\n.ui.star.rating .icon {\n  width: 1.25em;\n  height: auto;\n  background: transparent;\n  color: rgba(0, 0, 0, 0.15);\n  text-shadow: none;\n}\n\n/* Active Star */\n\n.ui.star.rating .active.icon {\n  background: transparent !important;\n  color: #ffe623 !important;\n  text-shadow: 0px -1px 0px #ddc507, -1px 0px 0px #ddc507, 0px 1px 0px #ddc507,\n    1px 0px 0px #ddc507 !important;\n}\n\n/* Selected Star */\n\n.ui.star.rating .icon.selected,\n.ui.star.rating .icon.selected.active {\n  background: transparent !important;\n  color: #ffcc00 !important;\n  text-shadow: 0px -1px 0px #e6a200, -1px 0px 0px #e6a200, 0px 1px 0px #e6a200,\n    1px 0px 0px #e6a200 !important;\n}\n\n/*-------------------\n        Heart\n--------------------*/\n\n.ui.heart.rating .icon {\n  width: 1.4em;\n  height: auto;\n  background: transparent;\n  color: rgba(0, 0, 0, 0.15);\n  text-shadow: none !important;\n}\n\n/* Active Heart */\n\n.ui.heart.rating .active.icon {\n  background: transparent !important;\n  color: #ff6d75 !important;\n  text-shadow: 0px -1px 0px #cd0707, -1px 0px 0px #cd0707, 0px 1px 0px #cd0707,\n    1px 0px 0px #cd0707 !important;\n}\n\n/* Selected Heart */\n\n.ui.heart.rating .icon.selected,\n.ui.heart.rating .icon.selected.active {\n  background: transparent !important;\n  color: #ff3000 !important;\n  text-shadow: 0px -1px 0px #aa0101, -1px 0px 0px #aa0101, 0px 1px 0px #aa0101,\n    1px 0px 0px #aa0101 !important;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*-------------------\n      Disabled\n--------------------*/\n\n/* disabled rating */\n\n.ui.disabled.rating .icon {\n  cursor: default;\n}\n\n/*-------------------\n  User Interactive\n--------------------*/\n\n/* Selected Rating */\n\n.ui.rating.selected .active.icon {\n  opacity: 1;\n}\n\n.ui.rating.selected .icon.selected,\n.ui.rating .icon.selected {\n  opacity: 1;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n.ui.mini.rating {\n  font-size: 0.78571429rem;\n}\n\n.ui.tiny.rating {\n  font-size: 0.85714286rem;\n}\n\n.ui.small.rating {\n  font-size: 0.92857143rem;\n}\n\n.ui.rating {\n  font-size: 1rem;\n}\n\n.ui.large.rating {\n  font-size: 1.14285714rem;\n}\n\n.ui.huge.rating {\n  font-size: 1.42857143rem;\n}\n\n.ui.massive.rating {\n  font-size: 2rem;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n@font-face {\n  font-family: \"Rating\";\n  src: url(data:application/x-font-ttf;charset=utf-8;base64,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) format(\"truetype\"),\n    url(data:application/font-woff;charset=utf-8;base64,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kIiRi5MI5vdUFfcni4vCBYufhJx8mn2ZepJ3i3aLeoR9fX18g3qLdwiLVAUO+yaLshWL+AQFi5GNkY+RjpCQj5KNj42PjI+LCPfAiwWPi4+Kj4mRiZCHj4aPhY2Fi4UIi/wEBYuEiYWHhoeGhoeFiIiKhoqHi4GLhI6EkQj7EvcN+xL7DQWEhYOIgouHi4eLh42EjoaPiJCHkImRi5IIDov3XRWLko2Rj5Kltq+vuKW4pbuZvYu9i7t9uHG4ca9npWCPhI2Fi4SLhYmEh4RxYGdoXnAIXnFbflmLWYtbmF6lXqZnrnG2h5KJkouRCLCLFaRkq2yxdLF0tH+4i7iLtJexorGiq6qksm64Z61goZZ3kXaLdItnfm1ycnJybX9oiwhoi22XcqRypH6pi6+LopGglp9gdWdpbl4I9xiwFYuHjIiOiI6IjoqPi4+LjoyOjo2OjY6Lj4ubkJmXl5eWmZGbi4+LjoyOjo2OjY6LjwiLj4mOiY6IjYiNh4tzi3eCenp6eoJ3i3MIDov3XRWLko2Sj5GouK+utqW3pbqYvouci5yJnIgIm6cFjY6NjI+LjIuNi42JjYqOio+JjomOiY6KjomOiY6JjoqNioyKjomMiYuHi4qLiouLCHdnbVVjQ2NDbVV3Zwh9cgWJiIiJiIuJi36SdJiIjYmOi46LjY+UlJlvl3KcdJ90oHeie6WHkYmSi5IIsIsVqlq0Z711CKGzBXqXfpqCnoKdhp6LoIuikaCWn2B1Z2luXgj3GLAVi4eMiI6IjoiOio+Lj4uOjI6OjY6NjouPi5uQmZeXl5aZkZuLj4uOjI6OjY6NjouPCIuPiY6JjoiNiI2Hi3OLd4J6enp6gneLcwji+10VoLAFtI+wmK2hrqKnqKKvdq1wp2uhCJ2rBZ1/nHycepx6mHqWeY+EjYWLhIuEiYWHhIR/gH1+fG9qaXJmeWV5Y4Jhiwi53BXb9yQFjIKMg4uEi3CDc3x1fHV3fHOBCA6L1BWL90sFi5WPlJKSkpKTj5aLCNmLBZKPmJqepJaZlZeVlY+Qj5ONl42WjpeOmI+YkZWTk5OSk46Vi5uLmYiYhZiFlIGSfgiSfo55i3WLeYd5gXgIvosFn4uchJl8mn2Seot3i3qGfIJ9jYSLhYuEi3yIfoR+i4eLh4uHi3eGen99i3CDdnt8CHt8dYNwiwhmiwV5i3mNeY95kHeRc5N1k36Ph4sIOYsFgIuDjoSShJKHlIuVCLCdFYuGjIePiI+Hj4mQi5CLj42Pj46OjY+LkIuQiZCIjoePh42Gi4aLh4mHh4eIioaLhgjUeRWUiwWNi46Lj4qOi4+KjYqOi4+Kj4mQio6KjYqNio+Kj4mQio6KjIqzfquEpIsIrosFr4uemouri5CKkYqQkY6QkI6SjpKNkouSi5KJkoiRlZWQlouYi5CKkImRiZGJj4iOCJGMkI+PlI+UjZKLkouViJODk4SSgo+CiwgmiwWLlpCalJ6UnpCbi5aLnoiYhJSFlH+QeYuGhoeDiYCJf4h/h3+IfoWBg4KHh4SCgH4Ii4qIiYiGh4aIh4mIiIiIh4eGh4aHh4eHiIiHiIeHiIiHiIeKh4mIioiLCIKLi/tLBQ6L90sVi/dLBYuVj5OSk5KSk46WiwjdiwWPi5iPoZOkk6CRnZCdj56Nn4sIq4sFpougg5x8m3yTd4txCIuJBZd8kHuLd4uHi4eLh5J+jn6LfIuEi4SJhZR9kHyLeot3hHp8fH19eoR3iwhYiwWVeI95i3mLdIh6hH6EfoKBfoV+hX2He4uBi4OPg5KFkYaTh5SHlYiTipOKk4qTiJMIiZSIkYiPgZSBl4CaeKR+moSPCD2LBYCLg4+EkoSSh5SLlQiw9zgVi4aMh4+Ij4ePiZCLkIuPjY+Pjo6Nj4uQi5CJkIiOh4+HjYaLhouHiYeHh4iKhouGCNT7OBWUiwWOi46Kj4mPio+IjoiPh4+IjoePiI+Hj4aPho6HjoiNiI6Hj4aOho6Ii4qWfpKDj4YIk4ORgY5+j36OgI1/jYCPg5CGnYuXj5GUkpSOmYuei5aGmoKfgp6GmouWCPCLBZSLlI+SkpOTjpOLlYuSiZKHlIeUho+Fi46PjY+NkY2RjJCLkIuYhpaBlY6RjZKLkgiLkomSiJKIkoaQhY6MkIyRi5CLm4aXgpOBkn6Pe4sIZosFcotrhGN9iouIioaJh4qHiomKiYqIioaKh4mHioiKiYuHioiLh4qIi4mLCIKLi/tLBQ77lIv3txWLkpCPlo0I9yOgzPcWBY6SkI+RiwiL/BL7FUcFh4mHioiLh4uIjImOiY6KjouPi4yLjYyOCKP3IyPwBYaQiZCLjwgOi/fFFYu1l6yjoqOjrZe2i5aLl4mYh5eHloWWhJaElIWShZOEkoWShJKSkpGTkpKRlJGWkgiWkpaRl4+Yj5eNlou2i61/o3OjdJdqi2GLYXVgYGAI+0b7QAWHiIeJhouGi4eNh44I+0b3QAWJjYmNh4+IjoaRg5SElIWVhZSFlYaWh5mGmImYi5gIsIsVi2ucaa9oCPc6+zT3OvczBa+vnK2Lq4ubiZiHl4eXhpSFkoSSg5GCj4KQgo2CjYONgYuBi4KLgIl/hoCGgIWChAiBg4OFhISEhYaFhoaIhoaJhYuFi4aNiJCGkIaRhJGEkoORgZOCkoCRgJB/kICNgosIgYuBi4OJgomCiYKGgoeDhYSEhYSGgod/h3+Jfot7CA77JouyFYv4BAWLkY2Rj5GOkJCPko2PjY+Mj4sI98CLBY+Lj4qPiZGJkIePho+FjYWLhQiL/AQFi4SJhYeGh4aGh4WIiIqGioeLgYuEjoSRCPsS9w37EvsNBYSFg4iCi4eLh4uHjYSOho+IkIeQiZGLkgiwkxX3JvchpHL3DfsIi/f3+7iLi/v3BQ5ni8sVi/c5BYuSjpKQkJCQko6Siwj3VIuLwgWLrpippKSkpKmYrouvi6l+pHKkcpdti2gIi0IFi4aKhoeIh4eHiYaLCHmLBYaLh42Hj4eOipCLkAiL1AWLn4OcfZp9mXqSdot3i3qEfX18fIR6i3cIi1SniwWSi5KIkIaQho6Ei4QIi/s5BYuDiIWGhoaFhImEiwj7p4sFhIuEjYaRhpCIkYuTCA5njPe6FYyQkI6UjQj3I6DM9xYFj5KPj5GLkIuQh4+ECMv7FvcjdgWUiZCIjYaNhoiFhYUIIyak+yMFjIWKhomHiYiIiYaLiIuHjIeNCPsUz/sVRwWHiYeKiIuHi4eNiY6Jj4uQjJEIo/cjI/AFhZGJkY2QCPeB+z0VnILlW3rxiJ6ZmNTS+wydgpxe54v7pwUOZ4vCFYv3SwWLkI2Pjo+Pjo+NkIsI3osFkIuPiY6Ij4eNh4uGCIv7SwWLhomHh4eIh4eKhosIOIsFhouHjIePiI+Jj4uQCLCvFYuGjIePh46IkImQi5CLj42Pjo6PjY+LkIuQiZCIjoePh42Gi4aLhomIh4eIioaLhgjvZxWL90sFi5CNj46Oj4+PjZCLj4ySkJWWlZaVl5SXmJuVl5GRjo6OkI6RjZCNkIyPjI6MkY2TCIySjJGMj4yPjZCOkY6RjpCPjo6Pj42Qi5SLk4qSiZKJkYiPiJCIjoiPho6GjYeMhwiNh4yGjIaMhYuHi4iLiIuHi4eLg4uEiYSJhImFiYeJh4mFh4WLioqJiomJiIqJiokIi4qKiIqJCNqLBZqLmIWWgJaAkH+LfIt6hn2Af46DjYSLhIt9h36Cf4+Bi3+HgImAhYKEhI12hnmAfgh/fXiDcosIZosFfot+jHyOfI5/joOOg41/j32Qc5N8j4SMhouHjYiOh4+Jj4uQCA5ni/c5FYuGjYaOiI+Hj4mQiwjeiwWQi4+Njo+Pjo2Qi5AIi/dKBYuQiZCHjoiPh42Giwg4iwWGi4eJh4eIiImGi4YIi/tKBbD3JhWLkIyPj4+OjpCNkIuQi4+Jj4iOh42Hi4aLhomHiIeHh4eKhouGi4aMiI+Hj4qPi5AI7/snFYv3SwWLkI2Qj46Oj4+NkIuSi5qPo5OZkJePk46TjZeOmo6ajpiMmIsIsIsFpIueg5d9ln6Qeol1koSRgo2Aj4CLgIeAlH+Pfot9i4WJhIiCloCQfIt7i3yFfoGACICAfoZ8iwg8iwWMiIyJi4mMiYyJjYmMiIyKi4mPhI2GjYeNh42GjYOMhIyEi4SLhouHi4iLiYuGioYIioWKhomHioeJh4iGh4eIh4aIh4iFiISJhImDioKLhouHjYiPh4+Ij4iRiJGJkIqPCIqPipGKkomTipGKj4qOiZCJkYiQiJCIjoWSgZZ+nIKXgZaBloGWhJGHi4aLh42HjwiIjomQi48IDviUFPiUFYsMCgAAAAADAgABkAAFAAABTAFmAAAARwFMAWYAAAD1ABkAhAAAAAAAAAAAAAAAAAAAAAEQAAAAAAAAAAAAAAAAAAAAAEAAAPFlAeD/4P/gAeAAIAAAAAEAAAAAAAAAAAAAACAAAAAAAAIAAAADAAAAFAADAAEAAAAUAAQAkAAAACAAIAAEAAAAAQAg5gXwBvAN8CPwLvBu8HDwivCX8JzxI/Fl//3//wAAAAAAIOYA8ATwDPAj8C7wbvBw8Ifwl/Cc8SPxZP/9//8AAf/jGgQQBhABD+wP4g+jD6IPjA+AD3wO9g62AAMAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAEAAJrVlLJfDzz1AAsCAAAAAADP/GODAAAAAM/8Y4MAAP/bAgAB2wAAAAgAAgAAAAAAAAABAAAB4P/gAAACAAAAAAACAAABAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAEAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAdwAAAHcAAACAAAjAZMAHwFJAAABbgAAAgAAAAIAAAACAAAAAgAAAAEAAAACAAAAAW4AAAHcAAAB3AABAdwAAAHcAAAAAFAAABwAAAAAAA4ArgABAAAAAAABAAwAAAABAAAAAAACAA4AQAABAAAAAAADAAwAIgABAAAAAAAEAAwATgABAAAAAAAFABYADAABAAAAAAAGAAYALgABAAAAAAAKADQAWgADAAEECQABAAwAAAADAAEECQACAA4AQAADAAEECQADAAwAIgADAAEECQAEAAwATgADAAEECQAFABYADAADAAEECQAGAAwANAADAAEECQAKADQAWgByAGEAdABpAG4AZwBWAGUAcgBzAGkAbwBuACAAMQAuADAAcgBhAHQAaQBuAGdyYXRpbmcAcgBhAHQAaQBuAGcAUgBlAGcAdQBsAGEAcgByAGEAdABpAG4AZwBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format(\"woff\");\n  font-weight: normal;\n  font-style: normal;\n}\n\n.ui.rating .icon {\n  font-family: \"Rating\";\n  line-height: 1;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  font-weight: normal;\n  font-style: normal;\n  text-align: center;\n}\n\n/* Empty Star */\n\n.ui.rating .icon:before {\n  content: \"\\f005\";\n}\n\n/* Active Star */\n\n.ui.rating .active.icon:before {\n  content: \"\\f005\";\n}\n\n/*-------------------\n        Star\n--------------------*/\n\n/* Unfilled Star */\n\n.ui.star.rating .icon:before {\n  content: \"\\f005\";\n}\n\n/* Active Star */\n\n.ui.star.rating .active.icon:before {\n  content: \"\\f005\";\n}\n\n/* Partial */\n\n.ui.star.rating .partial.icon:before {\n  content: \"\\f006\";\n}\n\n.ui.star.rating .partial.icon {\n  content: \"\\f005\";\n}\n\n/*-------------------\n        Heart\n--------------------*/\n\n/* Empty Heart\n.ui.heart.rating .icon:before {\n  content: '\\f08a';\n}\n*/\n\n.ui.heart.rating .icon:before {\n  content: \"\\f004\";\n}\n\n/* Active */\n\n.ui.heart.rating .active.icon:before {\n  content: \"\\f004\";\n}\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Search\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Search\n*******************************/\n\n.ui.search {\n  position: relative;\n}\n\n.ui.search > .prompt {\n  margin: 0em;\n  outline: none;\n  -webkit-appearance: none;\n  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);\n  text-shadow: none;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.21428571em;\n  padding: 0.67857143em 1em;\n  font-size: 1em;\n  background: #ffffff;\n  border: 1px solid rgba(34, 36, 38, 0.15);\n  color: rgba(0, 0, 0, 0.87);\n  -webkit-box-shadow: 0em 0em 0em 0em transparent inset;\n  box-shadow: 0em 0em 0em 0em transparent inset;\n  -webkit-transition: background-color 0.1s ease, color 0.1s ease,\n    border-color 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: background-color 0.1s ease, color 0.1s ease,\n    border-color 0.1s ease, -webkit-box-shadow 0.1s ease;\n  transition: background-color 0.1s ease, color 0.1s ease, box-shadow 0.1s ease,\n    border-color 0.1s ease;\n  transition: background-color 0.1s ease, color 0.1s ease, box-shadow 0.1s ease,\n    border-color 0.1s ease, -webkit-box-shadow 0.1s ease;\n}\n\n.ui.search .prompt {\n  border-radius: 500rem;\n}\n\n/*--------------\n    Icon\n---------------*/\n\n.ui.search .prompt ~ .search.icon {\n  cursor: pointer;\n}\n\n/*--------------\n    Results\n---------------*/\n\n.ui.search > .results {\n  display: none;\n  position: absolute;\n  top: 100%;\n  left: 0%;\n  -webkit-transform-origin: center top;\n  transform-origin: center top;\n  white-space: normal;\n  text-align: left;\n  text-transform: none;\n  background: #ffffff;\n  margin-top: 0.5em;\n  width: 18em;\n  border-radius: 0.28571429rem;\n  -webkit-box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12),\n    0px 2px 10px 0px rgba(34, 36, 38, 0.15);\n  border: 1px solid #d4d4d5;\n  z-index: 998;\n}\n\n.ui.search > .results > :first-child {\n  border-radius: 0.28571429rem 0.28571429rem 0em 0em;\n}\n\n.ui.search > .results > :last-child {\n  border-radius: 0em 0em 0.28571429rem 0.28571429rem;\n}\n\n/*--------------\n    Result\n---------------*/\n\n.ui.search > .results .result {\n  cursor: pointer;\n  display: block;\n  overflow: hidden;\n  font-size: 1em;\n  padding: 0.85714286em 1.14285714em;\n  color: rgba(0, 0, 0, 0.87);\n  line-height: 1.33;\n  border-bottom: 1px solid rgba(34, 36, 38, 0.1);\n}\n\n.ui.search > .results .result:last-child {\n  border-bottom: none !important;\n}\n\n/* Image */\n\n.ui.search > .results .result .image {\n  float: right;\n  overflow: hidden;\n  background: none;\n  width: 5em;\n  height: 3em;\n  border-radius: 0.25em;\n}\n\n.ui.search > .results .result .image img {\n  display: block;\n  width: auto;\n  height: 100%;\n}\n\n/*--------------\n      Info\n---------------*/\n\n.ui.search > .results .result .image + .content {\n  margin: 0em 6em 0em 0em;\n}\n\n.ui.search > .results .result .title {\n  margin: -0.14285714em 0em 0em;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-weight: bold;\n  font-size: 1em;\n  color: rgba(0, 0, 0, 0.85);\n}\n\n.ui.search > .results .result .description {\n  margin-top: 0;\n  font-size: 0.92857143em;\n  color: rgba(0, 0, 0, 0.4);\n}\n\n.ui.search > .results .result .price {\n  float: right;\n  color: #21ba45;\n}\n\n/*--------------\n    Message\n---------------*/\n\n.ui.search > .results > .message {\n  padding: 1em 1em;\n}\n\n.ui.search > .results > .message .header {\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-size: 1rem;\n  font-weight: bold;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.search > .results > .message .description {\n  margin-top: 0.25rem;\n  font-size: 1em;\n  color: rgba(0, 0, 0, 0.87);\n}\n\n/* View All Results */\n\n.ui.search > .results > .action {\n  display: block;\n  border-top: none;\n  background: #f3f4f5;\n  padding: 0.92857143em 1em;\n  color: rgba(0, 0, 0, 0.87);\n  font-weight: bold;\n  text-align: center;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------------\n      Focus\n---------------------*/\n\n.ui.search > .prompt:focus {\n  border-color: rgba(34, 36, 38, 0.35);\n  background: #ffffff;\n  color: rgba(0, 0, 0, 0.95);\n}\n\n/*--------------------\n      Loading\n---------------------*/\n\n.ui.loading.search .input > i.icon:before {\n  position: absolute;\n  content: \"\";\n  top: 50%;\n  left: 50%;\n  margin: -0.64285714em 0em 0em -0.64285714em;\n  width: 1.28571429em;\n  height: 1.28571429em;\n  border-radius: 500rem;\n  border: 0.2em solid rgba(0, 0, 0, 0.1);\n}\n\n.ui.loading.search .input > i.icon:after {\n  position: absolute;\n  content: \"\";\n  top: 50%;\n  left: 50%;\n  margin: -0.64285714em 0em 0em -0.64285714em;\n  width: 1.28571429em;\n  height: 1.28571429em;\n  -webkit-animation: button-spin 0.6s linear;\n  animation: button-spin 0.6s linear;\n  -webkit-animation-iteration-count: infinite;\n  animation-iteration-count: infinite;\n  border-radius: 500rem;\n  border-color: #767676 transparent transparent;\n  border-style: solid;\n  border-width: 0.2em;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent;\n  box-shadow: 0px 0px 0px 1px transparent;\n}\n\n/*--------------\n      Hover\n---------------*/\n\n.ui.search > .results .result:hover,\n.ui.category.search > .results .category .result:hover {\n  background: #f9fafb;\n}\n\n.ui.search .action:hover {\n  background: #e0e0e0;\n}\n\n/*--------------\n      Active\n---------------*/\n\n.ui.category.search > .results .category.active {\n  background: #f3f4f5;\n}\n\n.ui.category.search > .results .category.active > .name {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.search > .results .result.active,\n.ui.category.search > .results .category .result.active {\n  position: relative;\n  border-left-color: rgba(34, 36, 38, 0.1);\n  background: #f3f4f5;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n\n.ui.search > .results .result.active .title {\n  color: rgba(0, 0, 0, 0.85);\n}\n\n.ui.search > .results .result.active .description {\n  color: rgba(0, 0, 0, 0.85);\n}\n\n/*--------------------\n        Disabled\n----------------------*/\n\n/* Disabled */\n\n.ui.disabled.search {\n  cursor: default;\n  pointer-events: none;\n  opacity: 0.45;\n}\n\n/*******************************\n          Types\n*******************************/\n\n/*--------------\n    Selection\n---------------*/\n\n.ui.search.selection .prompt {\n  border-radius: 0.28571429rem;\n}\n\n/* Remove input */\n\n.ui.search.selection > .icon.input > .remove.icon {\n  pointer-events: none;\n  position: absolute;\n  left: auto;\n  opacity: 0;\n  color: \"\";\n  top: 0em;\n  right: 0em;\n  -webkit-transition: color 0.1s ease, opacity 0.1s ease;\n  transition: color 0.1s ease, opacity 0.1s ease;\n}\n\n.ui.search.selection > .icon.input > .active.remove.icon {\n  cursor: pointer;\n  opacity: 0.8;\n  pointer-events: auto;\n}\n\n.ui.search.selection > .icon.input:not([class*=\"left icon\"]) > .icon ~ .remove.icon {\n  right: 1.85714em;\n}\n\n.ui.search.selection > .icon.input > .remove.icon:hover {\n  opacity: 1;\n  color: #db2828;\n}\n\n/*--------------\n    Category\n---------------*/\n\n.ui.category.search .results {\n  width: 28em;\n}\n\n.ui.category.search .results.animating,\n.ui.category.search .results.visible {\n  display: table;\n}\n\n/* Category */\n\n.ui.category.search > .results .category {\n  display: table-row;\n  background: #f3f4f5;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  -webkit-transition: background 0.1s ease, border-color 0.1s ease;\n  transition: background 0.1s ease, border-color 0.1s ease;\n}\n\n/* Last Category */\n\n.ui.category.search > .results .category:last-child {\n  border-bottom: none;\n}\n\n/* First / Last */\n\n.ui.category.search > .results .category:first-child .name + .result {\n  border-radius: 0em 0.28571429rem 0em 0em;\n}\n\n.ui.category.search > .results .category:last-child .result:last-child {\n  border-radius: 0em 0em 0.28571429rem 0em;\n}\n\n/* Category Result Name */\n\n.ui.category.search > .results .category > .name {\n  display: table-cell;\n  text-overflow: ellipsis;\n  width: 100px;\n  white-space: nowrap;\n  background: transparent;\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  font-size: 1em;\n  padding: 0.4em 1em;\n  font-weight: bold;\n  color: rgba(0, 0, 0, 0.4);\n  border-bottom: 1px solid rgba(34, 36, 38, 0.1);\n}\n\n/* Category Result */\n\n.ui.category.search > .results .category .results {\n  display: table-cell;\n  background: #ffffff;\n  border-left: 1px solid rgba(34, 36, 38, 0.15);\n  border-bottom: 1px solid rgba(34, 36, 38, 0.1);\n}\n\n.ui.category.search > .results .category .result {\n  border-bottom: 1px solid rgba(34, 36, 38, 0.1);\n  -webkit-transition: background 0.1s ease, border-color 0.1s ease;\n  transition: background 0.1s ease, border-color 0.1s ease;\n  padding: 0.85714286em 1.14285714em;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n/*-------------------\n    Left / Right\n--------------------*/\n\n.ui[class*=\"left aligned\"].search > .results {\n  right: auto;\n  left: 0%;\n}\n\n.ui[class*=\"right aligned\"].search > .results {\n  right: 0%;\n  left: auto;\n}\n\n/*--------------\n    Fluid\n---------------*/\n\n.ui.fluid.search .results {\n  width: 100%;\n}\n\n/*--------------\n      Sizes\n---------------*/\n\n.ui.mini.search {\n  font-size: 0.78571429em;\n}\n\n.ui.small.search {\n  font-size: 0.92857143em;\n}\n\n.ui.search {\n  font-size: 1em;\n}\n\n.ui.large.search {\n  font-size: 1.14285714em;\n}\n\n.ui.big.search {\n  font-size: 1.28571429em;\n}\n\n.ui.huge.search {\n  font-size: 1.42857143em;\n}\n\n.ui.massive.search {\n  font-size: 1.71428571em;\n}\n\n/*--------------\n      Mobile\n---------------*/\n\n@media only screen and (width < 768px) {\n  .ui.search .results {\n    max-width: calc(100vw - 2rem);\n  }\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Shape\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n              Shape\n*******************************/\n\n.ui.shape {\n  position: relative;\n  vertical-align: top;\n  display: inline-block;\n  -webkit-perspective: 2000px;\n  perspective: 2000px;\n  -webkit-transition: left 0.6s ease-in-out, width 0.6s ease-in-out,\n    height 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;\n  transition: left 0.6s ease-in-out, width 0.6s ease-in-out,\n    height 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;\n  transition: transform 0.6s ease-in-out, left 0.6s ease-in-out,\n    width 0.6s ease-in-out, height 0.6s ease-in-out;\n  transition: transform 0.6s ease-in-out, left 0.6s ease-in-out,\n    width 0.6s ease-in-out, height 0.6s ease-in-out,\n    -webkit-transform 0.6s ease-in-out;\n}\n\n.ui.shape .sides {\n  -webkit-transform-style: preserve-3d;\n  transform-style: preserve-3d;\n}\n\n.ui.shape .side {\n  opacity: 1;\n  width: 100%;\n  margin: 0em !important;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n}\n\n.ui.shape .side {\n  display: none;\n}\n\n.ui.shape .side * {\n  -webkit-backface-visibility: visible !important;\n  backface-visibility: visible !important;\n}\n\n/*******************************\n            Types\n*******************************/\n\n.ui.cube.shape .side {\n  min-width: 15em;\n  height: 15em;\n  padding: 2em;\n  background-color: #e6e6e6;\n  color: rgba(0, 0, 0, 0.87);\n  -webkit-box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.3);\n  box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.3);\n}\n\n.ui.cube.shape .side > .content {\n  width: 100%;\n  height: 100%;\n  display: table;\n  text-align: center;\n  -webkit-user-select: text;\n  -moz-user-select: text;\n  -ms-user-select: text;\n  user-select: text;\n}\n\n.ui.cube.shape .side > .content > div {\n  display: table-cell;\n  vertical-align: middle;\n  font-size: 2em;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n.ui.text.shape.animating .sides {\n  position: static;\n}\n\n.ui.text.shape .side {\n  white-space: nowrap;\n}\n\n.ui.text.shape .side > * {\n  white-space: normal;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------\n    Loading\n---------------*/\n\n.ui.loading.shape {\n  position: absolute;\n  top: -9999px;\n  left: -9999px;\n}\n\n/*--------------\n    Animating\n---------------*/\n\n.ui.shape .animating.side {\n  position: absolute;\n  top: 0px;\n  left: 0px;\n  display: block;\n  z-index: 100;\n}\n\n.ui.shape .hidden.side {\n  opacity: 0.6;\n}\n\n/*--------------\n      CSS\n---------------*/\n\n.ui.shape.animating .sides {\n  position: absolute;\n}\n\n.ui.shape.animating .sides {\n  -webkit-transition: left 0.6s ease-in-out, width 0.6s ease-in-out,\n    height 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;\n  transition: left 0.6s ease-in-out, width 0.6s ease-in-out,\n    height 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;\n  transition: transform 0.6s ease-in-out, left 0.6s ease-in-out,\n    width 0.6s ease-in-out, height 0.6s ease-in-out;\n  transition: transform 0.6s ease-in-out, left 0.6s ease-in-out,\n    width 0.6s ease-in-out, height 0.6s ease-in-out,\n    -webkit-transform 0.6s ease-in-out;\n}\n\n.ui.shape.animating .side {\n  -webkit-transition: opacity 0.6s ease-in-out;\n  transition: opacity 0.6s ease-in-out;\n}\n\n/*--------------\n    Active\n---------------*/\n\n.ui.shape .active.side {\n  display: block;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        User Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Sidebar\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Sidebar\n*******************************/\n\n/* Sidebar Menu */\n\n.ui.sidebar {\n  position: fixed;\n  top: 0;\n  left: 0;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  -webkit-transition: none;\n  transition: none;\n  will-change: transform;\n  -webkit-transform: translate3d(0, 0, 0);\n  transform: translate3d(0, 0, 0);\n  visibility: hidden;\n  -webkit-overflow-scrolling: touch;\n  height: 100% !important;\n  max-height: 100%;\n  border-radius: 0em !important;\n  margin: 0em !important;\n  overflow-y: auto !important;\n  z-index: 102;\n}\n\n/* GPU Layers for Child Elements */\n\n.ui.sidebar > * {\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n}\n\n/*--------------\n  Direction\n---------------*/\n\n.ui.left.sidebar {\n  right: auto;\n  left: 0px;\n  -webkit-transform: translate3d(-100%, 0, 0);\n  transform: translate3d(-100%, 0, 0);\n}\n\n.ui.right.sidebar {\n  right: 0px !important;\n  left: auto !important;\n  -webkit-transform: translate3d(100%, 0%, 0);\n  transform: translate3d(100%, 0%, 0);\n}\n\n.ui.top.sidebar,\n.ui.bottom.sidebar {\n  width: 100% !important;\n  height: auto !important;\n}\n\n.ui.top.sidebar {\n  top: 0px !important;\n  bottom: auto !important;\n  -webkit-transform: translate3d(0, -100%, 0);\n  transform: translate3d(0, -100%, 0);\n}\n\n.ui.bottom.sidebar {\n  top: auto !important;\n  bottom: 0px !important;\n  -webkit-transform: translate3d(0, 100%, 0);\n  transform: translate3d(0, 100%, 0);\n}\n\n/*--------------\n    Pushable\n---------------*/\n\n.pushable {\n  height: 100%;\n  overflow-x: hidden;\n  padding: 0em !important;\n}\n\n/* Whole Page */\n\nbody.pushable {\n  background: #545454 !important;\n}\n\n/* Page Context */\n\n.pushable:not(body) {\n  -webkit-transform: translate3d(0, 0, 0);\n  transform: translate3d(0, 0, 0);\n}\n\n.pushable:not(body) > .ui.sidebar,\n.pushable:not(body) > .fixed,\n.pushable:not(body) > .pusher:after {\n  position: absolute;\n}\n\n/*--------------\n    Fixed\n---------------*/\n\n.pushable > .fixed {\n  position: fixed;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  -webkit-transition: -webkit-transform 500ms ease;\n  transition: -webkit-transform 500ms ease;\n  transition: transform 500ms ease;\n  transition: transform 500ms ease, -webkit-transform 500ms ease;\n  will-change: transform;\n  z-index: 101;\n}\n\n/*--------------\n    Page\n---------------*/\n\n.pushable > .pusher {\n  position: relative;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  overflow: hidden;\n  min-height: 100%;\n  -webkit-transition: -webkit-transform 500ms ease;\n  transition: -webkit-transform 500ms ease;\n  transition: transform 500ms ease;\n  transition: transform 500ms ease, -webkit-transform 500ms ease;\n  z-index: 2;\n}\n\nbody.pushable > .pusher {\n  background: #ffffff;\n}\n\n/* Pusher should inherit background from context */\n\n.pushable > .pusher {\n  background: inherit;\n}\n\n/*--------------\n    Dimmer\n---------------*/\n\n.pushable > .pusher:after {\n  position: fixed;\n  top: 0px;\n  right: 0px;\n  content: \"\";\n  background-color: rgba(0, 0, 0, 0.4);\n  overflow: hidden;\n  opacity: 0;\n  -webkit-transition: opacity 500ms;\n  transition: opacity 500ms;\n  will-change: opacity;\n  z-index: 1000;\n}\n\n/*--------------\n    Coupling\n---------------*/\n\n.ui.sidebar.menu .item {\n  border-radius: 0em !important;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------\n    Dimmed\n---------------*/\n\n.pushable > .pusher.dimmed:after {\n  width: 100% !important;\n  height: 100% !important;\n  opacity: 1 !important;\n}\n\n/*--------------\n    Animating\n---------------*/\n\n.ui.animating.sidebar {\n  visibility: visible;\n}\n\n/*--------------\n    Visible\n---------------*/\n\n.ui.visible.sidebar {\n  visibility: visible;\n  -webkit-transform: translate3d(0, 0, 0);\n  transform: translate3d(0, 0, 0);\n}\n\n/* Shadow Direction */\n\n.ui.left.visible.sidebar,\n.ui.right.visible.sidebar {\n  -webkit-box-shadow: 0px 0px 20px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 0px 20px rgba(34, 36, 38, 0.15);\n}\n\n.ui.top.visible.sidebar,\n.ui.bottom.visible.sidebar {\n  -webkit-box-shadow: 0px 0px 20px rgba(34, 36, 38, 0.15);\n  box-shadow: 0px 0px 20px rgba(34, 36, 38, 0.15);\n}\n\n/* Visible On Load */\n\n.ui.visible.left.sidebar ~ .fixed,\n.ui.visible.left.sidebar ~ .pusher {\n  -webkit-transform: translate3d(260px, 0, 0);\n  transform: translate3d(260px, 0, 0);\n}\n\n.ui.visible.right.sidebar ~ .fixed,\n.ui.visible.right.sidebar ~ .pusher {\n  -webkit-transform: translate3d(-260px, 0, 0);\n  transform: translate3d(-260px, 0, 0);\n}\n\n.ui.visible.top.sidebar ~ .fixed,\n.ui.visible.top.sidebar ~ .pusher {\n  -webkit-transform: translate3d(0, 36px, 0);\n  transform: translate3d(0, 36px, 0);\n}\n\n.ui.visible.bottom.sidebar ~ .fixed,\n.ui.visible.bottom.sidebar ~ .pusher {\n  -webkit-transform: translate3d(0, -36px, 0);\n  transform: translate3d(0, -36px, 0);\n}\n\n/* opposite sides visible forces content overlay */\n\n.ui.visible.left.sidebar ~ .ui.visible.right.sidebar ~ .fixed,\n.ui.visible.left.sidebar ~ .ui.visible.right.sidebar ~ .pusher,\n.ui.visible.right.sidebar ~ .ui.visible.left.sidebar ~ .fixed,\n.ui.visible.right.sidebar ~ .ui.visible.left.sidebar ~ .pusher {\n  -webkit-transform: translate3d(0, 0, 0);\n  transform: translate3d(0, 0, 0);\n}\n\n/*--------------\n      iOS\n---------------*/\n\n/*******************************\n          Variations\n*******************************/\n\n/*--------------\n    Width\n---------------*/\n\n/* Left / Right */\n\n.ui.thin.left.sidebar,\n.ui.thin.right.sidebar {\n  width: 150px;\n}\n\n.ui[class*=\"very thin\"].left.sidebar,\n.ui[class*=\"very thin\"].right.sidebar {\n  width: 60px;\n}\n\n.ui.left.sidebar,\n.ui.right.sidebar {\n  width: 260px;\n}\n\n.ui.wide.left.sidebar,\n.ui.wide.right.sidebar {\n  width: 350px;\n}\n\n.ui[class*=\"very wide\"].left.sidebar,\n.ui[class*=\"very wide\"].right.sidebar {\n  width: 475px;\n}\n\n/* Left Visible */\n\n.ui.visible.thin.left.sidebar ~ .fixed,\n.ui.visible.thin.left.sidebar ~ .pusher {\n  -webkit-transform: translate3d(150px, 0, 0);\n  transform: translate3d(150px, 0, 0);\n}\n\n.ui.visible[class*=\"very thin\"].left.sidebar ~ .fixed,\n.ui.visible[class*=\"very thin\"].left.sidebar ~ .pusher {\n  -webkit-transform: translate3d(60px, 0, 0);\n  transform: translate3d(60px, 0, 0);\n}\n\n.ui.visible.wide.left.sidebar ~ .fixed,\n.ui.visible.wide.left.sidebar ~ .pusher {\n  -webkit-transform: translate3d(350px, 0, 0);\n  transform: translate3d(350px, 0, 0);\n}\n\n.ui.visible[class*=\"very wide\"].left.sidebar ~ .fixed,\n.ui.visible[class*=\"very wide\"].left.sidebar ~ .pusher {\n  -webkit-transform: translate3d(475px, 0, 0);\n  transform: translate3d(475px, 0, 0);\n}\n\n/* Right Visible */\n\n.ui.visible.thin.right.sidebar ~ .fixed,\n.ui.visible.thin.right.sidebar ~ .pusher {\n  -webkit-transform: translate3d(-150px, 0, 0);\n  transform: translate3d(-150px, 0, 0);\n}\n\n.ui.visible[class*=\"very thin\"].right.sidebar ~ .fixed,\n.ui.visible[class*=\"very thin\"].right.sidebar ~ .pusher {\n  -webkit-transform: translate3d(-60px, 0, 0);\n  transform: translate3d(-60px, 0, 0);\n}\n\n.ui.visible.wide.right.sidebar ~ .fixed,\n.ui.visible.wide.right.sidebar ~ .pusher {\n  -webkit-transform: translate3d(-350px, 0, 0);\n  transform: translate3d(-350px, 0, 0);\n}\n\n.ui.visible[class*=\"very wide\"].right.sidebar ~ .fixed,\n.ui.visible[class*=\"very wide\"].right.sidebar ~ .pusher {\n  -webkit-transform: translate3d(-475px, 0, 0);\n  transform: translate3d(-475px, 0, 0);\n}\n\n/*******************************\n          Animations\n*******************************/\n\n/*--------------\n    Overlay\n---------------*/\n\n/* Set-up */\n\n.ui.overlay.sidebar {\n  z-index: 102;\n}\n\n/* Initial */\n\n.ui.left.overlay.sidebar {\n  -webkit-transform: translate3d(-100%, 0%, 0);\n  transform: translate3d(-100%, 0%, 0);\n}\n\n.ui.right.overlay.sidebar {\n  -webkit-transform: translate3d(100%, 0%, 0);\n  transform: translate3d(100%, 0%, 0);\n}\n\n.ui.top.overlay.sidebar {\n  -webkit-transform: translate3d(0%, -100%, 0);\n  transform: translate3d(0%, -100%, 0);\n}\n\n.ui.bottom.overlay.sidebar {\n  -webkit-transform: translate3d(0%, 100%, 0);\n  transform: translate3d(0%, 100%, 0);\n}\n\n/* Animation */\n\n.animating.ui.overlay.sidebar,\n.ui.visible.overlay.sidebar {\n  -webkit-transition: -webkit-transform 500ms ease;\n  transition: -webkit-transform 500ms ease;\n  transition: transform 500ms ease;\n  transition: transform 500ms ease, -webkit-transform 500ms ease;\n}\n\n/* End - Sidebar */\n\n.ui.visible.left.overlay.sidebar {\n  -webkit-transform: translate3d(0%, 0%, 0);\n  transform: translate3d(0%, 0%, 0);\n}\n\n.ui.visible.right.overlay.sidebar {\n  -webkit-transform: translate3d(0%, 0%, 0);\n  transform: translate3d(0%, 0%, 0);\n}\n\n.ui.visible.top.overlay.sidebar {\n  -webkit-transform: translate3d(0%, 0%, 0);\n  transform: translate3d(0%, 0%, 0);\n}\n\n.ui.visible.bottom.overlay.sidebar {\n  -webkit-transform: translate3d(0%, 0%, 0);\n  transform: translate3d(0%, 0%, 0);\n}\n\n/* End - Pusher */\n\n.ui.visible.overlay.sidebar ~ .fixed,\n.ui.visible.overlay.sidebar ~ .pusher {\n  -webkit-transform: none !important;\n  transform: none !important;\n}\n\n/*--------------\n      Push\n---------------*/\n\n/* Initial */\n\n.ui.push.sidebar {\n  -webkit-transition: -webkit-transform 500ms ease;\n  transition: -webkit-transform 500ms ease;\n  transition: transform 500ms ease;\n  transition: transform 500ms ease, -webkit-transform 500ms ease;\n  z-index: 102;\n}\n\n/* Sidebar - Initial */\n\n.ui.left.push.sidebar {\n  -webkit-transform: translate3d(-100%, 0, 0);\n  transform: translate3d(-100%, 0, 0);\n}\n\n.ui.right.push.sidebar {\n  -webkit-transform: translate3d(100%, 0, 0);\n  transform: translate3d(100%, 0, 0);\n}\n\n.ui.top.push.sidebar {\n  -webkit-transform: translate3d(0%, -100%, 0);\n  transform: translate3d(0%, -100%, 0);\n}\n\n.ui.bottom.push.sidebar {\n  -webkit-transform: translate3d(0%, 100%, 0);\n  transform: translate3d(0%, 100%, 0);\n}\n\n/* End */\n\n.ui.visible.push.sidebar {\n  -webkit-transform: translate3d(0%, 0, 0);\n  transform: translate3d(0%, 0, 0);\n}\n\n/*--------------\n    Uncover\n---------------*/\n\n/* Initial */\n\n.ui.uncover.sidebar {\n  -webkit-transform: translate3d(0, 0, 0);\n  transform: translate3d(0, 0, 0);\n  z-index: 1;\n}\n\n/* End */\n\n.ui.visible.uncover.sidebar {\n  -webkit-transform: translate3d(0, 0, 0);\n  transform: translate3d(0, 0, 0);\n  -webkit-transition: -webkit-transform 500ms ease;\n  transition: -webkit-transform 500ms ease;\n  transition: transform 500ms ease;\n  transition: transform 500ms ease, -webkit-transform 500ms ease;\n}\n\n/*--------------\n  Slide Along\n---------------*/\n\n/* Initial */\n\n.ui.slide.along.sidebar {\n  z-index: 1;\n}\n\n/* Sidebar - Initial */\n\n.ui.left.slide.along.sidebar {\n  -webkit-transform: translate3d(-50%, 0, 0);\n  transform: translate3d(-50%, 0, 0);\n}\n\n.ui.right.slide.along.sidebar {\n  -webkit-transform: translate3d(50%, 0, 0);\n  transform: translate3d(50%, 0, 0);\n}\n\n.ui.top.slide.along.sidebar {\n  -webkit-transform: translate3d(0, -50%, 0);\n  transform: translate3d(0, -50%, 0);\n}\n\n.ui.bottom.slide.along.sidebar {\n  -webkit-transform: translate3d(0%, 50%, 0);\n  transform: translate3d(0%, 50%, 0);\n}\n\n/* Animation */\n\n.ui.animating.slide.along.sidebar {\n  -webkit-transition: -webkit-transform 500ms ease;\n  transition: -webkit-transform 500ms ease;\n  transition: transform 500ms ease;\n  transition: transform 500ms ease, -webkit-transform 500ms ease;\n}\n\n/* End */\n\n.ui.visible.slide.along.sidebar {\n  -webkit-transform: translate3d(0%, 0, 0);\n  transform: translate3d(0%, 0, 0);\n}\n\n/*--------------\n  Slide Out\n---------------*/\n\n/* Initial */\n\n.ui.slide.out.sidebar {\n  z-index: 1;\n}\n\n/* Sidebar - Initial */\n\n.ui.left.slide.out.sidebar {\n  -webkit-transform: translate3d(50%, 0, 0);\n  transform: translate3d(50%, 0, 0);\n}\n\n.ui.right.slide.out.sidebar {\n  -webkit-transform: translate3d(-50%, 0, 0);\n  transform: translate3d(-50%, 0, 0);\n}\n\n.ui.top.slide.out.sidebar {\n  -webkit-transform: translate3d(0%, 50%, 0);\n  transform: translate3d(0%, 50%, 0);\n}\n\n.ui.bottom.slide.out.sidebar {\n  -webkit-transform: translate3d(0%, -50%, 0);\n  transform: translate3d(0%, -50%, 0);\n}\n\n/* Animation */\n\n.ui.animating.slide.out.sidebar {\n  -webkit-transition: -webkit-transform 500ms ease;\n  transition: -webkit-transform 500ms ease;\n  transition: transform 500ms ease;\n  transition: transform 500ms ease, -webkit-transform 500ms ease;\n}\n\n/* End */\n\n.ui.visible.slide.out.sidebar {\n  -webkit-transform: translate3d(0%, 0, 0);\n  transform: translate3d(0%, 0, 0);\n}\n\n/*--------------\n  Scale Down\n---------------*/\n\n/* Initial */\n\n.ui.scale.down.sidebar {\n  -webkit-transition: -webkit-transform 500ms ease;\n  transition: -webkit-transform 500ms ease;\n  transition: transform 500ms ease;\n  transition: transform 500ms ease, -webkit-transform 500ms ease;\n  z-index: 102;\n}\n\n/* Sidebar - Initial  */\n\n.ui.left.scale.down.sidebar {\n  -webkit-transform: translate3d(-100%, 0, 0);\n  transform: translate3d(-100%, 0, 0);\n}\n\n.ui.right.scale.down.sidebar {\n  -webkit-transform: translate3d(100%, 0, 0);\n  transform: translate3d(100%, 0, 0);\n}\n\n.ui.top.scale.down.sidebar {\n  -webkit-transform: translate3d(0%, -100%, 0);\n  transform: translate3d(0%, -100%, 0);\n}\n\n.ui.bottom.scale.down.sidebar {\n  -webkit-transform: translate3d(0%, 100%, 0);\n  transform: translate3d(0%, 100%, 0);\n}\n\n/* Pusher - Initial */\n\n.ui.scale.down.left.sidebar ~ .pusher {\n  -webkit-transform-origin: 75% 50%;\n  transform-origin: 75% 50%;\n}\n\n.ui.scale.down.right.sidebar ~ .pusher {\n  -webkit-transform-origin: 25% 50%;\n  transform-origin: 25% 50%;\n}\n\n.ui.scale.down.top.sidebar ~ .pusher {\n  -webkit-transform-origin: 50% 75%;\n  transform-origin: 50% 75%;\n}\n\n.ui.scale.down.bottom.sidebar ~ .pusher {\n  -webkit-transform-origin: 50% 25%;\n  transform-origin: 50% 25%;\n}\n\n/* Animation */\n\n.ui.animating.scale.down > .visible.ui.sidebar {\n  -webkit-transition: -webkit-transform 500ms ease;\n  transition: -webkit-transform 500ms ease;\n  transition: transform 500ms ease;\n  transition: transform 500ms ease, -webkit-transform 500ms ease;\n}\n\n.ui.visible.scale.down.sidebar ~ .pusher,\n.ui.animating.scale.down.sidebar ~ .pusher {\n  display: block !important;\n  width: 100%;\n  height: 100%;\n  overflow: hidden !important;\n}\n\n/* End */\n\n.ui.visible.scale.down.sidebar {\n  -webkit-transform: translate3d(0, 0, 0);\n  transform: translate3d(0, 0, 0);\n}\n\n.ui.visible.scale.down.sidebar ~ .pusher {\n  -webkit-transform: scale(0.75);\n  transform: scale(0.75);\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Sticky\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n            Sticky\n*******************************/\n\n.ui.sticky {\n  position: static;\n  -webkit-transition: none;\n  transition: none;\n  z-index: 800;\n}\n\n/*******************************\n            States\n*******************************/\n\n/* Bound */\n\n.ui.sticky.bound {\n  position: absolute;\n  left: auto;\n  right: auto;\n}\n\n/* Fixed */\n\n.ui.sticky.fixed {\n  position: fixed;\n  left: auto;\n  right: auto;\n}\n\n/* Bound/Fixed Position */\n\n.ui.sticky.bound.top,\n.ui.sticky.fixed.top {\n  top: 0px;\n  bottom: auto;\n}\n\n.ui.sticky.bound.bottom,\n.ui.sticky.fixed.bottom {\n  top: auto;\n  bottom: 0px;\n}\n\n/*******************************\n            Types\n*******************************/\n\n.ui.native.sticky {\n  position: -webkit-sticky;\n  position: -moz-sticky;\n  position: -ms-sticky;\n  position: -o-sticky;\n  position: sticky;\n}\n\n/*******************************\n        Theme Overrides\n*******************************/\n\n/*******************************\n        Site Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Tab\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n          UI Tabs\n*******************************/\n\n.ui.tab {\n  display: none;\n}\n\n/*******************************\n            States\n*******************************/\n\n/*--------------------\n      Active\n---------------------*/\n\n.ui.tab.active,\n.ui.tab.open {\n  display: block;\n}\n\n/*--------------------\n      Loading\n---------------------*/\n\n.ui.tab.loading {\n  position: relative;\n  overflow: hidden;\n  display: block;\n  min-height: 250px;\n}\n\n.ui.tab.loading * {\n  position: relative !important;\n  left: -10000px !important;\n}\n\n.ui.tab.loading:before,\n.ui.tab.loading.segment:before {\n  position: absolute;\n  content: \"\";\n  top: 100px;\n  left: 50%;\n  margin: -1.25em 0em 0em -1.25em;\n  width: 2.5em;\n  height: 2.5em;\n  border-radius: 500rem;\n  border: 0.2em solid rgba(0, 0, 0, 0.1);\n}\n\n.ui.tab.loading:after,\n.ui.tab.loading.segment:after {\n  position: absolute;\n  content: \"\";\n  top: 100px;\n  left: 50%;\n  margin: -1.25em 0em 0em -1.25em;\n  width: 2.5em;\n  height: 2.5em;\n  -webkit-animation: button-spin 0.6s linear;\n  animation: button-spin 0.6s linear;\n  -webkit-animation-iteration-count: infinite;\n  animation-iteration-count: infinite;\n  border-radius: 500rem;\n  border-color: #767676 transparent transparent;\n  border-style: solid;\n  border-width: 0.2em;\n  -webkit-box-shadow: 0px 0px 0px 1px transparent;\n  box-shadow: 0px 0px 0px 1px transparent;\n}\n\n/*******************************\n        Tab Overrides\n*******************************/\n\n/*******************************\n        User Overrides\n*******************************/\n/*!\n* # Semantic UI 2.4.0 - Transition\n* http://github.com/semantic-org/semantic-ui/\n*\n*\n* Released under the MIT license\n* http://opensource.org/licenses/MIT\n*\n*/\n\n/*******************************\n          Transitions\n*******************************/\n\n.transition {\n  -webkit-animation-iteration-count: 1;\n  animation-iteration-count: 1;\n  -webkit-animation-duration: 300ms;\n  animation-duration: 300ms;\n  -webkit-animation-timing-function: ease;\n  animation-timing-function: ease;\n  -webkit-animation-fill-mode: both;\n  animation-fill-mode: both;\n}\n\n/*******************************\n            States\n*******************************/\n\n/* Animating */\n\n.animating.transition {\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  visibility: visible !important;\n}\n\n/* Loading */\n\n.loading.transition {\n  position: absolute;\n  top: -99999px;\n  left: -99999px;\n}\n\n/* Hidden */\n\n.hidden.transition {\n  display: none;\n  visibility: hidden;\n}\n\n/* Visible */\n\n.visible.transition {\n  display: block !important;\n  visibility: visible !important;\n  /*  backface-visibility: @backfaceVisibility;\n  transform: @use3DAcceleration;*/\n}\n\n/* Disabled */\n\n.disabled.transition {\n  -webkit-animation-play-state: paused;\n  animation-play-state: paused;\n}\n\n/*******************************\n          Variations\n*******************************/\n\n.looping.transition {\n  -webkit-animation-iteration-count: infinite;\n  animation-iteration-count: infinite;\n}\n\n/*******************************\n          Transitions\n*******************************/\n\n/*\n  Some transitions adapted from Animate CSS\n  https://github.com/daneden/animate.css\n\n  Additional transitions adapted from Glide\n  by Nick Pettit - https://github.com/nickpettit/glide\n*/\n\n/*--------------\n    Browse\n---------------*/\n\n.transition.browse {\n  -webkit-animation-duration: 500ms;\n  animation-duration: 500ms;\n}\n\n.transition.browse.in {\n  -webkit-animation-name: browseIn;\n  animation-name: browseIn;\n}\n\n.transition.browse.out,\n.transition.browse.left.out {\n  -webkit-animation-name: browseOutLeft;\n  animation-name: browseOutLeft;\n}\n\n.transition.browse.right.out {\n  -webkit-animation-name: browseOutRight;\n  animation-name: browseOutRight;\n}\n\n/* In */\n\n@-webkit-keyframes browseIn {\n  0% {\n    -webkit-transform: scale(0.8) translateZ(0px);\n    transform: scale(0.8) translateZ(0px);\n    z-index: -1;\n  }\n\n  10% {\n    -webkit-transform: scale(0.8) translateZ(0px);\n    transform: scale(0.8) translateZ(0px);\n    z-index: -1;\n    opacity: 0.7;\n  }\n\n  80% {\n    -webkit-transform: scale(1.05) translateZ(0px);\n    transform: scale(1.05) translateZ(0px);\n    opacity: 1;\n    z-index: 999;\n  }\n\n  100% {\n    -webkit-transform: scale(1) translateZ(0px);\n    transform: scale(1) translateZ(0px);\n    z-index: 999;\n  }\n}\n\n@keyframes browseIn {\n  0% {\n    -webkit-transform: scale(0.8) translateZ(0px);\n    transform: scale(0.8) translateZ(0px);\n    z-index: -1;\n  }\n\n  10% {\n    -webkit-transform: scale(0.8) translateZ(0px);\n    transform: scale(0.8) translateZ(0px);\n    z-index: -1;\n    opacity: 0.7;\n  }\n\n  80% {\n    -webkit-transform: scale(1.05) translateZ(0px);\n    transform: scale(1.05) translateZ(0px);\n    opacity: 1;\n    z-index: 999;\n  }\n\n  100% {\n    -webkit-transform: scale(1) translateZ(0px);\n    transform: scale(1) translateZ(0px);\n    z-index: 999;\n  }\n}\n\n/* Out */\n\n@-webkit-keyframes browseOutLeft {\n  0% {\n    z-index: 999;\n    -webkit-transform: translateX(0%) rotateY(0deg) rotateX(0deg);\n    transform: translateX(0%) rotateY(0deg) rotateX(0deg);\n  }\n\n  50% {\n    z-index: -1;\n    -webkit-transform: translateX(-105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);\n    transform: translateX(-105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);\n  }\n\n  80% {\n    opacity: 1;\n  }\n\n  100% {\n    z-index: -1;\n    -webkit-transform: translateX(0%) rotateY(0deg) rotateX(0deg) translateZ(-10px);\n    transform: translateX(0%) rotateY(0deg) rotateX(0deg) translateZ(-10px);\n    opacity: 0;\n  }\n}\n\n@keyframes browseOutLeft {\n  0% {\n    z-index: 999;\n    -webkit-transform: translateX(0%) rotateY(0deg) rotateX(0deg);\n    transform: translateX(0%) rotateY(0deg) rotateX(0deg);\n  }\n\n  50% {\n    z-index: -1;\n    -webkit-transform: translateX(-105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);\n    transform: translateX(-105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);\n  }\n\n  80% {\n    opacity: 1;\n  }\n\n  100% {\n    z-index: -1;\n    -webkit-transform: translateX(0%) rotateY(0deg) rotateX(0deg) translateZ(-10px);\n    transform: translateX(0%) rotateY(0deg) rotateX(0deg) translateZ(-10px);\n    opacity: 0;\n  }\n}\n\n@-webkit-keyframes browseOutRight {\n  0% {\n    z-index: 999;\n    -webkit-transform: translateX(0%) rotateY(0deg) rotateX(0deg);\n    transform: translateX(0%) rotateY(0deg) rotateX(0deg);\n  }\n\n  50% {\n    z-index: 1;\n    -webkit-transform: translateX(105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);\n    transform: translateX(105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);\n  }\n\n  80% {\n    opacity: 1;\n  }\n\n  100% {\n    z-index: 1;\n    -webkit-transform: translateX(0%) rotateY(0deg) rotateX(0deg) translateZ(-10px);\n    transform: translateX(0%) rotateY(0deg) rotateX(0deg) translateZ(-10px);\n    opacity: 0;\n  }\n}\n\n@keyframes browseOutRight {\n  0% {\n    z-index: 999;\n    -webkit-transform: translateX(0%) rotateY(0deg) rotateX(0deg);\n    transform: translateX(0%) rotateY(0deg) rotateX(0deg);\n  }\n\n  50% {\n    z-index: 1;\n    -webkit-transform: translateX(105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);\n    transform: translateX(105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);\n  }\n\n  80% {\n    opacity: 1;\n  }\n\n  100% {\n    z-index: 1;\n    -webkit-transform: translateX(0%) rotateY(0deg) rotateX(0deg) translateZ(-10px);\n    transform: translateX(0%) rotateY(0deg) rotateX(0deg) translateZ(-10px);\n    opacity: 0;\n  }\n}\n\n/*--------------\n    Drop\n---------------*/\n\n.drop.transition {\n  -webkit-transform-origin: top center;\n  transform-origin: top center;\n  -webkit-animation-duration: 400ms;\n  animation-duration: 400ms;\n  -webkit-animation-timing-function: cubic-bezier(0.34, 1.61, 0.7, 1);\n  animation-timing-function: cubic-bezier(0.34, 1.61, 0.7, 1);\n}\n\n.drop.transition.in {\n  -webkit-animation-name: dropIn;\n  animation-name: dropIn;\n}\n\n.drop.transition.out {\n  -webkit-animation-name: dropOut;\n  animation-name: dropOut;\n}\n\n/* Drop */\n\n@-webkit-keyframes dropIn {\n  0% {\n    opacity: 0;\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n\n@keyframes dropIn {\n  0% {\n    opacity: 0;\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n\n@-webkit-keyframes dropOut {\n  0% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n}\n\n@keyframes dropOut {\n  0% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n}\n\n/*--------------\n      Fade\n---------------*/\n\n.transition.fade.in {\n  -webkit-animation-name: fadeIn;\n  animation-name: fadeIn;\n}\n\n.transition[class*=\"fade up\"].in {\n  -webkit-animation-name: fadeInUp;\n  animation-name: fadeInUp;\n}\n\n.transition[class*=\"fade down\"].in {\n  -webkit-animation-name: fadeInDown;\n  animation-name: fadeInDown;\n}\n\n.transition[class*=\"fade left\"].in {\n  -webkit-animation-name: fadeInLeft;\n  animation-name: fadeInLeft;\n}\n\n.transition[class*=\"fade right\"].in {\n  -webkit-animation-name: fadeInRight;\n  animation-name: fadeInRight;\n}\n\n.transition.fade.out {\n  -webkit-animation-name: fadeOut;\n  animation-name: fadeOut;\n}\n\n.transition[class*=\"fade up\"].out {\n  -webkit-animation-name: fadeOutUp;\n  animation-name: fadeOutUp;\n}\n\n.transition[class*=\"fade down\"].out {\n  -webkit-animation-name: fadeOutDown;\n  animation-name: fadeOutDown;\n}\n\n.transition[class*=\"fade left\"].out {\n  -webkit-animation-name: fadeOutLeft;\n  animation-name: fadeOutLeft;\n}\n\n.transition[class*=\"fade right\"].out {\n  -webkit-animation-name: fadeOutRight;\n  animation-name: fadeOutRight;\n}\n\n/* In */\n\n@-webkit-keyframes fadeIn {\n  0% {\n    opacity: 0;\n  }\n\n  100% {\n    opacity: 1;\n  }\n}\n\n@keyframes fadeIn {\n  0% {\n    opacity: 0;\n  }\n\n  100% {\n    opacity: 1;\n  }\n}\n\n@-webkit-keyframes fadeInUp {\n  0% {\n    opacity: 0;\n    -webkit-transform: translateY(10%);\n    transform: translateY(10%);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: translateY(0%);\n    transform: translateY(0%);\n  }\n}\n\n@keyframes fadeInUp {\n  0% {\n    opacity: 0;\n    -webkit-transform: translateY(10%);\n    transform: translateY(10%);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: translateY(0%);\n    transform: translateY(0%);\n  }\n}\n\n@-webkit-keyframes fadeInDown {\n  0% {\n    opacity: 0;\n    -webkit-transform: translateY(-10%);\n    transform: translateY(-10%);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: translateY(0%);\n    transform: translateY(0%);\n  }\n}\n\n@keyframes fadeInDown {\n  0% {\n    opacity: 0;\n    -webkit-transform: translateY(-10%);\n    transform: translateY(-10%);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: translateY(0%);\n    transform: translateY(0%);\n  }\n}\n\n@-webkit-keyframes fadeInLeft {\n  0% {\n    opacity: 0;\n    -webkit-transform: translateX(10%);\n    transform: translateX(10%);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: translateX(0%);\n    transform: translateX(0%);\n  }\n}\n\n@keyframes fadeInLeft {\n  0% {\n    opacity: 0;\n    -webkit-transform: translateX(10%);\n    transform: translateX(10%);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: translateX(0%);\n    transform: translateX(0%);\n  }\n}\n\n@-webkit-keyframes fadeInRight {\n  0% {\n    opacity: 0;\n    -webkit-transform: translateX(-10%);\n    transform: translateX(-10%);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: translateX(0%);\n    transform: translateX(0%);\n  }\n}\n\n@keyframes fadeInRight {\n  0% {\n    opacity: 0;\n    -webkit-transform: translateX(-10%);\n    transform: translateX(-10%);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: translateX(0%);\n    transform: translateX(0%);\n  }\n}\n\n/* Out */\n\n@-webkit-keyframes fadeOut {\n  0% {\n    opacity: 1;\n  }\n\n  100% {\n    opacity: 0;\n  }\n}\n\n@keyframes fadeOut {\n  0% {\n    opacity: 1;\n  }\n\n  100% {\n    opacity: 0;\n  }\n}\n\n@-webkit-keyframes fadeOutUp {\n  0% {\n    opacity: 1;\n    -webkit-transform: translateY(0%);\n    transform: translateY(0%);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translateY(5%);\n    transform: translateY(5%);\n  }\n}\n\n@keyframes fadeOutUp {\n  0% {\n    opacity: 1;\n    -webkit-transform: translateY(0%);\n    transform: translateY(0%);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translateY(5%);\n    transform: translateY(5%);\n  }\n}\n\n@-webkit-keyframes fadeOutDown {\n  0% {\n    opacity: 1;\n    -webkit-transform: translateY(0%);\n    transform: translateY(0%);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translateY(-5%);\n    transform: translateY(-5%);\n  }\n}\n\n@keyframes fadeOutDown {\n  0% {\n    opacity: 1;\n    -webkit-transform: translateY(0%);\n    transform: translateY(0%);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translateY(-5%);\n    transform: translateY(-5%);\n  }\n}\n\n@-webkit-keyframes fadeOutLeft {\n  0% {\n    opacity: 1;\n    -webkit-transform: translateX(0%);\n    transform: translateX(0%);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translateX(5%);\n    transform: translateX(5%);\n  }\n}\n\n@keyframes fadeOutLeft {\n  0% {\n    opacity: 1;\n    -webkit-transform: translateX(0%);\n    transform: translateX(0%);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translateX(5%);\n    transform: translateX(5%);\n  }\n}\n\n@-webkit-keyframes fadeOutRight {\n  0% {\n    opacity: 1;\n    -webkit-transform: translateX(0%);\n    transform: translateX(0%);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translateX(-5%);\n    transform: translateX(-5%);\n  }\n}\n\n@keyframes fadeOutRight {\n  0% {\n    opacity: 1;\n    -webkit-transform: translateX(0%);\n    transform: translateX(0%);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translateX(-5%);\n    transform: translateX(-5%);\n  }\n}\n\n/*--------------\n    Flips\n---------------*/\n\n.flip.transition.in,\n.flip.transition.out {\n  -webkit-animation-duration: 600ms;\n  animation-duration: 600ms;\n}\n\n.horizontal.flip.transition.in {\n  -webkit-animation-name: horizontalFlipIn;\n  animation-name: horizontalFlipIn;\n}\n\n.horizontal.flip.transition.out {\n  -webkit-animation-name: horizontalFlipOut;\n  animation-name: horizontalFlipOut;\n}\n\n.vertical.flip.transition.in {\n  -webkit-animation-name: verticalFlipIn;\n  animation-name: verticalFlipIn;\n}\n\n.vertical.flip.transition.out {\n  -webkit-animation-name: verticalFlipOut;\n  animation-name: verticalFlipOut;\n}\n\n/* In */\n\n@-webkit-keyframes horizontalFlipIn {\n  0% {\n    -webkit-transform: perspective(2000px) rotateY(-90deg);\n    transform: perspective(2000px) rotateY(-90deg);\n    opacity: 0;\n  }\n\n  100% {\n    -webkit-transform: perspective(2000px) rotateY(0deg);\n    transform: perspective(2000px) rotateY(0deg);\n    opacity: 1;\n  }\n}\n\n@keyframes horizontalFlipIn {\n  0% {\n    -webkit-transform: perspective(2000px) rotateY(-90deg);\n    transform: perspective(2000px) rotateY(-90deg);\n    opacity: 0;\n  }\n\n  100% {\n    -webkit-transform: perspective(2000px) rotateY(0deg);\n    transform: perspective(2000px) rotateY(0deg);\n    opacity: 1;\n  }\n}\n\n@-webkit-keyframes verticalFlipIn {\n  0% {\n    -webkit-transform: perspective(2000px) rotateX(-90deg);\n    transform: perspective(2000px) rotateX(-90deg);\n    opacity: 0;\n  }\n\n  100% {\n    -webkit-transform: perspective(2000px) rotateX(0deg);\n    transform: perspective(2000px) rotateX(0deg);\n    opacity: 1;\n  }\n}\n\n@keyframes verticalFlipIn {\n  0% {\n    -webkit-transform: perspective(2000px) rotateX(-90deg);\n    transform: perspective(2000px) rotateX(-90deg);\n    opacity: 0;\n  }\n\n  100% {\n    -webkit-transform: perspective(2000px) rotateX(0deg);\n    transform: perspective(2000px) rotateX(0deg);\n    opacity: 1;\n  }\n}\n\n/* Out */\n\n@-webkit-keyframes horizontalFlipOut {\n  0% {\n    -webkit-transform: perspective(2000px) rotateY(0deg);\n    transform: perspective(2000px) rotateY(0deg);\n    opacity: 1;\n  }\n\n  100% {\n    -webkit-transform: perspective(2000px) rotateY(90deg);\n    transform: perspective(2000px) rotateY(90deg);\n    opacity: 0;\n  }\n}\n\n@keyframes horizontalFlipOut {\n  0% {\n    -webkit-transform: perspective(2000px) rotateY(0deg);\n    transform: perspective(2000px) rotateY(0deg);\n    opacity: 1;\n  }\n\n  100% {\n    -webkit-transform: perspective(2000px) rotateY(90deg);\n    transform: perspective(2000px) rotateY(90deg);\n    opacity: 0;\n  }\n}\n\n@-webkit-keyframes verticalFlipOut {\n  0% {\n    -webkit-transform: perspective(2000px) rotateX(0deg);\n    transform: perspective(2000px) rotateX(0deg);\n    opacity: 1;\n  }\n\n  100% {\n    -webkit-transform: perspective(2000px) rotateX(-90deg);\n    transform: perspective(2000px) rotateX(-90deg);\n    opacity: 0;\n  }\n}\n\n@keyframes verticalFlipOut {\n  0% {\n    -webkit-transform: perspective(2000px) rotateX(0deg);\n    transform: perspective(2000px) rotateX(0deg);\n    opacity: 1;\n  }\n\n  100% {\n    -webkit-transform: perspective(2000px) rotateX(-90deg);\n    transform: perspective(2000px) rotateX(-90deg);\n    opacity: 0;\n  }\n}\n\n/*--------------\n      Scale\n---------------*/\n\n.scale.transition.in {\n  -webkit-animation-name: scaleIn;\n  animation-name: scaleIn;\n}\n\n.scale.transition.out {\n  -webkit-animation-name: scaleOut;\n  animation-name: scaleOut;\n}\n\n@-webkit-keyframes scaleIn {\n  0% {\n    opacity: 0;\n    -webkit-transform: scale(0.8);\n    transform: scale(0.8);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n\n@keyframes scaleIn {\n  0% {\n    opacity: 0;\n    -webkit-transform: scale(0.8);\n    transform: scale(0.8);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n\n/* Out */\n\n@-webkit-keyframes scaleOut {\n  0% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: scale(0.9);\n    transform: scale(0.9);\n  }\n}\n\n@keyframes scaleOut {\n  0% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: scale(0.9);\n    transform: scale(0.9);\n  }\n}\n\n/*--------------\n      Fly\n---------------*/\n\n/* Inward */\n\n.transition.fly {\n  -webkit-animation-duration: 0.6s;\n  animation-duration: 0.6s;\n  -webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n}\n\n.transition.fly.in {\n  -webkit-animation-name: flyIn;\n  animation-name: flyIn;\n}\n\n.transition[class*=\"fly up\"].in {\n  -webkit-animation-name: flyInUp;\n  animation-name: flyInUp;\n}\n\n.transition[class*=\"fly down\"].in {\n  -webkit-animation-name: flyInDown;\n  animation-name: flyInDown;\n}\n\n.transition[class*=\"fly left\"].in {\n  -webkit-animation-name: flyInLeft;\n  animation-name: flyInLeft;\n}\n\n.transition[class*=\"fly right\"].in {\n  -webkit-animation-name: flyInRight;\n  animation-name: flyInRight;\n}\n\n/* Outward */\n\n.transition.fly.out {\n  -webkit-animation-name: flyOut;\n  animation-name: flyOut;\n}\n\n.transition[class*=\"fly up\"].out {\n  -webkit-animation-name: flyOutUp;\n  animation-name: flyOutUp;\n}\n\n.transition[class*=\"fly down\"].out {\n  -webkit-animation-name: flyOutDown;\n  animation-name: flyOutDown;\n}\n\n.transition[class*=\"fly left\"].out {\n  -webkit-animation-name: flyOutLeft;\n  animation-name: flyOutLeft;\n}\n\n.transition[class*=\"fly right\"].out {\n  -webkit-animation-name: flyOutRight;\n  animation-name: flyOutRight;\n}\n\n/* In */\n\n@-webkit-keyframes flyIn {\n  0% {\n    opacity: 0;\n    -webkit-transform: scale3d(0.3, 0.3, 0.3);\n    transform: scale3d(0.3, 0.3, 0.3);\n  }\n\n  20% {\n    -webkit-transform: scale3d(1.1, 1.1, 1.1);\n    transform: scale3d(1.1, 1.1, 1.1);\n  }\n\n  40% {\n    -webkit-transform: scale3d(0.9, 0.9, 0.9);\n    transform: scale3d(0.9, 0.9, 0.9);\n  }\n\n  60% {\n    opacity: 1;\n    -webkit-transform: scale3d(1.03, 1.03, 1.03);\n    transform: scale3d(1.03, 1.03, 1.03);\n  }\n\n  80% {\n    -webkit-transform: scale3d(0.97, 0.97, 0.97);\n    transform: scale3d(0.97, 0.97, 0.97);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scale3d(1, 1, 1);\n    transform: scale3d(1, 1, 1);\n  }\n}\n\n@keyframes flyIn {\n  0% {\n    opacity: 0;\n    -webkit-transform: scale3d(0.3, 0.3, 0.3);\n    transform: scale3d(0.3, 0.3, 0.3);\n  }\n\n  20% {\n    -webkit-transform: scale3d(1.1, 1.1, 1.1);\n    transform: scale3d(1.1, 1.1, 1.1);\n  }\n\n  40% {\n    -webkit-transform: scale3d(0.9, 0.9, 0.9);\n    transform: scale3d(0.9, 0.9, 0.9);\n  }\n\n  60% {\n    opacity: 1;\n    -webkit-transform: scale3d(1.03, 1.03, 1.03);\n    transform: scale3d(1.03, 1.03, 1.03);\n  }\n\n  80% {\n    -webkit-transform: scale3d(0.97, 0.97, 0.97);\n    transform: scale3d(0.97, 0.97, 0.97);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scale3d(1, 1, 1);\n    transform: scale3d(1, 1, 1);\n  }\n}\n\n@-webkit-keyframes flyInUp {\n  0% {\n    opacity: 0;\n    -webkit-transform: translate3d(0, 1500px, 0);\n    transform: translate3d(0, 1500px, 0);\n  }\n\n  60% {\n    opacity: 1;\n    -webkit-transform: translate3d(0, -20px, 0);\n    transform: translate3d(0, -20px, 0);\n  }\n\n  75% {\n    -webkit-transform: translate3d(0, 10px, 0);\n    transform: translate3d(0, 10px, 0);\n  }\n\n  90% {\n    -webkit-transform: translate3d(0, -5px, 0);\n    transform: translate3d(0, -5px, 0);\n  }\n\n  100% {\n    -webkit-transform: translate3d(0, 0, 0);\n    transform: translate3d(0, 0, 0);\n  }\n}\n\n@keyframes flyInUp {\n  0% {\n    opacity: 0;\n    -webkit-transform: translate3d(0, 1500px, 0);\n    transform: translate3d(0, 1500px, 0);\n  }\n\n  60% {\n    opacity: 1;\n    -webkit-transform: translate3d(0, -20px, 0);\n    transform: translate3d(0, -20px, 0);\n  }\n\n  75% {\n    -webkit-transform: translate3d(0, 10px, 0);\n    transform: translate3d(0, 10px, 0);\n  }\n\n  90% {\n    -webkit-transform: translate3d(0, -5px, 0);\n    transform: translate3d(0, -5px, 0);\n  }\n\n  100% {\n    -webkit-transform: translate3d(0, 0, 0);\n    transform: translate3d(0, 0, 0);\n  }\n}\n\n@-webkit-keyframes flyInDown {\n  0% {\n    opacity: 0;\n    -webkit-transform: translate3d(0, -1500px, 0);\n    transform: translate3d(0, -1500px, 0);\n  }\n\n  60% {\n    opacity: 1;\n    -webkit-transform: translate3d(0, 25px, 0);\n    transform: translate3d(0, 25px, 0);\n  }\n\n  75% {\n    -webkit-transform: translate3d(0, -10px, 0);\n    transform: translate3d(0, -10px, 0);\n  }\n\n  90% {\n    -webkit-transform: translate3d(0, 5px, 0);\n    transform: translate3d(0, 5px, 0);\n  }\n\n  100% {\n    -webkit-transform: none;\n    transform: none;\n  }\n}\n\n@keyframes flyInDown {\n  0% {\n    opacity: 0;\n    -webkit-transform: translate3d(0, -1500px, 0);\n    transform: translate3d(0, -1500px, 0);\n  }\n\n  60% {\n    opacity: 1;\n    -webkit-transform: translate3d(0, 25px, 0);\n    transform: translate3d(0, 25px, 0);\n  }\n\n  75% {\n    -webkit-transform: translate3d(0, -10px, 0);\n    transform: translate3d(0, -10px, 0);\n  }\n\n  90% {\n    -webkit-transform: translate3d(0, 5px, 0);\n    transform: translate3d(0, 5px, 0);\n  }\n\n  100% {\n    -webkit-transform: none;\n    transform: none;\n  }\n}\n\n@-webkit-keyframes flyInLeft {\n  0% {\n    opacity: 0;\n    -webkit-transform: translate3d(1500px, 0, 0);\n    transform: translate3d(1500px, 0, 0);\n  }\n\n  60% {\n    opacity: 1;\n    -webkit-transform: translate3d(-25px, 0, 0);\n    transform: translate3d(-25px, 0, 0);\n  }\n\n  75% {\n    -webkit-transform: translate3d(10px, 0, 0);\n    transform: translate3d(10px, 0, 0);\n  }\n\n  90% {\n    -webkit-transform: translate3d(-5px, 0, 0);\n    transform: translate3d(-5px, 0, 0);\n  }\n\n  100% {\n    -webkit-transform: none;\n    transform: none;\n  }\n}\n\n@keyframes flyInLeft {\n  0% {\n    opacity: 0;\n    -webkit-transform: translate3d(1500px, 0, 0);\n    transform: translate3d(1500px, 0, 0);\n  }\n\n  60% {\n    opacity: 1;\n    -webkit-transform: translate3d(-25px, 0, 0);\n    transform: translate3d(-25px, 0, 0);\n  }\n\n  75% {\n    -webkit-transform: translate3d(10px, 0, 0);\n    transform: translate3d(10px, 0, 0);\n  }\n\n  90% {\n    -webkit-transform: translate3d(-5px, 0, 0);\n    transform: translate3d(-5px, 0, 0);\n  }\n\n  100% {\n    -webkit-transform: none;\n    transform: none;\n  }\n}\n\n@-webkit-keyframes flyInRight {\n  0% {\n    opacity: 0;\n    -webkit-transform: translate3d(-1500px, 0, 0);\n    transform: translate3d(-1500px, 0, 0);\n  }\n\n  60% {\n    opacity: 1;\n    -webkit-transform: translate3d(25px, 0, 0);\n    transform: translate3d(25px, 0, 0);\n  }\n\n  75% {\n    -webkit-transform: translate3d(-10px, 0, 0);\n    transform: translate3d(-10px, 0, 0);\n  }\n\n  90% {\n    -webkit-transform: translate3d(5px, 0, 0);\n    transform: translate3d(5px, 0, 0);\n  }\n\n  100% {\n    -webkit-transform: none;\n    transform: none;\n  }\n}\n\n@keyframes flyInRight {\n  0% {\n    opacity: 0;\n    -webkit-transform: translate3d(-1500px, 0, 0);\n    transform: translate3d(-1500px, 0, 0);\n  }\n\n  60% {\n    opacity: 1;\n    -webkit-transform: translate3d(25px, 0, 0);\n    transform: translate3d(25px, 0, 0);\n  }\n\n  75% {\n    -webkit-transform: translate3d(-10px, 0, 0);\n    transform: translate3d(-10px, 0, 0);\n  }\n\n  90% {\n    -webkit-transform: translate3d(5px, 0, 0);\n    transform: translate3d(5px, 0, 0);\n  }\n\n  100% {\n    -webkit-transform: none;\n    transform: none;\n  }\n}\n\n/* Out */\n\n@-webkit-keyframes flyOut {\n  20% {\n    -webkit-transform: scale3d(0.9, 0.9, 0.9);\n    transform: scale3d(0.9, 0.9, 0.9);\n  }\n\n  50%,\n  55% {\n    opacity: 1;\n    -webkit-transform: scale3d(1.1, 1.1, 1.1);\n    transform: scale3d(1.1, 1.1, 1.1);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: scale3d(0.3, 0.3, 0.3);\n    transform: scale3d(0.3, 0.3, 0.3);\n  }\n}\n\n@keyframes flyOut {\n  20% {\n    -webkit-transform: scale3d(0.9, 0.9, 0.9);\n    transform: scale3d(0.9, 0.9, 0.9);\n  }\n\n  50%,\n  55% {\n    opacity: 1;\n    -webkit-transform: scale3d(1.1, 1.1, 1.1);\n    transform: scale3d(1.1, 1.1, 1.1);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: scale3d(0.3, 0.3, 0.3);\n    transform: scale3d(0.3, 0.3, 0.3);\n  }\n}\n\n@-webkit-keyframes flyOutUp {\n  20% {\n    -webkit-transform: translate3d(0, 10px, 0);\n    transform: translate3d(0, 10px, 0);\n  }\n\n  40%,\n  45% {\n    opacity: 1;\n    -webkit-transform: translate3d(0, -20px, 0);\n    transform: translate3d(0, -20px, 0);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translate3d(0, 2000px, 0);\n    transform: translate3d(0, 2000px, 0);\n  }\n}\n\n@keyframes flyOutUp {\n  20% {\n    -webkit-transform: translate3d(0, 10px, 0);\n    transform: translate3d(0, 10px, 0);\n  }\n\n  40%,\n  45% {\n    opacity: 1;\n    -webkit-transform: translate3d(0, -20px, 0);\n    transform: translate3d(0, -20px, 0);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translate3d(0, 2000px, 0);\n    transform: translate3d(0, 2000px, 0);\n  }\n}\n\n@-webkit-keyframes flyOutDown {\n  20% {\n    -webkit-transform: translate3d(0, -10px, 0);\n    transform: translate3d(0, -10px, 0);\n  }\n\n  40%,\n  45% {\n    opacity: 1;\n    -webkit-transform: translate3d(0, 20px, 0);\n    transform: translate3d(0, 20px, 0);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translate3d(0, -2000px, 0);\n    transform: translate3d(0, -2000px, 0);\n  }\n}\n\n@keyframes flyOutDown {\n  20% {\n    -webkit-transform: translate3d(0, -10px, 0);\n    transform: translate3d(0, -10px, 0);\n  }\n\n  40%,\n  45% {\n    opacity: 1;\n    -webkit-transform: translate3d(0, 20px, 0);\n    transform: translate3d(0, 20px, 0);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translate3d(0, -2000px, 0);\n    transform: translate3d(0, -2000px, 0);\n  }\n}\n\n@-webkit-keyframes flyOutRight {\n  20% {\n    opacity: 1;\n    -webkit-transform: translate3d(20px, 0, 0);\n    transform: translate3d(20px, 0, 0);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translate3d(-2000px, 0, 0);\n    transform: translate3d(-2000px, 0, 0);\n  }\n}\n\n@keyframes flyOutRight {\n  20% {\n    opacity: 1;\n    -webkit-transform: translate3d(20px, 0, 0);\n    transform: translate3d(20px, 0, 0);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translate3d(-2000px, 0, 0);\n    transform: translate3d(-2000px, 0, 0);\n  }\n}\n\n@-webkit-keyframes flyOutLeft {\n  20% {\n    opacity: 1;\n    -webkit-transform: translate3d(-20px, 0, 0);\n    transform: translate3d(-20px, 0, 0);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translate3d(2000px, 0, 0);\n    transform: translate3d(2000px, 0, 0);\n  }\n}\n\n@keyframes flyOutLeft {\n  20% {\n    opacity: 1;\n    -webkit-transform: translate3d(-20px, 0, 0);\n    transform: translate3d(-20px, 0, 0);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: translate3d(2000px, 0, 0);\n    transform: translate3d(2000px, 0, 0);\n  }\n}\n\n/*--------------\n    Slide\n---------------*/\n\n.transition.slide.in,\n.transition[class*=\"slide down\"].in {\n  -webkit-animation-name: slideInY;\n  animation-name: slideInY;\n  -webkit-transform-origin: top center;\n  transform-origin: top center;\n}\n\n.transition[class*=\"slide up\"].in {\n  -webkit-animation-name: slideInY;\n  animation-name: slideInY;\n  -webkit-transform-origin: bottom center;\n  transform-origin: bottom center;\n}\n\n.transition[class*=\"slide left\"].in {\n  -webkit-animation-name: slideInX;\n  animation-name: slideInX;\n  -webkit-transform-origin: center right;\n  transform-origin: center right;\n}\n\n.transition[class*=\"slide right\"].in {\n  -webkit-animation-name: slideInX;\n  animation-name: slideInX;\n  -webkit-transform-origin: center left;\n  transform-origin: center left;\n}\n\n.transition.slide.out,\n.transition[class*=\"slide down\"].out {\n  -webkit-animation-name: slideOutY;\n  animation-name: slideOutY;\n  -webkit-transform-origin: top center;\n  transform-origin: top center;\n}\n\n.transition[class*=\"slide up\"].out {\n  -webkit-animation-name: slideOutY;\n  animation-name: slideOutY;\n  -webkit-transform-origin: bottom center;\n  transform-origin: bottom center;\n}\n\n.transition[class*=\"slide left\"].out {\n  -webkit-animation-name: slideOutX;\n  animation-name: slideOutX;\n  -webkit-transform-origin: center right;\n  transform-origin: center right;\n}\n\n.transition[class*=\"slide right\"].out {\n  -webkit-animation-name: slideOutX;\n  animation-name: slideOutX;\n  -webkit-transform-origin: center left;\n  transform-origin: center left;\n}\n\n/* In */\n\n@-webkit-keyframes slideInY {\n  0% {\n    opacity: 0;\n    -webkit-transform: scaleY(0);\n    transform: scaleY(0);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scaleY(1);\n    transform: scaleY(1);\n  }\n}\n\n@keyframes slideInY {\n  0% {\n    opacity: 0;\n    -webkit-transform: scaleY(0);\n    transform: scaleY(0);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scaleY(1);\n    transform: scaleY(1);\n  }\n}\n\n@-webkit-keyframes slideInX {\n  0% {\n    opacity: 0;\n    -webkit-transform: scaleX(0);\n    transform: scaleX(0);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scaleX(1);\n    transform: scaleX(1);\n  }\n}\n\n@keyframes slideInX {\n  0% {\n    opacity: 0;\n    -webkit-transform: scaleX(0);\n    transform: scaleX(0);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scaleX(1);\n    transform: scaleX(1);\n  }\n}\n\n/* Out */\n\n@-webkit-keyframes slideOutY {\n  0% {\n    opacity: 1;\n    -webkit-transform: scaleY(1);\n    transform: scaleY(1);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: scaleY(0);\n    transform: scaleY(0);\n  }\n}\n\n@keyframes slideOutY {\n  0% {\n    opacity: 1;\n    -webkit-transform: scaleY(1);\n    transform: scaleY(1);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: scaleY(0);\n    transform: scaleY(0);\n  }\n}\n\n@-webkit-keyframes slideOutX {\n  0% {\n    opacity: 1;\n    -webkit-transform: scaleX(1);\n    transform: scaleX(1);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: scaleX(0);\n    transform: scaleX(0);\n  }\n}\n\n@keyframes slideOutX {\n  0% {\n    opacity: 1;\n    -webkit-transform: scaleX(1);\n    transform: scaleX(1);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: scaleX(0);\n    transform: scaleX(0);\n  }\n}\n\n/*--------------\n    Swing\n---------------*/\n\n.transition.swing {\n  -webkit-animation-duration: 800ms;\n  animation-duration: 800ms;\n}\n\n.transition[class*=\"swing down\"].in {\n  -webkit-animation-name: swingInX;\n  animation-name: swingInX;\n  -webkit-transform-origin: top center;\n  transform-origin: top center;\n}\n\n.transition[class*=\"swing up\"].in {\n  -webkit-animation-name: swingInX;\n  animation-name: swingInX;\n  -webkit-transform-origin: bottom center;\n  transform-origin: bottom center;\n}\n\n.transition[class*=\"swing left\"].in {\n  -webkit-animation-name: swingInY;\n  animation-name: swingInY;\n  -webkit-transform-origin: center right;\n  transform-origin: center right;\n}\n\n.transition[class*=\"swing right\"].in {\n  -webkit-animation-name: swingInY;\n  animation-name: swingInY;\n  -webkit-transform-origin: center left;\n  transform-origin: center left;\n}\n\n.transition.swing.out,\n.transition[class*=\"swing down\"].out {\n  -webkit-animation-name: swingOutX;\n  animation-name: swingOutX;\n  -webkit-transform-origin: top center;\n  transform-origin: top center;\n}\n\n.transition[class*=\"swing up\"].out {\n  -webkit-animation-name: swingOutX;\n  animation-name: swingOutX;\n  -webkit-transform-origin: bottom center;\n  transform-origin: bottom center;\n}\n\n.transition[class*=\"swing left\"].out {\n  -webkit-animation-name: swingOutY;\n  animation-name: swingOutY;\n  -webkit-transform-origin: center right;\n  transform-origin: center right;\n}\n\n.transition[class*=\"swing right\"].out {\n  -webkit-animation-name: swingOutY;\n  animation-name: swingOutY;\n  -webkit-transform-origin: center left;\n  transform-origin: center left;\n}\n\n/* In */\n\n@-webkit-keyframes swingInX {\n  0% {\n    -webkit-transform: perspective(1000px) rotateX(90deg);\n    transform: perspective(1000px) rotateX(90deg);\n    opacity: 0;\n  }\n\n  40% {\n    -webkit-transform: perspective(1000px) rotateX(-30deg);\n    transform: perspective(1000px) rotateX(-30deg);\n    opacity: 1;\n  }\n\n  60% {\n    -webkit-transform: perspective(1000px) rotateX(15deg);\n    transform: perspective(1000px) rotateX(15deg);\n  }\n\n  80% {\n    -webkit-transform: perspective(1000px) rotateX(-7.5deg);\n    transform: perspective(1000px) rotateX(-7.5deg);\n  }\n\n  100% {\n    -webkit-transform: perspective(1000px) rotateX(0deg);\n    transform: perspective(1000px) rotateX(0deg);\n  }\n}\n\n@keyframes swingInX {\n  0% {\n    -webkit-transform: perspective(1000px) rotateX(90deg);\n    transform: perspective(1000px) rotateX(90deg);\n    opacity: 0;\n  }\n\n  40% {\n    -webkit-transform: perspective(1000px) rotateX(-30deg);\n    transform: perspective(1000px) rotateX(-30deg);\n    opacity: 1;\n  }\n\n  60% {\n    -webkit-transform: perspective(1000px) rotateX(15deg);\n    transform: perspective(1000px) rotateX(15deg);\n  }\n\n  80% {\n    -webkit-transform: perspective(1000px) rotateX(-7.5deg);\n    transform: perspective(1000px) rotateX(-7.5deg);\n  }\n\n  100% {\n    -webkit-transform: perspective(1000px) rotateX(0deg);\n    transform: perspective(1000px) rotateX(0deg);\n  }\n}\n\n@-webkit-keyframes swingInY {\n  0% {\n    -webkit-transform: perspective(1000px) rotateY(-90deg);\n    transform: perspective(1000px) rotateY(-90deg);\n    opacity: 0;\n  }\n\n  40% {\n    -webkit-transform: perspective(1000px) rotateY(30deg);\n    transform: perspective(1000px) rotateY(30deg);\n    opacity: 1;\n  }\n\n  60% {\n    -webkit-transform: perspective(1000px) rotateY(-17.5deg);\n    transform: perspective(1000px) rotateY(-17.5deg);\n  }\n\n  80% {\n    -webkit-transform: perspective(1000px) rotateY(7.5deg);\n    transform: perspective(1000px) rotateY(7.5deg);\n  }\n\n  100% {\n    -webkit-transform: perspective(1000px) rotateY(0deg);\n    transform: perspective(1000px) rotateY(0deg);\n  }\n}\n\n@keyframes swingInY {\n  0% {\n    -webkit-transform: perspective(1000px) rotateY(-90deg);\n    transform: perspective(1000px) rotateY(-90deg);\n    opacity: 0;\n  }\n\n  40% {\n    -webkit-transform: perspective(1000px) rotateY(30deg);\n    transform: perspective(1000px) rotateY(30deg);\n    opacity: 1;\n  }\n\n  60% {\n    -webkit-transform: perspective(1000px) rotateY(-17.5deg);\n    transform: perspective(1000px) rotateY(-17.5deg);\n  }\n\n  80% {\n    -webkit-transform: perspective(1000px) rotateY(7.5deg);\n    transform: perspective(1000px) rotateY(7.5deg);\n  }\n\n  100% {\n    -webkit-transform: perspective(1000px) rotateY(0deg);\n    transform: perspective(1000px) rotateY(0deg);\n  }\n}\n\n/* Out */\n\n@-webkit-keyframes swingOutX {\n  0% {\n    -webkit-transform: perspective(1000px) rotateX(0deg);\n    transform: perspective(1000px) rotateX(0deg);\n  }\n\n  40% {\n    -webkit-transform: perspective(1000px) rotateX(-7.5deg);\n    transform: perspective(1000px) rotateX(-7.5deg);\n  }\n\n  60% {\n    -webkit-transform: perspective(1000px) rotateX(17.5deg);\n    transform: perspective(1000px) rotateX(17.5deg);\n  }\n\n  80% {\n    -webkit-transform: perspective(1000px) rotateX(-30deg);\n    transform: perspective(1000px) rotateX(-30deg);\n    opacity: 1;\n  }\n\n  100% {\n    -webkit-transform: perspective(1000px) rotateX(90deg);\n    transform: perspective(1000px) rotateX(90deg);\n    opacity: 0;\n  }\n}\n\n@keyframes swingOutX {\n  0% {\n    -webkit-transform: perspective(1000px) rotateX(0deg);\n    transform: perspective(1000px) rotateX(0deg);\n  }\n\n  40% {\n    -webkit-transform: perspective(1000px) rotateX(-7.5deg);\n    transform: perspective(1000px) rotateX(-7.5deg);\n  }\n\n  60% {\n    -webkit-transform: perspective(1000px) rotateX(17.5deg);\n    transform: perspective(1000px) rotateX(17.5deg);\n  }\n\n  80% {\n    -webkit-transform: perspective(1000px) rotateX(-30deg);\n    transform: perspective(1000px) rotateX(-30deg);\n    opacity: 1;\n  }\n\n  100% {\n    -webkit-transform: perspective(1000px) rotateX(90deg);\n    transform: perspective(1000px) rotateX(90deg);\n    opacity: 0;\n  }\n}\n\n@-webkit-keyframes swingOutY {\n  0% {\n    -webkit-transform: perspective(1000px) rotateY(0deg);\n    transform: perspective(1000px) rotateY(0deg);\n  }\n\n  40% {\n    -webkit-transform: perspective(1000px) rotateY(7.5deg);\n    transform: perspective(1000px) rotateY(7.5deg);\n  }\n\n  60% {\n    -webkit-transform: perspective(1000px) rotateY(-10deg);\n    transform: perspective(1000px) rotateY(-10deg);\n  }\n\n  80% {\n    -webkit-transform: perspective(1000px) rotateY(30deg);\n    transform: perspective(1000px) rotateY(30deg);\n    opacity: 1;\n  }\n\n  100% {\n    -webkit-transform: perspective(1000px) rotateY(-90deg);\n    transform: perspective(1000px) rotateY(-90deg);\n    opacity: 0;\n  }\n}\n\n@keyframes swingOutY {\n  0% {\n    -webkit-transform: perspective(1000px) rotateY(0deg);\n    transform: perspective(1000px) rotateY(0deg);\n  }\n\n  40% {\n    -webkit-transform: perspective(1000px) rotateY(7.5deg);\n    transform: perspective(1000px) rotateY(7.5deg);\n  }\n\n  60% {\n    -webkit-transform: perspective(1000px) rotateY(-10deg);\n    transform: perspective(1000px) rotateY(-10deg);\n  }\n\n  80% {\n    -webkit-transform: perspective(1000px) rotateY(30deg);\n    transform: perspective(1000px) rotateY(30deg);\n    opacity: 1;\n  }\n\n  100% {\n    -webkit-transform: perspective(1000px) rotateY(-90deg);\n    transform: perspective(1000px) rotateY(-90deg);\n    opacity: 0;\n  }\n}\n\n/*--------------\n      Zoom\n---------------*/\n\n.transition.zoom.in {\n  -webkit-animation-name: zoomIn;\n  animation-name: zoomIn;\n}\n\n.transition.zoom.out {\n  -webkit-animation-name: zoomOut;\n  animation-name: zoomOut;\n}\n\n@-webkit-keyframes zoomIn {\n  0% {\n    opacity: 1;\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n\n@keyframes zoomIn {\n  0% {\n    opacity: 1;\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n\n@-webkit-keyframes zoomOut {\n  0% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n}\n\n@keyframes zoomOut {\n  0% {\n    opacity: 1;\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n\n  100% {\n    opacity: 1;\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n}\n\n/*******************************\n      Static Animations\n*******************************/\n\n/*--------------\n    Emphasis\n---------------*/\n\n.flash.transition {\n  -webkit-animation-duration: 750ms;\n  animation-duration: 750ms;\n  -webkit-animation-name: flash;\n  animation-name: flash;\n}\n\n.shake.transition {\n  -webkit-animation-duration: 750ms;\n  animation-duration: 750ms;\n  -webkit-animation-name: shake;\n  animation-name: shake;\n}\n\n.bounce.transition {\n  -webkit-animation-duration: 750ms;\n  animation-duration: 750ms;\n  -webkit-animation-name: bounce;\n  animation-name: bounce;\n}\n\n.tada.transition {\n  -webkit-animation-duration: 750ms;\n  animation-duration: 750ms;\n  -webkit-animation-name: tada;\n  animation-name: tada;\n}\n\n.pulse.transition {\n  -webkit-animation-duration: 500ms;\n  animation-duration: 500ms;\n  -webkit-animation-name: pulse;\n  animation-name: pulse;\n}\n\n.jiggle.transition {\n  -webkit-animation-duration: 750ms;\n  animation-duration: 750ms;\n  -webkit-animation-name: jiggle;\n  animation-name: jiggle;\n}\n\n.transition.glow {\n  -webkit-animation-duration: 2000ms;\n  animation-duration: 2000ms;\n  -webkit-animation-timing-function: cubic-bezier(0.19, 1, 0.22, 1);\n  animation-timing-function: cubic-bezier(0.19, 1, 0.22, 1);\n}\n\n.transition.glow {\n  -webkit-animation-name: glow;\n  animation-name: glow;\n}\n\n/* Flash */\n\n@-webkit-keyframes flash {\n\n  0%,\n  50%,\n  100% {\n    opacity: 1;\n  }\n\n  25%,\n  75% {\n    opacity: 0;\n  }\n}\n\n@keyframes flash {\n\n  0%,\n  50%,\n  100% {\n    opacity: 1;\n  }\n\n  25%,\n  75% {\n    opacity: 0;\n  }\n}\n\n/* Shake */\n\n@-webkit-keyframes shake {\n\n  0%,\n  100% {\n    -webkit-transform: translateX(0);\n    transform: translateX(0);\n  }\n\n  10%,\n  30%,\n  50%,\n  70%,\n  90% {\n    -webkit-transform: translateX(-10px);\n    transform: translateX(-10px);\n  }\n\n  20%,\n  40%,\n  60%,\n  80% {\n    -webkit-transform: translateX(10px);\n    transform: translateX(10px);\n  }\n}\n\n@keyframes shake {\n\n  0%,\n  100% {\n    -webkit-transform: translateX(0);\n    transform: translateX(0);\n  }\n\n  10%,\n  30%,\n  50%,\n  70%,\n  90% {\n    -webkit-transform: translateX(-10px);\n    transform: translateX(-10px);\n  }\n\n  20%,\n  40%,\n  60%,\n  80% {\n    -webkit-transform: translateX(10px);\n    transform: translateX(10px);\n  }\n}\n\n/* Bounce */\n\n@-webkit-keyframes bounce {\n\n  0%,\n  20%,\n  50%,\n  80%,\n  100% {\n    -webkit-transform: translateY(0);\n    transform: translateY(0);\n  }\n\n  40% {\n    -webkit-transform: translateY(-30px);\n    transform: translateY(-30px);\n  }\n\n  60% {\n    -webkit-transform: translateY(-15px);\n    transform: translateY(-15px);\n  }\n}\n\n@keyframes bounce {\n\n  0%,\n  20%,\n  50%,\n  80%,\n  100% {\n    -webkit-transform: translateY(0);\n    transform: translateY(0);\n  }\n\n  40% {\n    -webkit-transform: translateY(-30px);\n    transform: translateY(-30px);\n  }\n\n  60% {\n    -webkit-transform: translateY(-15px);\n    transform: translateY(-15px);\n  }\n}\n\n/* Tada */\n\n@-webkit-keyframes tada {\n  0% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n\n  10%,\n  20% {\n    -webkit-transform: scale(0.9) rotate(-3deg);\n    transform: scale(0.9) rotate(-3deg);\n  }\n\n  30%,\n  50%,\n  70%,\n  90% {\n    -webkit-transform: scale(1.1) rotate(3deg);\n    transform: scale(1.1) rotate(3deg);\n  }\n\n  40%,\n  60%,\n  80% {\n    -webkit-transform: scale(1.1) rotate(-3deg);\n    transform: scale(1.1) rotate(-3deg);\n  }\n\n  100% {\n    -webkit-transform: scale(1) rotate(0);\n    transform: scale(1) rotate(0);\n  }\n}\n\n@keyframes tada {\n  0% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n\n  10%,\n  20% {\n    -webkit-transform: scale(0.9) rotate(-3deg);\n    transform: scale(0.9) rotate(-3deg);\n  }\n\n  30%,\n  50%,\n  70%,\n  90% {\n    -webkit-transform: scale(1.1) rotate(3deg);\n    transform: scale(1.1) rotate(3deg);\n  }\n\n  40%,\n  60%,\n  80% {\n    -webkit-transform: scale(1.1) rotate(-3deg);\n    transform: scale(1.1) rotate(-3deg);\n  }\n\n  100% {\n    -webkit-transform: scale(1) rotate(0);\n    transform: scale(1) rotate(0);\n  }\n}\n\n/* Pulse */\n\n@-webkit-keyframes pulse {\n  0% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n    opacity: 1;\n  }\n\n  50% {\n    -webkit-transform: scale(0.9);\n    transform: scale(0.9);\n    opacity: 0.7;\n  }\n\n  100% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n    opacity: 1;\n  }\n}\n\n@keyframes pulse {\n  0% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n    opacity: 1;\n  }\n\n  50% {\n    -webkit-transform: scale(0.9);\n    transform: scale(0.9);\n    opacity: 0.7;\n  }\n\n  100% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n    opacity: 1;\n  }\n}\n\n/* Jiggle */\n\n@-webkit-keyframes jiggle {\n  0% {\n    -webkit-transform: scale3d(1, 1, 1);\n    transform: scale3d(1, 1, 1);\n  }\n\n  30% {\n    -webkit-transform: scale3d(1.25, 0.75, 1);\n    transform: scale3d(1.25, 0.75, 1);\n  }\n\n  40% {\n    -webkit-transform: scale3d(0.75, 1.25, 1);\n    transform: scale3d(0.75, 1.25, 1);\n  }\n\n  50% {\n    -webkit-transform: scale3d(1.15, 0.85, 1);\n    transform: scale3d(1.15, 0.85, 1);\n  }\n\n  65% {\n    -webkit-transform: scale3d(0.95, 1.05, 1);\n    transform: scale3d(0.95, 1.05, 1);\n  }\n\n  75% {\n    -webkit-transform: scale3d(1.05, 0.95, 1);\n    transform: scale3d(1.05, 0.95, 1);\n  }\n\n  100% {\n    -webkit-transform: scale3d(1, 1, 1);\n    transform: scale3d(1, 1, 1);\n  }\n}\n\n@keyframes jiggle {\n  0% {\n    -webkit-transform: scale3d(1, 1, 1);\n    transform: scale3d(1, 1, 1);\n  }\n\n  30% {\n    -webkit-transform: scale3d(1.25, 0.75, 1);\n    transform: scale3d(1.25, 0.75, 1);\n  }\n\n  40% {\n    -webkit-transform: scale3d(0.75, 1.25, 1);\n    transform: scale3d(0.75, 1.25, 1);\n  }\n\n  50% {\n    -webkit-transform: scale3d(1.15, 0.85, 1);\n    transform: scale3d(1.15, 0.85, 1);\n  }\n\n  65% {\n    -webkit-transform: scale3d(0.95, 1.05, 1);\n    transform: scale3d(0.95, 1.05, 1);\n  }\n\n  75% {\n    -webkit-transform: scale3d(1.05, 0.95, 1);\n    transform: scale3d(1.05, 0.95, 1);\n  }\n\n  100% {\n    -webkit-transform: scale3d(1, 1, 1);\n    transform: scale3d(1, 1, 1);\n  }\n}\n\n/* Glow */\n\n@-webkit-keyframes glow {\n  0% {\n    background-color: #fcfcfd;\n  }\n\n  30% {\n    background-color: #fff6cd;\n  }\n\n  100% {\n    background-color: #fcfcfd;\n  }\n}\n\n@keyframes glow {\n  0% {\n    background-color: #fcfcfd;\n  }\n\n  30% {\n    background-color: #fff6cd;\n  }\n\n  100% {\n    background-color: #fcfcfd;\n  }\n}\n\n/*******************************\n        Site Overrides\n*******************************/\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  resize: none !important;\n}\n\n.ui.progress .bar {\n  min-width: 0;\n}\n\n.ui.modal:not(.basic) > .close {\n  cursor: pointer;\n  position: absolute;\n  top: 0.5rem;\n  right: 0.5rem;\n  z-index: 1;\n  opacity: 0.8;\n  font-size: 20px;\n  color: #17394d;\n  width: 22px;\n  height: 22px;\n  box-sizing: content-box;\n  padding: 8px 7px 6px;\n  margin: 0;\n}\n\n.ui.modal:not(.basic) > .close:hover {\n  opacity: 1;\n  border-radius: 4px;\n}\n\n/*******************************\n            Input\n*******************************/\n\n.ui.input {\n  color: #17394d;\n}\n\n.ui.input > input {\n  line-height: 20px !important;\n  padding: 8px 12px !important;\n  color: #17394d !important;\n  border-radius: 3px !important;\n}\n\n/*******************************\n            Button\n*******************************/\n\n.ui.button {\n  background-color: #ebeef0;\n  box-shadow: 0 1px 0 0 rgba(9, 45, 66, 0.13);\n  color: #17394d;\n  line-height: 20px;\n  min-height: 32px;\n  padding: 5px 12px 3px;\n  transition: background 0.3s ease;\n}\n\n/*--------------\n      Hover\n---------------*/\n\n.ui.button:hover {\n  background-color: #dfe3e6;\n  box-shadow: 0 1px 0 0 rgba(9, 45, 66, 0.25);\n  color: #17394d;\n}\n\n.ui.button:hover .icon {\n  opacity: 1;\n}\n\n/*--------------\n      Focus\n---------------*/\n\n.ui.button:focus {\n  background-color: #dfe3e6;\n  box-shadow: 0 1px 0 0 rgba(9, 45, 66, 0.25);\n  color: #17394d;\n}\n\n.ui.button:focus .icon {\n  opacity: 1;\n}\n\n/*---------------\n    Positive\n----------------*/\n\n/* Standard */\n\n.ui.positive.buttons .button,\n.ui.positive.button {\n  background-color: #5aac44;\n}\n\n.ui.positive.button {\n  box-shadow: 0 1px 0 #519839;\n}\n\n.ui.positive.buttons .button:hover,\n.ui.positive.button:hover {\n  background-color: #519839;\n}\n\n.ui.positive.buttons .button:focus,\n.ui.positive.button:focus {\n  background-color: #519839;\n}\n\n.ui.positive.buttons .button:active,\n.ui.positive.button:active {\n  background-color: #519839;\n}\n\n.ui.positive.buttons .active.button,\n.ui.positive.buttons .active.button:active,\n.ui.positive.active.button,\n.ui.positive.button .active.button:active {\n  background-color: #519839;\n}\n\n/*---------------\n    Negative\n----------------*/\n\n/* Standard */\n\n.ui.negative.buttons .button,\n.ui.negative.button {\n  background-color: #cf513d;\n}\n\n.ui.negative.button {\n  box-shadow: 0 1px 0 #6e2f1a;\n}\n\n.ui.negative.buttons .button:hover,\n.ui.negative.button:hover {\n  background-color: #b04632;\n}\n\n.ui.negative.buttons .button:focus,\n.ui.negative.button:focus {\n  background-color: #b04632;\n}\n\n.ui.negative.buttons .button:active,\n.ui.negative.button:active {\n  background-color: #b04632;\n}\n\n.ui.negative.buttons .active.button,\n.ui.negative.buttons .active.button:active,\n.ui.negative.active.button,\n.ui.negative.button .active.button:active {\n  background-color: #b04632;\n}\n\n/*******************************\n            Loader\n*******************************/\n\n/*-------------------\n      Coupling\n--------------------*/\n\n/* Black Dimmer */\n\n.ui.dimmer .ui.loader.inverted {\n  color: rgba(0, 0, 0, 0.87);\n}\n\n.ui.dimmer .ui.loader.inverted:before {\n  border-color: rgba(0, 0, 0, 0.1);\n}\n\n.ui.dimmer .ui.loader.inverted:after {\n  border-color: #767676 transparent transparent;\n}\n\n/*******************************\n            Comment\n*******************************/\n\n/*--------------------\n    User Actions\n---------------------*/\n\n.ui.comments .comment .actions button {\n  background: none;\n  border: none;\n  color: rgba(0, 0, 0, 0.4);\n  cursor: pointer;\n  display: inline-block;\n  margin: 0em 0.75em 0em 0em;\n  padding: 0 !important;\n}\n\n.ui.comments .comment .actions button:focus {\n  outline: none;\n}\n\n.ui.comments .comment .actions button:last-child {\n  margin-right: 0em;\n}\n\n.ui.comments .comment .actions button.active,\n.ui.comments .comment .actions button:hover {\n  color: rgba(0, 0, 0, 0.8);\n}\n"
  },
  {
    "path": "client/src/lib/hooks/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport usePrevious from './use-previous';\nimport useToggle from './use-toggle';\nimport useForceUpdate from './use-force-update';\nimport useEventCallback from './use-event-callback';\nimport useDidUpdate from './use-did-update';\nimport useWindowWidth from './use-window-width';\nimport useTransitioning from './use-transitioning';\nimport useClickAwayListener from './use-click-away-listener';\n\nexport {\n  usePrevious,\n  useToggle,\n  useForceUpdate,\n  useEventCallback,\n  useDidUpdate,\n  useWindowWidth,\n  useTransitioning,\n  useClickAwayListener,\n};\n"
  },
  {
    "path": "client/src/lib/hooks/use-click-away-listener.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\n\nexport default (elementRefs, onAwayClick, onCancel) => {\n  const pressedElement = useRef(null);\n\n  const handlePress = useCallback((event) => {\n    pressedElement.current = event.target;\n  }, []);\n\n  useEffect(() => {\n    const handleEvent = (event) => {\n      const element = elementRefs.find(({ current }) => current?.contains(event.target))?.current;\n\n      if (element) {\n        if (!pressedElement.current || pressedElement.current !== element) {\n          onCancel();\n        }\n      } else if (pressedElement.current) {\n        onCancel();\n      } else {\n        onAwayClick();\n      }\n\n      pressedElement.current = null;\n    };\n\n    document.addEventListener('mouseup', handleEvent, true);\n    document.addEventListener('touchend', handleEvent, true);\n\n    return () => {\n      document.removeEventListener('mouseup', handleEvent, true);\n      document.removeEventListener('touchend', handleEvent, true);\n    };\n  }, [onAwayClick, onCancel]); // eslint-disable-line react-hooks/exhaustive-deps\n\n  const props = useMemo(\n    () => ({\n      onMouseDown: handlePress,\n      onTouchStart: handlePress,\n    }),\n    [handlePress],\n  );\n\n  return props;\n};\n"
  },
  {
    "path": "client/src/lib/hooks/use-did-update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useEffect, useRef } from 'react';\n\nexport default (callback, dependencies) => {\n  const isMountedRef = useRef(false);\n\n  useEffect(() => {\n    if (isMountedRef.current) {\n      callback();\n    } else {\n      isMountedRef.current = true;\n    }\n  }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps\n};\n"
  },
  {
    "path": "client/src/lib/hooks/use-event-callback.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\n\nexport default (callback, dependencies) => {\n  const callbackRef = useRef(() => {\n    throw new Error('Cannot call an event handler while rendering.');\n  });\n\n  useEffect(() => {\n    callbackRef.current = callback;\n  }, [callback, ...dependencies]); // eslint-disable-line react-hooks/exhaustive-deps\n\n  return useCallback((...args) => callbackRef.current(...args), []);\n};\n"
  },
  {
    "path": "client/src/lib/hooks/use-force-update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport useToggle from './use-toggle';\n\nexport default () => useToggle()[1];\n"
  },
  {
    "path": "client/src/lib/hooks/use-previous.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useEffect, useRef } from 'react';\n\nexport default (value) => {\n  const prevValueRef = useRef();\n\n  useEffect(() => {\n    prevValueRef.current = value;\n  }, [value]);\n\n  return prevValueRef.current;\n};\n"
  },
  {
    "path": "client/src/lib/hooks/use-toggle.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback, useState } from 'react';\n\nexport default (defaultState = false) => {\n  const [state, setState] = useState(defaultState);\n\n  const toggle = useCallback(() => {\n    setState((prevState) => !prevState);\n  }, []);\n\n  return [state, toggle];\n};\n"
  },
  {
    "path": "client/src/lib/hooks/use-transitioning.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useCallback, useLayoutEffect } from 'react';\n\nexport default (ref, className, dependencies) => {\n  const handleTransitionEnd = useCallback((event) => {\n    if (event.target === ref.current) {\n      ref.current.classList.remove(className);\n    }\n  }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n  useLayoutEffect(() => {\n    ref.current.classList.add(className);\n  }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps\n\n  return handleTransitionEnd;\n};\n"
  },
  {
    "path": "client/src/lib/hooks/use-window-width.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { useEffect, useState } from 'react';\n\nexport default () => {\n  const [width, setWidth] = useState(window.innerWidth);\n\n  useEffect(() => {\n    const handleResize = () => {\n      setWidth(window.innerWidth);\n    };\n\n    window.addEventListener('resize', handleResize);\n\n    return () => {\n      window.removeEventListener('resize', handleResize);\n    };\n  }, []);\n\n  return width;\n};\n"
  },
  {
    "path": "client/src/lib/popup/Popup.module.css",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n.closeButton {\n  background: transparent !important;\n  box-shadow: none !important;\n  margin: 0 !important;\n  padding: 10px 12px 10px 8px !important;\n  position: absolute;\n  right: 0;\n  top: 0;\n  width: 40px;\n  z-index: 2000;\n}\n\n.wrapper {\n  border-radius: 3px !important;\n  border-width: 0 !important;\n  box-shadow: 0 8px 16px -4px rgba(9, 45, 66, 0.25),\n    0 0 0 1px rgba(9, 45, 66, 0.08) !important;\n  margin-top: 6px !important;\n  max-height: calc(100% - 70px);\n  padding: 0 12px 12px !important;\n  width: 304px;\n}\n"
  },
  {
    "path": "client/src/lib/popup/close-popup.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport default () => {\n  document.body.click(); // FIXME: hack\n};\n"
  },
  {
    "path": "client/src/lib/popup/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport usePopup from './use-popup';\nimport closePopup from './close-popup';\n\nexport { usePopup, closePopup };\n"
  },
  {
    "path": "client/src/lib/popup/use-popup.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { ResizeObserver } from '@juggle/resize-observer';\nimport React, { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { Button, Popup as SemanticUIPopup } from 'semantic-ui-react';\n\nimport styles from './Popup.module.css';\n\nexport default (Step, { position, onOpen, onClose } = {}) => {\n  return useMemo(() => {\n    const Popup = React.forwardRef(({ children, ...stepProps }, ref) => {\n      const [stepParams, setStepParams] = useState(null);\n\n      const wrapperRef = useRef(null);\n      const resizeObserverRef = useRef(null);\n\n      const open = useCallback((params = {}) => {\n        setStepParams(params);\n\n        if (onOpen) {\n          onOpen();\n        }\n      }, []);\n\n      const handleOpen = useCallback(() => {\n        open();\n      }, [open]);\n\n      const handleClose = useCallback(() => {\n        setStepParams(null);\n      }, []);\n\n      const handleMouseDown = useCallback((event) => {\n        event.stopPropagation();\n      }, []);\n\n      const handleClick = useCallback((event) => {\n        event.stopPropagation();\n      }, []);\n\n      const handleTriggerClick = useCallback(\n        (event) => {\n          event.stopPropagation();\n          const { onClick } = children;\n\n          if (onClick) {\n            onClick(event);\n          }\n        },\n        [children],\n      );\n\n      const handleContentRef = useCallback((element) => {\n        if (resizeObserverRef.current) {\n          resizeObserverRef.current.disconnect();\n        }\n\n        if (!element) {\n          resizeObserverRef.current = null;\n          return;\n        }\n\n        resizeObserverRef.current = new ResizeObserver(() => {\n          if (resizeObserverRef.current.isInitial) {\n            resizeObserverRef.current.isInitial = false;\n            return;\n          }\n\n          wrapperRef.current.positionUpdate();\n        });\n\n        resizeObserverRef.current.isInitial = true;\n        resizeObserverRef.current.observe(element);\n      }, []);\n\n      useImperativeHandle(\n        ref,\n        () => ({\n          open,\n        }),\n        [open],\n      );\n\n      const tigger = React.cloneElement(children, {\n        onClick: handleTriggerClick,\n      });\n\n      return (\n        <SemanticUIPopup\n          basic\n          wide\n          ref={wrapperRef}\n          trigger={tigger}\n          on=\"click\"\n          open={!!stepParams}\n          position={position || 'bottom left'}\n          popperModifiers={[\n            {\n              name: 'preventOverflow',\n              enabled: true,\n              options: {\n                altAxis: true,\n                padding: 20,\n              },\n            },\n          ]}\n          className={styles.wrapper}\n          onOpen={handleOpen}\n          onClose={handleClose}\n          onUnmount={onClose}\n          onMouseDown={handleMouseDown}\n          onClick={handleClick}\n        >\n          <div ref={handleContentRef}>\n            <Button icon=\"close\" onClick={handleClose} className={styles.closeButton} />\n            {/* eslint-disable-next-line react/jsx-props-no-spreading */}\n            <Step {...stepProps} {...stepParams} onClose={handleClose} />\n          </div>\n        </SemanticUIPopup>\n      );\n    });\n\n    Popup.propTypes = {\n      children: PropTypes.node.isRequired,\n    };\n\n    return React.memo(Popup);\n  }, [position, onOpen, onClose]);\n};\n"
  },
  {
    "path": "client/src/lib/redux-router/ReduxRouter.jsx",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport React, { useLayoutEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Router } from 'react-router';\n\nimport { handleLocationChange } from './actions';\n\nfunction ReduxRouter({ children, history, selector, basename }) {\n  const state = useSelector(selector);\n  const dispatch = useDispatch();\n\n  useLayoutEffect(() => {\n    const unlisten = history.listen((nextState) => {\n      dispatch(handleLocationChange(nextState.location, nextState.action));\n    });\n\n    dispatch(handleLocationChange(history.location, history.action, true));\n\n    return unlisten;\n  }, [history, dispatch]);\n\n  return (\n    <Router\n      basename={basename}\n      location={state.location}\n      navigationType={state.action}\n      navigator={history}\n    >\n      {children}\n    </Router>\n  );\n}\n\nReduxRouter.propTypes = {\n  children: PropTypes.element.isRequired,\n  history: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  selector: PropTypes.func,\n  basename: PropTypes.string,\n};\n\nReduxRouter.defaultProps = {\n  selector: ({ router }) => router,\n  basename: undefined,\n};\n\nexport default ReduxRouter;\n"
  },
  {
    "path": "client/src/lib/redux-router/actions.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport const LOCATION_CHANGE_HANDLE = '@@router/LOCATION_CHANGE_HANDLE';\n\nexport const handleLocationChange = (location, action, isFirstRendering = false) => ({\n  type: LOCATION_CHANGE_HANDLE,\n  payload: {\n    location,\n    action,\n    isFirstRendering,\n  },\n});\n\nexport const HISTORY_METHOD_CALL = '@@router/HISTORY_METHOD_CALL';\n\nconst createHistoryMethodCaller = (method) => {\n  return (...args) => ({\n    type: HISTORY_METHOD_CALL,\n    payload: {\n      method,\n      args,\n    },\n  });\n};\n\nexport const push = createHistoryMethodCaller('push');\nexport const replace = createHistoryMethodCaller('replace');\nexport const go = createHistoryMethodCaller('go');\nexport const back = createHistoryMethodCaller('back');\nexport const forward = createHistoryMethodCaller('forward');\n"
  },
  {
    "path": "client/src/lib/redux-router/create-router-middleware.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { HISTORY_METHOD_CALL } from './actions';\n\nconst createRouterMiddleware = (history) => {\n  // eslint-disable-next-line consistent-return\n  return () => (next) => (action) => {\n    if (action.type !== HISTORY_METHOD_CALL) {\n      return next(action);\n    }\n\n    const {\n      payload: { method, args },\n    } = action;\n\n    history[method](...args);\n  };\n};\n\nexport default createRouterMiddleware;\n"
  },
  {
    "path": "client/src/lib/redux-router/create-router-reducer.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { LOCATION_CHANGE_HANDLE } from './actions';\n\nconst createRouterReducer = (history) => {\n  const initialState = {\n    location: history.location,\n    action: history.action,\n  };\n\n  return (state = initialState, { type, payload } = {}) => {\n    if (type === LOCATION_CHANGE_HANDLE) {\n      const { location, action, isFirstRendering } = payload;\n\n      if (isFirstRendering) {\n        return state;\n      }\n\n      return {\n        ...state,\n        location,\n        action,\n      };\n    }\n\n    return state;\n  };\n};\n\nexport default createRouterReducer;\n"
  },
  {
    "path": "client/src/lib/redux-router/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport {\n  LOCATION_CHANGE_HANDLE,\n  HISTORY_METHOD_CALL,\n  push,\n  replace,\n  go,\n  back,\n  forward,\n} from './actions';\nexport { default as createRouterReducer } from './create-router-reducer';\nexport { default as createRouterMiddleware } from './create-router-middleware';\nexport { default as ReduxRouter } from './ReduxRouter';\n"
  },
  {
    "path": "client/src/lib/syntax-highlighter/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport syntaxHighlighter from './syntax-highlighter';\n\nexport default syntaxHighlighter;\n"
  },
  {
    "path": "client/src/lib/syntax-highlighter/language-definitions/chapel.js",
    "content": "/*\n * This file derives from https://github.com/chapel-lang/highlightjs-chapel\n * Licensed under the BSD 3-Clause License:\n *\n * Copyright (c) 2006, Ivan Sagalaev.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * * Redistributions of source code must retain the above copyright notice, this\n *   list of conditions and the following disclaimer.\n *\n * * Redistributions in binary form must reproduce the above copyright notice,\n *   this list of conditions and the following disclaimer in the documentation\n *   and/or other materials provided with the distribution.\n *\n * * Neither the name of the copyright holder nor the names of its\n *   contributors may be used to endorse or promote products derived from\n *   this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n/*\nLanguage: Chapel\nAuthor: Brad Chamberlain <bradcray@gmail.com>\nContributors:\nDescription: A parallel language for productive programming at scale\nWebsite: https://chapel-lang.org\n*/\n\nexport default (hljs) => ({\n  name: 'Chapel',\n  aliases: ['chapel', 'chpl'],\n  disableAutodetect: true,\n  keywords: {\n    keyword:\n      'align as atomic begin bool borrowed break by bytes catch class cobegin coforall complex config const continue defer delete dmapped do domain else enum export except extern for forall forwarding if imag import in include index inline inout int iter label lambda let lifetime local locale module new noinit nothing on only otherwise out override owned param private proc prototype public real record reduce ref require return scan select serial shared single sparse string subdomain sync then this throw throws try try! type uint union unmanaged use var void when where while with yield zip',\n\n    // What other built-ins should we add here?\n    built_in: 'here Locales write writef writeln',\n    literal: 'false true nil none',\n  },\n  contains: [\n    // Our line comments are like C's\n    hljs.C_LINE_COMMENT_MODE,\n\n    // Like C's block comment mode, but supporting nested comments:\n    hljs.COMMENT('/\\\\*', '\\\\*/', { contains: ['self'] }),\n\n    // The following is similar to C's C_NUMBER_MODE, but it\n    // requires a digit after '9.' like Chapel does to avoid\n    // mis-coloring '0..10' and disambiguate calls like\n    // '9.square()'\n    {\n      className: 'number',\n      begin:\n        // eslint-disable-next-line no-multi-str\n        '(-?)(\\\\b0[xX][a-fA-F0-9]+|(\\\\b\\\\d+(\\\\.\\\\d+)?|\\\\.\\\\d\\\n+)([eE][-+]?\\\\d+)?)',\n      relevance: 0,\n    },\n    // More could/should be done here to handle escapes and\n    // the like, but for some reason, inheriting the C string\n    // definitions didn't work well for me, so I went with this\n    // basic approach for now.\n    {\n      className: 'string',\n      variants: [\n        {\n          begin: '(b)?\"',\n          end: '\"',\n        },\n        {\n          begin: \"(b)?'\",\n          end: \"'\",\n        },\n        {\n          begin: '(b)?\"\"\"',\n          end: '\"\"\"',\n        },\n        {\n          begin: \"(b)?'''\",\n          end: \"'''\",\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "client/src/lib/syntax-highlighter/language-definitions/dafny.js",
    "content": "/*\nLanguage: Dafny\nAuthor: Roberto Saltini\nDescription: Grammar definition for the Dafny language\nWebsite: https://github.com/dafny-lang/dafny\n*/\n\n/**\n * Copyright 2021, ConsenSys Software Inc.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n */\n\nexport default (hljs) => {\n  const IDENT_WITH_BRACKETS = /[a-zA-Z][a-zA-Z_<>0-9]*/;\n\n  const DAFNY_KEYWORDS = [\n    'abstract',\n    'allocated',\n    'as',\n    'assert',\n    'assume',\n    'break',\n    'by',\n    'calc',\n    'case',\n    'class',\n    'codatatype',\n    'colemma',\n    'const',\n    'constructor',\n    'copredicate',\n    'datatype',\n    'decreases',\n    'else',\n    'ensures',\n    'exists',\n    'export',\n    'extends',\n    'forall',\n    'fresh',\n    'function',\n    'for',\n    'ghost',\n    'if',\n    'import',\n    'include',\n    'inductive',\n    'invariant',\n    'is',\n    'iterator',\n    'label',\n    'lemma',\n    'match',\n    'method',\n    'modifies',\n    'modify',\n    'module',\n    'nameonly',\n    'new',\n    'newtype',\n    'old',\n    'opened',\n    'predicate',\n    'print',\n    'provides',\n    'reads',\n    'real',\n    'refines',\n    'requires',\n    'return',\n    'returns',\n    'reveal',\n    'reveals',\n    'static',\n    'string',\n    'then',\n    'this',\n    'trait',\n    'twostate',\n    'type',\n    'unchanged',\n    'var',\n    'while',\n    'witness',\n    'yield',\n    'yields',\n  ];\n\n  const DAFNY_TYPES = [\n    'bool',\n    'char',\n    'imap',\n    'iset',\n    'int',\n    'map',\n    'multiset',\n    'nat',\n    'object',\n    'object?',\n    'ORDINAL',\n    'seq',\n    'set',\n    'array',\n    'bc',\n  ];\n  const DAFNY_LITERALS = ['false', 'null', 'true'];\n  const KEYWORDS = {\n    keyword: DAFNY_KEYWORDS,\n    type: DAFNY_TYPES,\n    literal: DAFNY_LITERALS,\n  };\n  const TYPE_FOR_IDENTIFIERS = {\n    begin: [/(?<!:):/, /[\\s\\n]*/, IDENT_WITH_BRACKETS],\n    scope: {\n      1: 'operator',\n      3: 'type',\n    },\n    relevance: 0,\n  };\n\n  const OPERATORS = {\n    begin: `${/(?<!\\w)/.source}(${[\n      '&&',\n      '\\\\|\\\\|',\n      '<==>',\n      '==>',\n      '<==',\n      '==',\n      '!=',\n      '!',\n      '>',\n      '<',\n      '>=',\n      '<=',\n      '\\\\+',\n      '-',\n      '\\\\*',\n      '/',\n      '%',\n      '>>',\n      '<<',\n      '&',\n      '\\\\|',\n      '\\\\^',\n      '!!',\n      'in',\n      '!in',\n      ':=',\n      ':\\\\|',\n      '::',\n      '=',\n    ].join('|')})${/(?!\\w)/.source}`,\n    scope: 'operator',\n    relevance: 0,\n  };\n  return {\n    case_insensitive: false,\n    keywords: KEYWORDS,\n    contains: [\n      hljs.QUOTE_STRING_MODE,\n      hljs.C_LINE_COMMENT_MODE,\n      hljs.NUMBER_MODE,\n      hljs.COMMENT(\n        '/\\\\*', // begin\n        '\\\\*/', // end\n        {\n          contains: [\n            {\n              scope: 'doctag',\n              begin: '@\\\\w+',\n              relevance: 0,\n            },\n          ],\n        },\n      ),\n      TYPE_FOR_IDENTIFIERS,\n      OPERATORS,\n      {\n        begin: `\\\\b(${DAFNY_TYPES.join('|')})\\\\b${IDENT_WITH_BRACKETS.source}`,\n        scope: 'type',\n      },\n      {\n        begin: [/\\b(class|module|trait)\\b/, /\\s*/, hljs.IDENT_RE],\n        scope: {\n          1: 'keyword',\n          3: 'title.class',\n        },\n        relevance: 0,\n      },\n      {\n        begin: [`(?:${hljs.IDENT_RE}\\\\s+)`, hljs.IDENT_RE, /\\s*(?=\\(|<)/],\n        scope: {\n          2: 'title.function',\n        },\n        keywords: KEYWORDS,\n        contains: [\n          {\n            begin: /</,\n            end: />/,\n            scope: 'type',\n            relevance: 0,\n          },\n          {\n            scope: 'params',\n            begin: /\\(/,\n            end: /\\)/,\n            keywords: KEYWORDS,\n            relevance: 0,\n            contains: [\n              hljs.QUOTE_STRING_MODE,\n              hljs.C_BLOCK_COMMENT_MODE,\n              TYPE_FOR_IDENTIFIERS,\n              OPERATORS,\n              hljs.NUMBER_MODE,\n            ],\n          },\n          hljs.C_LINE_COMMENT_MODE,\n          hljs.C_BLOCK_COMMENT_MODE,\n        ],\n      },\n    ],\n  };\n};\n"
  },
  {
    "path": "client/src/lib/syntax-highlighter/language-definitions/gn.js",
    "content": "/*\n * This file derives from https://github.com/highlightjs/highlightjs-GN\n * Licensed under the MIT License:\n *\n * Copyright (c) 2019 Petr Hosek\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/*\n * highlight.js GN syntax highlighting definition\n *\n * @see https://github.com/highlightjs/highlight.js\n *\n * @package: highlightjs-GN\n * @author:  Petr Hosek <petrhosek@gmail.com>\n * @since:   2019-05-28\n *\n * Description: GN is a meta-build system that generates build files for Ninja.\n * Category: common\n */\n\nexport default (hljs) => {\n  const SUBST = {\n    className: 'subst',\n    relevance: 2,\n    variants: [\n      {\n        begin: '\\\\$[A-Za-z0-9_]+',\n      },\n      {\n        begin: '\\\\${',\n        end: '}',\n        contains: [\n          {\n            className: 'variable',\n            begin: hljs.UNDERSCORE_IDENT_RE,\n            relevance: 0,\n          },\n        ],\n      },\n    ],\n  };\n\n  const LINK = {\n    className: 'link',\n    relevance: 5,\n    begin: ':\\\\w+',\n  };\n\n  const NUMBER = {\n    className: 'number',\n    relevance: 0,\n    begin: hljs.NUMBER_RE,\n  };\n\n  const STRING = {\n    className: 'string',\n    relevance: 0,\n    begin: '\"',\n    end: '\"',\n    illegal: '\\\\n',\n    contains: [hljs.BACKSLASH_ESCAPE, SUBST, LINK],\n  };\n\n  const KEYWORDS = {\n    keyword: 'if else',\n    literal:\n      'true false ' +\n      'current_cpu current_os current_toolchain ' +\n      'default_toolchain host_cpu host_os ' +\n      'root_build_dir root_gen_dir root_out_dir ' +\n      'target_cpu target_gen_dir target_out_dir ' +\n      'target_os target_name invoker',\n    type:\n      'action action_foreach copy executable group ' +\n      'shared_library source_set static_library ' +\n      'loadable_module generated_file',\n    built_in:\n      'assert config declare_args defined exec_script ' +\n      'foreach get_label_info get_path_info ' +\n      'get_target_outputs getenv import print ' +\n      'process_file_template read_file rebase_path ' +\n      'set_default_toolchain set_defaults ' +\n      'set_sources_assignment_filter template tool ' +\n      'toolchain toolchain_args propagates_configs ' +\n      'write_file forward_variables_from target ' +\n      'get_name_info not_needed',\n    symbol:\n      'all_dependent_configs allow_circular_includes_from ' +\n      'args asmflags cflags cflags_c cflags_cc cflags_objc ' +\n      'cflags_objcc check_includes complete_static_lib ' +\n      'configs data data_deps defines depfile deps ' +\n      'include_dirs inputs ldflags lib_dirs libs ' +\n      'output_extension output_name outputs public ' +\n      'public_configs public_deps script sources testonly ' +\n      'visibility contents output_conversion rebase ' +\n      'data_keys walk_keys',\n  };\n\n  return {\n    aliases: ['gn', 'gni'],\n    keywords: KEYWORDS,\n    contains: [NUMBER, STRING, hljs.HASH_COMMENT_MODE],\n  };\n};\n"
  },
  {
    "path": "client/src/lib/syntax-highlighter/language-definitions/godot.js",
    "content": "/*\n * This file derives from https://github.com/highlightjs/highlightjs-gdscript\n * Licensed under the BSD 3-Clause License:\n *\n * Copyright (c) 2020, highlight.js\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its\n *    contributors may be used to endorse or promote products derived from\n *    this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n/*\nLanguage: GDScript\nAuthor: Khairul Hidayat <me@khairul.my.id>, Nelson Sylvest*r Fritsch <info@nelsonfritsch.de>, Hugo Locurcio <hugo.locurcio@hugo.pro>\nDescription: Programming language for Godot Engine\n*/\n\nexport default (hljs) => {\n  const KEYWORDS = {\n    keyword:\n      'and in not or self void as assert breakpoint class class_name ' +\n      'extends is func setget signal tool yield const enum export ' +\n      'onready static var break continue if elif else for pass return ' +\n      'match while remote sync master puppet remotesync mastersync ' +\n      'puppetsync',\n\n    built_in:\n      'Color8 ColorN abs acos asin atan atan2 bytes2var ' +\n      'cartesian2polar ceil char clamp convert cos cosh db2linear ' +\n      'decimals dectime deg2rad dict2inst ease exp floor fmod fposmod ' +\n      'funcref get_stack hash inst2dict instance_from_id inverse_lerp ' +\n      'is_equal_approx is_inf is_instance_valid is_nan is_zero_approx ' +\n      'len lerp lerp_angle linear2db load log max min move_toward ' +\n      'nearest_po2 ord parse_json polar2cartesian posmod pow preload ' +\n      'print_stack push_error push_warning rad2deg rand_range ' +\n      'rand_seed randf randi randomize range_lerp round seed sign sin ' +\n      'sinh smoothstep sqrt step_decimals stepify str str2var tan tanh ' +\n      'to_json type_exists typeof validate_json var2bytes var2str ' +\n      'weakref wrapf wrapi bool int float String NodePath ' +\n      'Vector2 Rect2 Transform2D Vector3 Rect3 Plane ' +\n      'Quat Basis Transform Color RID Object NodePath ' +\n      'Dictionary Array PoolByteArray PoolIntArray ' +\n      'PoolRealArray PoolStringArray PoolVector2Array ' +\n      'PoolVector3Array PoolColorArray',\n\n    literal: 'true false null',\n  };\n\n  return {\n    aliases: ['godot', 'gdscript'],\n    keywords: KEYWORDS,\n    contains: [\n      hljs.NUMBER_MODE,\n      hljs.HASH_COMMENT_MODE,\n      {\n        className: 'comment',\n        begin: /\"\"\"/,\n        end: /\"\"\"/,\n      },\n      hljs.QUOTE_STRING_MODE,\n      {\n        variants: [\n          {\n            className: 'function',\n            beginKeywords: 'func',\n          },\n          {\n            className: 'class',\n            beginKeywords: 'class',\n          },\n        ],\n        end: /:/,\n        contains: [hljs.UNDERSCORE_TITLE_MODE],\n      },\n    ],\n  };\n};\n"
  },
  {
    "path": "client/src/lib/syntax-highlighter/language-definitions/hlsl.js",
    "content": "/*\n * This file derives from https://github.com/highlightjs/highlightjs-hlsl\n * Licensed under the MIT License:\n *\n * Copyright (c) 2021 Stef Levesque (@stef-levesque)\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/*\nLanguage: HLSL\nDescription: High-level shader language\nAuthor: Stef Levesque\nWebsite: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl\nCategory: graphics\n*/\n\nconst HLSL_NUMBER_RE =\n  '(-?)(\\\\b0[xX][a-fA-F0-9]+|(\\\\b\\\\d+(\\\\.\\\\d*)?([hHfFlL]?)|\\\\.\\\\d+)([eE][-+]?\\\\d+)?([hHfFlL]?))'; // 0x..., 0..., decimal, float, half, double\n\nconst HLSL_NUMBER_MODE = {\n  className: 'number',\n  begin: HLSL_NUMBER_RE,\n  relevance: 0,\n};\n\nexport default (hljs) => {\n  const matrixBases =\n    // eslint-disable-next-line no-useless-concat\n    'bool double float half int uint ' + 'min16float min10float min16int min12int min16uint';\n\n  const matrixSuffixes = [\n    '',\n    '1',\n    '2',\n    '3',\n    '4',\n    '1x1',\n    '1x2',\n    '1x3',\n    '1x4',\n    '2x1',\n    '2x2',\n    '2x3',\n    '2x4',\n    '3x1',\n    '3x2',\n    '3x3',\n    '3x4',\n    '4x1',\n    '4x2',\n    '4x3',\n    '4x4',\n  ];\n\n  const matrixTypes = [];\n  // eslint-disable-next-line no-restricted-syntax\n  for (const base of matrixBases.split(' ')) {\n    // eslint-disable-next-line no-restricted-syntax\n    for (const suffix of matrixSuffixes) {\n      matrixTypes.push(base + suffix);\n    }\n  }\n\n  const semanticsSV =\n    'SV_Coverage SV_Depth SV_DispatchThreadID SV_DomainLocation ' +\n    'SV_GroupID SV_GroupIndex SV_GroupThreadID SV_GSInstanceID SV_InnerCoverage SV_InsideTessFactor ' +\n    'SV_InstanceID SV_IsFrontFace SV_OutputControlPointID SV_Position SV_PrimitiveID ' +\n    'SV_RenderTargetArrayIndex SV_SampleIndex SV_StencilRef SV_TessFactor SV_VertexID ' +\n    'SV_ViewportArrayIndex, SV_ShadingRate';\n\n  const semanticsNum =\n    'BINORMAL BLENDINDICES BLENDWEIGHT COLOR NORMAL POSITION PSIZE TANGENT TEXCOORD TESSFACTOR DEPTH ' +\n    'SV_ClipDistance SV_CullDistance SV_DepthGreaterEqual SV_DepthLessEqual SV_Target ' +\n    'SV_CLIPDISTANCE SV_CULLDISTANCE SV_DEPTHGREATEREQUAL SV_DEPTHLESSEQUAL SV_TARGET';\n\n  const semanticsTypes = semanticsNum.split(' ');\n  // eslint-disable-next-line no-restricted-syntax\n  for (const s of semanticsNum.split(' ')) {\n    // eslint-disable-next-line no-restricted-syntax\n    for (const n of Array(16).keys()) {\n      semanticsTypes.push(s + n.toString());\n    }\n  }\n\n  return {\n    name: 'HLSL',\n    keywords: {\n      keyword:\n        'AppendStructuredBuffer asm asm_fragment BlendState break Buffer ByteAddressBuffer case ' +\n        'cbuffer centroid class column_major compile compile_fragment CompileShader const continue ' +\n        'ComputeShader ConsumeStructuredBuffer default DepthStencilState DepthStencilView discard do ' +\n        'DomainShader dword else export extern false for fxgroup GeometryShader groupshared ' +\n        'Hullshader if in inline inout InputPatch interface line lineadj linear LineStream ' +\n        'matrix namespace nointerpolation noperspective ' +\n        'NULL out OutputPatch packoffset pass pixelfragment PixelShader point PointStream precise ' +\n        'RasterizerState RenderTargetView return register row_major RWBuffer RWByteAddressBuffer ' +\n        'RWStructuredBuffer RWTexture1D RWTexture1DArray RWTexture2D RWTexture2DArray RWTexture3D sample ' +\n        'sampler SamplerState SamplerComparisonState shared snorm stateblock stateblock_state static string ' +\n        'struct switch StructuredBuffer tbuffer technique technique10 technique11 texture Texture1D ' +\n        'Texture1DArray Texture2D Texture2DArray Texture2DMS Texture2DMSArray Texture3D TextureCube ' +\n        'TextureCubeArray true typedef triangle triangleadj TriangleStream uint uniform unorm unsigned ' +\n        'vector vertexfragment VertexShader void volatile while',\n\n      type:\n        // Data Types\n        `${matrixTypes.join(' ')} ` +\n        `Buffer vector matrix sampler SamplerState PixelShader VertexShader ` +\n        `texture Texture1D Texture1DArray Texture2D Texture2DArray Texture2DMS Texture2DMSArray Texture3D ` +\n        `TextureCube TextureCubeArray struct typedef`,\n\n      built_in:\n        // Semantics\n        `POSITIONT FOG PSIZE VFACE VPOS ${semanticsTypes.join(\n          ' ',\n        )} ${semanticsSV} ${semanticsSV.toUpperCase()} ` +\n        // Functions\n        `abort abs acos all AllMemoryBarrier AllMemoryBarrierWithGroupSync any asdouble asfloat asin asint asuint ` +\n        `atan atan2 ceil CheckAccessFullyMapped clamp clip cos cosh countbits cross D3DCOLORtoUBYTE4 ddx ddx_coarse ` +\n        `ddx_fine ddy ddy_coarse ddy_fine degrees determinant DeviceMemoryBarrier DeviceMemoryBarrierWithGroupSync ` +\n        `distance dot dst errorf EvaluateAttributeAtCentroid EvaluateAttributeAtSample EvaluateAttributeSnapped ` +\n        `exp exp2 f16tof32 f32tof16 faceforward firstbithigh firstbitlow floor fma fmod frac frexp fwidth ` +\n        `GetRenderTargetSampleCount GetRenderTargetSamplePosition GroupMemoryBarrier GroupMemoryBarrierWithGroupSync ` +\n        `InterlockedAdd InterlockedAnd InterlockedCompareExchange InterlockedCompareStore InterlockedExchange ` +\n        `InterlockedMax InterlockedMin InterlockedOr InterlockedXor isfinite isinf isnan ldexp length lerp lit log ` +\n        `log10 log2 mad max min modf msad4 mul noise normalize pow printf Process2DQuadTessFactorsAvg ` +\n        `Process2DQuadTessFactorsMax Process2DQuadTessFactorsMin ProcessIsolineTessFactors ProcessQuadTessFactorsAvg ` +\n        `ProcessQuadTessFactorsMax ProcessQuadTessFactorsMin ProcessTriTessFactorsAvg ProcessTriTessFactorsMax ` +\n        `ProcessTriTessFactorsMin radians rcp reflect refract reversebits round rsqrt saturate sign sin sincos sinh ` +\n        `smoothstep sqrt step tan tanh tex1D tex1Dbias tex1Dgrad tex1Dlod tex1Dproj tex2D tex2Dbias tex2Dgrad ` +\n        `tex2Dlod tex2Dproj tex3D tex3Dbias tex3Dgrad tex3Dlod tex3Dproj texCUBE texCUBEbias texCUBEgrad texCUBElod ` +\n        `texCUBEproj transpose trunc`,\n\n      literal: 'true false',\n    },\n    illegal: '\"',\n    contains: [\n      hljs.C_LINE_COMMENT_MODE,\n      hljs.C_BLOCK_COMMENT_MODE,\n      HLSL_NUMBER_MODE,\n      {\n        className: 'meta',\n        begin: '#',\n        end: '$',\n      },\n    ],\n  };\n};\n"
  },
  {
    "path": "client/src/lib/syntax-highlighter/language-definitions/terraform.js",
    "content": "/*\n * This file derives from https://github.com/highlightjs/highlightjs-terraform\n * Licensed under the MIT License:\n *\n * Copyright (c) 2020 highlightjs-terraform\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/*\n * highlight.js terraform syntax highlighting definition\n *\n * @see https://github.com/highlightjs/highlight.js\n *\n * :TODO:\n *\n * @package: highlightjs-terraform\n * @author:  Nikos Tsirmirakis <nikos.tsirmirakis@winopsdba.com>\n * @since:   2019-03-20\n *\n * Description: Terraform (HCL) language definition\n * Category: scripting\n */\n\nexport default (hljs) => {\n  const NUMBERS = {\n    className: 'number',\n    begin: '\\\\b\\\\d+(\\\\.\\\\d+)?',\n    relevance: 0,\n  };\n  const STRINGS = {\n    className: 'string',\n    begin: '\"',\n    end: '\"',\n    contains: [\n      {\n        className: 'variable',\n        begin: '\\\\${',\n        end: '\\\\}',\n        relevance: 9,\n        contains: [\n          {\n            className: 'string',\n            begin: '\"',\n            end: '\"',\n          },\n          {\n            className: 'meta',\n            begin: '[A-Za-z_0-9]*' + '\\\\(', // eslint-disable-line no-useless-concat\n            end: '\\\\)',\n            contains: [\n              NUMBERS,\n              {\n                className: 'string',\n                begin: '\"',\n                end: '\"',\n                contains: [\n                  {\n                    className: 'variable',\n                    begin: '\\\\${',\n                    end: '\\\\}',\n                    contains: [\n                      {\n                        className: 'string',\n                        begin: '\"',\n                        end: '\"',\n                        contains: [\n                          {\n                            className: 'variable',\n                            begin: '\\\\${',\n                            end: '\\\\}',\n                          },\n                        ],\n                      },\n                      {\n                        className: 'meta',\n                        begin: '[A-Za-z_0-9]*' + '\\\\(', // eslint-disable-line no-useless-concat\n                        end: '\\\\)',\n                      },\n                    ],\n                  },\n                ],\n              },\n              'self',\n            ],\n          },\n        ],\n      },\n    ],\n  };\n\n  return {\n    aliases: ['tf', 'hcl'],\n    keywords: 'resource variable provider output locals module data terraform|10',\n    literal: 'false true null',\n    contains: [hljs.COMMENT('\\\\#', '$'), NUMBERS, STRINGS],\n  };\n};\n"
  },
  {
    "path": "client/src/lib/syntax-highlighter/languages-map.json",
    "content": "{\r\n  \"4d\": {\r\n    \"e\": [\r\n      \"4dm\"\r\n    ]\r\n  },\r\n  \"sap-abap\": {\r\n    \"e\": [\r\n      \"abap\"\r\n    ]\r\n  },\r\n  \"abnf\": {\r\n    \"e\": [\r\n      \"abnf\"\r\n    ]\r\n  },\r\n  \"ada\": {\r\n    \"e\": [\r\n      \"adb\",\r\n      \"ada\",\r\n      \"ads\"\r\n    ]\r\n  },\r\n  \"apex\": {\r\n    \"e\": [\r\n      \"cls\"\r\n    ]\r\n  },\r\n  \"actionscript\": {\r\n    \"e\": [\r\n      \"as\"\r\n    ]\r\n  },\r\n  \"alan\": {\r\n    \"e\": [\r\n      \"alan\",\r\n      \"i\"\r\n    ]\r\n  },\r\n  \"angelscript\": {\r\n    \"e\": [\r\n      \"as\",\r\n      \"angelscript\"\r\n    ]\r\n  },\r\n  \"apache\": {\r\n    \"f\": [\r\n      \".htaccess\",\r\n      \"apache2.conf\",\r\n      \"httpd.conf\"\r\n    ],\r\n    \"e\": [\r\n      \"apacheconf\",\r\n      \"vhost\"\r\n    ]\r\n  },\r\n  \"applescript\": {\r\n    \"e\": [\r\n      \"applescript\",\r\n      \"scpt\"\r\n    ]\r\n  },\r\n  \"asciidoc\": {\r\n    \"e\": [\r\n      \"asciidoc\",\r\n      \"adoc\",\r\n      \"asc\"\r\n    ]\r\n  },\r\n  \"aspectj\": {\r\n    \"e\": [\r\n      \"aj\"\r\n    ]\r\n  },\r\n  \"autohotkey\": {\r\n    \"e\": [\r\n      \"ahk\",\r\n      \"ahkl\"\r\n    ]\r\n  },\r\n  \"autoit\": {\r\n    \"e\": [\r\n      \"au3\"\r\n    ]\r\n  },\r\n  \"ballerina\": {\r\n    \"e\": [\r\n      \"bal\"\r\n    ]\r\n  },\r\n  \"awk\": {\r\n    \"e\": [\r\n      \"awk\",\r\n      \"auk\",\r\n      \"gawk\",\r\n      \"mawk\",\r\n      \"nawk\"\r\n    ]\r\n  },\r\n  \"bash\": {\r\n    \"f\": [\r\n      \".bash_aliases\",\r\n      \".bash_history\",\r\n      \".bash_logout\",\r\n      \".bash_profile\",\r\n      \".bashrc\",\r\n      \".cshrc\",\r\n      \".env\",\r\n      \".env.example\",\r\n      \".flaskenv\",\r\n      \".login\",\r\n      \".profile\",\r\n      \".zlogin\",\r\n      \".zlogout\",\r\n      \".zprofile\",\r\n      \".zshenv\",\r\n      \".zshrc\",\r\n      \"9fs\",\r\n      \"pkgbuild\",\r\n      \"bash_aliases\",\r\n      \"bash_logout\",\r\n      \"bash_profile\",\r\n      \"bashrc\",\r\n      \"cshrc\",\r\n      \"gradlew\",\r\n      \"login\",\r\n      \"man\",\r\n      \"profile\",\r\n      \"zlogin\",\r\n      \"zlogout\",\r\n      \"zprofile\",\r\n      \"zshenv\",\r\n      \"zshrc\"\r\n    ],\r\n    \"e\": [\r\n      \"sh\",\r\n      \"bash\",\r\n      \"bats\",\r\n      \"cgi\",\r\n      \"command\",\r\n      \"env\",\r\n      \"fcgi\",\r\n      \"ksh\",\r\n      \"sh.in\",\r\n      \"tmux\",\r\n      \"tool\",\r\n      \"zsh\"\r\n    ]\r\n  },\r\n  \"basic\": {\r\n    \"e\": [\r\n      \"bas\",\r\n      \"bb\",\r\n      \"decls\",\r\n      \"bi\",\r\n      \"pb\",\r\n      \"pbi\",\r\n      \"rbbas\",\r\n      \"rbfrm\",\r\n      \"rbmnu\",\r\n      \"rbres\",\r\n      \"rbtbar\",\r\n      \"rbuistate\",\r\n      \"cls\",\r\n      \"frm\",\r\n      \"frx\",\r\n      \"vba\",\r\n      \"vb\",\r\n      \"vbhtml\"\r\n    ]\r\n  },\r\n  \"blade\": {\r\n    \"e\": [\r\n      \"blade\",\r\n      \"blade.php\"\r\n    ]\r\n  },\r\n  \"brainfuck\": {\r\n    \"e\": [\r\n      \"b\",\r\n      \"bf\"\r\n    ]\r\n  },\r\n  \"csharp\": {\r\n    \"e\": [\r\n      \"cs\",\r\n      \"cake\",\r\n      \"csx\",\r\n      \"linq\"\r\n    ]\r\n  },\r\n  \"c\": {\r\n    \"e\": [\r\n      \"c\",\r\n      \"cats\",\r\n      \"h\",\r\n      \"idc\"\r\n    ]\r\n  },\r\n  \"cpp\": {\r\n    \"e\": [\r\n      \"cpp\",\r\n      \"c++\",\r\n      \"cc\",\r\n      \"cp\",\r\n      \"cxx\",\r\n      \"h\",\r\n      \"h++\",\r\n      \"hh\",\r\n      \"hpp\",\r\n      \"hxx\",\r\n      \"inc\",\r\n      \"inl\",\r\n      \"ino\",\r\n      \"ipp\",\r\n      \"re\",\r\n      \"tcc\",\r\n      \"tpp\"\r\n    ]\r\n  },\r\n  \"cmake\": {\r\n    \"f\": [\r\n      \"cmakelists.txt\"\r\n    ],\r\n    \"e\": [\r\n      \"cmake\",\r\n      \"cmake.in\"\r\n    ]\r\n  },\r\n  \"cobol\": {\r\n    \"e\": [\r\n      \"cob\",\r\n      \"cbl\",\r\n      \"ccp\",\r\n      \"cobol\",\r\n      \"cpy\"\r\n    ]\r\n  },\r\n  \"coq\": {\r\n    \"e\": [\r\n      \"coq\",\r\n      \"v\"\r\n    ]\r\n  },\r\n  \"css\": {\r\n    \"e\": [\r\n      \"css\",\r\n      \"sass\"\r\n    ]\r\n  },\r\n  \"chapel\": {\r\n    \"e\": [\r\n      \"chpl\"\r\n    ]\r\n  },\r\n  \"clojure\": {\r\n    \"f\": [\r\n      \"riemann.config\"\r\n    ],\r\n    \"e\": [\r\n      \"clj\",\r\n      \"boot\",\r\n      \"cl2\",\r\n      \"cljc\",\r\n      \"cljs\",\r\n      \"cljs.hl\",\r\n      \"cljscm\",\r\n      \"cljx\",\r\n      \"hic\"\r\n    ]\r\n  },\r\n  \"coffeescript\": {\r\n    \"f\": [\r\n      \"cakefile\"\r\n    ],\r\n    \"e\": [\r\n      \"coffee\",\r\n      \"_coffee\",\r\n      \"cake\",\r\n      \"cjsx\",\r\n      \"iced\",\r\n      \"cson\"\r\n    ]\r\n  },\r\n  \"crystal\": {\r\n    \"e\": [\r\n      \"cr\"\r\n    ]\r\n  },\r\n  \"d\": {\r\n    \"e\": [\r\n      \"d\",\r\n      \"di\"\r\n    ]\r\n  },\r\n  \"dafny\": {\r\n    \"e\": [\r\n      \"dfy\"\r\n    ]\r\n  },\r\n  \"dart\": {\r\n    \"e\": [\r\n      \"dart\"\r\n    ]\r\n  },\r\n  \"delphi\": {\r\n    \"e\": [\r\n      \"pas\",\r\n      \"dfm\",\r\n      \"dpr\",\r\n      \"inc\",\r\n      \"lpr\",\r\n      \"pascal\",\r\n      \"pp\"\r\n    ]\r\n  },\r\n  \"diff\": {\r\n    \"e\": [\r\n      \"diff\",\r\n      \"patch\"\r\n    ]\r\n  },\r\n  \"django\": {\r\n    \"e\": [\r\n      \"jinja\",\r\n      \"j2\",\r\n      \"jinja2\"\r\n    ]\r\n  },\r\n  \"dockerfile\": {\r\n    \"f\": [\r\n      \"dockerfile\"\r\n    ],\r\n    \"e\": [\r\n      \"dockerfile\"\r\n    ]\r\n  },\r\n  \"dos\": {\r\n    \"e\": [\r\n      \"bat\",\r\n      \"cmd\"\r\n    ]\r\n  },\r\n  \"ebnf\": {\r\n    \"e\": [\r\n      \"ebnf\"\r\n    ]\r\n  },\r\n  \"elixir\": {\r\n    \"f\": [\r\n      \"mix.lock\"\r\n    ],\r\n    \"e\": [\r\n      \"ex\",\r\n      \"exs\"\r\n    ]\r\n  },\r\n  \"elm\": {\r\n    \"e\": [\r\n      \"elm\"\r\n    ]\r\n  },\r\n  \"erlang\": {\r\n    \"f\": [\r\n      \"emakefile\",\r\n      \"rebar.config\",\r\n      \"rebar.config.lock\",\r\n      \"rebar.lock\"\r\n    ],\r\n    \"e\": [\r\n      \"erl\",\r\n      \"app.src\",\r\n      \"es\",\r\n      \"escript\",\r\n      \"hrl\",\r\n      \"xrl\",\r\n      \"yrl\"\r\n    ]\r\n  },\r\n  \"fsharp\": {\r\n    \"e\": [\r\n      \"fs\",\r\n      \"fsi\",\r\n      \"fsx\"\r\n    ]\r\n  },\r\n  \"fortran\": {\r\n    \"e\": [\r\n      \"f\",\r\n      \"f77\",\r\n      \"for\",\r\n      \"fpp\",\r\n      \"f90\",\r\n      \"f03\",\r\n      \"f08\",\r\n      \"f95\"\r\n    ]\r\n  },\r\n  \"gcode\": {\r\n    \"e\": [\r\n      \"g\",\r\n      \"cnc\",\r\n      \"gco\",\r\n      \"gcode\"\r\n    ]\r\n  },\r\n  \"gams\": {\r\n    \"e\": [\r\n      \"gms\"\r\n    ]\r\n  },\r\n  \"godot\": {\r\n    \"e\": [\r\n      \"gd\"\r\n    ]\r\n  },\r\n  \"gherkin\": {\r\n    \"e\": [\r\n      \"feature\",\r\n      \"story\"\r\n    ]\r\n  },\r\n  \"gn\": {\r\n    \"f\": [\r\n      \".gn\"\r\n    ],\r\n    \"e\": [\r\n      \"gn\",\r\n      \"gni\"\r\n    ]\r\n  },\r\n  \"go\": {\r\n    \"e\": [\r\n      \"go\"\r\n    ]\r\n  },\r\n  \"gf\": {\r\n    \"e\": [\r\n      \"gf\"\r\n    ]\r\n  },\r\n  \"golo\": {\r\n    \"e\": [\r\n      \"golo\"\r\n    ]\r\n  },\r\n  \"gradle\": {\r\n    \"e\": [\r\n      \"gradle\"\r\n    ]\r\n  },\r\n  \"graphql\": {\r\n    \"e\": [\r\n      \"graphql\",\r\n      \"gql\",\r\n      \"graphqls\"\r\n    ]\r\n  },\r\n  \"groovy\": {\r\n    \"f\": [\r\n      \"jenkinsfile\"\r\n    ],\r\n    \"e\": [\r\n      \"groovy\",\r\n      \"grt\",\r\n      \"gtpl\",\r\n      \"gvy\"\r\n    ]\r\n  },\r\n  \"xml\": {\r\n    \"f\": [\r\n      \".classpath\",\r\n      \".cproject\",\r\n      \".project\",\r\n      \"app.config\",\r\n      \"nuget.config\",\r\n      \"settings.stylecop\",\r\n      \"web.debug.config\",\r\n      \"web.release.config\",\r\n      \"web.config\",\r\n      \"packages.config\"\r\n    ],\r\n    \"e\": [\r\n      \"xml\",\r\n      \"adml\",\r\n      \"admx\",\r\n      \"ant\",\r\n      \"axml\",\r\n      \"builds\",\r\n      \"ccproj\",\r\n      \"ccxml\",\r\n      \"clixml\",\r\n      \"cproject\",\r\n      \"cscfg\",\r\n      \"csdef\",\r\n      \"csl\",\r\n      \"csproj\",\r\n      \"ct\",\r\n      \"depproj\",\r\n      \"dita\",\r\n      \"ditamap\",\r\n      \"ditaval\",\r\n      \"dll.config\",\r\n      \"dotsettings\",\r\n      \"filters\",\r\n      \"fsproj\",\r\n      \"fxml\",\r\n      \"glade\",\r\n      \"gml\",\r\n      \"gmx\",\r\n      \"grxml\",\r\n      \"gst\",\r\n      \"iml\",\r\n      \"ivy\",\r\n      \"jelly\",\r\n      \"jsproj\",\r\n      \"kml\",\r\n      \"launch\",\r\n      \"mdpolicy\",\r\n      \"mjml\",\r\n      \"mm\",\r\n      \"mod\",\r\n      \"mxml\",\r\n      \"natvis\",\r\n      \"ncl\",\r\n      \"ndproj\",\r\n      \"nproj\",\r\n      \"nuspec\",\r\n      \"odd\",\r\n      \"osm\",\r\n      \"pkgproj\",\r\n      \"pluginspec\",\r\n      \"proj\",\r\n      \"props\",\r\n      \"ps1xml\",\r\n      \"psc1\",\r\n      \"pt\",\r\n      \"rdf\",\r\n      \"res\",\r\n      \"resx\",\r\n      \"rs\",\r\n      \"rss\",\r\n      \"sch\",\r\n      \"scxml\",\r\n      \"sfproj\",\r\n      \"shproj\",\r\n      \"srdf\",\r\n      \"storyboard\",\r\n      \"sublime-snippet\",\r\n      \"targets\",\r\n      \"tml\",\r\n      \"ts\",\r\n      \"tsx\",\r\n      \"ui\",\r\n      \"urdf\",\r\n      \"ux\",\r\n      \"vbproj\",\r\n      \"vcxproj\",\r\n      \"vsixmanifest\",\r\n      \"vssettings\",\r\n      \"vstemplate\",\r\n      \"vxml\",\r\n      \"wixproj\",\r\n      \"workflow\",\r\n      \"wsdl\",\r\n      \"wsf\",\r\n      \"wxi\",\r\n      \"wxl\",\r\n      \"wxs\",\r\n      \"x3d\",\r\n      \"xacro\",\r\n      \"xaml\",\r\n      \"xib\",\r\n      \"xlf\",\r\n      \"xliff\",\r\n      \"xmi\",\r\n      \"xml.dist\",\r\n      \"xmp\",\r\n      \"xproj\",\r\n      \"xsd\",\r\n      \"xspec\",\r\n      \"xul\",\r\n      \"zcml\",\r\n      \"svg\",\r\n      \"html\",\r\n      \"hta\",\r\n      \"htm\",\r\n      \"html.hl\",\r\n      \"inc\",\r\n      \"xht\",\r\n      \"xhtml\",\r\n      \"eml\",\r\n      \"mbox\"\r\n    ]\r\n  },\r\n  \"http\": {\r\n    \"e\": [\r\n      \"http\"\r\n    ]\r\n  },\r\n  \"haml\": {\r\n    \"e\": [\r\n      \"haml\",\r\n      \"haml.deface\"\r\n    ]\r\n  },\r\n  \"handlebars\": {\r\n    \"e\": [\r\n      \"handlebars\",\r\n      \"hbs\"\r\n    ]\r\n  },\r\n  \"haskell\": {\r\n    \"e\": [\r\n      \"hs\",\r\n      \"hs-boot\",\r\n      \"hsc\"\r\n    ]\r\n  },\r\n  \"haxe\": {\r\n    \"e\": [\r\n      \"hx\",\r\n      \"hxsl\"\r\n    ]\r\n  },\r\n  \"hlsl\": {\r\n    \"e\": [\r\n      \"hlsl\",\r\n      \"cginc\",\r\n      \"fx\",\r\n      \"fxh\",\r\n      \"hlsli\"\r\n    ]\r\n  },\r\n  \"hy\": {\r\n    \"e\": [\r\n      \"hy\"\r\n    ]\r\n  },\r\n  \"ini\": {\r\n    \"f\": [\r\n      \"buildozer.spec\",\r\n      \"cargo.lock\",\r\n      \"gopkg.lock\",\r\n      \"pipfile\",\r\n      \"poetry.lock\"\r\n    ],\r\n    \"e\": [\r\n      \"ini\",\r\n      \"cfg\",\r\n      \"dof\",\r\n      \"lektorproject\",\r\n      \"prefs\",\r\n      \"pro\",\r\n      \"properties\",\r\n      \"toml\"\r\n    ]\r\n  },\r\n  \"inform7\": {\r\n    \"e\": [\r\n      \"ni\",\r\n      \"i7x\"\r\n    ]\r\n  },\r\n  \"json\": {\r\n    \"f\": [\r\n      \".arcconfig\",\r\n      \".auto-changelog\",\r\n      \".c8rc\",\r\n      \".htmlhintrc\",\r\n      \".imgbotconfig\",\r\n      \".nycrc\",\r\n      \".tern-config\",\r\n      \".tern-project\",\r\n      \".watchmanconfig\",\r\n      \"pipfile.lock\",\r\n      \"composer.lock\",\r\n      \"mcmod.info\",\r\n      \".babelrc\",\r\n      \".eslintrc.json\",\r\n      \".jscsrc\",\r\n      \".jshintrc\",\r\n      \".jslintrc\",\r\n      \"api-extractor.json\",\r\n      \"devcontainer.json\",\r\n      \"jsconfig.json\",\r\n      \"language-configuration.json\",\r\n      \"tsconfig.json\",\r\n      \"tslint.json\"\r\n    ],\r\n    \"e\": [\r\n      \"json\",\r\n      \"avsc\",\r\n      \"geojson\",\r\n      \"gltf\",\r\n      \"har\",\r\n      \"ice\",\r\n      \"json-tmlanguage\",\r\n      \"jsonl\",\r\n      \"mcmeta\",\r\n      \"tfstate\",\r\n      \"tfstate.backup\",\r\n      \"topojson\",\r\n      \"webapp\",\r\n      \"webmanifest\",\r\n      \"yy\",\r\n      \"yyp\",\r\n      \"jsonc\",\r\n      \"sublime-build\",\r\n      \"sublime-commands\",\r\n      \"sublime-completions\",\r\n      \"sublime-keymap\",\r\n      \"sublime-macro\",\r\n      \"sublime-menu\",\r\n      \"sublime-mousemap\",\r\n      \"sublime-project\",\r\n      \"sublime-settings\",\r\n      \"sublime-theme\",\r\n      \"sublime-workspace\",\r\n      \"sublime_metrics\",\r\n      \"sublime_session\",\r\n      \"json5\",\r\n      \"jsonld\",\r\n      \"jq\"\r\n    ]\r\n  },\r\n  \"java\": {\r\n    \"e\": [\r\n      \"java\",\r\n      \"jav\",\r\n      \"jsp\"\r\n    ]\r\n  },\r\n  \"javascript\": {\r\n    \"f\": [\r\n      \"jakefile\"\r\n    ],\r\n    \"e\": [\r\n      \"js\",\r\n      \"_js\",\r\n      \"bones\",\r\n      \"cjs\",\r\n      \"es\",\r\n      \"es6\",\r\n      \"frag\",\r\n      \"gs\",\r\n      \"jake\",\r\n      \"javascript\",\r\n      \"jsb\",\r\n      \"jscad\",\r\n      \"jsfl\",\r\n      \"jsm\",\r\n      \"jss\",\r\n      \"jsx\",\r\n      \"mjs\",\r\n      \"njs\",\r\n      \"pac\",\r\n      \"sjs\",\r\n      \"ssjs\",\r\n      \"xsjs\",\r\n      \"xsjslib\"\r\n    ]\r\n  },\r\n  \"jolie\": {\r\n    \"e\": [\r\n      \"ol\",\r\n      \"iol\"\r\n    ]\r\n  },\r\n  \"julia\": {\r\n    \"e\": [\r\n      \"jl\"\r\n    ]\r\n  },\r\n  \"kotlin\": {\r\n    \"e\": [\r\n      \"kt\",\r\n      \"ktm\",\r\n      \"kts\"\r\n    ]\r\n  },\r\n  \"latex\": {\r\n    \"e\": [\r\n      \"tex\",\r\n      \"aux\",\r\n      \"bbx\",\r\n      \"cbx\",\r\n      \"cls\",\r\n      \"dtx\",\r\n      \"ins\",\r\n      \"lbx\",\r\n      \"ltx\",\r\n      \"mkii\",\r\n      \"mkiv\",\r\n      \"mkvi\",\r\n      \"sty\",\r\n      \"toc\"\r\n    ]\r\n  },\r\n  \"lean\": {\r\n    \"e\": [\r\n      \"lean\",\r\n      \"hlean\"\r\n    ]\r\n  },\r\n  \"lasso\": {\r\n    \"e\": [\r\n      \"lasso\",\r\n      \"las\",\r\n      \"lasso8\",\r\n      \"lasso9\"\r\n    ]\r\n  },\r\n  \"less\": {\r\n    \"e\": [\r\n      \"less\"\r\n    ]\r\n  },\r\n  \"lisp\": {\r\n    \"f\": [\r\n      \".abbrev_defs\",\r\n      \".emacs\",\r\n      \".emacs.desktop\",\r\n      \".gnus\",\r\n      \".spacemacs\",\r\n      \".viper\",\r\n      \"cask\",\r\n      \"project.ede\",\r\n      \"_emacs\",\r\n      \"abbrev_defs\"\r\n    ],\r\n    \"e\": [\r\n      \"lisp\",\r\n      \"asd\",\r\n      \"cl\",\r\n      \"l\",\r\n      \"lsp\",\r\n      \"ny\",\r\n      \"podsl\",\r\n      \"sexp\",\r\n      \"el\",\r\n      \"emacs\",\r\n      \"emacs.desktop\"\r\n    ]\r\n  },\r\n  \"lookml\": {\r\n    \"e\": [\r\n      \"lookml\",\r\n      \"model.lkml\",\r\n      \"view.lkml\"\r\n    ]\r\n  },\r\n  \"lua\": {\r\n    \"f\": [\r\n      \".luacheckrc\"\r\n    ],\r\n    \"e\": [\r\n      \"lua\",\r\n      \"fcgi\",\r\n      \"nse\",\r\n      \"p8\",\r\n      \"pd_lua\",\r\n      \"rbxs\",\r\n      \"rockspec\",\r\n      \"wlua\"\r\n    ]\r\n  },\r\n  \"macaulay2\": {\r\n    \"e\": [\r\n      \"m2\"\r\n    ]\r\n  },\r\n  \"makefile\": {\r\n    \"f\": [\r\n      \"bsdmakefile\",\r\n      \"gnumakefile\",\r\n      \"kbuild\",\r\n      \"makefile\",\r\n      \"makefile.am\",\r\n      \"makefile.boot\",\r\n      \"makefile.frag\",\r\n      \"makefile.in\",\r\n      \"makefile.inc\",\r\n      \"makefile.wat\",\r\n      \"makefile.sco\",\r\n      \"mkfile\"\r\n    ],\r\n    \"e\": [\r\n      \"mak\",\r\n      \"d\",\r\n      \"make\",\r\n      \"makefile\",\r\n      \"mk\",\r\n      \"mkfile\"\r\n    ]\r\n  },\r\n  \"markdown\": {\r\n    \"f\": [\r\n      \"contents.lr\"\r\n    ],\r\n    \"e\": [\r\n      \"md\",\r\n      \"markdown\",\r\n      \"mdown\",\r\n      \"mdwn\",\r\n      \"mdx\",\r\n      \"mkd\",\r\n      \"mkdn\",\r\n      \"mkdown\",\r\n      \"ronn\",\r\n      \"scd\",\r\n      \"workbook\"\r\n    ]\r\n  },\r\n  \"mathematica\": {\r\n    \"e\": [\r\n      \"mathematica\",\r\n      \"cdf\",\r\n      \"m\",\r\n      \"ma\",\r\n      \"mt\",\r\n      \"nb\",\r\n      \"nbp\",\r\n      \"wl\",\r\n      \"wlt\"\r\n    ]\r\n  },\r\n  \"matlab\": {\r\n    \"e\": [\r\n      \"matlab\",\r\n      \"m\"\r\n    ]\r\n  },\r\n  \"mercury\": {\r\n    \"e\": [\r\n      \"m\",\r\n      \"moo\"\r\n    ]\r\n  },\r\n  \"mlir\": {\r\n    \"e\": [\r\n      \"mlir\"\r\n    ]\r\n  },\r\n  \"monkey\": {\r\n    \"e\": [\r\n      \"monkey\",\r\n      \"monkey2\"\r\n    ]\r\n  },\r\n  \"moonscript\": {\r\n    \"e\": [\r\n      \"moon\"\r\n    ]\r\n  },\r\n  \"nsis\": {\r\n    \"e\": [\r\n      \"nsi\",\r\n      \"nsh\"\r\n    ]\r\n  },\r\n  \"nginx\": {\r\n    \"f\": [\r\n      \"nginx.conf\"\r\n    ],\r\n    \"e\": [\r\n      \"nginx\",\r\n      \"nginxconf\",\r\n      \"vhost\"\r\n    ]\r\n  },\r\n  \"nim\": {\r\n    \"f\": [\r\n      \"nim.cfg\"\r\n    ],\r\n    \"e\": [\r\n      \"nim\",\r\n      \"nim.cfg\",\r\n      \"nimble\",\r\n      \"nimrod\",\r\n      \"nims\"\r\n    ]\r\n  },\r\n  \"nix\": {\r\n    \"e\": [\r\n      \"nix\"\r\n    ]\r\n  },\r\n  \"ocaml\": {\r\n    \"e\": [\r\n      \"ml\",\r\n      \"eliom\",\r\n      \"eliomi\",\r\n      \"ml4\",\r\n      \"mli\",\r\n      \"mll\",\r\n      \"mly\"\r\n    ]\r\n  },\r\n  \"objectivec\": {\r\n    \"e\": [\r\n      \"m\",\r\n      \"h\",\r\n      \"mm\"\r\n    ]\r\n  },\r\n  \"glsl\": {\r\n    \"e\": [\r\n      \"glsl\",\r\n      \"fp\",\r\n      \"frag\",\r\n      \"frg\",\r\n      \"fs\",\r\n      \"fsh\",\r\n      \"fshader\",\r\n      \"geo\",\r\n      \"geom\",\r\n      \"glslf\",\r\n      \"glslv\",\r\n      \"gs\",\r\n      \"gshader\",\r\n      \"shader\",\r\n      \"tesc\",\r\n      \"tese\",\r\n      \"vert\",\r\n      \"vrx\",\r\n      \"vsh\",\r\n      \"vshader\"\r\n    ]\r\n  },\r\n  \"openscad\": {\r\n    \"e\": [\r\n      \"scad\"\r\n    ]\r\n  },\r\n  \"oxygene\": {\r\n    \"e\": [\r\n      \"oxygene\"\r\n    ]\r\n  },\r\n  \"php\": {\r\n    \"f\": [\r\n      \".php\",\r\n      \".php_cs\",\r\n      \".php_cs.dist\",\r\n      \"phakefile\"\r\n    ],\r\n    \"e\": [\r\n      \"php\",\r\n      \"aw\",\r\n      \"ctp\",\r\n      \"fcgi\",\r\n      \"inc\",\r\n      \"php3\",\r\n      \"php4\",\r\n      \"php5\",\r\n      \"phps\",\r\n      \"phpt\"\r\n    ]\r\n  },\r\n  \"papyrus\": {\r\n    \"e\": [\r\n      \"psc\"\r\n    ]\r\n  },\r\n  \"perl\": {\r\n    \"f\": [\r\n      \"makefile.pl\",\r\n      \"rexfile\",\r\n      \"ack\",\r\n      \"cpanfile\"\r\n    ],\r\n    \"e\": [\r\n      \"pl\",\r\n      \"al\",\r\n      \"cgi\",\r\n      \"fcgi\",\r\n      \"perl\",\r\n      \"ph\",\r\n      \"plx\",\r\n      \"pm\",\r\n      \"psgi\",\r\n      \"t\",\r\n      \"6pl\",\r\n      \"6pm\",\r\n      \"nqp\",\r\n      \"p6\",\r\n      \"p6l\",\r\n      \"p6m\",\r\n      \"pl6\",\r\n      \"pm6\",\r\n      \"raku\",\r\n      \"rakumod\"\r\n    ]\r\n  },\r\n  \"plaintext\": {\r\n    \"f\": [\r\n      \"copying\",\r\n      \"copying.regex\",\r\n      \"copyright.regex\",\r\n      \"fontlog\",\r\n      \"install\",\r\n      \"install.mysql\",\r\n      \"license\",\r\n      \"license.mysql\",\r\n      \"news\",\r\n      \"readme.me\",\r\n      \"readme.mysql\",\r\n      \"readme.nss\",\r\n      \"click.me\",\r\n      \"delete.me\",\r\n      \"keep.me\",\r\n      \"package.mask\",\r\n      \"package.use.mask\",\r\n      \"package.use.stable.mask\",\r\n      \"read.me\",\r\n      \"readme.1st\",\r\n      \"test.me\",\r\n      \"use.mask\",\r\n      \"use.stable.mask\"\r\n    ],\r\n    \"e\": [\r\n      \"txt\",\r\n      \"fr\",\r\n      \"nb\",\r\n      \"ncl\",\r\n      \"no\"\r\n    ]\r\n  },\r\n  \"pony\": {\r\n    \"e\": [\r\n      \"pony\"\r\n    ]\r\n  },\r\n  \"pgsql\": {\r\n    \"e\": [\r\n      \"pgsql\",\r\n      \"sql\"\r\n    ]\r\n  },\r\n  \"powershell\": {\r\n    \"e\": [\r\n      \"ps1\",\r\n      \"psd1\",\r\n      \"psm1\"\r\n    ]\r\n  },\r\n  \"processing\": {\r\n    \"e\": [\r\n      \"pde\"\r\n    ]\r\n  },\r\n  \"prolog\": {\r\n    \"e\": [\r\n      \"pl\",\r\n      \"pro\",\r\n      \"prolog\",\r\n      \"yap\"\r\n    ]\r\n  },\r\n  \"proto\": {\r\n    \"e\": [\r\n      \"proto\"\r\n    ]\r\n  },\r\n  \"puppet\": {\r\n    \"f\": [\r\n      \"modulefile\"\r\n    ],\r\n    \"e\": [\r\n      \"pp\"\r\n    ]\r\n  },\r\n  \"python\": {\r\n    \"f\": [\r\n      \".gclient\",\r\n      \"deps\",\r\n      \"sconscript\",\r\n      \"sconstruct\",\r\n      \"snakefile\",\r\n      \"wscript\"\r\n    ],\r\n    \"e\": [\r\n      \"py\",\r\n      \"cgi\",\r\n      \"fcgi\",\r\n      \"gyp\",\r\n      \"gypi\",\r\n      \"lmi\",\r\n      \"py3\",\r\n      \"pyde\",\r\n      \"pyi\",\r\n      \"pyp\",\r\n      \"pyt\",\r\n      \"pyw\",\r\n      \"rpy\",\r\n      \"smk\",\r\n      \"spec\",\r\n      \"tac\",\r\n      \"wsgi\",\r\n      \"xpy\"\r\n    ]\r\n  },\r\n  \"qsharp\": {\r\n    \"e\": [\r\n      \"qs\"\r\n    ]\r\n  },\r\n  \"k\": {\r\n    \"e\": [\r\n      \"q\"\r\n    ]\r\n  },\r\n  \"qml\": {\r\n    \"e\": [\r\n      \"qml\",\r\n      \"qbs\"\r\n    ]\r\n  },\r\n  \"r\": {\r\n    \"f\": [\r\n      \".rprofile\",\r\n      \"expr-dist\"\r\n    ],\r\n    \"e\": [\r\n      \"r\",\r\n      \"rd\",\r\n      \"rsx\"\r\n    ]\r\n  },\r\n  \"cshtml\": {\r\n    \"e\": [\r\n      \"cshtml\",\r\n      \"razor\"\r\n    ]\r\n  },\r\n  \"redbol\": {\r\n    \"e\": [\r\n      \"reb\",\r\n      \"r\",\r\n      \"r2\",\r\n      \"r3\",\r\n      \"rebol\",\r\n      \"red\",\r\n      \"reds\"\r\n    ]\r\n  },\r\n  \"rpm-specfile\": {\r\n    \"e\": [\r\n      \"spec\"\r\n    ]\r\n  },\r\n  \"ruby\": {\r\n    \"f\": [\r\n      \".irbrc\",\r\n      \".pryrc\",\r\n      \".simplecov\",\r\n      \"appraisals\",\r\n      \"berksfile\",\r\n      \"brewfile\",\r\n      \"buildfile\",\r\n      \"capfile\",\r\n      \"dangerfile\",\r\n      \"deliverfile\",\r\n      \"fastfile\",\r\n      \"gemfile\",\r\n      \"guardfile\",\r\n      \"jarfile\",\r\n      \"mavenfile\",\r\n      \"podfile\",\r\n      \"puppetfile\",\r\n      \"rakefile\",\r\n      \"snapfile\",\r\n      \"thorfile\",\r\n      \"vagrantfile\"\r\n    ],\r\n    \"e\": [\r\n      \"rb\",\r\n      \"builder\",\r\n      \"eye\",\r\n      \"fcgi\",\r\n      \"gemspec\",\r\n      \"god\",\r\n      \"jbuilder\",\r\n      \"mspec\",\r\n      \"pluginspec\",\r\n      \"podspec\",\r\n      \"prawn\",\r\n      \"rabl\",\r\n      \"rake\",\r\n      \"rbi\",\r\n      \"rbuild\",\r\n      \"rbw\",\r\n      \"rbx\",\r\n      \"ru\",\r\n      \"ruby\",\r\n      \"spec\",\r\n      \"thor\",\r\n      \"watchr\"\r\n    ]\r\n  },\r\n  \"rust\": {\r\n    \"e\": [\r\n      \"rs\",\r\n      \"rs.in\"\r\n    ]\r\n  },\r\n  \"sas\": {\r\n    \"e\": [\r\n      \"sas\"\r\n    ]\r\n  },\r\n  \"scss\": {\r\n    \"e\": [\r\n      \"scss\"\r\n    ]\r\n  },\r\n  \"sql\": {\r\n    \"e\": [\r\n      \"sql\",\r\n      \"cql\",\r\n      \"ddl\",\r\n      \"inc\",\r\n      \"mysql\",\r\n      \"prc\",\r\n      \"tab\",\r\n      \"udf\",\r\n      \"viw\"\r\n    ]\r\n  },\r\n  \"scala\": {\r\n    \"e\": [\r\n      \"scala\",\r\n      \"kojo\",\r\n      \"sbt\",\r\n      \"sc\"\r\n    ]\r\n  },\r\n  \"scheme\": {\r\n    \"e\": [\r\n      \"scm\",\r\n      \"sch\",\r\n      \"sld\",\r\n      \"sls\",\r\n      \"sps\",\r\n      \"ss\"\r\n    ]\r\n  },\r\n  \"scilab\": {\r\n    \"e\": [\r\n      \"sci\",\r\n      \"sce\",\r\n      \"tst\"\r\n    ]\r\n  },\r\n  \"shell\": {\r\n    \"e\": [\r\n      \"sh-session\"\r\n    ]\r\n  },\r\n  \"smali\": {\r\n    \"e\": [\r\n      \"smali\"\r\n    ]\r\n  },\r\n  \"smalltalk\": {\r\n    \"e\": [\r\n      \"st\",\r\n      \"cs\"\r\n    ]\r\n  },\r\n  \"sml\": {\r\n    \"e\": [\r\n      \"ml\",\r\n      \"fun\",\r\n      \"sig\",\r\n      \"sml\"\r\n    ]\r\n  },\r\n  \"solidity\": {\r\n    \"e\": [\r\n      \"sol\"\r\n    ]\r\n  },\r\n  \"stan\": {\r\n    \"e\": [\r\n      \"stan\"\r\n    ]\r\n  },\r\n  \"stata\": {\r\n    \"e\": [\r\n      \"do\",\r\n      \"ado\",\r\n      \"doh\",\r\n      \"ihlp\",\r\n      \"mata\",\r\n      \"matah\",\r\n      \"sthlp\"\r\n    ]\r\n  },\r\n  \"stylus\": {\r\n    \"e\": [\r\n      \"styl\"\r\n    ]\r\n  },\r\n  \"supercollider\": {\r\n    \"e\": [\r\n      \"sc\",\r\n      \"scd\"\r\n    ]\r\n  },\r\n  \"svelte\": {\r\n    \"e\": [\r\n      \"svelte\"\r\n    ]\r\n  },\r\n  \"swift\": {\r\n    \"e\": [\r\n      \"swift\"\r\n    ]\r\n  },\r\n  \"tcl\": {\r\n    \"f\": [\r\n      \"owh\",\r\n      \"starfield\"\r\n    ],\r\n    \"e\": [\r\n      \"tcl\",\r\n      \"adp\",\r\n      \"tcl.in\",\r\n      \"tm\"\r\n    ]\r\n  },\r\n  \"terraform\": {\r\n    \"e\": [\r\n      \"hcl\",\r\n      \"nomad\",\r\n      \"tf\",\r\n      \"tfvars\",\r\n      \"workflow\"\r\n    ]\r\n  },\r\n  \"thrift\": {\r\n    \"e\": [\r\n      \"thrift\"\r\n    ]\r\n  },\r\n  \"tsql\": {\r\n    \"e\": [\r\n      \"sql\"\r\n    ]\r\n  },\r\n  \"twig\": {\r\n    \"e\": [\r\n      \"twig\"\r\n    ]\r\n  },\r\n  \"typescript\": {\r\n    \"e\": [\r\n      \"ts\",\r\n      \"tsx\"\r\n    ]\r\n  },\r\n  \"vbnet\": {\r\n    \"e\": [\r\n      \"vb\",\r\n      \"vbhtml\"\r\n    ]\r\n  },\r\n  \"vbscript\": {\r\n    \"e\": [\r\n      \"vbs\"\r\n    ]\r\n  },\r\n  \"vhdl\": {\r\n    \"e\": [\r\n      \"vhdl\",\r\n      \"vhd\",\r\n      \"vhf\",\r\n      \"vhi\",\r\n      \"vho\",\r\n      \"vhs\",\r\n      \"vht\",\r\n      \"vhw\"\r\n    ]\r\n  },\r\n  \"vala\": {\r\n    \"e\": [\r\n      \"vala\",\r\n      \"vapi\"\r\n    ]\r\n  },\r\n  \"verilog\": {\r\n    \"e\": [\r\n      \"v\",\r\n      \"veo\"\r\n    ]\r\n  },\r\n  \"vim\": {\r\n    \"f\": [\r\n      \".exrc\",\r\n      \".gvimrc\",\r\n      \".nvimrc\",\r\n      \".vimrc\",\r\n      \"_vimrc\",\r\n      \"gvimrc\",\r\n      \"nvimrc\",\r\n      \"vimrc\"\r\n    ],\r\n    \"e\": [\r\n      \"vim\",\r\n      \"vba\",\r\n      \"vmb\"\r\n    ]\r\n  },\r\n  \"xsharp\": {\r\n    \"e\": [\r\n      \"xs\"\r\n    ]\r\n  },\r\n  \"x86asm\": {\r\n    \"e\": [\r\n      \"asm\",\r\n      \"a51\",\r\n      \"i\",\r\n      \"inc\",\r\n      \"nasm\"\r\n    ]\r\n  },\r\n  \"xquery\": {\r\n    \"e\": [\r\n      \"xquery\",\r\n      \"xq\",\r\n      \"xql\",\r\n      \"xqm\",\r\n      \"xqy\"\r\n    ]\r\n  },\r\n  \"yml\": {\r\n    \"f\": [\r\n      \".clang-format\",\r\n      \".clang-tidy\",\r\n      \".gemrc\",\r\n      \"glide.lock\",\r\n      \"yarn.lock\"\r\n    ],\r\n    \"e\": [\r\n      \"yml\",\r\n      \"mir\",\r\n      \"reek\",\r\n      \"rviz\",\r\n      \"sublime-syntax\",\r\n      \"syntax\",\r\n      \"yaml\",\r\n      \"yaml-tmlanguage\",\r\n      \"yaml.sed\",\r\n      \"yml.mysql\"\r\n    ]\r\n  },\r\n  \"zenscript\": {\r\n    \"e\": [\r\n      \"zs\"\r\n    ]\r\n  },\r\n  \"zephir\": {\r\n    \"e\": [\r\n      \"zep\"\r\n    ]\r\n  }\r\n}\r\n"
  },
  {
    "path": "client/src/lib/syntax-highlighter/languages.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport languagesMap from './languages-map.json';\n\nconst LANGUAGES_BY_FILENAME = {};\nconst LANGUAGES_BY_EXTENSION = {};\n\nObject.entries(languagesMap).forEach(([language, { f: filenames, e: extensions }]) => {\n  if (filenames) {\n    filenames.forEach((filename) => {\n      if (!LANGUAGES_BY_FILENAME[filename]) {\n        LANGUAGES_BY_FILENAME[filename] = [];\n      }\n\n      LANGUAGES_BY_FILENAME[filename].push(language);\n    });\n  }\n\n  if (extensions) {\n    extensions.forEach((extension) => {\n      if (!LANGUAGES_BY_EXTENSION[extension]) {\n        LANGUAGES_BY_EXTENSION[extension] = [];\n      }\n\n      LANGUAGES_BY_EXTENSION[extension].push(language);\n    });\n  }\n});\n\nexport { LANGUAGES_BY_FILENAME, LANGUAGES_BY_EXTENSION };\n\nexport const detectLanguagesByFilename = (filename) => {\n  let languages = LANGUAGES_BY_FILENAME[filename];\n\n  if (languages) {\n    return languages;\n  }\n\n  const extensions = filename.substring(1).split('.');\n\n  if (extensions.length === 1) {\n    return [];\n  }\n\n  if (extensions.length > 2) {\n    const extension = extensions.slice(-2).join('.');\n    languages = LANGUAGES_BY_EXTENSION[extension];\n\n    if (languages) {\n      return languages;\n    }\n  }\n\n  return LANGUAGES_BY_EXTENSION[extensions[extensions.length - 1]] || [];\n};\n\nconst createLanguageLoader = (language, loader) => async (registerLanguage) => {\n  const { default: definition } = await loader();\n  registerLanguage(language, definition);\n};\n\nexport const languageLoaders = {\n  '4d': createLanguageLoader('4d', () => import('highlightjs-4d')),\n  'sap-abap': createLanguageLoader('sap-abap', () => import('highlightjs-sap-abap')),\n  apex: createLanguageLoader('apex', () => import('highlightjs-apex')),\n  alan: createLanguageLoader('alan', () => import('highlightjs-alan')),\n  ballerina: createLanguageLoader('ballerina', () => import('@ballerina/highlightjs-ballerina')),\n  blade: createLanguageLoader('blade', () => import('highlightjs-blade')),\n  cobol: createLanguageLoader('cobol', () => import('highlightjs-cobol')),\n  chapel: createLanguageLoader('chapel', () => import('./language-definitions/chapel')),\n  dafny: createLanguageLoader('dafny', () => import('./language-definitions/dafny')),\n  godot: createLanguageLoader('godot', () => import('./language-definitions/godot')),\n  gn: createLanguageLoader('gn', () => import('./language-definitions/gn')),\n  gf: createLanguageLoader('gf', () => import('highlightjs-gf')),\n  hlsl: createLanguageLoader('hlsl', () => import('./language-definitions/hlsl')),\n  jolie: createLanguageLoader('jolie', () => import('highlightjs-jolie')),\n  lean: createLanguageLoader('lean', () => import('highlightjs-lean')),\n  lookml: createLanguageLoader('lookml', () => import('highlightjs-lookml')),\n  macaulay2: createLanguageLoader('macaulay2', () => import('highlightjs-macaulay2')),\n  mlir: createLanguageLoader('mlir', () => import('highlightjs-mlir')),\n  papyrus: createLanguageLoader('papyrus', () => import('hightlightjs-papyrus')),\n  qsharp: createLanguageLoader('qsharp', () => import('highlightjs-qsharp')),\n  cshtml: createLanguageLoader('cshtml', () => import('highlightjs-cshtml-razor')),\n  redbol: createLanguageLoader('redbol', () => import('highlightjs-redbol')),\n  'rpm-specfile': createLanguageLoader('rpm-specfile', () => import('highlightjs-rpm-specfile')),\n  solidity: createLanguageLoader('solidity', () => import('highlightjs-solidity')),\n  supercollider: createLanguageLoader('supercollider', () => import('highlightjs-supercollider')),\n  svelte: createLanguageLoader('svelte', () => import('highlightjs-svelte')),\n  terraform: createLanguageLoader('terraform', () => import('./language-definitions/terraform')),\n  xsharp: createLanguageLoader('xsharp', () => import('highlightjs-xsharp')),\n  zenscript: createLanguageLoader('zenscript', () => import('highlightjs-zenscript')),\n};\n"
  },
  {
    "path": "client/src/lib/syntax-highlighter/syntax-highlighter.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport hljs from 'highlight.js';\n\nimport { detectLanguagesByFilename, languageLoaders } from './languages';\n\nconst loadLanguages = async (languages) => {\n  for (let i = 0; i < languages.length; i += 1) {\n    const language = languages[i];\n\n    if (!hljs.getLanguage(language) && languageLoaders[language]) {\n      // eslint-disable-next-line no-await-in-loop\n      await languageLoaders[language](hljs.registerLanguage);\n    }\n  }\n};\n\nconst highlight = (content, languages) =>\n  languages.length === 1\n    ? hljs.highlight(content, { language: languages[0] })\n    : hljs.highlightAuto(content, languages.length === 0 ? undefined : languages);\n\nexport default {\n  detectLanguagesByFilename,\n  loadLanguages,\n  highlight,\n};\n"
  },
  {
    "path": "client/src/locales/ar-YE/core.js",
    "content": "import dateFns from 'date-fns/locale/ar';\nimport timeAgo from 'javascript-time-ago/locale/ar';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'M/d/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'MMM d',\n    longDateTime: \"MMMM d 'at' p\",\n    fullDate: 'MMM d, y',\n    fullDateTime: \"MMMM d, y 'at' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'حول التطبيق',\n      aboutPlanka_title: 'حول PLANKA',\n      accessToken: 'رمز الوصول',\n      account: 'الحساب',\n      actions: 'إجراءات',\n      activateUser_title: 'تفعيل المستخدم',\n      active: 'نشط',\n      addAttachment_title: 'إضافة مرفق',\n      addCustomFieldGroup_title: 'إضافة مجموعة حقل مخصص',\n      addCustomField_title: 'إضافة حقل مخصص',\n      addManager_title: 'إضافة مدير',\n      addMember_title: 'إضافة عضو',\n      addTaskList_title: 'إضافة قائمة مهام',\n      addUser_title: 'إضافة مستخدم',\n      admin: 'مدير',\n      administration: 'الإدارة',\n      all: 'الكل',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'سيتم حفظ جميع التغييرات تلقائياً<br />بعد استعادة الإتصال.',\n      alphabetically: 'أبجدياً',\n      alwaysDisplayCardCreator: 'عرض منشئ البطاقة دائماً',\n      apiKeyCreated_title: 'تم إنشاء مفتاح API',\n      apiKey_title: 'مفتاح API',\n      archive: 'أرشيف',\n      archiveCard_title: 'أرشفة البطاقة',\n      archiveCards_title: 'أرشفة البطاقات',\n      areYouSureYouWantToActivateThisUser: 'هل أنت متأكد أنك تريد تفعيل هذا المستخدم؟',\n      areYouSureYouWantToArchiveCards: 'هل أنت متأكد أنك تريد أرشفة البطاقات؟',\n      areYouSureYouWantToArchiveThisCard: 'هل أنت متأكد أنك تريد أرشفة هذه البطاقة؟',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'هل أنت متأكد أنك تريد تعيين مدير المشروع هذا كمالك؟',\n      areYouSureYouWantToDeactivateThisUser: 'هل أنت متأكد أنك تريد إلغاء تفعيل هذا المستخدم؟',\n      areYouSureYouWantToDeleteThisApiKey: 'هل أنت متأكد أنك تريد حذف مفتاح API هذا؟',\n      areYouSureYouWantToDeleteThisAttachment: 'هل أنت متأكد أنك تريد حذف هذا المرفق؟',\n      areYouSureYouWantToDeleteThisBackgroundImage: 'هل أنت متأكد أنك تريد حذف صورة الخلفية هذه؟',\n      areYouSureYouWantToDeleteThisBoard: 'هل أنت متأكد أنك تريد حذف هذه اللوحة؟',\n      areYouSureYouWantToDeleteThisCard: 'هل أنت متأكد أنك تريد حذف هذه البطاقة؟',\n      areYouSureYouWantToDeleteThisCardForever: 'هل أنت متأكد أنك تريد حذف هذه البطاقة نهائياً؟',\n      areYouSureYouWantToDeleteThisComment: 'هل أنت متأكد أنك تريد حذف هذا التعليق؟',\n      areYouSureYouWantToDeleteThisCustomField: 'هل أنت متأكد أنك تريد حذف هذا الحقل المخصص؟',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'هل أنت متأكد أنك تريد حذف مجموعة الحقل المخصص هذه؟',\n      areYouSureYouWantToDeleteThisLabel: 'هل أنت متأكد أنك تريد حذف هذا الملصق؟',\n      areYouSureYouWantToDeleteThisList:\n        'هل أنت متأكد أنك تريد حذف هذه القائمة؟ سيتم نقل جميع البطاقات إلى سلة المهملات.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'هل أنت متأكد أنك تريد حذف خدمة الإشعارات هذه؟',\n      areYouSureYouWantToDeleteThisProject: 'هل أنت متأكد أنك تريد حذف هذا المشروع؟',\n      areYouSureYouWantToDeleteThisTask: 'هل أنت متأكد أنك تريد حذف هذه المهمة؟',\n      areYouSureYouWantToDeleteThisTaskList: 'هل أنت متأكد أنك تريد حذف قائمة المهام هذه؟',\n      areYouSureYouWantToDeleteThisUser: 'هل أنت متأكد أنك تريد حذف هذا المستخدم؟',\n      areYouSureYouWantToDeleteThisWebhook: 'هل أنت متأكد أنك تريد حذف هذا الويب هوك؟',\n      areYouSureYouWantToEmptyTrash: 'هل أنت متأكد أنك تريد إفراغ سلة المهملات؟',\n      areYouSureYouWantToLeaveBoard: 'هل أنت متأكد أنك تريد مغادرة اللوحة؟',\n      areYouSureYouWantToLeaveProject: 'هل أنت متأكد أنك تريد مغادرة المشروع؟',\n      areYouSureYouWantToMakeThisProjectPrivate: 'هل أنت متأكد أنك تريد جعل هذا المشروع خاصاً؟',\n      areYouSureYouWantToMakeThisProjectShared: 'هل أنت متأكد أنك تريد مشاركة هذا المشروع؟',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'هل أنت متأكد أنك تريد إعادة إنشاء مفتاح API هذا؟ لن يعمل المفتاح السابق بعد الآن.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'هل أنت متأكد أنك تريد إزالة هذا المدير من المشروع؟',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'هل أنت متأكد أنك تريد إزالة هذا العضو من اللوحة؟',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'هل أنت متأكد أنك تريد إلغاء ربط تسجيل الدخول الموحد من هذا المستخدم؟ سيسمح هذا للمستخدم بتسجيل الدخول باستخدام كلمة المرور.',\n      assignAsOwner_title: 'تعيين كمالك',\n      atLeastOneListMustBePresent: 'يجب وجود قائمة واحدة على الأقل',\n      attachment: 'مرفق',\n      attachments: 'مرفقات',\n      authentication: 'المصادقة',\n      background: 'الخلفية',\n      baseCustomFields_title: 'الحقول المخصصة الأساسية',\n      baseGroup: 'المجموعة الأساسية',\n      board: 'لوحة',\n      boardActions_title: 'إجراءات اللوحة',\n      boardNotFound_title: 'لم يتم العثور على اللوحة',\n      boardSubscribed: 'تم الاشتراك في اللوحة',\n      boardUser: 'مستخدم اللوحة',\n      byCreationTime: 'حسب وقت الإنشاء',\n      byDefault: 'افتراضياً',\n      byDueDate: 'حسب تاريخ الاستحقاق',\n      canBeInvitedToWorkInBoards: 'يمكن دعوته للعمل في اللوحات.',\n      canComment: 'يمكن التعليق',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'يمكنه إنشاء مشاريعه الخاصة ودعوته للعمل في مشاريع أخرى.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'يمكنه تعديل تخطيط اللوحة وتعيين الأعضاء للبطاقات.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'يمكنه إدارة إعدادات النظام والعمل كمالك للمشروع.',\n      canOnlyViewBoard: 'يمكن فقط عرض اللوحة.',\n      cardActions_title: 'إجراءات البطاقة',\n      cardNotFound_title: 'لم يتم العثور على البطاقة',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'البطاقات في هذه القائمة متاحة لجميع أعضاء اللوحة.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'البطاقات في هذه القائمة مكتملة وجاهزة للأرشفة.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'البطاقات في هذه القائمة جاهزة للعمل عليها.',\n      clickHereOrRefreshPageToUpdate: '<0>انقر هنا</0> أو حدث الصفحة للتحديث.',\n      clientHostnameInEhlo: 'اسم مضيف العميل في EHLO',\n      closed: 'مغلق',\n      color: 'اللون',\n      comments: 'التعليقات',\n      contentExceedsLimit: 'المحتوى يتجاوز {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay: 'محتوى هذا المرفق كبير جداً للعرض.',\n      copy_inline: 'نسخ',\n      createBoard_title: 'إنشاء لوحة',\n      createCustomFieldGroup_title: 'إنشاء مجموعة حقل مخصص',\n      createLabel_title: 'إنشاء ملصق',\n      createNewOneOrSelectExistingOne: 'أنشئ واحدة جديدة أو اختر<br />واحدة موجودة.',\n      createProject_title: 'إنشاء مشروع',\n      createTextFile_title: 'إنشاء ملف نصي',\n      creator: 'المنشئ',\n      currentPassword: 'كلمة المرور الحالية',\n      currentUser: 'المستخدم الحالي',\n      customFieldGroup_title: 'مجموعة الحقل المخصص',\n      customFieldGroups_title: 'مجموعات الحقول المخصصة',\n      customField_title: 'الحقل المخصص',\n      customFields_title: 'الحقول المخصصة',\n      customerPanel_title: 'لوحة العملاء',\n      dangerZone_title: 'منطقة الخطر',\n      date: 'تاريخ',\n      deactivateUser_title: 'إلغاء تفعيل المستخدم',\n      defaultCardType_title: 'نوع البطاقة الافتراضي',\n      defaultFrom: 'افتراضي من',\n      defaultView_title: 'العرض الافتراضي',\n      deleteAllBoardsToBeAbleToDeleteThisProject: 'احذف جميع اللوحات لتتمكن من حذف هذا المشروع',\n      deleteApiKey_title: 'حذف مفتاح API',\n      deleteAttachment_title: 'حذف المرفق',\n      deleteBackgroundImage_title: 'حذف صورة الخلفية',\n      deleteBoard_title: 'حذف اللوحة',\n      deleteCardForever_title: 'حذف البطاقة نهائياً',\n      deleteCard_title: 'حذف البطاقة',\n      deleteComment_title: 'حذف التعليق',\n      deleteCustomFieldGroup_title: 'حذف مجموعة الحقل المخصص',\n      deleteCustomField_title: 'حذف الحقل المخصص',\n      deleteLabel_title: 'حذف الملصق',\n      deleteList_title: 'حذف القائمة',\n      deleteNotificationService_title: 'حذف خدمة الإشعارات',\n      deleteProject_title: 'حذف المشروع',\n      deleteTaskList_title: 'حذف قائمة المهام',\n      deleteTask_title: 'حذف المهمة',\n      deleteUser_title: 'حذف المستخدم',\n      deleteWebhook_title: 'حذف الويب هوك',\n      deletedUser_title: 'مستخدم محذوف',\n      description: 'الوصف',\n      display: 'عرض',\n      displayCardAges: 'عرض أعمار البطاقات',\n      dropFileToUpload: 'أفلت الملف لرفعه',\n      dueDate_title: 'تاريخ الاستحقاق',\n      dynamicAndUnevenlySpacedLayout: 'تخطيط ديناميكي وغير متساوي المسافات.',\n      editAttachment_title: 'تعديل المرفق',\n      editAvatar_title: 'تحرير الصورة الرمزية',\n      editColor_title: 'تعديل اللون',\n      editCustomFieldGroup_title: 'تعديل مجموعة الحقل المخصص',\n      editCustomField_title: 'تعديل الحقل المخصص',\n      editDueDate_title: 'تعديل تاريخ الاستحقاق',\n      editEmail_title: 'تعديل البريد الإلكتروني',\n      editInformation_title: 'تعديل المعلومات',\n      editLabel_title: 'تعديل الملصق',\n      editPassword_title: 'تعديل كلمة المرور',\n      editPermissions_title: 'تعديل الأذونات',\n      editRole_title: 'تعديل الدور',\n      editStopwatch_title: 'تعديل المؤقت',\n      editType_title: 'تعديل النوع',\n      editUsername_title: 'تعديل اسم المستخدم',\n      editor: 'محرر',\n      editors: 'المحررون',\n      email: 'البريد الإلكتروني',\n      emptyTrash_title: 'إفراغ سلة المهملات',\n      enterCardTitle: 'أدخل عنوان البطاقة...',\n      enterDescription: 'أدخل الوصف...',\n      enterFilename: 'أدخل اسم الملف',\n      enterListTitle: 'أدخل عنوان القائمة...',\n      enterTaskDescription: 'أدخل وصف المهمة...',\n      events: 'الأحداث',\n      excludedEvents: 'الأحداث المستبعدة',\n      expandTaskListsByDefault: 'توسيع قوائم المهام افتراضياً',\n      filterByLabels_title: 'تصفية حسب الملصقات',\n      filterByMembers_title: 'تصفية حسب الأعضاء',\n      forPersonalProjects: 'للمشاريع الشخصية.',\n      forTeamBasedProjects: 'للمشاريع الجماعية.',\n      fromComputer_title: 'من الكمبيوتر',\n      fromTrello: 'من Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'المفتاح الكامل مخفي لأسباب أمنية. قم بإعادة إنشائه لإنشاء واحد جديد.',\n      general: 'عام',\n      gradients: 'التدرجات',\n      grid: 'الشبكة',\n      hideCompletedTasks: 'إخفاء المهام المكتملة',\n      hideFromProjectListAndFavorites: 'إخفاء من قائمة المشاريع والمفضلة',\n      host: 'المضيف',\n      hours: 'ساعات',\n      identity: 'الهوية',\n      importBoard_title: 'استيراد اللوحة',\n      information: 'المعلومات',\n      invalidCurrentPassword: 'كلمة المرور الحالية غير صالحة',\n      kanban: 'كانبان',\n      labels: 'الملصقات',\n      language: 'اللغة',\n      leaveBoard_title: 'غادر اللوحة',\n      leaveProject_title: 'غادر المشروع',\n      limitCardTypesToDefaultOne: 'تقييد أنواع البطاقات للنوع الافتراضي',\n      linkToCard: 'رابط للبطاقة',\n      list: 'القائمة',\n      listActions_title: 'قائمة الإجراءات',\n      lists: 'القوائم',\n      makeProjectPrivate_title: 'جعل المشروع خاصاً',\n      makeProjectShared_title: 'مشاركة المشروع',\n      managers: 'المديرون',\n      memberActions_title: 'إجراءات العضو',\n      members: 'الأعضاء',\n      minutes: 'الدقائق',\n      moreActions: 'المزيد من الإجراءات',\n      moreActions_title: 'المزيد من الإجراءات',\n      moveCard_title: 'نقل البطاقة',\n      moveList_title: 'نقل القائمة',\n      myOwn_title: 'ملكي',\n      name: 'الاسم',\n      newEmail: 'بريد إلكتروني جديد',\n      newPassword: 'كلمة سر جديدة',\n      newUsername: 'مستخدم جديد',\n      newVersionAvailable: 'إصدار جديد متاح',\n      newestFirst: 'الأحدث أولاً',\n      noApiKeyCreated: 'لم يتم إنشاء مفتاح API.',\n      noBoards: 'لا توجد لوحات',\n      noCardsFound: 'لم يتم العثور على بطاقات.',\n      noConnectionToServer: 'لا يوجد اتصال بالخادم',\n      noLists: 'لاتوجد قوائم',\n      noProjects: 'لاتوجد مشاريع',\n      noUnreadNotifications: 'لاتوجد إشعارات غير مقروءة',\n      notifications: 'الإشعارات',\n      oldestFirst: 'الأقدم أولاً',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'يجب أن يبقى مدير واحد فقط لجعل هذا المشروع خاصاً',\n      openBoard_title: 'فتح اللوحة',\n      optional_inline: 'اختياري',\n      organization: 'المنظمة',\n      others: 'آخرون',\n      passwordIsSet: 'تم تعيين كلمة المرور',\n      phone: 'الهاتف',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'تستخدم بلانكا <1><0>Apprise</0></1> لإرسال الإشعارات إلى أكثر من 100 خدمة شائعة.',\n      port: 'المنفذ',\n      preferences: 'التفضيلات',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'نصيحة: اضغط على Ctrl-V (Cmd-V على Mac) لإضافة مرفق من الحافظة.',\n      private: 'خاص',\n      project: 'مشروع',\n      projectNotFound_title: 'المشروع غير موجود',\n      projectOwner: 'مالك المشروع',\n      referenceDataAndKnowledgeStorage: 'تخزين البيانات المرجعية والمعرفة.',\n      regenerateApiKey_title: 'إعادة إنشاء مفتاح API',\n      rejectUnauthorizedTlsCertificates: 'رفض شهادات TLS غير المصرح بها',\n      removeManager_title: 'إزالة المدير',\n      removeMember_title: 'إزالة العضو',\n      role: 'الدور',\n      saveThisKeyItWillNotBeShownAgain: 'احفظ هذا المفتاح — لن يتم عرضه مرة أخرى!',\n      searchCards: 'البحث عن البطاقات...',\n      searchCustomFieldGroups: 'البحث عن مجموعات الحقول المخصصة...',\n      searchCustomFields: 'البحث عن الحقول المخصصة...',\n      searchLabels: 'البحث عن التصنيفات...',\n      searchLists: 'البحث عن القوائم...',\n      searchMembers: 'البحث عن الأعضاء...',\n      searchProjects: 'البحث عن المشاريع...',\n      searchUsers: 'البحث عن المستخدمين...',\n      seconds: 'ثواني',\n      selectAssignee_title: 'اختيار المكلف',\n      selectBoard: 'اختر لوحة',\n      selectList: 'اختر قائمة',\n      selectListToRestoreThisCard: 'اختر قائمة لاستعادة هذه البطاقة',\n      selectOrder_title: 'اختيار الترتيب',\n      selectPermissions_title: 'حدد الأذونات',\n      selectProject: 'حدد المشروع',\n      selectRole_title: 'اختيار الدور',\n      selectType_title: 'اختيار النوع',\n      sequentialDisplayOfCards: 'العرض المتسلسل للبطاقات.',\n      settings: 'الإعدادات',\n      shared: 'مشترك',\n      sharedWithMe_title: 'مشترك معي',\n      showOnFrontOfCard: 'عرض في مقدمة البطاقة',\n      smtp: 'SMTP',\n      sortList_title: 'فرز القائمة',\n      sourceCardIsNoLongerAvailableForCopying: 'البطاقة المصدر لم تعد متاحة للنسخ.',\n      sourceCardIsNoLongerAvailableForMoving: 'البطاقة المصدر لم تعد متاحة للنقل.',\n      stopwatch: 'المؤقت',\n      story: 'القصة',\n      subscribeToCardWhenCommenting: 'الاشتراك في البطاقة عند التعليق',\n      subscribeToMyOwnCardsByDefault: 'الاشتراك في بطاقاتي الخاصة إفتراضياً',\n      taskActions_title: 'إجراءات المهمة',\n      taskAssignmentAndProjectCompletion: 'تعيين المهام وإنجاز المشروع.',\n      taskListActions_title: 'إجراءات قائمة المهام',\n      taskList_title: 'قائمة المهام',\n      team: 'الفريق',\n      termsOfService_title: 'شروط الخدمة',\n      testLog_title: 'سجل الاختبار',\n      thereIsNoPreviewAvailableForThisAttachment: 'لا يوجد معاينة متاحة لهذا المرفق.',\n      time: 'الوقت',\n      title: 'العنوان',\n      trash: 'سلة المهملات',\n      trashHasBeenSuccessfullyEmptied: 'تم إفراغ سلة المهملات بنجاح.',\n      turnOffRecentCardHighlighting: 'إيقاف تمييز البطاقات الحديثة',\n      typeNameToConfirm: 'اكتب الاسم للتأكيد.',\n      typeTitleToConfirm: 'اكتب العنوان للتأكيد.',\n      unlinkSso_title: 'إلغاء ربط تسجيل الدخول الموحد',\n      unsavedChanges: 'تغييرات غير محفوظة',\n      uploadFailedFileIsTooBig: 'فشل الرفع: الملف كبير جداً.',\n      uploadFailedNotEnoughStorageSpace: 'فشل الرفع: مساحة التخزين غير كافية.',\n      uploadedImages: 'الصور المرفوعة',\n      url: 'الرابط',\n      useSecureConnection: 'استخدام اتصال آمن',\n      userActions_title: 'إجراءات المستخدم',\n      userAddedCardToList: '<0>{{user}}</0> أضاف <2>{{card}}</2> إلى {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> أضاف هذه البطاقة إلى {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> أضاف {{addedUser}} إلى <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> أضاف {{addedUser}} إلى هذه البطاقة',\n      userAddedYouToCard: '<0>{{user}}</0> أضافك إلى <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> أكمل {{task}} في <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> أكمل {{task}} في هذه البطاقة',\n      userJoinedCard: '<0>{{user}}</0> انضم إلى <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> انضم إلى هذه البطاقة',\n      userLeftCard: '<0>{{user}}</0> غادر <2>{{card}}</2>',\n      userLeftNewCommentToCard: '<0>{{user}}</0> ترك تعليق جديد «{{comment}}» إلى <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> غادر هذه البطاقة',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> وضع علامة على {{task}} كغير مكتملة في <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> وضع علامة على {{task}} كغير مكتملة في هذه البطاقة',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> ذكرك في تعليق «{{comment}}» على <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> نقل <2>{{card}}</2> من {{fromList}} إلى {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> نقل هذه البطاقة من {{fromList}} إلى {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> أزال {{removedUser}} من <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> أزال {{removedUser}} من هذه البطاقة',\n      username: 'اسم المستخدم',\n      users: 'المستخدمين',\n      viewer: 'مشاهد',\n      viewers: 'المشاهدون',\n      visualTaskManagementWithLists: 'إدارة المهام البصرية بالقوائم.',\n      webhooks: 'الويب هوكس',\n      whatsNew_title: 'ما الجديد',\n      withoutBaseGroup: 'بدون مجموعة أساسية',\n      writeComment: 'اكتب تعليقاً...',\n    },\n\n    action: {\n      activateUser: 'تفعيل المستخدم',\n      activateUser_title: 'تفعيل المستخدم',\n      addAnotherCard: 'إضافة بطاقة أخرى',\n      addAnotherList: 'إضافة قائمة أخرى',\n      addAnotherTask: 'إضافة مهمة أخرى',\n      addCard: 'إضافة بطاقة',\n      addCard_title: 'إضافة بطاقة',\n      addComment: 'إضافة تعليق',\n      addCustomField: 'إضافة حقل مخصص',\n      addCustomFieldGroup: 'إضافة مجموعة حقل مخصص',\n      addList: 'إضافة قائمة',\n      addMember: 'إضافة عضو',\n      addMoreDetailedDescription: 'إضافة وصف أكثر تفصيلاً',\n      addTask: 'إضافة مهمة',\n      addTaskList: 'إضافة قائمة مهام',\n      addToCard: 'إضافة إلى البطاقة',\n      addUser: 'إضافة مستخدم',\n      addWebhook: 'إضافة ويب هوك',\n      archive: 'أرشفة',\n      archiveCard: 'أرشفة البطاقة',\n      archiveCard_title: 'أرشفة البطاقة',\n      archiveCards: 'أرشفة البطاقات',\n      archiveCards_title: 'أرشفة البطاقات',\n      assignAsOwner: 'تعيين كمالك',\n      cancel: 'إلغاء',\n      copy: 'نسخ',\n      copyCard_title: 'نسخ البطاقة',\n      createApiKey: 'إنشاء مفتاح API',\n      createBoard: 'إنشاء لوحة',\n      createCustomFieldGroup: 'إنشاء مجموعة حقل مخصص',\n      createFile: 'إنشاء ملف',\n      createLabel: 'إنشاء ملصق',\n      createNewLabel: 'إنشاء ملصق جديد',\n      createProject: 'إنشاء مشروع',\n      cut: 'قص',\n      cutCard_title: 'قص البطاقة',\n      deactivateUser: 'إلغاء تفعيل المستخدم',\n      deactivateUser_title: 'إلغاء تفعيل المستخدم',\n      delete: 'حذف',\n      deleteApiKey: 'حذف مفتاح API',\n      deleteAttachment: 'حذف المرفق',\n      deleteAvatar: 'حذف الصورة الرمزية',\n      deleteBackgroundImage: 'حذف صورة الخلفية',\n      deleteBoard: 'حذف اللوحة',\n      deleteBoard_title: 'حذف اللوحة',\n      deleteCard: 'حذف البطاقة',\n      deleteCardForever: 'حذف البطاقة نهائياً',\n      deleteCard_title: 'حذف البطاقة',\n      deleteComment: 'حذف التعليق',\n      deleteCustomField: 'حذف الحقل المخصص',\n      deleteCustomFieldGroup: 'حذف مجموعة الحقل المخصص',\n      deleteForever_title: 'حذف نهائياً',\n      deleteGroup: 'حذف المجموعة',\n      deleteLabel: 'حذف الملصق',\n      deleteList: 'حذف القائمة',\n      deleteList_title: 'حذف القائمة',\n      deleteNotificationService: 'حذف خدمة الإشعارات',\n      deleteProject: 'حذف المشروع',\n      deleteProject_title: 'حذف المشروع',\n      deleteTask: 'حذف المهمة',\n      deleteTaskList: 'حذف قائمة المهام',\n      deleteTask_title: 'حذف المهمة',\n      deleteUser: 'حذف المستخدم',\n      deleteUser_title: 'حذف المستخدم',\n      deleteWebhook: 'حذف الويب هوك',\n      dismissAll: 'رفض الكل',\n      download: 'تحميل',\n      duplicateCard_title: 'تكرار البطاقة',\n      edit: 'تعديل',\n      editColor_title: 'تعديل اللون',\n      editDescription_title: 'تعديل الوصف',\n      editDueDate_title: 'تعديل تاريخ الاستحقاق',\n      editEmail_title: 'تعديل البريد الإلكتروني',\n      editGroup: 'تعديل المجموعة',\n      editInformation_title: 'تعديل المعلومات',\n      editPassword_title: 'تعديل كلمة المرور',\n      editPermissions: 'تعديل الأذونات',\n      editRole_title: 'تعديل الدور',\n      editStopwatch_title: 'تعديل المؤقت',\n      editTitle_title: 'تعديل العنوان',\n      editType_title: 'تعديل النوع',\n      editUsername_title: 'تعديل اسم المستخدم',\n      emptyTrash: 'إفراغ سلة المهملات',\n      emptyTrash_title: 'إفراغ سلة المهملات',\n      import: 'استيراد',\n      join: 'انضمام',\n      leave: 'مغادرة',\n      leaveBoard: 'غادر اللوحة',\n      leaveProject: 'غادر المشروع',\n      logOut_title: 'تسجيل الخروج',\n      makeCover_title: 'إصنع غلافاً',\n      makeProjectPrivate: 'جعل المشروع خاصاً',\n      makeProjectPrivate_title: 'جعل المشروع خاصاً',\n      makeProjectShared: 'مشاركة المشروع',\n      makeProjectShared_title: 'مشاركة المشروع',\n      move: 'نقل',\n      moveCard_title: 'نقل البطاقة',\n      moveList_title: 'نقل القائمة',\n      regenerateApiKey: 'إعادة إنشاء مفتاح API',\n      remove: 'حذف',\n      removeAssignee: 'إزالة المكلف',\n      removeColor: 'إزالة اللون',\n      removeCover_title: 'إزالة الغلاف',\n      removeFromBoard: 'إزالة اللوحة',\n      removeFromProject: 'إزالة المشروع',\n      removeManager: 'إزالة المدير',\n      removeMember: 'إزالة العضو',\n      restoreToList: 'استعادة إلى {{list}}',\n      returnToBoard: 'العودة إلى اللوحة',\n      save: 'حفظ',\n      sendTestEmail: 'إرسال بريد إلكتروني تجريبي',\n      showActive: 'عرض النشط',\n      showAllAttachments: 'إظهار جميع المرفقات ({{hidden}} مخفي)',\n      showCardsWithThisUser: 'عرض البطاقات مع هذا المستخدم',\n      showDeactivated: 'عرض المعطل',\n      showFewerAttachments: 'عرض مرفقات أقل',\n      showLess: 'عرض أقل',\n      showMore: 'عرض المزيد',\n      sortList_title: 'فرز القائمة',\n      start: 'ابدأ',\n      stop: 'توقف',\n      subscribe: 'اشترك',\n      unlinkSso: 'إلغاء ربط تسجيل الدخول الموحد',\n      unlinkSso_title: 'إلغاء ربط تسجيل الدخول الموحد',\n      unsubscribe: 'إلغاء الاشتراك',\n      uploadNewAvatar: 'رفع صورة رمزية جديدة',\n      uploadNewImage: 'رفع صورة جديدة',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ar-YE/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'ar-YE',\n  country: 'ye',\n  name: 'العربية',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/ar-YE/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'تم الوصول إلى حد المستخدمين النشطين',\n      adminLoginRequiredToInitializeInstance: 'مطلوب تسجيل دخول المدير لتهيئة المثيل',\n      emailAlreadyInUse: 'البريد الإلكتروني مستخدم بالفعل',\n      emailOrUsername: 'البريد الإلكتروني أو اسم المستخدم',\n      invalidCredentials: 'بيانات الاعتماد غير صالحة',\n      invalidEmailOrUsername: 'البريد الإلكتروني أو اسم المستخدم غير صالح',\n      invalidPassword: 'كلمة المرور غير صالحة',\n      logIn_title: 'تسجيل الدخول',\n      noInternetConnection: 'لا يوجد اتصال بالإنترنت',\n      or: 'أو',\n      pageNotFound_title: 'الصفحة غير موجودة',\n      password: 'كلمة المرور',\n      poweredByPlanka: 'مدعوم بواسطة <1>PLANKA</1>',\n      serverConnectionFailed: 'فشل الاتصال بالخادم',\n      unknownError: 'خطأ غير معروف، يرجى المحاولة لاحقاً',\n      useSingleSignOn: 'استخدم تسجيل الدخول الموحد',\n      usernameAlreadyInUse: 'اسم المستخدم تم استخدامه بالفعل',\n      whoops_title: 'عفواً!',\n    },\n\n    action: {\n      cancelAndClose: 'إلغاء وإغلاق',\n      continue: 'متابعة',\n      debugSso: 'تصحيح أخطاء تسجيل الدخول الموحد',\n      goBack: 'العودة',\n      goHome: 'الذهاب للرئيسية',\n      logIn: 'تسجيل الدخول',\n      logInWithSso: 'تسجيل الدخول باستخدام SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ar-YE/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"هذا نص بدون عنوان.\\nيمكن تمييز كل من العنوان والنص\\nبالخط العريض والمائل واللون\\nوالشطب والتسطير.\",\n    \"text-with-head\": \"هذا نص مع عنوان.\\nيمكن تمييز كل من العنوان والنص\\nبالخط العريض والمائل واللون\\nوالشطب والتسطير.\",\n    \"heading\": \"العنوان\"\n  },\n  \"bundle\": {\n    \"error-title\": \"خطأ في محرر markdown\",\n    \"settings_wysiwyg\": \"المحرر المرئي (wysiwyg)\",\n    \"settings_markup\": \"ترميز markdown\",\n    \"markup_placeholder\": \"أدخل ترميز markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"إزالة\",\n    \"empty_option\": \"لم يتم العثور على تطابقات\",\n    \"show_line_numbers\": \"ترقيم الأسطر\"\n  },\n  \"common\": {\n    \"delete\": \"حذف\",\n    \"edit\": \"تحرير\",\n    \"toolbar_action_disabled\": \"عنصر ترميز غير متوافق\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"إلغاء\",\n    \"common_action_submit\": \"إرسال\",\n    \"common_action_upload\": \"اختيار\",\n    \"common_tab_attach\": \"إضافة من الجهاز\",\n    \"common_tab_link\": \"إضافة بالرابط\",\n    \"common_link\": \"رابط\",\n    \"common_sizes\": \"الحجم، px\",\n    \"image_name\": \"العنوان\",\n    \"image_link_href\": \"رابط الصورة\",\n    \"image_link_href_help\": \"العنوان الذي يؤدي إليه رابط الصورة.\",\n    \"image_alt\": \"النص البديل\",\n    \"image_alt_help\": \"يتم عرض النص البديل إذا لم يتم تحميل الصورة.\",\n    \"image_upload_help\": \"صورة JPEG أو GIF أو PNG لا تزيد عن 1 ميجابايت.\",\n    \"image_upload_failed\": \"فشل في إضافة الصورة\",\n    \"image_size_width\": \"العرض\",\n    \"image_size_height\": \"الارتفاع\",\n    \"link_url_help\": \"العنوان الذي يؤدي إليه الرابط.\",\n    \"link_text\": \"نص الرابط\",\n    \"link_text_help\": \"النص المعروض كرابط.\",\n    \"link_open_help\": \"فتح الرابط في علامة تبويب جديدة\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"رأس\",\n    \"header_hint\": \"# نصك\",\n    \"italic_title\": \"مائل\",\n    \"italic_hint\": \"_نصك_\",\n    \"bold_title\": \"عريض\",\n    \"bold_hint\": \"**نصك**\",\n    \"strikethrough_title\": \"مشطوب\",\n    \"strikethrough_hint\": \"~~نصك~~\",\n    \"blockquote_title\": \"اقتباس\",\n    \"blockquote_hint\": \"> نصك\",\n    \"code_title\": \"كود\",\n    \"code_hint\": \"```نصك```\",\n    \"link_title\": \"رابط\",\n    \"link_hint\": \"[نصك](url)\",\n    \"image_title\": \"صورة\",\n    \"image_hint\": \"![نصك](url)\",\n    \"list_title\": \"عنصر قائمة\",\n    \"list_hint\": \"- نصك\",\n    \"numbered-list_title\": \"قائمة مرقمة\",\n    \"numbered-list_hint\": \"1. نصك\",\n    \"documentation\": \"التوثيق\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"عريض\",\n    \"code\": \"كود\",\n    \"code_inline\": \"كود مضمن\",\n    \"codeblock\": \"كتلة كود\",\n    \"colorify\": \"لون النص\",\n    \"colorify__color_blue\": \"أزرق\",\n    \"colorify__color_default\": \"افتراضي\",\n    \"colorify__color_gray\": \"رمادي\",\n    \"colorify__color_green\": \"أخضر\",\n    \"colorify__color_orange\": \"برتقالي\",\n    \"colorify__color_red\": \"أحمر\",\n    \"colorify__color_violet\": \"بنفسجي\",\n    \"colorify__color_yellow\": \"أصفر\",\n    \"colorify__group_text\": \"نص\",\n    \"cut\": \"قص\",\n    \"emoji\": \"رموز تعبيرية\",\n    \"emoji__hint\": \"يمكن إضافة الرموز التعبيرية في WYSIWYG أو يدوياً بالترميز\",\n    \"heading\": \"عنوان\",\n    \"heading1\": \"عنوان 1\",\n    \"heading2\": \"عنوان 2\",\n    \"heading3\": \"عنوان 3\",\n    \"heading4\": \"عنوان 4\",\n    \"heading5\": \"عنوان 5\",\n    \"heading6\": \"عنوان 6\",\n    \"hrule\": \"فاصل\",\n    \"image\": \"صورة\",\n    \"italic\": \"مائل\",\n    \"link\": \"رابط\",\n    \"list\": \"قائمة\",\n    \"list__action_lift\": \"رفع العنصر\",\n    \"list__action_sink\": \"خفض العنصر\",\n    \"list_action_disabled\": \"يتعارض مع منطق القائمة\",\n    \"mark\": \"مميز\",\n    \"mono\": \"أحادي المسافة\",\n    \"more_action\": \"المزيد من الإجراءات\",\n    \"note\": \"ملاحظة\",\n    \"olist\": \"قائمة مرتبة\",\n    \"quote\": \"اقتباس\",\n    \"redo\": \"إعادة\",\n    \"strike\": \"مشطوب\",\n    \"table\": \"جدول\",\n    \"text\": \"نص\",\n    \"ulist\": \"قائمة نقطية\",\n    \"underline\": \"مسطر\",\n    \"undo\": \"تراجع\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"اكتب / لاستخدام أوامر الشرطة المائلة...\",\n    \"checkbox\": \"أدخل وصف المهمة...\",\n    \"deflist_term\": \"مصطلح\",\n    \"deflist_desc\": \"وصف التعريف\",\n    \"heading\": \"عنوان\",\n    \"cut_title\": \"العنوان\",\n    \"cut_content\": \"المحتوى المراد عرضه عند النقر\",\n    \"note_title\": \"العنوان\",\n    \"note_content\": \"محتوى الملاحظة\",\n    \"table_cell\": \"محتوى الخلية\",\n    \"select_filter\": \"البحث في اللغات...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"حساس لحالة الأحرف\",\n    \"label_whole-word\": \"كلمة كاملة\",\n    \"title\": \"البحث في الكود\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"غير موجود\"\n  },\n  \"widgets\": {\n    \"image\": \"إضافة صورة\",\n    \"link\": \"إضافة رابط\"\n  },\n  \"yfm-note\": {\n    \"info\": \"ملاحظة\",\n    \"tip\": \"نصيحة\",\n    \"warning\": \"تحذير\",\n    \"alert\": \"تنبيه\",\n    \"remove\": \"إزالة\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"إضافة عمود قبل\",\n    \"column.add.after\": \"إضافة عمود بعد\",\n    \"column.remove\": \"إزالة العمود\",\n    \"column.remove.multiple\": \"إزالة الأعمدة\",\n    \"row.add.before\": \"إضافة صف قبل\",\n    \"row.add.after\": \"إضافة صف بعد\",\n    \"row.remove\": \"إزالة الصف\",\n    \"row.remove.multiple\": \"إزالة الصفوف\",\n    \"cells.clear\": \"مسح الخلايا\",\n    \"table.remove\": \"إزالة الجدول\",\n    \"table.menu.cell.align.left\": \"محاذاة محتوى الخلية إلى اليسار\",\n    \"table.menu.cell.align.right\": \"محاذاة محتوى الخلية إلى اليمين\",\n    \"table.menu.cell.align.center\": \"محاذاة محتوى الخلية إلى المركز\",\n    \"table.menu.row.add\": \"إضافة صف بعد\",\n    \"table.menu.row.remove\": \"إزالة الصف\",\n    \"table.menu.column.add\": \"إضافة عمود بعد\",\n    \"table.menu.column.remove\": \"إزالة العمود\",\n    \"table.menu.convert.yfm\": \"تحويل إلى جدول YFM\",\n    \"table.menu.table.remove\": \"إزالة الجدول\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/bg-BG/core.js",
    "content": "import dateFns from 'date-fns/locale/bg';\nimport timeAgo from 'javascript-time-ago/locale/bg';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd.MM.yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd. MMM',\n    longDateTime: \"d. MMMM 'в' p\",\n    fullDate: 'd. MMM. y',\n    fullDateTime: \"d. MMMM. y 'в' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'За приложението',\n      aboutPlanka_title: 'За PLANKA',\n      accessToken: 'Токен за достъп',\n      account: 'Акаунт',\n      actions: 'Действия',\n      activateUser_title: 'Активиране на потребител',\n      active: 'Активен',\n      addAttachment_title: 'Добавяне на прикачен файл',\n      addCustomFieldGroup_title: 'Добавяне на група персонализирани полета',\n      addCustomField_title: 'Добавяне на персонализирано поле',\n      addManager_title: 'Добавяне на мениджър',\n      addMember_title: 'Добавяне на член',\n      addTaskList_title: 'Добавяне на списък със задачи',\n      addUser_title: 'Добавяне на потребител',\n      admin: 'Администратор',\n      administration: 'Администрация',\n      all: 'Всички',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Всички промени ще бъдат автоматично запазени<br />след възстановяване на връзката.',\n      alphabetically: 'По азбучен ред',\n      alwaysDisplayCardCreator: 'Винаги показвай създателя на картата',\n      apiKeyCreated_title: 'API ключ създаден',\n      apiKey_title: 'API ключ',\n      archive: 'Архив',\n      archiveCard_title: 'Архивиране на карта',\n      archiveCards_title: 'Архивиране на карти',\n      areYouSureYouWantToActivateThisUser:\n        'Сигурни ли сте, че искате да активирате този потребител?',\n      areYouSureYouWantToArchiveCards: 'Сигурни ли сте, че искате да архивирате картите?',\n      areYouSureYouWantToArchiveThisCard: 'Сигурни ли сте, че искате да архивирате тази карта?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Сигурни ли сте, че искате да назначите този мениджър на проекта като собственик?',\n      areYouSureYouWantToDeactivateThisUser:\n        'Сигурни ли сте, че искате да деактивирате този потребител?',\n      areYouSureYouWantToDeleteThisApiKey: 'Сигурни ли сте, че искате да изтриете този API ключ?',\n      areYouSureYouWantToDeleteThisAttachment:\n        'Сигурни ли сте, че искате да изтриете този прикачен файл?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Сигурни ли сте, че искате да изтриете това фоново изображение?',\n      areYouSureYouWantToDeleteThisBoard: 'Сигурни ли сте, че искате да изтриете това табло?',\n      areYouSureYouWantToDeleteThisCard: 'Сигурни ли сте, че искате да изтриете тази карта?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Сигурни ли сте, че искате да изтриете тази карта завинаги?',\n      areYouSureYouWantToDeleteThisComment: 'Сигурни ли сте, че искате да изтриете този коментар?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Сигурни ли сте, че искате да изтриете това персонализирано поле?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Сигурни ли сте, че искате да изтриете тази група персонализирани полета?',\n      areYouSureYouWantToDeleteThisLabel: 'Сигурни ли сте, че искате да изтриете този етикет?',\n      areYouSureYouWantToDeleteThisList:\n        'Сигурни ли сте, че искате да изтриете този списък? Всички карти ще бъдат преместени в кошчето.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Сигурни ли сте, че искате да изтриете тази услуга за известия?',\n      areYouSureYouWantToDeleteThisProject: 'Сигурни ли сте, че искате да изтриете този проект?',\n      areYouSureYouWantToDeleteThisTask: 'Сигурни ли сте, че искате да изтриете тази задача?',\n      areYouSureYouWantToDeleteThisTaskList:\n        'Сигурни ли сте, че искате да изтриете този списък със задачи?',\n      areYouSureYouWantToDeleteThisUser: 'Сигурни ли сте, че искате да изтриете този потребител?',\n      areYouSureYouWantToDeleteThisWebhook: 'Сигурни ли сте, че искате да изтриете този webhook?',\n      areYouSureYouWantToEmptyTrash: 'Сигурни ли сте, че искате да изпразните кошчето?',\n      areYouSureYouWantToLeaveBoard: 'Сигурни ли сте, че искате да напуснете таблото?',\n      areYouSureYouWantToLeaveProject: 'Сигурни ли сте, че искате да напуснете проекта?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Сигурни ли сте, че искате да направите този проект частен?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Сигурни ли сте, че искате да споделите този проект?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Сигурни ли сте, че искате да регенерирате този API ключ? Предишният ключ няма да работи повече.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Сигурни ли сте, че искате да премахнете този мениджър от проекта?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Сигурни ли сте, че искате да премахнете този член от таблото?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Сигурни ли сте, че искате да премахнете SSO връзката от този потребител? Това ще позволи на потребителя да влиза с парола.',\n      assignAsOwner_title: 'Назначаване като собственик',\n      atLeastOneListMustBePresent: 'Трябва да присъства поне един списък',\n      attachment: 'Прикачен файл',\n      attachments: 'Прикачени файлове',\n      authentication: 'Удостоверяване',\n      background: 'Фон',\n      baseCustomFields_title: 'Основни персонализирани полета',\n      baseGroup: 'Основна група',\n      board: 'Табло',\n      boardActions_title: 'Действия с табло',\n      boardNotFound_title: 'Таблото не е намерено',\n      boardSubscribed: 'Абониран за табло',\n      boardUser: 'Потребител на табло',\n      byCreationTime: 'По време на създаване',\n      byDefault: 'По подразбиране',\n      byDueDate: 'По краен срок',\n      canBeInvitedToWorkInBoards: 'Може да бъде поканен да работи в табла.',\n      canComment: 'Може да коментира',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Може да създава собствени проекти и да бъде поканен да работи в други.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Може да редактира оформлението на таблото и да назначава членове към карти.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Може да управлява системните настройки и да действа като собственик на проект.',\n      canOnlyViewBoard: 'Може само да преглежда таблото.',\n      cardActions_title: 'Действия с карта',\n      cardNotFound_title: 'Картата не може да бъде открита',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Картите в този списък са достъпни за всички членове на таблото.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Картите в този списък са завършени и готови за архивиране.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Картите в този списък са готови за работа.',\n      clickHereOrRefreshPageToUpdate:\n        '<0>Кликнете тук</0> или обновете страницата за актуализация.',\n      clientHostnameInEhlo: 'Име на клиентския хост в EHLO',\n      closed: 'Затворен',\n      color: 'Цвят',\n      comments: 'Коментари',\n      contentExceedsLimit: 'Съдържанието надвишава {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Съдържанието на този прикачен файл е твърде голямо за показване.',\n      copy_inline: 'копие',\n      createBoard_title: 'Създаване на табло',\n      createCustomFieldGroup_title: 'Създаване на група персонализирани полета',\n      createLabel_title: 'Създаване на етикет',\n      createNewOneOrSelectExistingOne: 'Създайте нов или изберете<br />съществуващ.',\n      createProject_title: 'Създаване на проект',\n      createTextFile_title: 'Създаване на текстов файл',\n      creator: 'Създател',\n      currentPassword: 'Текуща парола',\n      currentUser: 'Текущ потребител',\n      customFieldGroup_title: 'Група персонализирани полета',\n      customFieldGroups_title: 'Групи персонализирани полета',\n      customField_title: 'Персонализирано поле',\n      customFields_title: 'Персонализирани полета',\n      customerPanel_title: 'Панел на клиента',\n      dangerZone_title: 'Опасна зона',\n      date: 'Дата',\n      deactivateUser_title: 'Деактивиране на потребител',\n      defaultCardType_title: 'Тип карта по подразбиране',\n      defaultFrom: 'По подразбиране от',\n      defaultView_title: 'Изглед по подразбиране',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Изтрийте всички табла, за да можете да изтриете този проект',\n      deleteApiKey_title: 'Изтриване на API ключ',\n      deleteAttachment_title: 'Изтриване на прикачен файл',\n      deleteBackgroundImage_title: 'Изтриване на фоново изображение',\n      deleteBoard_title: 'Изтриване на табло',\n      deleteCardForever_title: 'Изтриване на карта завинаги',\n      deleteCard_title: 'Изтриване на карта',\n      deleteComment_title: 'Изтриване на коментар',\n      deleteCustomFieldGroup_title: 'Изтриване на група персонализирани полета',\n      deleteCustomField_title: 'Изтриване на персонализирано поле',\n      deleteLabel_title: 'Изтриване на етикета',\n      deleteList_title: 'Изтриване на списък',\n      deleteNotificationService_title: 'Изтриване на услуга за известия',\n      deleteProject_title: 'Изтриване на проект',\n      deleteTaskList_title: 'Изтриване на списък със задачи',\n      deleteTask_title: 'Изтриване на задача',\n      deleteUser_title: 'Изтриване на потребител',\n      deleteWebhook_title: 'Изтриване на webhook',\n      deletedUser_title: 'Изтрит потребител',\n      description: 'Описание',\n      display: 'Показване',\n      displayCardAges: 'Показвай възрастта на картите',\n      dropFileToUpload: 'Пуснете файл за качване',\n      dueDate_title: 'Краен срок',\n      dynamicAndUnevenlySpacedLayout: 'Динамично и неравномерно разположение.',\n      editAttachment_title: 'Редактирай прикачения файл',\n      editAvatar_title: 'Редактиране на аватар',\n      editColor_title: 'Редактиране на цвят',\n      editCustomFieldGroup_title: 'Редактиране на група персонализирани полета',\n      editCustomField_title: 'Редактиране на персонализирано поле',\n      editDueDate_title: 'Редактиране на дата на падеж',\n      editEmail_title: 'Редактиране на имейл',\n      editInformation_title: 'Редактиране на информация',\n      editLabel_title: 'Редактиране на табло',\n      editPassword_title: 'Редактиране на парола',\n      editPermissions_title: 'Редактиране на права',\n      editRole_title: 'Редактиране на роля',\n      editStopwatch_title: 'Редактиране на хронометър',\n      editType_title: 'Редактиране на тип',\n      editUsername_title: 'Редактиране на потребителско име',\n      editor: 'Редактор',\n      editors: 'Редактори',\n      email: 'Имейл',\n      emptyTrash_title: 'Изпразване на кошчето',\n      enterCardTitle: 'Въведете заглавие на картата...',\n      enterDescription: 'Въведете описание...',\n      enterFilename: 'Въведете име на файла',\n      enterListTitle: 'Въведете заглавие на списък...',\n      enterTaskDescription: 'Въведете описание на задачата...',\n      events: 'Събития',\n      excludedEvents: 'Изключени събития',\n      expandTaskListsByDefault: 'Разширяване на списъците със задачи по подразбиране',\n      filterByLabels_title: 'Филтриране по етикети',\n      filterByMembers_title: 'Филтриране по членове',\n      forPersonalProjects: 'За лични проекти.',\n      forTeamBasedProjects: 'За екипни проекти.',\n      fromComputer_title: 'От компютър',\n      fromTrello: 'От Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Пълният ключ е скрит от съображения за сигурност. Регенерирайте го, за да създадете нов.',\n      general: 'Общ',\n      gradients: 'Градиенти',\n      grid: 'Мрежа',\n      hideCompletedTasks: 'Скриване на завършени задачи',\n      hideFromProjectListAndFavorites: 'Скриване от списъка с проекти и любими',\n      host: 'Хост',\n      hours: 'Часове',\n      identity: 'Самоличност',\n      importBoard_title: 'Импортиране на табло',\n      information: 'Информация',\n      invalidCurrentPassword: 'Невалидна текуща парола',\n      kanban: 'Канбан',\n      labels: 'Етикети',\n      language: 'Език',\n      leaveBoard_title: 'Напуснете таблото',\n      leaveProject_title: 'Напуснете проекта',\n      limitCardTypesToDefaultOne: 'Ограничаване на типовете карти до подразбиращия се',\n      linkToCard: 'Връзка към карта',\n      list: 'Списък',\n      listActions_title: 'Списък с действия',\n      lists: 'Списъци',\n      makeProjectPrivate_title: 'Направете проекта частен',\n      makeProjectShared_title: 'Споделете проекта',\n      managers: 'Мениджъри',\n      memberActions_title: 'Действия с членове',\n      members: 'Членове',\n      minutes: 'Минути',\n      moreActions: 'Още действия',\n      moreActions_title: 'Още действия',\n      moveCard_title: 'Преместване на карта',\n      moveList_title: 'Преместване на списък',\n      myOwn_title: 'Моите собствени',\n      name: 'Име',\n      newEmail: 'Нов имейл',\n      newPassword: 'Нова парола',\n      newUsername: 'Ново потребителско име',\n      newVersionAvailable: 'Налична е нова версия',\n      newestFirst: 'Първо най-новите',\n      noApiKeyCreated: 'Няма създаден API ключ.',\n      noBoards: 'Няма табла',\n      noCardsFound: 'Не са намерени карти.',\n      noConnectionToServer: 'Няма връзка със сървъра',\n      noLists: 'Няма списъци',\n      noProjects: 'Няма проекти',\n      noUnreadNotifications: 'Няма непрочетени известия.',\n      notifications: 'Уведомления',\n      oldestFirst: 'Първо най-старите',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Трябва да остане само един мениджър, за да направите този проект частен',\n      openBoard_title: 'Отворете табло',\n      optional_inline: 'по желание',\n      organization: 'Организация',\n      others: 'Други',\n      passwordIsSet: 'Паролата е зададена',\n      phone: 'Телефон',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA използва <1><0>Apprise</0></1> за изпращане на уведомления до над 100 популярни услуги.',\n      port: 'Порт',\n      preferences: 'Предпочитания',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Съвет: натиснете Ctrl-V (Cmd-V на Mac), за да добавите прикачен файл от клипборда',\n      private: 'Частен',\n      project: 'Проект',\n      projectNotFound_title: 'Проектът не е намерен',\n      projectOwner: 'Собственик на проект',\n      referenceDataAndKnowledgeStorage: 'Съхранение на референтни данни и знания.',\n      regenerateApiKey_title: 'Регенериране на API ключ',\n      rejectUnauthorizedTlsCertificates: 'Отхвърляне на неоторизирани TLS сертификати',\n      removeManager_title: 'Премахване на мениджър',\n      removeMember_title: 'Премахване на член',\n      role: 'Роля',\n      saveThisKeyItWillNotBeShownAgain: 'Запазете този ключ — няма да бъде показан отново!',\n      searchCards: 'Търсене на карти...',\n      searchCustomFieldGroups: 'Търсене на групи персонализирани полета...',\n      searchCustomFields: 'Търсене на персонализирани полета...',\n      searchLabels: 'Търсене на етикети...',\n      searchLists: 'Търсене на списъци...',\n      searchMembers: 'Търсене на членове...',\n      searchProjects: 'Търсене на проекти...',\n      searchUsers: 'Търсене на потребители...',\n      seconds: 'секунди',\n      selectAssignee_title: 'Избор на изпълнител',\n      selectBoard: 'Изберете табло',\n      selectList: 'Изберете списък',\n      selectListToRestoreThisCard: 'Изберете списък за възстановяване на тази карта',\n      selectOrder_title: 'Избор на ред',\n      selectPermissions_title: 'Изберете правата',\n      selectProject: 'Изберете проект',\n      selectRole_title: 'Избор на роля',\n      selectType_title: 'Избор на тип',\n      sequentialDisplayOfCards: 'Последователно показване на карти.',\n      settings: 'Настройки',\n      shared: 'Споделен',\n      sharedWithMe_title: 'Споделени с мен',\n      showOnFrontOfCard: 'Показване отпред на картата',\n      smtp: 'SMTP',\n      sortList_title: 'Сортиране на списък',\n      sourceCardIsNoLongerAvailableForCopying: 'Оригиналната карта вече не е налична за копиране.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'Оригиналната карта вече не е налична за преместване.',\n      stopwatch: 'Хронометър',\n      story: 'История',\n      subscribeToCardWhenCommenting: 'Абониране за карта при коментиране',\n      subscribeToMyOwnCardsByDefault: 'Абонирайте се за собствените си карти по подразбиране',\n      taskActions_title: 'Действия със задачи',\n      taskAssignmentAndProjectCompletion: 'Назначаване на задачи и завършване на проект.',\n      taskListActions_title: 'Действия със списък задачи',\n      taskList_title: 'Списък със задачи',\n      team: 'Екип',\n      termsOfService_title: 'Условия за ползване',\n      testLog_title: 'Тестов дневник',\n      thereIsNoPreviewAvailableForThisAttachment: 'Няма наличен преглед за този прикачен файл.',\n      time: 'Време',\n      title: 'Заглавие',\n      trash: 'Кошче',\n      trashHasBeenSuccessfullyEmptied: 'Кошчето беше успешно изпразнено.',\n      turnOffRecentCardHighlighting: 'Изключване на подчертаването на скорошни карти',\n      typeNameToConfirm: 'Въведете име за потвърждение.',\n      typeTitleToConfirm: 'Въведете заглавие за потвърждение.',\n      unlinkSso_title: 'Премахване на SSO връзка',\n      unsavedChanges: 'Незапазени промени',\n      uploadFailedFileIsTooBig: 'Качването неуспешно: файлът е твърде голям.',\n      uploadFailedNotEnoughStorageSpace:\n        'Качването неуспешно: няма достатъчно място за съхранение.',\n      uploadedImages: 'Качени изображения',\n      url: 'URL',\n      useSecureConnection: 'Използване на сигурна връзка',\n      userActions_title: 'Потребителски действия',\n      userAddedCardToList: '<0>{{user}}</0> добави <2>{{card}}</2> в {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> добави тази карта в {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> добави {{addedUser}} към <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> добави {{addedUser}} към тази карта',\n      userAddedYouToCard: '<0>{{user}}</0> ви добави към <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> завърши {{task}} в <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> завърши {{task}} в тази карта',\n      userJoinedCard: '<0>{{user}}</0> се присъедини към <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> се присъедини към тази карта',\n      userLeftCard: '<0>{{user}}</0> напусна <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> остави нов коментар «{{comment}}» в <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> напусна тази карта',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> отбеляза {{task}} като незавършена в <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> отбеляза {{task}} като незавършена в тази карта',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> ви спомена в коментар «{{comment}}» в <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> премести <2>{{card}}</2> от {{fromList}} към {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> премести тази карта от {{fromList}} към {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> премахна {{removedUser}} от <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> премахна {{removedUser}} от тази карта',\n      username: 'Потребителско име',\n      users: 'Потребители',\n      viewer: 'Зрител',\n      viewers: 'Зрители',\n      visualTaskManagementWithLists: 'Визуално управление на задачи със списъци.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Какво еново',\n      withoutBaseGroup: 'Без основна група',\n      writeComment: 'Напишете коментар...',\n    },\n\n    action: {\n      activateUser: 'Активиране на потребител',\n      activateUser_title: 'Активиране на потребител',\n      addAnotherCard: 'Добавяне на друга карта',\n      addAnotherList: 'Добавяне на друг списък',\n      addAnotherTask: 'Добавяне на друга задача',\n      addCard: 'Добавяне на карта',\n      addCard_title: 'Добавяне на карта',\n      addComment: 'Добавяне на коментар',\n      addCustomField: 'Добавяне на персонализирано поле',\n      addCustomFieldGroup: 'Добавяне на група персонализирани полета',\n      addList: 'Добавяне на списък',\n      addMember: 'Добавяне на член',\n      addMoreDetailedDescription: 'Добавяне на по-подробно описание',\n      addTask: 'Добавяне на задача',\n      addTaskList: 'Добавяне на списък със задачи',\n      addToCard: 'Добавяне към карта',\n      addUser: 'Добавяне на потребител',\n      addWebhook: 'Добавяне на webhook',\n      archive: 'Архивиране',\n      archiveCard: 'Архивиране на карта',\n      archiveCard_title: 'Архивиране на карта',\n      archiveCards: 'Архивиране на карти',\n      archiveCards_title: 'Архивиране на карти',\n      assignAsOwner: 'Назначаване като собственик',\n      cancel: 'Отказ',\n      copy: 'Копиране',\n      copyCard_title: 'Копиране на карта',\n      createApiKey: 'Създаване на API ключ',\n      createBoard: 'Създаване на табло',\n      createCustomFieldGroup: 'Създаване на група персонализирани полета',\n      createFile: 'Създаване на файл',\n      createLabel: 'Създаване на етикет',\n      createNewLabel: 'Създаване на нов етикет',\n      createProject: 'Създаване на проект',\n      cut: 'Изрязване',\n      cutCard_title: 'Изрязване на карта',\n      deactivateUser: 'Деактивиране на потребител',\n      deactivateUser_title: 'Деактивиране на потребител',\n      delete: 'Изтриване',\n      deleteApiKey: 'Изтриване на API ключ',\n      deleteAttachment: 'Изтриване на прикачения файл',\n      deleteAvatar: 'Изтриване на аватар',\n      deleteBackgroundImage: 'Изтриване на фоново изображение',\n      deleteBoard: 'Изтриване на табло',\n      deleteBoard_title: 'Изтриване на табло',\n      deleteCard: 'Изтриване на карта',\n      deleteCardForever: 'Изтриване на карта завинаги',\n      deleteCard_title: 'Изтриване на карта',\n      deleteComment: 'Изтриване на коментара',\n      deleteCustomField: 'Изтриване на персонализирано поле',\n      deleteCustomFieldGroup: 'Изтриване на група персонализирани полета',\n      deleteForever_title: 'Изтриване завинаги',\n      deleteGroup: 'Изтриване на група',\n      deleteLabel: 'Изтриване на етикета',\n      deleteList: 'Изтриване на списък',\n      deleteList_title: 'Изтриване на списък',\n      deleteNotificationService: 'Изтриване на услуга за известия',\n      deleteProject: 'Изтриване на проект',\n      deleteProject_title: 'Изтриване на проект',\n      deleteTask: 'Изтриване на задача',\n      deleteTaskList: 'Изтриване на списък със задачи',\n      deleteTask_title: 'Изтриване на задача',\n      deleteUser: 'Изтриване на потребител',\n      deleteUser_title: 'Изтриване на потребител',\n      deleteWebhook: 'Изтриване на webhook',\n      dismissAll: 'Отхвърляне на всички',\n      download: 'Изтегляне',\n      duplicateCard_title: 'Дублирана карта',\n      edit: 'Редактиране',\n      editColor_title: 'Редактиране на цвят',\n      editDescription_title: 'Редактиране на описание',\n      editDueDate_title: 'Редактиране на краен срок',\n      editEmail_title: 'Редактиране на имейл',\n      editGroup: 'Редактиране на група',\n      editInformation_title: 'Редактиране на информация',\n      editPassword_title: 'Редактиране на парола',\n      editPermissions: 'Разрешения за редактиране',\n      editRole_title: 'Редактиране на роля',\n      editStopwatch_title: 'Редактиране на хронометър',\n      editTitle_title: 'Редактиране на заглавието',\n      editType_title: 'Редактиране на тип',\n      editUsername_title: 'Редактиране на потребителско име',\n      emptyTrash: 'Изпразване на кошчето',\n      emptyTrash_title: 'Изпразване на кошчето',\n      import: 'Импортиране',\n      join: 'Присъединяване',\n      leave: 'Напускане',\n      leaveBoard: 'Напускане на дъската',\n      leaveProject: 'Напускане на проекта',\n      logOut_title: 'Излезте',\n      makeCover_title: 'Създаване на корица',\n      makeProjectPrivate: 'Направете проекта частен',\n      makeProjectPrivate_title: 'Направете проекта частен',\n      makeProjectShared: 'Споделете проекта',\n      makeProjectShared_title: 'Споделете проекта',\n      move: 'Преместване',\n      moveCard_title: 'Преместване на карта',\n      moveList_title: 'Преместване на списък',\n      regenerateApiKey: 'Регенериране на API ключ',\n      remove: 'Премахване',\n      removeAssignee: 'Премахване на изпълнител',\n      removeColor: 'Премахване на цвят',\n      removeCover_title: 'Премахване на корицата',\n      removeFromBoard: 'Премахване от борда',\n      removeFromProject: 'Премахване от проекта',\n      removeManager: 'Премахване на мениджър',\n      removeMember: 'Премахване на член',\n      restoreToList: 'Възстановяване в {{list}}',\n      returnToBoard: 'Връщане към табло',\n      save: 'Запазване',\n      sendTestEmail: 'Изпращане на тестов имейл',\n      showActive: 'Показване на активни',\n      showAllAttachments: 'Показване на всички прикачени файлове ({{hidden}} скрити)',\n      showCardsWithThisUser: 'Показване на карти с този потребител',\n      showDeactivated: 'Показване на деактивирани',\n      showFewerAttachments: 'Показване на по-малко прикачени файлове',\n      showLess: 'Показване на по-малко',\n      showMore: 'Показване на повече',\n      sortList_title: 'Списък за сортиране',\n      start: 'Старт',\n      stop: 'Стоп',\n      subscribe: 'Абонирайте се',\n      unlinkSso: 'Премахни SSO връзка',\n      unlinkSso_title: 'Премахни SSO връзка',\n      unsubscribe: 'Отписване',\n      uploadNewAvatar: 'Качване на нов аватар',\n      uploadNewImage: 'Качване на ново изображение',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/bg-BG/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'bg-BG',\n  country: 'bg',\n  name: 'Български',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/bg-BG/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Достигнат е лимитът на активни потребители',\n      adminLoginRequiredToInitializeInstance:\n        'Необходимо е влизане на администратор за инициализиране на инстанцията',\n      emailAlreadyInUse: 'Имейлът вече се използва',\n      emailOrUsername: 'Имейл или потребителско име',\n      invalidCredentials: 'Невалидни данни за вход',\n      invalidEmailOrUsername: 'Невалиден имейл или потребителско име',\n      invalidPassword: 'Невалидна парола',\n      logIn_title: 'Вход',\n      noInternetConnection: 'Няма интернет връзка',\n      or: 'Или',\n      pageNotFound_title: 'Страницата не е намерена',\n      password: 'Парола',\n      poweredByPlanka: 'Задвижвано от <1>PLANKA</1>',\n      serverConnectionFailed: 'Неуспешна връзка със сървъра',\n      unknownError: 'Неизвестна грешка, опитайте отново по-късно',\n      useSingleSignOn: 'Използване на single sign-on',\n      usernameAlreadyInUse: 'Потребителското име вече се използва',\n      whoops_title: 'Опа!',\n    },\n\n    action: {\n      cancelAndClose: 'Отказ и затваряне',\n      continue: 'Продължи',\n      debugSso: 'Дебъгване на SSO',\n      goBack: 'Назад',\n      goHome: 'Към началото',\n      logIn: 'Вход',\n      logInWithSso: 'Вход чрез SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/bg-BG/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Това е текст без заглавие.\\nЗаглавието и текстът\\nмогат да бъдат подчертани с удебелен, курсив, цвят,\\nзачертан и подчертан.\",\n    \"text-with-head\": \"Това е текст със заглавие.\\nЗаглавието и текстът\\nмогат да бъдат подчертани с удебелен, курсив, цвят,\\nзачертан и подчертан.\",\n    \"heading\": \"Заглавие\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Грешка в markdown редактора\",\n    \"settings_wysiwyg\": \"Визуален редактор (wysiwyg)\",\n    \"settings_markup\": \"Markdown маркиране\",\n    \"markup_placeholder\": \"Въведете markdown маркиране...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Премахни\",\n    \"empty_option\": \"Няма намерени съвпадения\",\n    \"show_line_numbers\": \"Номериране на редове\"\n  },\n  \"common\": {\n    \"delete\": \"Изтрий\",\n    \"edit\": \"Редактирай\",\n    \"toolbar_action_disabled\": \"Несъвместим елемент на маркиране\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Отказ\",\n    \"common_action_submit\": \"Изпрати\",\n    \"common_action_upload\": \"Избери\",\n    \"common_tab_attach\": \"Добави от устройство\",\n    \"common_tab_link\": \"Добави чрез връзка\",\n    \"common_link\": \"Връзка\",\n    \"common_sizes\": \"Размер, px\",\n    \"image_name\": \"Заглавие\",\n    \"image_link_href\": \"Връзка към изображение\",\n    \"image_link_href_help\": \"Адресът, към който води връзката.\",\n    \"image_alt\": \"Алтернативен текст\",\n    \"image_alt_help\": \"Алтернативният текст се показва, ако изображението не може да бъде заредено.\",\n    \"image_upload_help\": \"JPEG, GIF или PNG изображение с размер до 1 MB.\",\n    \"image_upload_failed\": \"Неуспешно добавяне на изображение\",\n    \"image_size_width\": \"Ширина\",\n    \"image_size_height\": \"Височина\",\n    \"link_url_help\": \"Адресът, към който води връзката.\",\n    \"link_text\": \"Текст на връзката\",\n    \"link_text_help\": \"Текстът, който се показва като връзка.\",\n    \"link_open_help\": \"Отвори връзката в нов раздел\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Заглавие\",\n    \"header_hint\": \"# Вашият текст\",\n    \"italic_title\": \"Курсив\",\n    \"italic_hint\": \"_Вашият текст_\",\n    \"bold_title\": \"Удебелен\",\n    \"bold_hint\": \"**Вашият текст**\",\n    \"strikethrough_title\": \"Зачертан\",\n    \"strikethrough_hint\": \"~~Вашият текст~~\",\n    \"blockquote_title\": \"Цитат\",\n    \"blockquote_hint\": \"> Вашият текст\",\n    \"code_title\": \"Код\",\n    \"code_hint\": \"```Вашият текст```\",\n    \"link_title\": \"Връзка\",\n    \"link_hint\": \"[Вашият текст](url)\",\n    \"image_title\": \"Изображение\",\n    \"image_hint\": \"![Вашият текст](url)\",\n    \"list_title\": \"Елемент от списък\",\n    \"list_hint\": \"- Вашият текст\",\n    \"numbered-list_title\": \"Номериран списък\",\n    \"numbered-list_hint\": \"1. Вашият текст\",\n    \"documentation\": \"Документация\",\n    \"documentation_link\": \"https://diplodoc.com/docs/bg/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Удебелен\",\n    \"code\": \"Код\",\n    \"code_inline\": \"Вграден код\",\n    \"codeblock\": \"Блок с код\",\n    \"colorify\": \"Цвят на текста\",\n    \"colorify__color_blue\": \"Син\",\n    \"colorify__color_default\": \"По подразбиране\",\n    \"colorify__color_gray\": \"Сив\",\n    \"colorify__color_green\": \"Зелен\",\n    \"colorify__color_orange\": \"Оранжев\",\n    \"colorify__color_red\": \"Червен\",\n    \"colorify__color_violet\": \"Виолетов\",\n    \"colorify__color_yellow\": \"Жълт\",\n    \"colorify__group_text\": \"Текст\",\n    \"cut\": \"Изрежи\",\n    \"emoji\": \"Емоджи\",\n    \"emoji__hint\": \"Емоджита могат да се добавят във WYSIWYG или ръчно с маркиране\",\n    \"heading\": \"Заглавие\",\n    \"heading1\": \"Заглавие 1\",\n    \"heading2\": \"Заглавие 2\",\n    \"heading3\": \"Заглавие 3\",\n    \"heading4\": \"Заглавие 4\",\n    \"heading5\": \"Заглавие 5\",\n    \"heading6\": \"Заглавие 6\",\n    \"hrule\": \"Разделител\",\n    \"image\": \"Изображение\",\n    \"italic\": \"Курсив\",\n    \"link\": \"Връзка\",\n    \"list\": \"Списък\",\n    \"list__action_lift\": \"Премести елемента нагоре\",\n    \"list__action_sink\": \"Премести елемента надолу\",\n    \"list_action_disabled\": \"Противоречи на логиката на списъка\",\n    \"mark\": \"Маркиран\",\n    \"mono\": \"Моноширинен\",\n    \"more_action\": \"Още действия\",\n    \"note\": \"Бележка\",\n    \"olist\": \"Номериран списък\",\n    \"quote\": \"Цитат\",\n    \"redo\": \"Повтори\",\n    \"strike\": \"Зачертан\",\n    \"table\": \"Таблица\",\n    \"text\": \"Текст\",\n    \"ulist\": \"Маркиран списък\",\n    \"underline\": \"Подчертан\",\n    \"undo\": \"Отмени\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Въведете / за да използвате команди...\",\n    \"checkbox\": \"Въведете описание на задачата...\",\n    \"deflist_term\": \"Термин\",\n    \"deflist_desc\": \"Описание на дефиницията\",\n    \"heading\": \"Заглавие\",\n    \"cut_title\": \"Заглавие\",\n    \"cut_content\": \"Съдържание, което ще се покаже при кликване\",\n    \"note_title\": \"Заглавие\",\n    \"note_content\": \"Съдържание на бележката\",\n    \"table_cell\": \"Съдържание на клетката\",\n    \"select_filter\": \"Търсене на езици...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Чувствителен към малки/главни букви\",\n    \"label_whole-word\": \"Цяла дума\",\n    \"title\": \"Търсене в кода\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Няма намерени\"\n  },\n  \"widgets\": {\n    \"image\": \"Добави изображение\",\n    \"link\": \"Добави връзка\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Бележка\",\n    \"tip\": \"Съвет\",\n    \"warning\": \"Предупреждение\",\n    \"alert\": \"Известие\",\n    \"remove\": \"Премахни\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Добави колона преди\",\n    \"column.add.after\": \"Добави колона след\",\n    \"column.remove\": \"Премахни колона\",\n    \"column.remove.multiple\": \"Премахни колони\",\n    \"row.add.before\": \"Добави ред преди\",\n    \"row.add.after\": \"Добави ред след\",\n    \"row.remove\": \"Премахни ред\",\n    \"row.remove.multiple\": \"Премахни редове\",\n    \"cells.clear\": \"Изчисти клетки\",\n    \"table.remove\": \"Премахни таблица\",\n    \"table.menu.cell.align.left\": \"Подравни съдържанието вляво\",\n    \"table.menu.cell.align.right\": \"Подравни съдържанието вдясно\",\n    \"table.menu.cell.align.center\": \"Подравни съдържанието в центъра\",\n    \"table.menu.row.add\": \"Добави ред след\",\n    \"table.menu.row.remove\": \"Премахни ред\",\n    \"table.menu.column.add\": \"Добави колона след\",\n    \"table.menu.column.remove\": \"Премахни колона\",\n    \"table.menu.convert.yfm\": \"Преобразувай в YFM таблица\",\n    \"table.menu.table.remove\": \"Премахни таблица\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/ca-ES/core.js",
    "content": "import dateFns from 'date-fns/locale/ca';\nimport timeAgo from 'javascript-time-ago/locale/ca';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd/M/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d 'de' MMMM 'a les' p\",\n    fullDate: 'd MMM y',\n    fullDateTime: \"d 'de' MMMM 'de' y 'a les' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: \"Quant a l'aplicació\",\n      aboutPlanka_title: 'Quant a PLANKA',\n      accessToken: \"Token d'accés\",\n      account: 'Compte',\n      actions: 'Accions',\n      activateUser_title: 'Activar usuari',\n      active: 'Actiu',\n      addAttachment_title: 'Afegir fitxer adjunt',\n      addCustomFieldGroup_title: 'Afegir grup de camps personalitzats',\n      addCustomField_title: 'Afegir camp personalitzat',\n      addManager_title: 'Afegir gestor',\n      addMember_title: 'Afegir membre',\n      addTaskList_title: 'Afegir llista de tasques',\n      addUser_title: 'Afegir usuari',\n      admin: 'Administrador',\n      administration: 'Administració',\n      all: 'Tot',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Tots els canvis es desaran automàticament<br />quan es restableixi la connexió.',\n      alphabetically: 'Alfabèticament',\n      alwaysDisplayCardCreator: 'Mostrar sempre el creador de la targeta',\n      apiKeyCreated_title: 'Clau API creada',\n      apiKey_title: 'Clau API',\n      archive: 'Arxivar',\n      archiveCard_title: 'Arxivar targeta',\n      archiveCards_title: 'Arxivar targetes',\n      areYouSureYouWantToActivateThisUser: 'Estàs segur que vols activar aquest usuari?',\n      areYouSureYouWantToArchiveCards: 'Estàs segur que vols arxivar les targetes?',\n      areYouSureYouWantToArchiveThisCard: 'Estàs segur que vols arxivar aquesta targeta?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Estàs segur que vols assignar aquest gestor de projecte com a propietari?',\n      areYouSureYouWantToDeactivateThisUser: 'Estàs segur que vols desactivar aquest usuari?',\n      areYouSureYouWantToDeleteThisApiKey: 'Estàs segur que vols eliminar aquesta clau API?',\n      areYouSureYouWantToDeleteThisAttachment:\n        'Estàs segur que vols eliminar aquest fitxer adjunt?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Estàs segur que vols eliminar aquesta imatge de fons?',\n      areYouSureYouWantToDeleteThisBoard: 'Estàs segur que vols eliminar aquest tauler?',\n      areYouSureYouWantToDeleteThisCard: 'Estàs segur que vols eliminar aquesta targeta?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Estàs segur que vols eliminar aquesta targeta per sempre?',\n      areYouSureYouWantToDeleteThisComment: 'Estàs segur que vols eliminar aquest comentari?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Estàs segur que vols eliminar aquest camp personalitzat?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Estàs segur que vols eliminar aquest grup de camps personalitzats?',\n      areYouSureYouWantToDeleteThisLabel: 'Estàs segur que vols eliminar aquesta etiqueta?',\n      areYouSureYouWantToDeleteThisList:\n        'Estàs segur que vols eliminar aquesta llista? Totes les targetes es mouran a la paperera.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Estàs segur que vols eliminar aquest servei de notificació?',\n      areYouSureYouWantToDeleteThisProject: 'Estàs segur que vols eliminar aquest projecte?',\n      areYouSureYouWantToDeleteThisTask: 'Estàs segur que vols eliminar aquesta tasca?',\n      areYouSureYouWantToDeleteThisTaskList:\n        'Estàs segur que vols eliminar aquesta llista de tasques?',\n      areYouSureYouWantToDeleteThisUser: 'Estàs segur que vols eliminar aquest usuari?',\n      areYouSureYouWantToDeleteThisWebhook: 'Estàs segur que vols eliminar aquest webhook?',\n      areYouSureYouWantToEmptyTrash: 'Estàs segur que vols buidar la paperera?',\n      areYouSureYouWantToLeaveBoard: 'Estàs segur que vols abandonar el tauler?',\n      areYouSureYouWantToLeaveProject: 'Estàs segur que vols abandonar el projecte?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Estàs segur que vols canviar aquest projecte a privat?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Estàs segur que vols canviar aquest projecte a compartit?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Estàs segur que vols regenerar aquesta clau API? La clau anterior ja no funcionarà.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Estàs segur que vols eliminar aquest gestor del projecte?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Estàs segur que vols eliminar aquest membre del tauler?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        \"Estàs segur que vols desvincular SSO d'aquest usuari? Això permetrà que l'usuari iniciï sessió amb una contrasenya.\",\n      assignAsOwner_title: 'Assignar com a propietari',\n      atLeastOneListMustBePresent: \"Hi ha d'haver com a mínim una llista\",\n      attachment: 'Fitxer adjunt',\n      attachments: 'Fitxers adjunts',\n      authentication: 'Autenticació',\n      background: 'Fons',\n      baseCustomFields_title: 'Camps personalitzats base',\n      baseGroup: 'Grup base',\n      board: 'Tauler',\n      boardActions_title: 'Accions del tauler',\n      boardNotFound_title: 'Tauler no trobat',\n      boardSubscribed: 'Tauler subscrit',\n      boardUser: 'Usuari del tauler',\n      byCreationTime: 'Per data de creació',\n      byDefault: 'Per defecte',\n      byDueDate: 'Per data de venciment',\n      canBeInvitedToWorkInBoards: 'Pot ser convidat a treballar a taulers.',\n      canComment: 'Pot comentar',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Pot crear projectes propis i ser convidat a treballar a altres.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Pot editar el disseny del tauler i assignar membres a les targetes.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Pot gestionar la configuració general del sistema i actuar com a propietari del projecte.',\n      canOnlyViewBoard: 'Només pot veure el tauler.',\n      cardActions_title: 'Accions de la targeta',\n      cardNotFound_title: 'Targeta no trobada',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        \"Les targetes d'aquesta llista estan disponibles per a tots els membres del tauler.\",\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        \"Les targetes d'aquesta llista estan completes i llestes per ser arxivades.\",\n      cardsOnThisListAreReadyToBeWorkedOn:\n        \"Les targetes d'aquesta llista estan llestes per treballar-hi.\",\n      clickHereOrRefreshPageToUpdate:\n        '<0>Fes clic aquí</0> o actualitza la pàgina per actualitzar.',\n      clientHostnameInEhlo: \"Nom de l'amfitrió del client en EHLO\",\n      closed: 'Tancat',\n      color: 'Color',\n      comments: 'Comentaris',\n      contentExceedsLimit: 'El contingut excedeix {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        \"El contingut d'aquest fitxer adjunt és massa gran per mostrar-se.\",\n      copy_inline: 'còpia',\n      createBoard_title: 'Crear tauler',\n      createCustomFieldGroup_title: 'Crear grup de camps personalitzats',\n      createLabel_title: 'Crear etiqueta',\n      createNewOneOrSelectExistingOne: \"Crea'n un de nou o selecciona'n<br />un d'existent.\",\n      createProject_title: 'Crear projecte',\n      createTextFile_title: 'Crear fitxer de text',\n      creator: 'Creador',\n      currentPassword: 'Contrasenya actual',\n      currentUser: 'Usuari actual',\n      customFieldGroup_title: 'Grup de camps personalitzats',\n      customFieldGroups_title: 'Grups de camps personalitzats',\n      customField_title: 'Camp personalitzat',\n      customFields_title: 'Camps personalitzats',\n      customerPanel_title: 'Panell del client',\n      dangerZone_title: 'Zona de perill',\n      date: 'Data',\n      deactivateUser_title: 'Desactivar usuari',\n      defaultCardType_title: 'Tipus de targeta per defecte',\n      defaultFrom: '\"De\" per defecte',\n      defaultView_title: 'Vista per defecte',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Elimina tots els taulers per poder eliminar aquest projecte',\n      deleteApiKey_title: 'Eliminar clau API',\n      deleteAttachment_title: 'Eliminar fitxer adjunt',\n      deleteBackgroundImage_title: 'Eliminar imatge de fons',\n      deleteBoard_title: 'Eliminar tauler',\n      deleteCardForever_title: 'Eliminar targeta per sempre',\n      deleteCard_title: 'Eliminar targeta',\n      deleteComment_title: 'Eliminar comentari',\n      deleteCustomFieldGroup_title: 'Eliminar grup de camps personalitzats',\n      deleteCustomField_title: 'Eliminar camp personalitzat',\n      deleteLabel_title: 'Eliminar etiqueta',\n      deleteList_title: 'Eliminar llista',\n      deleteNotificationService_title: 'Eliminar servei de notificació',\n      deleteProject_title: 'Eliminar projecte',\n      deleteTaskList_title: 'Eliminar llista de tasques',\n      deleteTask_title: 'Eliminar tasca',\n      deleteUser_title: 'Eliminar usuari',\n      deleteWebhook_title: 'Eliminar webhook',\n      deletedUser_title: 'Usuari eliminat',\n      description: 'Descripció',\n      display: 'Mostrar',\n      displayCardAges: \"Mostrar l'antiguitat de les targetes\",\n      dropFileToUpload: 'Arrossega fitxer per pujar-lo',\n      dueDate_title: 'Data de venciment',\n      dynamicAndUnevenlySpacedLayout: 'Disseny dinàmic i amb espaiat irregular.',\n      editAttachment_title: 'Editar fitxer adjunt',\n      editAvatar_title: 'Editar avatar',\n      editColor_title: 'Editar color',\n      editCustomFieldGroup_title: 'Editar grup de camps personalitzats',\n      editCustomField_title: 'Editar camp personalitzat',\n      editDueDate_title: 'Editar data de venciment',\n      editEmail_title: 'Editar correu electrònic',\n      editInformation_title: 'Editar informació',\n      editLabel_title: 'Editar etiqueta',\n      editPassword_title: 'Editar contrasenya',\n      editPermissions_title: 'Editar permisos',\n      editRole_title: 'Editar rol',\n      editStopwatch_title: 'Editar cronòmetre',\n      editType_title: 'Editar tipus',\n      editUsername_title: \"Editar nom d'usuari\",\n      editor: 'Editor',\n      editors: 'Editors',\n      email: 'Correu electrònic',\n      emptyTrash_title: 'Buidar paperera',\n      enterCardTitle: 'Introdueix el títol de la targeta...',\n      enterDescription: 'Introdueix una descripció...',\n      enterFilename: 'Introdueix el nom del fitxer',\n      enterListTitle: 'Introdueix el títol de la llista...',\n      enterTaskDescription: 'Introdueix la descripció de la tasca...',\n      events: 'Esdeveniments',\n      excludedEvents: 'Esdeveniments exclosos',\n      expandTaskListsByDefault: 'Expandir llistes de tasques per defecte',\n      filterByLabels_title: 'Filtrar per etiquetes',\n      filterByMembers_title: 'Filtrar per membres',\n      forPersonalProjects: 'Per a projectes personals.',\n      forTeamBasedProjects: 'Per a projectes en equip.',\n      fromComputer_title: \"Des de l'ordinador\",\n      fromTrello: 'Des de Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'La clau completa està oculta per raons de seguretat. Regenera-la per crear una de nova.',\n      general: 'General',\n      gradients: 'Degradats',\n      grid: 'Graella',\n      hideCompletedTasks: 'Ocultar tasques completes',\n      hideFromProjectListAndFavorites: 'Ocultar de la llista de projectes i favorits',\n      host: 'Amfitrió',\n      hours: 'Hores',\n      identity: 'Identitat',\n      importBoard_title: 'Importar tauler',\n      information: 'Informació',\n      invalidCurrentPassword: 'Contrasenya actual incorrecta',\n      kanban: 'Kanban',\n      labels: 'Etiquetes',\n      language: 'Idioma',\n      leaveBoard_title: 'Abandonar tauler',\n      leaveProject_title: 'Abandonar projecte',\n      limitCardTypesToDefaultOne: 'Limitar tipus de targeta al predeterminat',\n      linkToCard: 'Enllaç a la targeta',\n      list: 'Llista',\n      listActions_title: 'Accions de la llista',\n      lists: 'Llistes',\n      makeProjectPrivate_title: 'Canviar el projecte a privat',\n      makeProjectShared_title: 'Canviar el projecte a compartit',\n      managers: 'Gestors',\n      memberActions_title: 'Accions del membre',\n      members: 'Membres',\n      minutes: 'Minuts',\n      moreActions: 'Més accions',\n      moreActions_title: 'Més accions',\n      moveCard_title: 'Moure targeta',\n      moveList_title: 'Moure llista',\n      myOwn_title: 'Propis',\n      name: 'Nom',\n      newEmail: 'Nou correu electrònic',\n      newPassword: 'Nova contrasenya',\n      newUsername: \"Nou nom d'usuari\",\n      newVersionAvailable: 'Nova versió disponible',\n      newestFirst: 'Més recents primer',\n      noApiKeyCreated: \"No s'ha creat cap clau API.\",\n      noBoards: 'No hi ha taulers',\n      noCardsFound: \"No s'han trobat targetes.\",\n      noConnectionToServer: 'Sense connexió al servidor',\n      noLists: 'No hi ha llistes',\n      noProjects: 'No hi ha projectes',\n      noUnreadNotifications: 'No hi ha notificacions sense llegir.',\n      notifications: 'Notificacions',\n      oldestFirst: 'Més antics primer',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Només ha de quedar un gestor per canviar aquest projecte a privat',\n      openBoard_title: 'Obrir tauler',\n      optional_inline: 'opcional',\n      organization: 'Organització',\n      others: 'Altres',\n      passwordIsSet: 'La contrasenya està establerta',\n      phone: 'Telèfon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA utilitza <1><0>Apprise</0></1> per enviar notificacions a més de 100 serveis populars.',\n      port: 'Port',\n      preferences: 'Preferències',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Consell: prem Ctrl-V (Cmd-V a Mac) per afegir un fitxer adjunt des del porta-retalls.',\n      private: 'Privat',\n      project: 'Projecte',\n      projectNotFound_title: \"No s'ha trobat el projecte\",\n      projectOwner: 'Propietari del projecte',\n      referenceDataAndKnowledgeStorage: 'Emmagatzematge de dades de referència i coneixement.',\n      regenerateApiKey_title: 'Regenerar clau API',\n      rejectUnauthorizedTlsCertificates: 'Rebutjar certificats TLS no autoritzats',\n      removeManager_title: 'Eliminar gestor',\n      removeMember_title: 'Eliminar membre',\n      role: 'Rol',\n      saveThisKeyItWillNotBeShownAgain: 'Desa aquesta clau, no es tornarà a mostrar!',\n      searchCards: 'Cercar targetes...',\n      searchCustomFieldGroups: 'Cercar grups de camps personalitzats...',\n      searchCustomFields: 'Cercar camps personalitzats...',\n      searchLabels: 'Cercar etiquetes...',\n      searchLists: 'Cercar llistes...',\n      searchMembers: 'Cercar membres...',\n      searchProjects: 'Cercar projectes...',\n      searchUsers: 'Cercar usuaris...',\n      seconds: 'Segons',\n      selectAssignee_title: 'Seleccionar assignat',\n      selectBoard: 'Seleccionar tauler',\n      selectList: 'Seleccionar llista',\n      selectListToRestoreThisCard: 'Selecciona una llista per restaurar aquesta targeta',\n      selectOrder_title: 'Seleccionar ordre',\n      selectPermissions_title: 'Seleccionar permisos',\n      selectProject: 'Seleccionar projecte',\n      selectRole_title: 'Seleccionar rol',\n      selectType_title: 'Seleccionar tipus',\n      sequentialDisplayOfCards: 'Visualització seqüencial de targetes.',\n      settings: 'Configuració',\n      shared: 'Compartit',\n      sharedWithMe_title: 'Compartit amb mi',\n      showOnFrontOfCard: 'Mostrar a la part frontal de la targeta',\n      smtp: 'SMTP',\n      sortList_title: 'Ordenar llista',\n      sourceCardIsNoLongerAvailableForCopying:\n        \"La targeta d'origen ja no està disponible per copiar.\",\n      sourceCardIsNoLongerAvailableForMoving:\n        \"La targeta d'origen ja no està disponible per moure.\",\n      stopwatch: 'Cronòmetre',\n      story: 'Història',\n      subscribeToCardWhenCommenting: \"Subscriure's a la targeta en comentar\",\n      subscribeToMyOwnCardsByDefault: \"Subscriure's a les meves pròpies targetes per defecte\",\n      taskActions_title: 'Accions de la tasca',\n      taskAssignmentAndProjectCompletion: 'Assignació de tasques i finalització del projecte.',\n      taskListActions_title: 'Accions de la llista de tasques',\n      taskList_title: 'Llista de tasques',\n      team: 'Equip',\n      termsOfService_title: 'Termes del servei',\n      testLog_title: 'Registre de prova',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'No hi ha vista prèvia disponible per a aquest fitxer adjunt.',\n      time: 'Hora',\n      title: 'Títol',\n      trash: 'Paperera',\n      trashHasBeenSuccessfullyEmptied: \"La paperera s'ha buidat correctament.\",\n      turnOffRecentCardHighlighting: 'Desactivar ressaltat de targetes recents',\n      typeNameToConfirm: 'Escriu el nom per confirmar.',\n      typeTitleToConfirm: 'Escriu el títol per confirmar.',\n      unlinkSso_title: 'Desvinculació de SSO',\n      unsavedChanges: 'Canvis sense desar',\n      uploadFailedFileIsTooBig: 'Error en pujar: El fitxer és massa gran.',\n      uploadFailedNotEnoughStorageSpace: \"Error en pujar: No hi ha prou espai d'emmagatzematge.\",\n      uploadedImages: 'Imatges pujades',\n      url: 'URL',\n      useSecureConnection: 'Utilitzar connexió segura',\n      userActions_title: \"Accions de l'usuari\",\n      userAddedCardToList: '<0>{{user}}</0> ha afegit <2>{{card}}</2> a {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> ha afegit aquesta targeta a {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> ha afegit {{addedUser}} a <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> ha afegit {{addedUser}} a aquesta targeta',\n      userAddedYouToCard: \"<0>{{user}}</0> t'ha afegit a <2>{{card}}</2>\",\n      userCompletedTaskOnCard: '<0>{{user}}</0> ha completat {{task}} a <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> ha completat {{task}} a aquesta targeta',\n      userJoinedCard: \"<0>{{user}}</0> s'ha unit a <2>{{card}}</2>\",\n      userJoinedThisCard: \"<0>{{user}}</0> s'ha unit a aquesta targeta\",\n      userLeftCard: '<0>{{user}}</0> ha abandonat <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> ha deixat un nou comentari «{{comment}}» a <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> ha abandonat aquesta targeta',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> ha marcat {{task}} com a incompleta a <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> ha marcat {{task}} com a incompleta a aquesta targeta',\n      userMentionedYouInCommentOnCard:\n        \"<0>{{user}}</0> t'ha esmentat en un comentari «{{comment}}» a <2>{{card}}</2>\",\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> ha mogut <2>{{card}}</2> de {{fromList}} a {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> ha mogut aquesta targeta de {{fromList}} a {{toList}}',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0> ha eliminat {{removedUser}} de <4>{{card}}</4>',\n      userRemovedUserFromThisCard:\n        \"<0>{{actorUser}}</0> ha eliminat {{removedUser}} d'aquesta targeta\",\n      username: \"Nom d'usuari\",\n      users: 'Usuaris',\n      viewer: 'Observador',\n      viewers: 'Observadors',\n      visualTaskManagementWithLists: 'Gestió visual de tasques amb llistes.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Què hi ha de nou',\n      withoutBaseGroup: 'Sense grup base',\n      writeComment: 'Escriu un comentari...',\n    },\n\n    action: {\n      activateUser: 'Activar usuari',\n      activateUser_title: 'Activar usuari',\n      addAnotherCard: 'Afegir una altra targeta',\n      addAnotherList: 'Afegir una altra llista',\n      addAnotherTask: 'Afegir una altra tasca',\n      addCard: 'Afegir targeta',\n      addCard_title: 'Afegir targeta',\n      addComment: 'Afegir comentari',\n      addCustomField: 'Afegir camp personalitzat',\n      addCustomFieldGroup: 'Afegir grup de camps personalitzats',\n      addList: 'Afegir llista',\n      addMember: 'Afegir membre',\n      addMoreDetailedDescription: 'Afegir una descripció més detallada',\n      addTask: 'Afegir tasca',\n      addTaskList: 'Afegir llista de tasques',\n      addToCard: 'Afegir a targeta',\n      addUser: 'Afegir usuari',\n      addWebhook: 'Afegir webhook',\n      archive: 'Arxivar',\n      archiveCard: 'Arxivar targeta',\n      archiveCard_title: 'Arxivar targeta',\n      archiveCards: 'Arxivar targetes',\n      archiveCards_title: 'Arxivar targetes',\n      assignAsOwner: 'Assignar com a propietari',\n      cancel: 'Cancel·lar',\n      copy: 'Copiar',\n      copyCard_title: 'Copiar targeta',\n      createApiKey: 'Crear clau API',\n      createBoard: 'Crear tauler',\n      createCustomFieldGroup: 'Crear grup de camps personalitzats',\n      createFile: 'Crear fitxer',\n      createLabel: 'Crear etiqueta',\n      createNewLabel: 'Crear nova etiqueta',\n      createProject: 'Crear projecte',\n      cut: 'Tallar',\n      cutCard_title: 'Tallar targeta',\n      deactivateUser: 'Desactivar usuari',\n      deactivateUser_title: 'Desactivar usuari',\n      delete: 'Eliminar',\n      deleteApiKey: 'Eliminar clau API',\n      deleteAttachment: 'Eliminar fitxer adjunt',\n      deleteAvatar: 'Eliminar avatar',\n      deleteBackgroundImage: 'Eliminar imatge de fons',\n      deleteBoard: 'Eliminar tauler',\n      deleteBoard_title: 'Eliminar tauler',\n      deleteCard: 'Eliminar targeta',\n      deleteCardForever: 'Eliminar targeta per sempre',\n      deleteCard_title: 'Eliminar targeta',\n      deleteComment: 'Eliminar comentari',\n      deleteCustomField: 'Eliminar camp personalitzat',\n      deleteCustomFieldGroup: 'Eliminar grup de camps personalitzats',\n      deleteForever_title: 'Eliminar per sempre',\n      deleteGroup: 'Eliminar grup',\n      deleteLabel: 'Eliminar etiqueta',\n      deleteList: 'Eliminar llista',\n      deleteList_title: 'Eliminar llista',\n      deleteNotificationService: 'Eliminar servei de notificació',\n      deleteProject: 'Eliminar projecte',\n      deleteProject_title: 'Eliminar projecte',\n      deleteTask: 'Eliminar tasca',\n      deleteTaskList: 'Eliminar llista de tasques',\n      deleteTask_title: 'Eliminar tasca',\n      deleteUser: 'Eliminar usuari',\n      deleteUser_title: 'Eliminar usuari',\n      deleteWebhook: 'Eliminar webhook',\n      dismissAll: 'Descartar tot',\n      download: 'Descarregar',\n      duplicateCard_title: 'Duplicar targeta',\n      edit: 'Editar',\n      editColor_title: 'Editar color',\n      editDescription_title: 'Editar descripció',\n      editDueDate_title: 'Editar data de venciment',\n      editEmail_title: 'Editar correu electrònic',\n      editGroup: 'Editar grup',\n      editInformation_title: 'Editar informació',\n      editPassword_title: 'Editar contrasenya',\n      editPermissions: 'Editar permisos',\n      editRole_title: 'Editar rol',\n      editStopwatch_title: 'Editar cronòmetre',\n      editTitle_title: 'Editar títol',\n      editType_title: 'Editar tipus',\n      editUsername_title: \"Editar nom d'usuari\",\n      emptyTrash: 'Buidar paperera',\n      emptyTrash_title: 'Buidar paperera',\n      import: 'Importar',\n      join: 'Unir-se',\n      leave: 'Abandonar',\n      leaveBoard: 'Abandonar tauler',\n      leaveProject: 'Abandonar projecte',\n      logOut_title: 'Tancar sessió',\n      makeCover_title: 'Fer portada',\n      makeProjectPrivate: 'Fer projecte privat',\n      makeProjectPrivate_title: 'Fer projecte privat',\n      makeProjectShared: 'Fer projecte compartit',\n      makeProjectShared_title: 'Fer projecte compartit',\n      move: 'Moure',\n      moveCard_title: 'Moure targeta',\n      moveList_title: 'Moure llista',\n      regenerateApiKey: 'Regenerar clau API',\n      remove: 'Eliminar',\n      removeAssignee: 'Eliminar assignat',\n      removeColor: 'Eliminar color',\n      removeCover_title: 'Eliminar portada',\n      removeFromBoard: 'Eliminar del tauler',\n      removeFromProject: 'Eliminar del projecte',\n      removeManager: 'Eliminar gestor',\n      removeMember: 'Eliminar membre',\n      restoreToList: 'Restaurar a {{list}}',\n      returnToBoard: 'Tornar al tauler',\n      save: 'Desar',\n      sendTestEmail: 'Enviar correu de prova',\n      showActive: 'Mostrar actius',\n      showAllAttachments: 'Mostrar tots els fitxers adjunts ({{hidden}} ocults)',\n      showCardsWithThisUser: 'Mostrar targetes amb aquest usuari',\n      showDeactivated: 'Mostrar desactivats',\n      showFewerAttachments: 'Mostrar menys fitxers adjunts',\n      showLess: 'Mostrar menys',\n      showMore: 'Mostrar més',\n      sortList_title: 'Ordenar llista',\n      start: 'Iniciar',\n      stop: 'Aturar',\n      subscribe: \"Subscriure's\",\n      unlinkSso: 'Desvincular SSO',\n      unlinkSso_title: 'Desvincular SSO',\n      unsubscribe: 'Cancel·lar subscripció',\n      uploadNewAvatar: 'Pujar nou avatar',\n      uploadNewImage: 'Pujar nova imatge',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ca-ES/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'ca-ES',\n  country: 'es',\n  name: 'Català',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/ca-ES/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: \"S'ha assolit el límit d'usuaris actius\",\n      adminLoginRequiredToInitializeInstance:\n        \"Es requereix inici de sessió d'administrador per inicialitzar la instància\",\n      emailAlreadyInUse: 'Correu electrònic ja en ús',\n      emailOrUsername: \"Correu electrònic o nom d'usuari\",\n      invalidCredentials: 'Credencials no vàlides',\n      invalidEmailOrUsername: \"Correu electrònic o nom d'usuari no vàlid\",\n      invalidPassword: 'Contrasenya no vàlida',\n      logIn_title: 'Iniciar sessió',\n      noInternetConnection: 'Sense connexió a internet',\n      or: 'O',\n      pageNotFound_title: 'Pàgina no trobada',\n      password: 'Contrasenya',\n      poweredByPlanka: 'Desenvolupat amb <1>PLANKA</1>',\n      serverConnectionFailed: 'Error de connexió amb el servidor',\n      unknownError: 'Error desconegut, torna-ho a provar més tard',\n      useSingleSignOn: 'Utilitzar inici de sessió únic',\n      usernameAlreadyInUse: \"Nom d'usuari ja en ús\",\n      whoops_title: 'Ups!',\n    },\n\n    action: {\n      cancelAndClose: 'Cancel·lar i tancar',\n      continue: 'Continuar',\n      debugSso: 'Depurar SSO',\n      goBack: 'Tornar',\n      goHome: \"Anar a l'inici\",\n      logIn: 'Iniciar sessió',\n      logInWithSso: 'Iniciar sessió amb SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ca-ES/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Aquest és un text sense títol.\\nTant el títol com el text\\npoden ressaltar-se en negreta, cursiva, color,\\nratllat i subratllat.\",\n    \"text-with-head\": \"Aquest és un text amb títol.\\nTant el títol com el text\\npoden ressaltar-se en negreta, cursiva, color,\\nratllat i subratllat.\",\n    \"heading\": \"Títol\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Error en l'editor de markdown\",\n    \"settings_wysiwyg\": \"Editor visual (wysiwyg)\",\n    \"settings_markup\": \"Markdown\",\n    \"markup_placeholder\": \"Introdueix markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Eliminar\",\n    \"empty_option\": \"No s'han trobat coincidències\",\n    \"show_line_numbers\": \"Numeració de línies\"\n  },\n  \"common\": {\n    \"delete\": \"Eliminar\",\n    \"edit\": \"Editar\",\n    \"toolbar_action_disabled\": \"Element de marcat incompatible\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Cancel·lar\",\n    \"common_action_submit\": \"Enviar\",\n    \"common_action_upload\": \"Seleccionar\",\n    \"common_tab_attach\": \"Afegir des del dispositiu\",\n    \"common_tab_link\": \"Afegir per enllaç\",\n    \"common_link\": \"Enllaç\",\n    \"common_sizes\": \"Mida, px\",\n    \"image_name\": \"Títol\",\n    \"image_link_href\": \"Enllaç de la imatge\",\n    \"image_link_href_help\": \"Adreça a la qual porta l'enllaç de la imatge.\",\n    \"image_alt\": \"Text alternatiu\",\n    \"image_alt_help\": \"El text alternatiu es mostra si la imatge no es pot carregar.\",\n    \"image_upload_help\": \"Imatge JPEG, GIF o PNG no més gran que 1 MB.\",\n    \"image_upload_failed\": \"Error en afegir imatge\",\n    \"image_size_width\": \"Amplada\",\n    \"image_size_height\": \"Alçada\",\n    \"link_url_help\": \"Adreça a la qual porta l'enllaç.\",\n    \"link_text\": \"Text de l'enllaç\",\n    \"link_text_help\": \"Text mostrat com a enllaç.\",\n    \"link_open_help\": \"Obrir l'enllaç en una nova pestanya\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Capçalera\",\n    \"header_hint\": \"# El teu text\",\n    \"italic_title\": \"Cursiva\",\n    \"italic_hint\": \"_El teu text_\",\n    \"bold_title\": \"Negreta\",\n    \"bold_hint\": \"**El teu text**\",\n    \"strikethrough_title\": \"Ratllat\",\n    \"strikethrough_hint\": \"~~El teu text~~\",\n    \"blockquote_title\": \"Cita\",\n    \"blockquote_hint\": \"> El teu text\",\n    \"code_title\": \"Codi\",\n    \"code_hint\": \"```El teu text```\",\n    \"link_title\": \"Enllaç\",\n    \"link_hint\": \"[El teu text](url)\",\n    \"image_title\": \"Imatge\",\n    \"image_hint\": \"![El teu text](url)\",\n    \"list_title\": \"Element de llista\",\n    \"list_hint\": \"- El teu text\",\n    \"numbered-list_title\": \"Llista numerada\",\n    \"numbered-list_hint\": \"1. El teu text\",\n    \"documentation\": \"Documentació\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Negreta\",\n    \"code\": \"Codi\",\n    \"code_inline\": \"Codi en línia\",\n    \"codeblock\": \"Bloc de codi\",\n    \"colorify\": \"Color del text\",\n    \"colorify__color_blue\": \"Blau\",\n    \"colorify__color_default\": \"Per defecte\",\n    \"colorify__color_gray\": \"Gris\",\n    \"colorify__color_green\": \"Verd\",\n    \"colorify__color_orange\": \"Taronja\",\n    \"colorify__color_red\": \"Vermell\",\n    \"colorify__color_violet\": \"Violeta\",\n    \"colorify__color_yellow\": \"Groc\",\n    \"colorify__group_text\": \"Text\",\n    \"cut\": \"Tallar\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Els emojis es poden afegir en WYSIWYG o manualment amb marcat\",\n    \"heading\": \"Capçalera\",\n    \"heading1\": \"Capçalera 1\",\n    \"heading2\": \"Capçalera 2\",\n    \"heading3\": \"Capçalera 3\",\n    \"heading4\": \"Capçalera 4\",\n    \"heading5\": \"Capçalera 5\",\n    \"heading6\": \"Capçalera 6\",\n    \"hrule\": \"Separador\",\n    \"image\": \"Imatge\",\n    \"italic\": \"Cursiva\",\n    \"link\": \"Enllaç\",\n    \"list\": \"Llista\",\n    \"list__action_lift\": \"Elevar element\",\n    \"list__action_sink\": \"Baixar element\",\n    \"list_action_disabled\": \"Contradiu la lògica de la llista\",\n    \"mark\": \"Marcat\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Més accions\",\n    \"note\": \"Nota\",\n    \"olist\": \"Llista ordenada\",\n    \"quote\": \"Cita\",\n    \"redo\": \"Refer\",\n    \"strike\": \"Ratllat\",\n    \"table\": \"Taula\",\n    \"text\": \"Text\",\n    \"ulist\": \"Llista amb vinyetes\",\n    \"underline\": \"Subratllat\",\n    \"undo\": \"Desfer\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Escriu / per usar ordres de barra...\",\n    \"checkbox\": \"Introdueix descripció de la tasca...\",\n    \"deflist_term\": \"Terme\",\n    \"deflist_desc\": \"Descripció de la definició\",\n    \"heading\": \"Capçalera\",\n    \"cut_title\": \"Títol\",\n    \"cut_content\": \"Contingut a mostrar en fer clic\",\n    \"note_title\": \"Títol\",\n    \"note_content\": \"Contingut de la nota\",\n    \"table_cell\": \"Contingut de la cel·la\",\n    \"select_filter\": \"Cercar idiomes...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Distinguir majúscules\",\n    \"label_whole-word\": \"Paraula completa\",\n    \"title\": \"Cercar en el codi\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"No trobat\"\n  },\n  \"widgets\": {\n    \"image\": \"Afegir imatge\",\n    \"link\": \"Afegir enllaç\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Informació\",\n    \"tip\": \"Consell\",\n    \"warning\": \"Advertència\",\n    \"alert\": \"Alerta\",\n    \"remove\": \"Eliminar\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Afegir columna abans\",\n    \"column.add.after\": \"Afegir columna després\",\n    \"column.remove\": \"Eliminar columna\",\n    \"column.remove.multiple\": \"Eliminar columnes\",\n    \"row.add.before\": \"Afegir fila abans\",\n    \"row.add.after\": \"Afegir fila després\",\n    \"row.remove\": \"Eliminar fila\",\n    \"row.remove.multiple\": \"Eliminar files\",\n    \"cells.clear\": \"Netejar cel·les\",\n    \"table.remove\": \"Eliminar taula\",\n    \"table.menu.cell.align.left\": \"Alinear contingut de la cel·la a l'esquerra\",\n    \"table.menu.cell.align.right\": \"Alinear contingut de la cel·la a la dreta\",\n    \"table.menu.cell.align.center\": \"Centrar contingut de la cel·la\",\n    \"table.menu.row.add\": \"Afegir fila després\",\n    \"table.menu.row.remove\": \"Eliminar fila\",\n    \"table.menu.column.add\": \"Afegir columna després\",\n    \"table.menu.column.remove\": \"Eliminar columna\",\n    \"table.menu.convert.yfm\": \"Convertir a taula YFM\",\n    \"table.menu.table.remove\": \"Eliminar taula\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/cs-CZ/core.js",
    "content": "import dateFns from 'date-fns/locale/cs';\nimport timeAgo from 'javascript-time-ago/locale/cs';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd.M.yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'v' p\",\n    fullDate: 'd MMM, y',\n    fullDateTime: \"d MMMM, y 'v' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'O aplikaci',\n      aboutPlanka_title: 'O PLANKA',\n      accessToken: 'Přístupový token',\n      account: 'Účet',\n      actions: 'Akce',\n      activateUser_title: 'Aktivovat uživatele',\n      active: 'Aktivní',\n      addAttachment_title: 'Přidat přílohu',\n      addCustomFieldGroup_title: 'Přidat vlastní skupinu polí',\n      addCustomField_title: 'Přidat vlastní pole',\n      addManager_title: 'Přidat správce',\n      addMember_title: 'Přidat člena',\n      addTaskList_title: 'Přidat seznam úkolů',\n      addUser_title: 'Přidat uživatele',\n      admin: 'Admin',\n      administration: 'Správa',\n      all: 'Vše',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Všechny změny budou automaticky uloženy<br />po obnovení spojení.',\n      alphabetically: 'Abecedně',\n      alwaysDisplayCardCreator: 'Vždy zobrazit tvůrce karty',\n      apiKeyCreated_title: 'API klíč vytvořen',\n      apiKey_title: 'API klíč',\n      archive: 'Archivovat',\n      archiveCard_title: 'Archivovat kartu',\n      archiveCards_title: 'Archiv karet',\n      areYouSureYouWantToActivateThisUser: 'Opravdu chcete tohoto uživatele aktivovat?',\n      areYouSureYouWantToArchiveCards: 'Opravdu chcete archivovat karty?',\n      areYouSureYouWantToArchiveThisCard: 'Opravdu chcete archivovat tuto kartu?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Opravdu chcete tohoto správce přiřadit jako vlastníka?',\n      areYouSureYouWantToDeactivateThisUser: 'Opravdu chcete deaktivovat tohoto uživatele?',\n      areYouSureYouWantToDeleteThisApiKey: 'Opravdu chcete smazat tento API klíč?',\n      areYouSureYouWantToDeleteThisAttachment: 'Opravdu chcete smazat tuto přílohu?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Opravdu chcete tento obrázek na pozadí odstranit?',\n      areYouSureYouWantToDeleteThisBoard: 'Opravdu chcete smazat tuto nástěnku?',\n      areYouSureYouWantToDeleteThisCard: 'Opravdu chcete smazat tuto kartu?',\n      areYouSureYouWantToDeleteThisCardForever: 'Opravdu chcete tuto kartu navždy odstranit?',\n      areYouSureYouWantToDeleteThisComment: 'Opravdu chcete smazat tento komentář?',\n      areYouSureYouWantToDeleteThisCustomField: 'Opravdu chcete odstranit toto vlastní pole?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Opravdu chcete tuto skupinu vlastních polí odstranit?',\n      areYouSureYouWantToDeleteThisLabel: 'Opravdu chcete smazat tento štítek?',\n      areYouSureYouWantToDeleteThisList:\n        'Opravdu chcete smazat tento seznam? Všechny karty budou přesunuty do koše.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Opravdu chcete tuto oznamovací službu odstranit?',\n      areYouSureYouWantToDeleteThisProject: 'Opravdu chcete smazat tento projekt?',\n      areYouSureYouWantToDeleteThisTask: 'Opravdu chcete smazat tento úkol?',\n      areYouSureYouWantToDeleteThisTaskList: 'Opravdu chcete tento seznam úkolů odstranit?',\n      areYouSureYouWantToDeleteThisUser: 'Opravdu chcete smazat tohoto uživatele?',\n      areYouSureYouWantToDeleteThisWebhook: 'Opravdu chcete tento webhook smazat?',\n      areYouSureYouWantToEmptyTrash: 'Opravdu chcete vysypat koš?',\n      areYouSureYouWantToLeaveBoard: 'Opravdu chcete opustit tuto nástěnku?',\n      areYouSureYouWantToLeaveProject: 'Opravdu chcete opustit projekt?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Opravdu chcete tento projekt nastavit jako soukromý?',\n      areYouSureYouWantToMakeThisProjectShared: 'Opravdu chcete tento projekt sdílet?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Opravdu chcete znovu vygenerovat tento API klíč? Předchozí klíč již nebude fungovat.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Opravdu chcete tohoto správce z projektu odebrat?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Opravdu chcete tohoto člena odebrat z nástěnky?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Opravdu chcete odpojit SSO od tohoto uživatele? Tím umožníte uživateli přihlásit se pomocí hesla.',\n      assignAsOwner_title: 'Přiřadit jako vlastníka',\n      atLeastOneListMustBePresent: 'Musí být k dispozici alespoň jeden seznam',\n      attachment: 'Příloha',\n      attachments: 'Přílohy',\n      authentication: 'Ověření',\n      background: 'Pozadí',\n      baseCustomFields_title: 'Základní vlastní pole',\n      baseGroup: 'Základní skupina',\n      board: 'Nástěnka',\n      boardActions_title: 'Akce nástěnky',\n      boardNotFound_title: 'Nástěnka nenalezena',\n      boardSubscribed: 'Nástěnka odebíraná',\n      boardUser: 'Uživatel nástěnky',\n      byCreationTime: 'Podle času vytvoření',\n      byDefault: 'Ve výchozím nastavení',\n      byDueDate: 'Podle data platnosti',\n      canBeInvitedToWorkInBoards: 'Může být pozván k práci v nástěnkách.',\n      canComment: 'Může komentovat',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Může vytvářet vlastní projekty a být pozván k práci na jiných.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Může upravovat rozvržení nástěnky a přiřazovat členy ke kartám.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Může spravovat nastavení celého systému a být jako vlastník projektu.',\n      canOnlyViewBoard: 'Může zobrazit pouze nástěnku.',\n      cardActions_title: 'Akce karty',\n      cardNotFound_title: 'Karta nenalezena',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Karty na tomto seznamu jsou k dispozici všem členům nástěnky.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Karty na tomto seznamu jsou kompletní a připravené k archivaci.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Karty na tomto seznamu jsou připraveny k práci.',\n      clickHereOrRefreshPageToUpdate: '<0>Klikněte sem</0> nebo aktualizujte stránku.',\n      clientHostnameInEhlo: 'Hostname klienta v EHLO',\n      closed: 'Uzavřeno',\n      color: 'Barva',\n      comments: 'Komentáře',\n      contentExceedsLimit: 'Obsah překračuje {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay: 'Obsah této přílohy je příliš velký na zobrazení.',\n      copy_inline: 'kopie',\n      createBoard_title: 'Vytvořit nástěnku',\n      createCustomFieldGroup_title: 'Vytvořit vlastní skupinu polí',\n      createLabel_title: 'Vytvořit štítek',\n      createNewOneOrSelectExistingOne: 'Vytvořit nový nebo vybrat<br />již existující.',\n      createProject_title: 'Vytvořit projekt',\n      createTextFile_title: 'Vytvořit textový soubor',\n      creator: 'Tvůrce',\n      currentPassword: 'Aktuální heslo',\n      currentUser: 'Aktuální uživatel',\n      customFieldGroup_title: 'Skupina vlastního pole',\n      customFieldGroups_title: 'Skupina vlastních polí',\n      customField_title: 'Vlastní pole',\n      customFields_title: 'Vlastní pole',\n      customerPanel_title: 'Panel zákazníka',\n      dangerZone_title: 'Nebezpečná zóna',\n      date: 'Datum',\n      deactivateUser_title: 'Deaktivace uživatele',\n      defaultCardType_title: 'Výchozí typ karty',\n      defaultFrom: 'Výchozí \"od\"',\n      defaultView_title: 'Výchozí zobrazení',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Pro smazání tohoto projektu je třeba nejprve smazat všechny nástěnky',\n      deleteApiKey_title: 'Smazat API klíč',\n      deleteAttachment_title: 'Smazat přílohu',\n      deleteBackgroundImage_title: 'Smazat obrázek pozadí',\n      deleteBoard_title: 'Smazat nástěnku',\n      deleteCardForever_title: 'Smazat kartu navždy',\n      deleteCard_title: 'Smazat kartu',\n      deleteComment_title: 'Smazat komentář',\n      deleteCustomFieldGroup_title: 'Smazat skupinu vlastního pole',\n      deleteCustomField_title: 'Smazat vlastní pole',\n      deleteLabel_title: 'Smazat štítek',\n      deleteList_title: 'Smazat seznam',\n      deleteNotificationService_title: 'Smazat službu oznámení',\n      deleteProject_title: 'Smazat projekt',\n      deleteTaskList_title: 'Smazat seznam úkolů',\n      deleteTask_title: 'Smazat úkol',\n      deleteUser_title: 'Smazat uživatele',\n      deleteWebhook_title: 'Smazat webhook',\n      deletedUser_title: 'Smazaný uživatel',\n      description: 'Popis',\n      display: 'Zobrazit',\n      displayCardAges: 'Zobrazit stáří karet',\n      dropFileToUpload: 'Přetažením nahrát soubor',\n      dueDate_title: 'Termín',\n      dynamicAndUnevenlySpacedLayout: 'Dynamické a nerovnoměrné rozložení.',\n      editAttachment_title: 'Upravit přílohu',\n      editAvatar_title: 'Upravit avatar',\n      editColor_title: 'Upravit barvu',\n      editCustomFieldGroup_title: 'Upravit skupinu vlastního pole',\n      editCustomField_title: 'Upravit vlastní pole',\n      editDueDate_title: 'Upravit termín',\n      editEmail_title: 'Upravit e-mail',\n      editInformation_title: 'Upravit informace',\n      editLabel_title: 'Upravit štítek',\n      editPassword_title: 'Upravit heslo',\n      editPermissions_title: 'Upravit oprávnění',\n      editRole_title: 'Upravit roli',\n      editStopwatch_title: 'Upravit časovač',\n      editType_title: 'Upravit typ',\n      editUsername_title: 'Upravit uživatelské jméno',\n      editor: 'Editor',\n      editors: 'Editoři',\n      email: 'E-mail',\n      emptyTrash_title: 'Vyprázdnit koš',\n      enterCardTitle: 'Vlož titulek karty...',\n      enterDescription: 'Vlož popis...',\n      enterFilename: 'Vlož název souboru',\n      enterListTitle: 'Vlož název seznamu...',\n      enterTaskDescription: 'Vlož popis úkolu...',\n      events: 'Události',\n      excludedEvents: 'Vyloučené události',\n      expandTaskListsByDefault: 'Jako výchozí rozbalit seznamy úkolů',\n      filterByLabels_title: 'Filtrovat podle štítku',\n      filterByMembers_title: 'Filtrovat podle člena',\n      forPersonalProjects: 'Pro osobní projekty.',\n      forTeamBasedProjects: 'Pro týmové projekty.',\n      fromComputer_title: 'Z počítače',\n      fromTrello: 'Z Trella',\n      fullKeyIsHiddenForSecurityReasons:\n        'Celý klíč je z bezpečnostních důvodů skrytý. Vygenerujte ho znovu pro vytvoření nového.',\n      general: 'Obecné',\n      gradients: 'Přechody',\n      grid: 'Mřížka',\n      hideCompletedTasks: 'Skrýt dokončené úkoly',\n      hideFromProjectListAndFavorites: 'Skrýt ze seznamu projektů a oblíbených položek',\n      host: 'Host',\n      hours: 'Hodiny',\n      identity: 'Identita',\n      importBoard_title: 'Importovat nástěnku',\n      information: 'Informace',\n      invalidCurrentPassword: 'Neplatné aktuální heslo',\n      kanban: 'Kanban',\n      labels: 'Štítky',\n      language: 'Jazyk',\n      leaveBoard_title: 'Opustit nástěnku',\n      leaveProject_title: 'Opustit projekt',\n      limitCardTypesToDefaultOne: 'Omezit typy karet na jeden výchozí',\n      linkToCard: 'Odkaz na kartu',\n      list: 'Seznam',\n      listActions_title: 'Seznam akcí',\n      lists: 'Seznamy',\n      makeProjectPrivate_title: 'Nastavit projekt jako soukromý',\n      makeProjectShared_title: 'Vytvořit sdílený projekt',\n      managers: 'Správci',\n      memberActions_title: 'Akce člena',\n      members: 'Členové',\n      minutes: 'Minuty',\n      moreActions: 'Další akce',\n      moreActions_title: 'Další akce',\n      moveCard_title: 'Přesunout kartu',\n      moveList_title: 'Přesunout seznam',\n      myOwn_title: 'Moje vlastní',\n      name: 'Jméno',\n      newEmail: 'Nový e-mail',\n      newPassword: 'Nové heslo',\n      newUsername: 'Nové uživatelské jméno',\n      newVersionAvailable: 'Nová verze je k dispozici',\n      newestFirst: 'Nejnovější',\n      noApiKeyCreated: 'Nebyl vytvořen žádný API klíč.',\n      noBoards: 'Žádné nástěnky',\n      noCardsFound: 'Nebyly nalezeny žádné karty.',\n      noConnectionToServer: 'Není spojení k serveru',\n      noLists: 'Žádné seznamy',\n      noProjects: 'Žádné projekty',\n      noUnreadNotifications: 'Žádné nepřečtené oznámení.',\n      notifications: 'Oznámení',\n      oldestFirst: 'Nejstarší',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Aby byl tento projekt soukromý, měl by zůstat pouze jeden manažer.',\n      openBoard_title: 'Otevřít nástěnku',\n      optional_inline: 'volitelné',\n      organization: 'Společnost',\n      others: 'Jiné',\n      passwordIsSet: 'Heslo je nastaveno',\n      phone: 'Telefon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA používá <1><0>Apprise</0></1> k zasílání oznámení do více než 100 oblíbených služeb.',\n      port: 'Port',\n      preferences: 'Volby',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Tip: stisknutím Ctrl-V (Cmd-V na Macu) přidáte přílohu ze schránky.',\n      private: 'Soukromé',\n      project: 'Projekt',\n      projectNotFound_title: 'Projekt nenalezen',\n      projectOwner: 'Vlastník projektu',\n      referenceDataAndKnowledgeStorage: 'Uchovávání referenčních údajů a znalostí.',\n      regenerateApiKey_title: 'Znovu vygenerovat API klíč',\n      rejectUnauthorizedTlsCertificates: 'Odmítnout neautorizované TLS certifikáty',\n      removeManager_title: 'Odstranit správce',\n      removeMember_title: 'Odstranit člena',\n      role: 'Role',\n      saveThisKeyItWillNotBeShownAgain: 'Uložte si tento klíč — již nebude znovu zobrazen!',\n      searchCards: 'Hledat karty...',\n      searchCustomFieldGroups: 'Hledat skupiny vlastních polí...',\n      searchCustomFields: 'Hledat vlastní pole...',\n      searchLabels: 'Hledat štítky...',\n      searchLists: 'Hledat seznamy...',\n      searchMembers: 'Hledat členy...',\n      searchProjects: 'Hledat projekty...',\n      searchUsers: 'Hledat uživatele...',\n      seconds: 'Vteřin',\n      selectAssignee_title: 'Vybrat přiřazení',\n      selectBoard: 'Vybrat nástěnku',\n      selectList: 'Vybrat seznam',\n      selectListToRestoreThisCard: 'Vybrat seznam pro obnovení této karty',\n      selectOrder_title: 'Vybrat řazení',\n      selectPermissions_title: 'Vybrat oprávnění',\n      selectProject: 'Vybrat projekt',\n      selectRole_title: 'Vybrat roli',\n      selectType_title: 'Vybrat typ',\n      sequentialDisplayOfCards: 'Sekvenční zobrazení karet.',\n      settings: 'Nastavení',\n      shared: 'Sdílené',\n      sharedWithMe_title: 'Sdíleno se mnou',\n      showOnFrontOfCard: 'Zobrazit na přední straně karty',\n      smtp: 'SMTP',\n      sortList_title: 'Řadit podle',\n      sourceCardIsNoLongerAvailableForCopying:\n        'Zdrojová karta již není k dispozici pro kopírování.',\n      sourceCardIsNoLongerAvailableForMoving: 'Zdrojová karta již není k dispozici pro přesunutí.',\n      stopwatch: 'Časovač',\n      story: 'Příběh',\n      subscribeToCardWhenCommenting: 'Odebírat karty při komentování',\n      subscribeToMyOwnCardsByDefault: 'Ve výchozím nastavení odebírat vlastní karty',\n      taskActions_title: 'Akce úkolu',\n      taskAssignmentAndProjectCompletion: 'Zadání úkolu a dokončení projektu.',\n      taskListActions_title: 'Akce seznamu úkolů',\n      taskList_title: 'Seznam úkolů',\n      team: 'Tým',\n      termsOfService_title: 'Podmínky služby',\n      testLog_title: 'Testovací protokol',\n      thereIsNoPreviewAvailableForThisAttachment: 'Pro tuto přílohu není k dispozici žádný náhled.',\n      time: 'Čas',\n      title: 'Titulek',\n      trash: 'Koš',\n      trashHasBeenSuccessfullyEmptied: 'Koš byl úspěšně vyprázdněn.',\n      turnOffRecentCardHighlighting: 'Vypnout zvýraznění posledních karet',\n      typeNameToConfirm: 'Zadejte název pro potvrzení.',\n      typeTitleToConfirm: 'Zadejte titulek pro potvrzení.',\n      unlinkSso_title: 'Odpojení SSO',\n      unsavedChanges: 'Neuložené změny',\n      uploadFailedFileIsTooBig: 'Nahrávání se nezdařilo: Soubor je příliš velký.',\n      uploadFailedNotEnoughStorageSpace: 'Nahrávání se nezdařilo: Nedostatek úložného prostoru.',\n      uploadedImages: 'Nahrané obrázky',\n      url: 'URL',\n      useSecureConnection: 'Použít zabezpečené připojení',\n      userActions_title: 'Akce uživatele',\n      userAddedCardToList: '<0>{{user}}</0> přidal <2>{{card}}</2> do {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> přidal kartu do {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> přidal {{addedUser}} do <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> přidal {{addedUser}} k této kartě',\n      userAddedYouToCard: '<0>{{user}}</0> vás přidal do <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> dokončil {{task}} na <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> dokončil {{task}} na této kartě',\n      userJoinedCard: '<0>{{user}}</0> se připojil k <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> se připojil k této kartě',\n      userLeftCard: '<0>{{user}}</0> opustil <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> zanechal nový komentář «{{comment}}» k <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> opustil tuto kartu',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> označil {{task}} jako neúplný na <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> označil {{task}} na této kartě jako neúplný.',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> vás zmínil v komentáři «{{comment}}» na <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> přesunul <2>{{card}}</2> z {{fromList}} do {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> přesunul tuto kartu z {{fromList}} do {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> odstranil {{removedUser}} z <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> odstranil {{removedUser}} z této karty',\n      username: 'Uživatelské jméno',\n      users: 'Uživatelé',\n      viewer: 'Prohlížeč',\n      viewers: 'Diváci',\n      visualTaskManagementWithLists: 'Vizuální správa úkolů pomocí seznamů.',\n      webhooks: 'Webhooky',\n      whatsNew_title: 'Co je nového',\n      withoutBaseGroup: 'Bez základní skupiny',\n      writeComment: 'Napsat komentář...',\n    },\n\n    action: {\n      activateUser: 'Aktivovat uživatele',\n      activateUser_title: 'Aktivovat uživatele',\n      addAnotherCard: 'Přidat další kartu',\n      addAnotherList: 'Přidat další seznam',\n      addAnotherTask: 'Přidat další úkol',\n      addCard: 'Přidat kartu',\n      addCard_title: 'Přidat kartu',\n      addComment: 'Přidat komentář',\n      addCustomField: 'Přidat vlastní pole',\n      addCustomFieldGroup: 'Přidat vlastní skupinu polí',\n      addList: 'Přidat seznam',\n      addMember: 'Přidat člena',\n      addMoreDetailedDescription: 'Přidat další detailní popis',\n      addTask: 'Přidat úkol',\n      addTaskList: 'Přidat seznam úkolů',\n      addToCard: 'Přidat na kartu',\n      addUser: 'Přidat uživatele',\n      addWebhook: 'Přidat webhook',\n      archive: 'Archivovat',\n      archiveCard: 'Archivovat kartu',\n      archiveCard_title: 'Archivovat kartu',\n      archiveCards: 'Archiv karet',\n      archiveCards_title: 'Archiv karet',\n      assignAsOwner: 'Přiřadit jako vlastníka',\n      cancel: 'Zrušit',\n      copy: 'Kopírovat',\n      copyCard_title: 'Kopírovat kartu',\n      createApiKey: 'Vytvořit API klíč',\n      createBoard: 'Vytvořit nástěnku',\n      createCustomFieldGroup: 'Vytvořit vlastní skupinu polí',\n      createFile: 'Vytvořit soubor',\n      createLabel: 'Vytvořit štítek',\n      createNewLabel: 'Vytvořit nový štítek',\n      createProject: 'Vytvořit projekt',\n      cut: 'Vyjmout',\n      cutCard_title: 'Vyjmout kartu',\n      deactivateUser: 'Deaktivace uživatele',\n      deactivateUser_title: 'Deaktivace uživatele',\n      delete: 'Smazat',\n      deleteApiKey: 'Smazat API klíč',\n      deleteAttachment: 'Smazat přílohu',\n      deleteAvatar: 'Smazat avatar',\n      deleteBackgroundImage: 'Smazat obrázek pozadí',\n      deleteBoard: 'Smazat nástěnku',\n      deleteBoard_title: 'Smazat nástěnku',\n      deleteCard: 'Smazat kartu',\n      deleteCardForever: 'Smazat kartu navždy',\n      deleteCard_title: 'Smazat kartu',\n      deleteComment: 'Smazat komentář',\n      deleteCustomField: 'Smazat vlastní pole',\n      deleteCustomFieldGroup: 'Smazat skupinu vlastního pole',\n      deleteForever_title: 'Smazat navždy',\n      deleteGroup: 'Smazat skupinu',\n      deleteLabel: 'Smazat štítek',\n      deleteList: 'Smazat seznam',\n      deleteList_title: 'Smazat seznam',\n      deleteNotificationService: 'Smazat službu oznámení',\n      deleteProject: 'Smazat projekt',\n      deleteProject_title: 'Smazat projekt',\n      deleteTask: 'Smazat úkol',\n      deleteTaskList: 'Smazat seznam úkolů',\n      deleteTask_title: 'Smazat úkol',\n      deleteUser: 'Smazat uživatele',\n      deleteUser_title: 'Smazat uživatele',\n      deleteWebhook: 'Smazat webhook',\n      dismissAll: 'Vše přečteno',\n      download: 'Stáhnout',\n      duplicateCard_title: 'Duplikovat kartu',\n      edit: 'Upravit',\n      editColor_title: 'Upravit barvu',\n      editDescription_title: 'Upravit popis',\n      editDueDate_title: 'Upravit termín',\n      editEmail_title: 'Upravit e-mail',\n      editGroup: 'Upravit skupinu',\n      editInformation_title: 'Upravit informace',\n      editPassword_title: 'Upravit heslo',\n      editPermissions: 'Upravit oprávnění',\n      editRole_title: 'Upravit roli',\n      editStopwatch_title: 'Upravit časovač',\n      editTitle_title: 'Upravit titulek',\n      editType_title: 'Upravit typ',\n      editUsername_title: 'Upravit uživatelské jméno',\n      emptyTrash: 'Vyprázdnit koš',\n      emptyTrash_title: 'Vyprázdnit koš',\n      import: 'Import',\n      join: 'Připojit',\n      leave: 'Opustit',\n      leaveBoard: 'Opustit nástěnku',\n      leaveProject: 'Opustit projekt',\n      logOut_title: 'Odhlásit se',\n      makeCover_title: 'Vytvořit obal',\n      makeProjectPrivate: 'Nastavit projekt jako soukromý',\n      makeProjectPrivate_title: 'Nastavit projekt jako soukromý',\n      makeProjectShared: 'Vytvořit sdílený projekt',\n      makeProjectShared_title: 'Vytvořit sdílený projekt',\n      move: 'Přesunout',\n      moveCard_title: 'Přesunout kartu',\n      moveList_title: 'Přesunout seznam',\n      regenerateApiKey: 'Znovu vygenerovat API klíč',\n      remove: 'Odstranit',\n      removeAssignee: 'Odstranit přiřazení',\n      removeColor: 'Smazat barvu',\n      removeCover_title: 'Odstranit obal',\n      removeFromBoard: 'Odstranit z nástěnky',\n      removeFromProject: 'Odstranit z projektu',\n      removeManager: 'Odstranit správce',\n      removeMember: 'Odstranit člena',\n      restoreToList: 'Obnovit do {{list}}',\n      returnToBoard: 'Návrat na nástěnku',\n      save: 'Uložit',\n      sendTestEmail: 'Odeslat testovací e-mail',\n      showActive: 'Zobrazit aktivní',\n      showAllAttachments: 'Zobrazit všechny přílohy ({{hidden}} skryté)',\n      showCardsWithThisUser: 'Zobrazit karty tohoto uživatele',\n      showDeactivated: 'Zobrazit deaktivované',\n      showFewerAttachments: 'Zobrazit méně příloh',\n      showLess: 'Zobrazit méně',\n      showMore: 'Zobrazit více',\n      sortList_title: 'Řadit podle',\n      start: 'Start',\n      stop: 'Stop',\n      subscribe: 'Odebírat',\n      unlinkSso: 'Odpojit SSO',\n      unlinkSso_title: 'Odpojit SSO',\n      unsubscribe: 'Neodebírat',\n      uploadNewAvatar: 'Nahrát nový avatar',\n      uploadNewImage: 'Nahrát nový obrázek',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/cs-CZ/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'cs-CZ',\n  country: 'cz',\n  name: 'Čeština',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/cs-CZ/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Dosažený limit aktivních uživatelů',\n      adminLoginRequiredToInitializeInstance:\n        'K inicializaci instance je nutné přihlášení správce.',\n      emailAlreadyInUse: 'E-mail se již používá',\n      emailOrUsername: 'E-mail nebo uživatelské jméno',\n      invalidCredentials: 'Neplatné přihlašovací údaje',\n      invalidEmailOrUsername: 'Nesprávný e-mail nebo uživatelské jméno',\n      invalidPassword: 'Nesprávné heslo',\n      logIn_title: 'Přihlásit',\n      noInternetConnection: 'Bez připojení k internetu',\n      or: 'Nebo',\n      pageNotFound_title: 'Stránka nenalezena',\n      password: 'Heslo',\n      poweredByPlanka: 'Poháněno technologií <1>PLANKA</1>',\n      serverConnectionFailed: 'Připojení k serveru selhalo',\n      unknownError: 'Neznámá chyba, zkuste to později',\n      useSingleSignOn: 'Použít jednorázové přihlášení',\n      usernameAlreadyInUse: 'Uživatelské jméno se již používá',\n      whoops_title: 'Jejda!',\n    },\n\n    action: {\n      cancelAndClose: 'Zrušit a zavřít',\n      continue: 'Pokračovat',\n      debugSso: 'Ladit SSO',\n      goBack: 'Zpět',\n      goHome: 'Domů',\n      logIn: 'Přihlásit se',\n      logInWithSso: 'Přihlásit se pomocí SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/cs-CZ/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Toto je text bez nadpisu.\\nNadpis i text\\nlze zvýraznit tučně, kurzívou, barvou,\\npřeškrtnutím a podtržením.\",\n    \"text-with-head\": \"Toto je text s nadpisem.\\nNadpis i text\\nlze zvýraznit tučně, kurzívou, barvou,\\npřeškrtnutím a podtržením.\",\n    \"heading\": \"Nadpis\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Chyba v editoru markdownu\",\n    \"settings_wysiwyg\": \"Vizuální editor (wysiwyg)\",\n    \"settings_markup\": \"Markdown zápis\",\n    \"markup_placeholder\": \"Zadejte markdown zápis...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Odstranit\",\n    \"empty_option\": \"Nebyly nalezeny žádné shody\",\n    \"show_line_numbers\": \"Číslování řádků\"\n  },\n  \"common\": {\n    \"delete\": \"Smazat\",\n    \"edit\": \"Upravit\",\n    \"toolbar_action_disabled\": \"Nekompatibilní prvek značky\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Zrušit\",\n    \"common_action_submit\": \"Odeslat\",\n    \"common_action_upload\": \"Vybrat\",\n    \"common_tab_attach\": \"Přidat ze zařízení\",\n    \"common_tab_link\": \"Přidat pomocí odkazu\",\n    \"common_link\": \"Odkaz\",\n    \"common_sizes\": \"Velikost, px\",\n    \"image_name\": \"Název\",\n    \"image_link_href\": \"Odkaz na obrázek\",\n    \"image_link_href_help\": \"Adresa, na kterou odkaz vede.\",\n    \"image_alt\": \"Alternativní text\",\n    \"image_alt_help\": \"Alternativní text se zobrazí, pokud nelze obrázek načíst.\",\n    \"image_upload_help\": \"Obrázek JPEG, GIF nebo PNG o velikosti nejvýše 1 MB.\",\n    \"image_upload_failed\": \"Nepodařilo se přidat obrázek\",\n    \"image_size_width\": \"Šířka\",\n    \"image_size_height\": \"Výška\",\n    \"link_url_help\": \"Adresa, na kterou odkaz vede.\",\n    \"link_text\": \"Text odkazu\",\n    \"link_text_help\": \"Text zobrazený jako odkaz.\",\n    \"link_open_help\": \"Otevřít odkaz na nové kartě\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Nadpis\",\n    \"header_hint\": \"# Váš text\",\n    \"italic_title\": \"Kurzíva\",\n    \"italic_hint\": \"_Váš text_\",\n    \"bold_title\": \"Tučné\",\n    \"bold_hint\": \"**Váš text**\",\n    \"strikethrough_title\": \"Přeškrtnutí\",\n    \"strikethrough_hint\": \"~~Váš text~~\",\n    \"blockquote_title\": \"Citace\",\n    \"blockquote_hint\": \"> Váš text\",\n    \"code_title\": \"Kód\",\n    \"code_hint\": \"```Váš text```\",\n    \"link_title\": \"Odkaz\",\n    \"link_hint\": \"[Váš text](url)\",\n    \"image_title\": \"Obrázek\",\n    \"image_hint\": \"![Váš text](url)\",\n    \"list_title\": \"Položka seznamu\",\n    \"list_hint\": \"- Váš text\",\n    \"numbered-list_title\": \"Číslovaný seznam\",\n    \"numbered-list_hint\": \"1. Váš text\",\n    \"documentation\": \"Dokumentace\",\n    \"documentation_link\": \"https://diplodoc.com/docs/cs/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Tučné\",\n    \"code\": \"Kód\",\n    \"code_inline\": \"Řádkový kód\",\n    \"codeblock\": \"Blok kódu\",\n    \"colorify\": \"Barva textu\",\n    \"colorify__color_blue\": \"Modrá\",\n    \"colorify__color_default\": \"Výchozí\",\n    \"colorify__color_gray\": \"Šedá\",\n    \"colorify__color_green\": \"Zelená\",\n    \"colorify__color_orange\": \"Oranžová\",\n    \"colorify__color_red\": \"Červená\",\n    \"colorify__color_violet\": \"Fialová\",\n    \"colorify__color_yellow\": \"Žlutá\",\n    \"colorify__group_text\": \"Text\",\n    \"cut\": \"Vyjmout\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emotikony lze přidat ve WYSIWYG nebo ručně pomocí značky\",\n    \"heading\": \"Nadpis\",\n    \"heading1\": \"Nadpis 1\",\n    \"heading2\": \"Nadpis 2\",\n    \"heading3\": \"Nadpis 3\",\n    \"heading4\": \"Nadpis 4\",\n    \"heading5\": \"Nadpis 5\",\n    \"heading6\": \"Nadpis 6\",\n    \"hrule\": \"Oddělovač\",\n    \"image\": \"Obrázek\",\n    \"italic\": \"Kurzíva\",\n    \"link\": \"Odkaz\",\n    \"list\": \"Seznam\",\n    \"list__action_lift\": \"Zvednout položku\",\n    \"list__action_sink\": \"Snížit položku\",\n    \"list_action_disabled\": \"Nesouhlasí s logikou seznamu\",\n    \"mark\": \"Označené\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Další akce\",\n    \"note\": \"Poznámka\",\n    \"olist\": \"Číslovaný seznam\",\n    \"quote\": \"Citace\",\n    \"redo\": \"Znovu\",\n    \"strike\": \"Přeškrtnutí\",\n    \"table\": \"Tabulka\",\n    \"text\": \"Text\",\n    \"ulist\": \"Odrážkový seznam\",\n    \"underline\": \"Podtržení\",\n    \"undo\": \"Zpět\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Napište / pro použití příkazů...\",\n    \"checkbox\": \"Zadejte popis úkolu...\",\n    \"deflist_term\": \"Termín\",\n    \"deflist_desc\": \"Popis definice\",\n    \"heading\": \"Nadpis\",\n    \"cut_title\": \"Název\",\n    \"cut_content\": \"Obsah, který se zobrazí po kliknutí\",\n    \"note_title\": \"Název\",\n    \"note_content\": \"Obsah poznámky\",\n    \"table_cell\": \"Obsah buňky\",\n    \"select_filter\": \"Hledat jazyky...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Rozlišovat malá/velká písmena\",\n    \"label_whole-word\": \"Celé slovo\",\n    \"title\": \"Hledat v kódu\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Nenalezeno\"\n  },\n  \"widgets\": {\n    \"image\": \"Přidat obrázek\",\n    \"link\": \"Přidat odkaz\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Poznámka\",\n    \"tip\": \"Tip\",\n    \"warning\": \"Varování\",\n    \"alert\": \"Upozornění\",\n    \"remove\": \"Odstranit\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Přidat sloupec před\",\n    \"column.add.after\": \"Přidat sloupec za\",\n    \"column.remove\": \"Odstranit sloupec\",\n    \"column.remove.multiple\": \"Odstranit sloupce\",\n    \"row.add.before\": \"Přidat řádek před\",\n    \"row.add.after\": \"Přidat řádek za\",\n    \"row.remove\": \"Odstranit řádek\",\n    \"row.remove.multiple\": \"Odstranit řádky\",\n    \"cells.clear\": \"Vymazat buňky\",\n    \"table.remove\": \"Odstranit tabulku\",\n    \"table.menu.cell.align.left\": \"Zarovnat obsah buňky vlevo\",\n    \"table.menu.cell.align.right\": \"Zarovnat obsah buňky vpravo\",\n    \"table.menu.cell.align.center\": \"Zarovnat obsah buňky na střed\",\n    \"table.menu.row.add\": \"Přidat řádek za\",\n    \"table.menu.row.remove\": \"Odstranit řádek\",\n    \"table.menu.column.add\": \"Přidat sloupec za\",\n    \"table.menu.column.remove\": \"Odstranit sloupec\",\n    \"table.menu.convert.yfm\": \"Převést na YFM tabulku\",\n    \"table.menu.table.remove\": \"Odstranit tabulku\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/da-DK/core.js",
    "content": "import dateFns from 'date-fns/locale/da';\nimport timeAgo from 'javascript-time-ago/locale/da';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'dd.MM.yyyy',\n    time: 'HH.mm',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd. MMM',\n    longDateTime: \"d. MMMM 'kl.' HH:mm\",\n    fullDate: 'd. MMM y',\n    fullDateTime: \"d. MMMM y 'kl.' HH:mm\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Om appen',\n      aboutPlanka_title: 'Om PLANKA',\n      accessToken: 'Adgangstoken',\n      account: 'Konto',\n      actions: 'Handlinger',\n      activateUser_title: 'Aktiv bruger',\n      active: 'Aktiv',\n      addAttachment_title: 'Vedhæft fil',\n      addCustomFieldGroup_title: 'Tilføj brugerdefineret feltgruppe',\n      addCustomField_title: 'Tilføj brugerdefineret felt',\n      addManager_title: 'Tilføj projektleder',\n      addMember_title: 'Tilføj medlem',\n      addTaskList_title: 'Tilføj opgaveliste',\n      addUser_title: 'Tilføj bruger',\n      admin: 'Administrator',\n      administration: 'Administration',\n      all: 'Alle',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Alle ændringer vil automatisk blive gemt<br />ved genoprettelse af forbindelsen.',\n      alphabetically: 'Alfabetisk',\n      alwaysDisplayCardCreator: 'Vis altid kortets skaber',\n      apiKeyCreated_title: 'API-nøgle oprettet',\n      apiKey_title: 'API-nøgle',\n      archive: 'Arkiv',\n      archiveCard_title: 'Arkiver kort',\n      archiveCards_title: 'Arkiver kort',\n      areYouSureYouWantToActivateThisUser: 'Er du sikker på, at du vil aktivere denne bruger?',\n      areYouSureYouWantToArchiveCards: 'Er du sikker på, at du vil arkivere kort?',\n      areYouSureYouWantToArchiveThisCard: 'Er du sikker på, at du vil arkivere dette kort?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Er du sikker på, at du vil sætte denne projektleder som ejer?',\n      areYouSureYouWantToDeactivateThisUser: 'Er du sikker på, at du vil deaktivere denne bruger?',\n      areYouSureYouWantToDeleteThisApiKey: 'Er du sikker på, at du vil slette denne API-nøgle?',\n      areYouSureYouWantToDeleteThisAttachment:\n        'Er du sikker på at du vil slette denne vedhæftede fil?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Er du sikker på, at du vil slette dette baggrundsbillede?',\n      areYouSureYouWantToDeleteThisBoard: 'Er du sikker på at du vil slette denne tavle?',\n      areYouSureYouWantToDeleteThisCard: 'Er du sikker på at du vil slette dette kort?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Er du sikker på at du vil slette dette kort permanent?',\n      areYouSureYouWantToDeleteThisComment: 'Er du sikker på at du vil slette denne kommentar?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Er du sikker på at du vil slette dette brugerdefinerede felt?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Er du sikker på at du vil slette denne brugerdefinerede feltgruppe?',\n      areYouSureYouWantToDeleteThisLabel: 'Er du sikker på at du vil slette denne label?',\n      areYouSureYouWantToDeleteThisList:\n        'Er du sikker på at du vil slette denne liste? Alle kort vil blive flyttet til papirkurven.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Er du sikker på at du vil fjerne denne notifikationstjeneste?',\n      areYouSureYouWantToDeleteThisProject: 'Er du sikker på at du vil slette dette projekt?',\n      areYouSureYouWantToDeleteThisTask: 'Er du sikker på at du vil slette denne opgave?',\n      areYouSureYouWantToDeleteThisTaskList: 'Er du sikker på at du vil slette denne opgaveliste?',\n      areYouSureYouWantToDeleteThisUser: 'Er du sikker på at du vil slette denne bruger?',\n      areYouSureYouWantToDeleteThisWebhook: 'Er du sikker på at du vil slette denne webhook?',\n      areYouSureYouWantToEmptyTrash: 'Er du sikker på at du vil tømme papirkurven?',\n      areYouSureYouWantToLeaveBoard: 'Er du sikker på at du vil forlade denne tavle?',\n      areYouSureYouWantToLeaveProject: 'Er du sikker på at du vil forlade dette projekt?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Er du sikker på at du vil gøre dette projekt privat?',\n      areYouSureYouWantToMakeThisProjectShared: 'Er du sikker på at du vil dele dette projekt?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Er du sikker på, at du vil regenerere denne API-nøgle? Den forrige nøgle vil ikke længere virke.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Er du sikker på at du vil fjerne denne projektleder fra projektet?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Er du sikker på at du vil fjerne dette medlem fra tavlen?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Er du sikker på, at du vil fjerne SSO-forbindelsen fra denne bruger? Dette vil tillade brugeren at logge ind med en adgangskode.',\n      assignAsOwner_title: 'Sæt som ejer',\n      atLeastOneListMustBePresent: 'Mindst én liste skal være til stede',\n      attachment: 'Vedhæft fil',\n      attachments: 'Vedhæftede filer',\n      authentication: 'Autentificering',\n      background: 'Baggrundsbillede',\n      baseCustomFields_title: 'Standard brugerdefinerede felter',\n      baseGroup: 'Standardgruppe',\n      board: 'Tavle',\n      boardActions_title: 'Tavlehandlinger',\n      boardNotFound_title: 'Tavle ikke fundet',\n      boardSubscribed: 'Følger tavle',\n      boardUser: 'Tavlebruger',\n      byCreationTime: 'Efter oprettelsestidspunkt',\n      byDefault: 'Som standard',\n      byDueDate: 'Efter frist',\n      canBeInvitedToWorkInBoards: 'Kan inviteres til at arbejde i tavler.',\n      canComment: 'Kan kommentere',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Kan oprette egne projekter og blive inviteret til at arbejde i andres.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Kan redigere tavlens layout og tildele medlemmer til kort.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Kan administrere systemindstillinger og fungere som projektejer.',\n      canOnlyViewBoard: 'Kan kun se denne tavle.',\n      cardActions_title: 'Korthandlinger',\n      cardNotFound_title: 'Kort ikke fundet',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Kort på denne liste er tilgængelige for alle tavlemedlemmer.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Kort på denne liste er afsluttede og klar til at blive arkiveret.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Kort på denne liste er klar til at blive arbejdet på.',\n      clickHereOrRefreshPageToUpdate: '<0>Klik her</0> eller opdater siden for at opdatere.',\n      clientHostnameInEhlo: 'Klient hostname i EHLO',\n      closed: 'Lukket',\n      color: 'Farve',\n      comments: 'Kommentarer',\n      contentExceedsLimit: 'Indhold overskrider {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Indholdet af denne vedhæftning er for stort til at blive vist.',\n      copy_inline: 'kopi',\n      createBoard_title: 'Opret tavle',\n      createCustomFieldGroup_title: 'Opret brugerdefineret feltgruppe',\n      createLabel_title: 'Opret label',\n      createNewOneOrSelectExistingOne: 'Lav en ny eller vælg<br />en eksisterende.',\n      createProject_title: 'Opret projekt',\n      createTextFile_title: 'Opret tekstfil',\n      creator: 'Skaber',\n      currentPassword: 'Nuværende adgangskode',\n      currentUser: 'Nuværende bruger',\n      customFieldGroup_title: 'Brugerdefineret feltgruppe',\n      customFieldGroups_title: 'Brugerdefinerede feltgrupper',\n      customField_title: 'Brugerdefineret felt',\n      customFields_title: 'Brugerdefinerede felter',\n      customerPanel_title: 'Kundepanel',\n      dangerZone_title: 'Farezone',\n      date: 'Dato',\n      deactivateUser_title: 'Deaktiver bruger',\n      defaultCardType_title: 'Standard korttype',\n      defaultFrom: 'Standard \"fra\"',\n      defaultView_title: 'Standard visning',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Slet alle tavler for at kunne slette dette projekt.',\n      deleteApiKey_title: 'Slet API-nøgle',\n      deleteAttachment_title: 'Slet vedhæftning',\n      deleteBackgroundImage_title: 'Slet baggrundsbillede',\n      deleteBoard_title: 'Slet tavle',\n      deleteCardForever_title: 'Slet kort permanent',\n      deleteCard_title: 'Slet kort',\n      deleteComment_title: 'Slet kommentar',\n      deleteCustomFieldGroup_title: 'Slet brugerdefineret feltgruppe',\n      deleteCustomField_title: 'Slet brugerdefineret felt',\n      deleteLabel_title: 'Slet label',\n      deleteList_title: 'Slet liste',\n      deleteNotificationService_title: 'Fjern notifikationstjeneste',\n      deleteProject_title: 'Slet projekt',\n      deleteTaskList_title: 'Slet opgaveliste',\n      deleteTask_title: 'Slet opgave',\n      deleteUser_title: 'Slet bruger',\n      deleteWebhook_title: 'Slet webhook',\n      deletedUser_title: 'Slettet bruger',\n      description: 'Beskrivelse',\n      display: 'Vis',\n      displayCardAges: 'Vis kortalder',\n      dropFileToUpload: 'Slip fil for at uploade',\n      dueDate_title: 'Frist',\n      dynamicAndUnevenlySpacedLayout: 'Dynamisk og ujævnt fordelt layout.',\n      editAttachment_title: 'Rediger vedhæftning',\n      editAvatar_title: 'Rediger profilbillede',\n      editColor_title: 'Rediger farve',\n      editCustomFieldGroup_title: 'Rediger brugerdefineret feltgruppe',\n      editCustomField_title: 'Rediger brugerdefineret felt',\n      editDueDate_title: 'Rediger frist',\n      editEmail_title: 'Rediger E-mail',\n      editInformation_title: 'Rediger information',\n      editLabel_title: 'Rediger label',\n      editPassword_title: 'Skift adgangskode',\n      editPermissions_title: 'Rediger tilladelser',\n      editRole_title: 'Rediger rolle',\n      editStopwatch_title: 'Rediger stopur',\n      editType_title: 'Rediger type',\n      editUsername_title: 'Rediger brugernavn',\n      editor: 'Redaktør',\n      editors: 'Redaktører',\n      email: 'E-mail',\n      emptyTrash_title: 'Tøm papirkurv',\n      enterCardTitle: 'Angiv kortets overskrift...',\n      enterDescription: 'Angiv beskrivelsen...',\n      enterFilename: 'Angiv filnavn',\n      enterListTitle: 'Angiv listens overskrift...',\n      enterTaskDescription: 'Angiv opgavens beskrivelse...',\n      events: 'Begivenheder',\n      excludedEvents: 'Udelukkede begivenheder',\n      expandTaskListsByDefault: 'Udvid opgavelister som standard',\n      filterByLabels_title: 'Filtrer labels',\n      filterByMembers_title: 'Filtrer medlemmer',\n      forPersonalProjects: 'For personlige projekter.',\n      forTeamBasedProjects: 'For team-baserede projekter.',\n      fromComputer_title: 'Fra computer',\n      fromTrello: 'Fra Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Den fulde nøgle er skjult af sikkerhedsmæssige årsager. Regenerer den for at oprette en ny.',\n      general: 'Generelt',\n      gradients: 'Gradienter',\n      grid: 'Gitter',\n      hideCompletedTasks: 'Skjul fuldførte opgaver',\n      hideFromProjectListAndFavorites: 'Skjul fra projektliste og favoritter',\n      host: 'Vært',\n      hours: 'Timer',\n      identity: 'Identitet',\n      importBoard_title: 'Importer tavle',\n      information: 'Information',\n      invalidCurrentPassword: 'Nuværende adgangskode er ugyldig',\n      kanban: 'Kanban',\n      labels: 'Labels',\n      language: 'Sprog',\n      leaveBoard_title: 'Forlad tavle',\n      leaveProject_title: 'Forlad projekt',\n      limitCardTypesToDefaultOne: 'Begræns korttyper til standardtypen',\n      linkToCard: 'Link til kort',\n      list: 'Liste',\n      listActions_title: 'Listehandlinger',\n      lists: 'Lister',\n      makeProjectPrivate_title: 'Gør projekt privat',\n      makeProjectShared_title: 'Del projekt',\n      managers: 'Projektledere',\n      memberActions_title: 'Medlemshandlinger',\n      members: 'Medlemmer',\n      minutes: 'Minutter',\n      moreActions: 'Flere handlinger',\n      moreActions_title: 'Flere handlinger',\n      moveCard_title: 'Flyt kort',\n      moveList_title: 'Flyt liste',\n      myOwn_title: 'Egne',\n      name: 'Navn',\n      newEmail: 'Ny e-mail',\n      newPassword: 'Ny adgangskode',\n      newUsername: 'Nyt brugernavn',\n      newVersionAvailable: 'Ny version tilgængelig',\n      newestFirst: 'Nyeste først',\n      noApiKeyCreated: 'Ingen API-nøgle oprettet.',\n      noBoards: 'Ingen tavler',\n      noCardsFound: 'Ingen kort fundet.',\n      noConnectionToServer: 'Ingen forbindelse til serveren',\n      noLists: 'Ingen lister',\n      noProjects: 'Ingen projekter',\n      noUnreadNotifications: 'Ingen ulæste notifikationer.',\n      notifications: 'Notifikationer',\n      oldestFirst: 'Ældste først',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Kun én projektleder skal forblive for at gøre dette projekt privat',\n      openBoard_title: 'Åbn tavle',\n      optional_inline: 'valgfri',\n      organization: 'Organisation',\n      others: 'Andre',\n      passwordIsSet: 'Adgangskode er indstillet',\n      phone: 'Telefon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA bruger <1><0>Apprise</0></1> til at sende notifikationer til over 100 populære tjenester.',\n      port: 'Port',\n      preferences: 'Præferencer',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Tip: Tryk Ctrl-V (Cmd-V på Mac) for at vedhæfte direkte fra udklipsholder.',\n      private: 'Privat',\n      project: 'Kanban',\n      projectNotFound_title: 'Projekt ikke fundet',\n      projectOwner: 'Projektejer',\n      referenceDataAndKnowledgeStorage: 'Reference data og vidensopbevaring.',\n      regenerateApiKey_title: 'Regenerer API-nøgle',\n      rejectUnauthorizedTlsCertificates: 'Afvis uautoriserede TLS-certifikater',\n      removeManager_title: 'Fjern projektleder',\n      removeMember_title: 'Fjern medlem',\n      role: 'Rolle',\n      saveThisKeyItWillNotBeShownAgain: 'Gem denne nøgle — den vises ikke igen!',\n      searchCards: 'Søg efter kort...',\n      searchCustomFieldGroups: 'Søg efter brugerdefinerede feltgrupper...',\n      searchCustomFields: 'Søg efter brugerdefinerede felter...',\n      searchLabels: 'Søg efter labels...',\n      searchLists: 'Søg efter lister...',\n      searchMembers: 'Søg efter medlemmer...',\n      searchProjects: 'Søg efter projekter...',\n      searchUsers: 'Søg efter brugere...',\n      seconds: 'Sekunder',\n      selectAssignee_title: 'Vælg ansvarlig',\n      selectBoard: 'Vælg tavle',\n      selectList: 'Vælg liste',\n      selectListToRestoreThisCard: 'Vælg liste for at gendanne dette kort',\n      selectOrder_title: 'Vælg rækkefølge',\n      selectPermissions_title: 'Vælg tilladelser',\n      selectProject: 'Vælg projekt',\n      selectRole_title: 'Vælg rolle',\n      selectType_title: 'Vælg type',\n      sequentialDisplayOfCards: 'Sekventiel visning af kort.',\n      settings: 'Indstillinger',\n      shared: 'Delt',\n      sharedWithMe_title: 'Delt med mig',\n      showOnFrontOfCard: 'Vis på forsiden af kortet',\n      smtp: 'SMTP',\n      sortList_title: 'Sortér liste',\n      sourceCardIsNoLongerAvailableForCopying:\n        'Kildekortet er ikke længere tilgængeligt til kopiering.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'Kildekortet er ikke længere tilgængeligt til flytning.',\n      stopwatch: 'Stopur',\n      story: 'Story',\n      subscribeToCardWhenCommenting: 'Abonnér på kort ved kommentering',\n      subscribeToMyOwnCardsByDefault: 'Abonnér på egne kort som standard',\n      taskActions_title: 'Opgave handlinger',\n      taskAssignmentAndProjectCompletion: 'Opgave tildeling og projekt færdiggørelse.',\n      taskListActions_title: 'Opgaveliste handlinger',\n      taskList_title: 'Opgaveliste',\n      team: 'Team',\n      termsOfService_title: 'Servicevilkår',\n      testLog_title: 'Test log',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'Der er ingen forhåndsvisning tilgængelig for denne vedhæftning.',\n      time: 'Tid',\n      title: 'Overskrift',\n      trash: 'Papirkurv',\n      trashHasBeenSuccessfullyEmptied: 'Papirkurven er blevet tømt.',\n      turnOffRecentCardHighlighting: 'Slå fremhævelse af nylige kort fra',\n      typeNameToConfirm: 'Skriv navnet for at bekræfte.',\n      typeTitleToConfirm: 'Skriv overskriften for at bekræfte.',\n      unlinkSso_title: 'Fjernelse af SSO-forbindelse',\n      unsavedChanges: 'Ikke-gemte ændringer',\n      uploadFailedFileIsTooBig: 'Upload mislykkedes: Filen er for stor.',\n      uploadFailedNotEnoughStorageSpace: 'Upload mislykkedes: Ikke nok lagerplads.',\n      uploadedImages: 'Uploadede billeder',\n      url: 'URL',\n      useSecureConnection: 'Brug sikker forbindelse',\n      userActions_title: 'Brugerhandlinger',\n      userAddedCardToList: '<0>{{user}}</0> tilføjede <2>{{card}}</2> til {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> tilføjede kortet til {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> tilføjede {{addedUser}} til <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> tilføjede {{addedUser}} til dette kort',\n      userAddedYouToCard: '<0>{{user}}</0> tilføjede dig til <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> færdiggjorte {{task}} på <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> færdiggjorte {{task}} på dette kort',\n      userJoinedCard: '<0>{{user}}</0> tilsluttede sig <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> tilsluttede sig dette kort',\n      userLeftCard: '<0>{{user}}</0> forlod <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> skrev en ny kommentar «{{comment}}» på <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> forlod dette kort',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> satte {{task}} som ufærdig på <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> satte {{task}} som ufærdig på dette kort',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> nævnte dig i «{{comment}}» på <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> flyttede <2>{{card}}</2> fra {{fromList}} til {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> flyttede kortet fra {{fromList}} til {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> fjernede {{removedUser}} fra <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> fjernede {{removedUser}} fra dette kort',\n      username: 'Brugernavn',\n      users: 'Brugere',\n      viewer: 'Læser',\n      viewers: 'Læsere',\n      visualTaskManagementWithLists: 'Visuel opgavestyring med lister.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Hvad er nyt',\n      withoutBaseGroup: 'Uden standardgruppe',\n      writeComment: 'Skriv en kommentar...',\n    },\n\n    action: {\n      activateUser: 'Aktiver bruger',\n      activateUser_title: 'Aktiver bruger',\n      addAnotherCard: 'Tilføj endnu et kort',\n      addAnotherList: 'Tilføj endnu en liste',\n      addAnotherTask: 'Tilføj endnu en opgave',\n      addCard: 'Tilføj kort',\n      addCard_title: 'Tilføj kort',\n      addComment: 'Tilføj kommentar',\n      addCustomField: 'Tilføj brugerdefineret felt',\n      addCustomFieldGroup: 'Tilføj brugerdefineret feltgruppe',\n      addList: 'Tilføj liste',\n      addMember: 'Tilføj medlem',\n      addMoreDetailedDescription: 'Tilføj en uddybende beskrivelse',\n      addTask: 'Tilføj opgave',\n      addTaskList: 'Tilføj opgaveliste',\n      addToCard: 'Tilføj til kort',\n      addUser: 'Tilføj bruger',\n      addWebhook: 'Tilføj webhook',\n      archive: 'Arkivér',\n      archiveCard: 'Arkivér kort',\n      archiveCard_title: 'Arkivér kort',\n      archiveCards: 'Arkivér kort',\n      archiveCards_title: 'Arkivér kort',\n      assignAsOwner: 'Sæt som ejer',\n      cancel: 'Annuller',\n      copy: 'Kopiér',\n      copyCard_title: 'Kopiér kort',\n      createApiKey: 'Opret API-nøgle',\n      createBoard: 'Opret tavle',\n      createCustomFieldGroup: 'Opret brugerdefineret feltgruppe',\n      createFile: 'Opret fil',\n      createLabel: 'Opret label',\n      createNewLabel: 'Opret ny label',\n      createProject: 'Opret projekt',\n      cut: 'Klip',\n      cutCard_title: 'Klip kort',\n      deactivateUser: 'Deaktivér bruger',\n      deactivateUser_title: 'Deaktivér bruger',\n      delete: 'Slet',\n      deleteApiKey: 'Slet API-nøgle',\n      deleteAttachment: 'Slet vedhæftning',\n      deleteAvatar: 'Slet profilbillede',\n      deleteBackgroundImage: 'Slet baggrundsbillede',\n      deleteBoard: 'Slet tavle',\n      deleteBoard_title: 'Slet tavle',\n      deleteCard: 'Slet kort',\n      deleteCardForever: 'Slet kort permanent',\n      deleteCard_title: 'Slet kort',\n      deleteComment: 'Slet kommentar',\n      deleteCustomField: 'Slet brugerdefineret felt',\n      deleteCustomFieldGroup: 'Slet brugerdefineret feltgruppe',\n      deleteForever_title: 'Slet permanent',\n      deleteGroup: 'Slet gruppe',\n      deleteLabel: 'Slet label',\n      deleteList: 'Slet liste',\n      deleteList_title: 'Slet liste',\n      deleteNotificationService: 'Fjern notifikationstjeneste',\n      deleteProject: 'Slet projekt',\n      deleteProject_title: 'Slet projekt',\n      deleteTask: 'Slet opgave',\n      deleteTaskList: 'Slet opgaveliste',\n      deleteTask_title: 'Slet opgave',\n      deleteUser: 'Slet bruger',\n      deleteUser_title: 'Slet bruger',\n      deleteWebhook: 'Slet webhook',\n      dismissAll: 'Afvis alle',\n      download: 'Download',\n      duplicateCard_title: 'Duplikér kort',\n      edit: 'Rediger',\n      editColor_title: 'Rediger farve',\n      editDescription_title: 'Rediger beskrivelse',\n      editDueDate_title: 'Rediger frist',\n      editEmail_title: 'Rediger e-mail',\n      editGroup: 'Rediger gruppe',\n      editInformation_title: 'Rediger information',\n      editPassword_title: 'Skift adgangskode',\n      editPermissions: 'Rediger tilladelser',\n      editRole_title: 'Rediger rolle',\n      editStopwatch_title: 'Rediger stopur',\n      editTitle_title: 'Rediger overskrift',\n      editType_title: 'Rediger type',\n      editUsername_title: 'Rediger brugernavn',\n      emptyTrash: 'Tøm papirkurv',\n      emptyTrash_title: 'Tøm papirkurv',\n      import: 'Importer',\n      join: 'Deltag',\n      leave: 'Forlad',\n      leaveBoard: 'Forlad tavle',\n      leaveProject: 'Forlad projekt',\n      logOut_title: 'Log ud',\n      makeCover_title: 'Gør til coverbillede',\n      makeProjectPrivate: 'Gør projekt privat',\n      makeProjectPrivate_title: 'Gør projekt privat',\n      makeProjectShared: 'Del projekt',\n      makeProjectShared_title: 'Del projekt',\n      move: 'Flyt',\n      moveCard_title: 'Flyt kort',\n      moveList_title: 'Flyt liste',\n      regenerateApiKey: 'Regenerer API-nøgle',\n      remove: 'Fjern',\n      removeAssignee: 'Fjern ansvarlig',\n      removeColor: 'Fjern farve',\n      removeCover_title: 'Fjern som coverbillede',\n      removeFromBoard: 'Fjern fra tavle',\n      removeFromProject: 'Fjern fra projekt',\n      removeManager: 'Fjern projektleder',\n      removeMember: 'Fjern medlem',\n      restoreToList: 'Gendan til {{list}}',\n      returnToBoard: 'Tilbage til tavle',\n      save: 'Gem ændringer',\n      sendTestEmail: 'Send test e-mail',\n      showActive: 'Vis aktive',\n      showAllAttachments: 'Vis alle vedhæftninger ({{hidden}} skjulte)',\n      showCardsWithThisUser: 'Vis kort med denne bruger',\n      showDeactivated: 'Vis deaktiverede',\n      showFewerAttachments: 'Vis færre vedhæftninger',\n      showLess: 'Vis mindre',\n      showMore: 'Vis mere',\n      sortList_title: 'Sortér liste',\n      start: 'Start',\n      stop: 'Stop',\n      subscribe: 'Abonnér',\n      unlinkSso: 'Fjern SSO-forbindelse',\n      unlinkSso_title: 'Fjern SSO-forbindelse',\n      unsubscribe: 'Opsig abonnement',\n      uploadNewAvatar: 'Tilføj nyt profilbillede',\n      uploadNewImage: 'Tilføj nyt billede',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/da-DK/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'da-DK',\n  country: 'dk',\n  name: 'Dansk',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/da-DK/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Grænsen for aktive brugere er nået',\n      adminLoginRequiredToInitializeInstance:\n        'Administrator login påkrævet for at initialisere instans',\n      emailAlreadyInUse: 'E-mail allerede i brug',\n      emailOrUsername: 'E-mail eller brugernavn',\n      invalidCredentials: 'Forkerte loginoplysninger',\n      invalidEmailOrUsername: 'Ugyldig e-mail eller brugernavn',\n      invalidPassword: 'Ugyldig adgangskode',\n      logIn_title: 'Log på',\n      noInternetConnection: 'Ingen forbindelse til internettet',\n      or: 'Eller',\n      pageNotFound_title: 'Side ikke fundet',\n      password: 'Adgangskode',\n      poweredByPlanka: 'Drevet af <1>PLANKA</1>',\n      serverConnectionFailed: 'Ingen forbindelse til serveren',\n      unknownError: 'Ukendt fejl - prøv igen',\n      useSingleSignOn: 'Anvend single sign-on',\n      usernameAlreadyInUse: 'Brugernavn allerede i brug',\n      whoops_title: 'Ups!',\n    },\n\n    action: {\n      cancelAndClose: 'Annuller og luk',\n      continue: 'Fortsæt',\n      debugSso: 'Fejlfind SSO',\n      goBack: 'Gå tilbage',\n      goHome: 'Gå hjem',\n      logIn: 'Log på',\n      logInWithSso: 'Log på med SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/da-DK/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Denne tekst har ingen overskrift.\\nBåde titel og tekst\\nkan markeres med fed, kursiv, farve,\\noverstregning, og understregning.\",\n    \"text-with-head\": \"Denne tekst har en overskrift.\\nBåde titel og tekst\\nkan markeres med fed, kursiv, farve,\\noverstregning, og understregning.\",\n    \"heading\": \"Titel\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Fejl i markdown editor\",\n    \"settings_wysiwyg\": \"Visuel editor (WYSIWYG)\",\n    \"settings_markup\": \"Markdown markup\",\n    \"markup_placeholder\": \"Indtast tekst med markdown markup...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Fjern\",\n    \"empty_option\": \"Ingen resultater fundet\",\n    \"show_line_numbers\": \"Linjenummerering\"\n  },\n  \"common\": {\n    \"delete\": \"Slet\",\n    \"edit\": \"Rediger\",\n    \"toolbar_action_disabled\": \"Inkompatibelt markup element\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Annuller\",\n    \"common_action_submit\": \"OK\",\n    \"common_action_upload\": \"Vælg\",\n    \"common_tab_attach\": \"Tilføj fra enhed\",\n    \"common_tab_link\": \"Tilføj via link\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Størrelse, px\",\n    \"image_name\": \"Titel\",\n    \"image_link_href\": \"Billedlink\",\n    \"image_link_href_help\": \"URL-adressen billedet fører til.\",\n    \"image_alt\": \"Alt. tekst\",\n    \"image_alt_help\": \"Alternativ tekst vises, hvis billedet ikke kan loades.\",\n    \"image_upload_help\": \"JPEG, GIF eller PNG billeder, på maks. 1 MB.\",\n    \"image_upload_failed\": \"Kunne ikke tilføje billede\",\n    \"image_size_width\": \"Bredde\",\n    \"image_size_height\": \"Højde\",\n    \"link_url_help\": \"URL-adressen linket fører til.\",\n    \"link_text\": \"Linktekst\",\n    \"link_text_help\": \"Tekst der vises som link.\",\n    \"link_open_help\": \"Åbn link i ny fane\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Overskrift\",\n    \"header_hint\": \"# Tekst\",\n    \"italic_title\": \"Kursiv\",\n    \"italic_hint\": \"_Tekst_\",\n    \"bold_title\": \"Fed\",\n    \"bold_hint\": \"**Tekst**\",\n    \"strikethrough_title\": \"Overstreget\",\n    \"strikethrough_hint\": \"~~Tekst~~\",\n    \"blockquote_title\": \"Citat\",\n    \"blockquote_hint\": \"> Tekst\",\n    \"code_title\": \"Kode\",\n    \"code_hint\": \"```Tekst```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[Tekst](url)\",\n    \"image_title\": \"Billede\",\n    \"image_hint\": \"![Tekst](url)\",\n    \"list_title\": \"Punktopstilling\",\n    \"list_hint\": \"- Tekst\",\n    \"numbered-list_title\": \"Nummereret liste\",\n    \"numbered-list_hint\": \"1. Tekst\",\n    \"documentation\": \"Dokumentation\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Fed\",\n    \"code\": \"Kode\",\n    \"code_inline\": \"Inline kode\",\n    \"codeblock\": \"Kodeblok\",\n    \"colorify\": \"Tekstfarve\",\n    \"colorify__color_blue\": \"Blå\",\n    \"colorify__color_default\": \"Standard\",\n    \"colorify__color_gray\": \"Grå\",\n    \"colorify__color_green\": \"Grøn\",\n    \"colorify__color_orange\": \"Orange\",\n    \"colorify__color_red\": \"Rød\",\n    \"colorify__color_violet\": \"Lilla\",\n    \"colorify__color_yellow\": \"Gul\",\n    \"colorify__group_text\": \"Tekst\",\n    \"cut\": \"Udvidelig sektion\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emojis kan tilføjes i WYSIWYG eller manuelt med markup\",\n    \"heading\": \"Overskrift\",\n    \"heading1\": \"Overskrift 1\",\n    \"heading2\": \"Overskrift 2\",\n    \"heading3\": \"Overskrift 3\",\n    \"heading4\": \"Overskrift 4\",\n    \"heading5\": \"Overskrift 5\",\n    \"heading6\": \"Overskrift 6\",\n    \"hrule\": \"Separator\",\n    \"image\": \"Billede\",\n    \"italic\": \"Kursiv\",\n    \"link\": \"Link\",\n    \"list\": \"Liste\",\n    \"list__action_lift\": \"Formindsk indrykning\",\n    \"list__action_sink\": \"Forøg indrykning\",\n    \"list_action_disabled\": \"Modsiger listens logik\",\n    \"mark\": \"Markeret\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Se flere\",\n    \"note\": \"Note\",\n    \"olist\": \"Nummereret liste\",\n    \"quote\": \"Citat\",\n    \"redo\": \"Gentag\",\n    \"strike\": \"Overstreget\",\n    \"table\": \"Tabel\",\n    \"text\": \"Brødtekst\",\n    \"ulist\": \"Punktopstilling\",\n    \"underline\": \"Understreget\",\n    \"undo\": \"Fortryd\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Skriv / for at bruge slash kommandoer...\",\n    \"checkbox\": \"Opgavebeskrivelse...\",\n    \"deflist_term\": \"Term\",\n    \"deflist_desc\": \"Definitionsbeskrivelse\",\n    \"heading\": \"Overskrift\",\n    \"cut_title\": \"Titel\",\n    \"cut_content\": \"Indhold som vises ved udvidelse\",\n    \"note_title\": \"Titel\",\n    \"note_content\": \"Noteindhold\",\n    \"table_cell\": \"Celleindhold\",\n    \"select_filter\": \"Søg sprog...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Versalfølsom\",\n    \"label_whole-word\": \"Helt ord\",\n    \"title\": \"Søg i kode\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Ikke fundet\"\n  },\n  \"widgets\": {\n    \"image\": \"Tilføj billede\",\n    \"link\": \"Tilføj link\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Note\",\n    \"tip\": \"Tip\",\n    \"warning\": \"Advarsel\",\n    \"alert\": \"Alarm\",\n    \"remove\": \"Fjern\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Tilføj kolonne før\",\n    \"column.add.after\": \"Tilføj kolonne efter\",\n    \"column.remove\": \"Fjern kolonne\",\n    \"column.remove.multiple\": \"Fjern kolonner\",\n    \"row.add.before\": \"Tilføj række før\",\n    \"row.add.after\": \"Tilføj række efter\",\n    \"row.remove\": \"Fjern række\",\n    \"row.remove.multiple\": \"Fjern rækker\",\n    \"cells.clear\": \"Ryd celler\",\n    \"table.remove\": \"Fjern tabel\",\n    \"table.menu.cell.align.left\": \"Juster celleindhold til venstre\",\n    \"table.menu.cell.align.right\": \"Juster celleindhold til højre\",\n    \"table.menu.cell.align.center\": \"Juster celleindhold til midten\",\n    \"table.menu.row.add\": \"Tilføj række efter\",\n    \"table.menu.row.remove\": \"Fjern række\",\n    \"table.menu.column.add\": \"Tilføj kolonne efter\",\n    \"table.menu.column.remove\": \"Fjern kolonne\",\n    \"table.menu.convert.yfm\": \"Konverter til YFM tabel\",\n    \"table.menu.table.remove\": \"Fjern tabel\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/de-DE/core.js",
    "content": "import dateFns from 'date-fns/locale/de';\nimport timeAgo from 'javascript-time-ago/locale/de';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd.MM.yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    fullDate: 'd. MMM. y',\n    fullDateTime: \"d. MMMM. y 'um' p\",\n    longDate: 'd. MMM',\n    longDateTime: \"d. MMMM yy 'um' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Über die App',\n      aboutPlanka_title: 'Über PLANKA',\n      accessToken: 'Zugriffstoken',\n      account: 'Konto',\n      actions: 'Aktionen',\n      activateUser_title: 'Benutzer aktivieren',\n      active: 'Aktiv',\n      addAttachment_title: 'Anhang hinzufügen',\n      addCustomFieldGroup_title: 'Feldgruppe hinzufügen',\n      addCustomField_title: 'Datenfeld hinzufügen',\n      addManager_title: 'Projektleiter hinzufügen',\n      addMember_title: 'Mitglied hinzufügen',\n      addTaskList_title: 'Aufgaben hinzufügen',\n      addUser_title: 'Benutzer hinzufügen',\n      admin: 'Administrator',\n      administration: 'Verwaltung',\n      all: 'Alle',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Alle Änderungen werden automatisch gespeichert, sobald die Verbindung wiederhergestellt wurde.',\n      alphabetically: 'Alphabetisch',\n      alwaysDisplayCardCreator: 'Kartenersteller immer anzeigen',\n      apiKeyCreated_title: 'API-Schlüssel erstellt',\n      apiKey_title: 'API-Schlüssel',\n      archive: 'Archiv',\n      archiveCard_title: 'Karte archivieren',\n      archiveCards_title: 'Karten archivieren',\n      areYouSureYouWantToActivateThisUser:\n        'Sind Sie sicher, dass Sie diesen Benutzer aktivieren möchten?',\n      areYouSureYouWantToArchiveCards:\n        'Sind Sie sicher, dass Sie diese Karten archivieren möchten?',\n      areYouSureYouWantToArchiveThisCard:\n        'Sind Sie sicher, dass Sie diese Karte archivieren möchten?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Sind Sie sicher, dass Sie diesen Projektleiter als Eigentümer festlegen möchten?',\n      areYouSureYouWantToDeactivateThisUser:\n        'Sind Sie sicher, dass Sie diesen Benutzer deaktivieren möchten?',\n      areYouSureYouWantToDeleteThisApiKey:\n        'Sind Sie sicher, dass Sie diesen API-Schlüssel löschen möchten?',\n      areYouSureYouWantToDeleteThisAttachment:\n        'Sind Sie sicher, dass Sie diesen Anhang löschen möchten?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Sind Sie sicher, dass Sie dieses Hintergrundbild löschen möchten?',\n      areYouSureYouWantToDeleteThisBoard:\n        'Sind Sie sicher, dass Sie diesen Arbeitsbereich löschen möchten?',\n      areYouSureYouWantToDeleteThisCard: 'Sind Sie sicher, dass Sie diese Karte löschen möchten?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Sind Sie sicher, dass Sie diese Karte endgültig löschen möchten?',\n      areYouSureYouWantToDeleteThisComment:\n        'Sind Sie sicher, dass Sie diesen Kommentar löschen möchten?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Sind Sie sicher, dass Sie dieses Datenfeld löschen möchten?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Sind Sie sicher, dass Sie diese Feldgruppe löschen möchten?',\n      areYouSureYouWantToDeleteThisLabel: 'Sind Sie sicher, dass Sie dieses Label löschen möchten?',\n      areYouSureYouWantToDeleteThisList:\n        'Sind Sie sicher, dass Sie diese Liste löschen möchten? Alle Karten werden in den Papierkorb verschoben.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Sind Sie sicher, dass Sie diesen Benachrichtigungsdienst löschen möchten?',\n      areYouSureYouWantToDeleteThisProject:\n        'Sind Sie sicher, dass Sie dieses Projekt löschen möchten?',\n      areYouSureYouWantToDeleteThisTask: 'Sind Sie sicher, dass Sie diese Aufgabe löschen möchten?',\n      areYouSureYouWantToDeleteThisTaskList:\n        'Sind Sie sicher, dass Sie diese Aufgaben löschen möchten?',\n      areYouSureYouWantToDeleteThisUser:\n        'Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?',\n      areYouSureYouWantToDeleteThisWebhook:\n        'Sind Sie sicher, dass Sie diesen Webhook löschen möchten?',\n      areYouSureYouWantToEmptyTrash: 'Sind Sie sicher, dass Sie den Papierkorb leeren möchten?',\n      areYouSureYouWantToLeaveBoard:\n        'Sind Sie sicher, dass Sie den Arbeitsbereich verlassen möchten?',\n      areYouSureYouWantToLeaveProject: 'Sind Sie sicher, dass Sie das Projekt verlassen möchten?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Sind Sie sicher, dass Sie dieses Projekt privat machen möchten?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Sind Sie sicher, dass Sie dieses Projekt freigeben möchten?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Sind Sie sicher, dass Sie diesen API-Schlüssel neu generieren möchten? Der vorherige Schlüssel wird nicht mehr funktionieren.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Sind Sie sicher, dass Sie diesen Projektleiter aus dem Projekt entfernen möchten?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Sind Sie sicher, dass Sie dieses Mitglied aus dem Arbeitsbereich entfernen möchten?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Sind Sie sicher, dass Sie die SSO-Verknüpfung von diesem Benutzer aufheben möchten? Dies ermöglicht dem Benutzer die Anmeldung mit einem Passwort.',\n      assignAsOwner_title: 'Als Eigentümer zuweisen',\n      atLeastOneListMustBePresent: 'Mindestens eine Liste muss vorhanden sein',\n      attachment: 'Anhang',\n      attachments: 'Anhänge',\n      authentication: 'Authentifizierung',\n      background: 'Hintergrund',\n      baseCustomFields_title: 'Feldgruppe',\n      baseGroup: 'Feldgruppe',\n      board: 'Arbeitsbereich',\n      boardActions_title: 'Arbeitsbereich-Aktionen',\n      boardNotFound_title: 'Arbeitsbereich nicht gefunden',\n      boardSubscribed: 'Arbeitsbereich abonniert',\n      boardUser: 'Benutzer',\n      byCreationTime: 'Nach Erstellungszeit',\n      byDefault: 'Standardmäßig',\n      byDueDate: 'Nach Fälligkeitsdatum',\n      canBeInvitedToWorkInBoards: 'Kann zu Projekten und Arbeitsbereichen eingeladen werden.',\n      canComment: 'Kann kommentieren',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Kann eigene Projekte erstellen und in Andere eingeladen werden.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Kann das Arbeitsbereich-Layout bearbeiten und Mitgliedern Karten zuweisen.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Kann systemweite Einstellungen vornehmen und als Projektleiter arbeiten.',\n      canOnlyViewBoard: 'Kann das Board nur ansehen.',\n      cardActions_title: 'Kartenaktionen',\n      cardNotFound_title: 'Karte nicht gefunden',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Karten in dieser Liste sind für alle Arbeitsbereich-Mitglieder verfügbar.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Karten in dieser Liste sind abgeschlossen und können archiviert werden.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Karten in dieser Liste sind bereit zur Bearbeitung.',\n      clickHereOrRefreshPageToUpdate:\n        '<0>Hier klicken</0> oder Seite aktualisieren, um zu aktualisieren.',\n      clientHostnameInEhlo: 'Client-Hostname in EHLO',\n      closed: 'Geschlossen',\n      color: 'Farbe',\n      comments: 'Kommentare',\n      contentExceedsLimit: 'Inhalt überschreitet {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Der Inhalt dieses Anhangs ist zu groß für die Anzeige.',\n      copy_inline: 'Kopie',\n      createBoard_title: 'Arbeitsbereich erstellen',\n      createCustomFieldGroup_title: 'Feldgruppe erstellen',\n      createLabel_title: 'Label erstellen',\n      createNewOneOrSelectExistingOne:\n        'Erstellen Sie eine neue oder wählen Sie<br />eine vorhandene aus.',\n      createProject_title: 'Projekt erstellen',\n      createTextFile_title: 'Textdatei erstellen',\n      creator: 'Ersteller',\n      currentPassword: 'Derzeitiges Passwort',\n      currentUser: 'Aktueller Benutzer',\n      customFieldGroup_title: 'Feldgruppe',\n      customFieldGroups_title: 'Benutzerdefinierte Feldgruppen',\n      customField_title: 'Feldgruppe',\n      customFields_title: 'Feldgruppen',\n      customerPanel_title: 'Kundenpanel',\n      dangerZone_title: 'Gefahrenbereich',\n      date: 'Datum',\n      deactivateUser_title: 'Benutzer deaktivieren',\n      defaultCardType_title: 'Standard-Kartentyp',\n      defaultFrom: 'Standard \"Von\"',\n      defaultView_title: 'Standardansicht',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Löschen Sie alle Arbeitsbereiche, um dieses Projekt löschen zu können',\n      deleteApiKey_title: 'API-Schlüssel löschen',\n      deleteAttachment_title: 'Anhang löschen',\n      deleteBackgroundImage_title: 'Hintergrundbild löschen',\n      deleteBoard_title: 'Arbeitsbereich löschen',\n      deleteCardForever_title: 'Karte endgültig löschen',\n      deleteCard_title: 'Karte löschen',\n      deleteComment_title: 'Kommentar löschen',\n      deleteCustomFieldGroup_title: 'Feldgruppe löschen',\n      deleteCustomField_title: 'Datenfeld löschen',\n      deleteLabel_title: 'Label löschen',\n      deleteList_title: 'Liste löschen',\n      deleteNotificationService_title: 'Benachrichtigungsdienst löschen',\n      deleteProject_title: 'Projekt löschen',\n      deleteTaskList_title: 'Aufgaben löschen',\n      deleteTask_title: 'Aufgabe löschen',\n      deleteUser_title: 'Benutzer löschen',\n      deleteWebhook_title: 'Webhook löschen',\n      deletedUser_title: 'Gelöschter Benutzer',\n      description: 'Beschreibung',\n      display: 'Anzeige',\n      displayCardAges: 'Kartenalter anzeigen',\n      dropFileToUpload: 'Datei für Upload ablegen',\n      dueDate_title: 'Fälligkeitsdatum',\n      dynamicAndUnevenlySpacedLayout: 'Dynamisches und ungleichmäßig verteiltes Layout.',\n      editAttachment_title: 'Anhang bearbeiten',\n      editAvatar_title: 'Profilbild bearbeiten',\n      editColor_title: 'Farbe bearbeiten',\n      editCustomFieldGroup_title: 'Feldgruppe bearbeiten',\n      editCustomField_title: 'Datenfeld bearbeiten',\n      editDueDate_title: 'Fälligkeitsdatum bearbeiten',\n      editEmail_title: 'E-mail Adresse ändern',\n      editInformation_title: 'Informationen bearbeiten',\n      editLabel_title: 'Label bearbeiten',\n      editPassword_title: 'Passwort ändern',\n      editPermissions_title: 'Benutzerrolle ändern',\n      editRole_title: 'Rolle zuweisen',\n      editStopwatch_title: 'Stoppuhr bearbeiten',\n      editType_title: 'Typ ändern',\n      editUsername_title: 'Benutzername ändern',\n      editor: 'Verwalter',\n      editors: 'Verwalter',\n      email: 'E-mail',\n      emptyTrash_title: 'Papierkorb leeren',\n      enterCardTitle: 'Kartentitel eingeben...',\n      enterDescription: 'Beschreibung eingeben...',\n      enterFilename: 'Dateiname eingeben',\n      enterListTitle: 'Listentitel eingeben...',\n      enterTaskDescription: 'Aufgabenbeschreibung eingeben...',\n      events: 'Ereignisse',\n      excludedEvents: 'Ausgeschlossene Ereignisse',\n      expandTaskListsByDefault: 'Aufgabenlisten standardmäßig erweitern',\n      filterByLabels_title: 'Nach Label filtern',\n      filterByMembers_title: 'Nach Mitgliedern filtern',\n      forPersonalProjects: 'Für persönliche Projekte.',\n      forTeamBasedProjects: 'Für teambasierte Projekte.',\n      fromComputer_title: 'Vom Computer',\n      fromTrello: 'Von Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Der vollständige Schlüssel ist aus Sicherheitsgründen verborgen. Generieren Sie ihn neu, um einen neuen zu erstellen.',\n      general: 'Allgemein',\n      gradients: 'Verläufe',\n      grid: 'Raster',\n      hideCompletedTasks: 'Erledigte Aufgaben ausblenden',\n      hideFromProjectListAndFavorites: 'Aus Projektliste und Favoriten ausblenden',\n      host: 'Host',\n      hours: 'Stunden',\n      identity: 'Identität',\n      importBoard_title: 'Arbeitsbereich importieren',\n      information: 'Information',\n      invalidCurrentPassword: 'Das aktuelle Passwort ist falsch',\n      kanban: 'Kanban',\n      labels: 'Labels',\n      language: 'Sprache',\n      leaveBoard_title: 'Arbeitsbereich verlassen',\n      leaveProject_title: 'Projekt verlassen',\n      limitCardTypesToDefaultOne: 'Kartentypen auf Standardtyp beschränken',\n      linkToCard: 'Link zur Karte',\n      list: 'Liste',\n      listActions_title: 'Listenaktionen',\n      lists: 'Listen',\n      makeProjectPrivate_title: 'Projekt privat machen',\n      makeProjectShared_title: 'Projekt freigeben',\n      managers: 'Projektleiter',\n      memberActions_title: 'Mitglieder-Aktionen',\n      members: 'Mitglieder',\n      minutes: 'Minuten',\n      moreActions: 'Weitere Aktionen',\n      moreActions_title: 'Weitere Aktionen',\n      moveCard_title: 'Karte verschieben',\n      moveList_title: 'Liste verschieben',\n      myOwn_title: 'Meine eigenen',\n      name: 'Name',\n      newEmail: 'Neue E-Mail-Adresse',\n      newPassword: 'Neues Passwort',\n      newUsername: 'Neuer Benutzername',\n      newVersionAvailable: 'Neue Version verfügbar',\n      newestFirst: 'Neueste zuerst',\n      noApiKeyCreated: 'Kein API-Schlüssel erstellt.',\n      noBoards: 'Keine Arbeitsbereiche',\n      noCardsFound: 'Keine Karten gefunden.',\n      noConnectionToServer: 'Keine Verbindung zum Server',\n      noLists: 'Keine Listen',\n      noProjects: 'Keine Projekte',\n      noUnreadNotifications: 'Keine ungelesenen Benachrichtigungen.',\n      notifications: 'Benachrichtigungen',\n      oldestFirst: 'Älteste zuerst',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Nur ein Manager sollte verbleiben, um dieses Projekt privat zu machen',\n      openBoard_title: 'Arbeitsbereich öffnen',\n      optional_inline: 'optional',\n      organization: 'Organisation',\n      others: 'Andere',\n      passwordIsSet: 'Passwort ist gesetzt',\n      phone: 'Telefon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA verwendet <1><0>Apprise</0></1>, um Benachrichtigungen an über 100 beliebte Dienste zu senden.',\n      port: 'Port',\n      preferences: 'Voreinstellungen',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Tipp: Drücken Sie STRG-V (Cmd-V auf Mac), um einen Anhang aus der Zwischenablage hinzuzufügen.',\n      private: 'Privat',\n      project: 'Projekt',\n      projectNotFound_title: 'Projekt nicht gefunden',\n      projectOwner: 'Projektleitung',\n      referenceDataAndKnowledgeStorage: 'Speichern von Wissen und Referenzen.',\n      regenerateApiKey_title: 'API-Schlüssel neu generieren',\n      rejectUnauthorizedTlsCertificates: 'Nicht autorisierte TLS-Zertifikate ablehnen',\n      removeManager_title: 'Projektleiter entfernen',\n      removeMember_title: 'Mitglied entfernen',\n      role: 'Rolle',\n      saveThisKeyItWillNotBeShownAgain:\n        'Speichern Sie diesen Schlüssel — er wird nicht erneut angezeigt!',\n      searchCards: 'Karte suchen...',\n      searchCustomFieldGroups: 'Benutzerdefinierte Feldgruppen suchen...',\n      searchCustomFields: 'In Feldgruppen suchen...',\n      searchLabels: 'Label suchen...',\n      searchLists: 'Liste suchen...',\n      searchMembers: 'Mitglied suchen...',\n      searchProjects: 'Projekte suchen...',\n      searchUsers: 'Benutzer suchen...',\n      seconds: 'Sekunden',\n      selectAssignee_title: 'Zuständigen auswählen',\n      selectBoard: 'Arbeitsbereich auswählen',\n      selectList: 'Liste auswählen',\n      selectListToRestoreThisCard: 'Liste auswählen, um diese Karte wiederherzustellen',\n      selectOrder_title: 'Reihenfolge auswählen',\n      selectPermissions_title: 'Berechtigungen auswählen',\n      selectProject: 'Projekt auswählen',\n      selectRole_title: 'Rolle auswählen',\n      selectType_title: 'Typ auswählen',\n      sequentialDisplayOfCards: 'Sequenzielle Anzeige von Karten.',\n      settings: 'Einstellungen',\n      shared: 'Geteilt',\n      sharedWithMe_title: 'Mit mir geteilt',\n      showOnFrontOfCard: 'Auf der Vorderseite der Karte anzeigen',\n      smtp: 'SMTP',\n      sortList_title: 'Liste sortieren',\n      sourceCardIsNoLongerAvailableForCopying: 'Quellkarte ist nicht mehr zum Kopieren verfügbar.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'Quellkarte ist nicht mehr zum Verschieben verfügbar.',\n      stopwatch: 'Stoppuhr',\n      story: 'Wissen',\n      subscribeToCardWhenCommenting: 'Karte beim Kommentieren abonnieren',\n      subscribeToMyOwnCardsByDefault: 'Standardmäßig meine eigenen Karten abonnieren',\n      taskActions_title: 'Aufgabenaktionen',\n      taskAssignmentAndProjectCompletion: 'Verwaltung von Aufgaben und Projekten.',\n      taskListActions_title: 'Aufgaben-Aktionen',\n      taskList_title: 'Aufgaben',\n      team: 'Team',\n      termsOfService_title: 'Nutzungsbedingungen',\n      testLog_title: 'Test-Protokoll',\n      thereIsNoPreviewAvailableForThisAttachment: 'Für diesen Anhang ist keine Vorschau verfügbar.',\n      time: 'Zeit',\n      title: 'Titel',\n      trash: 'Papierkorb',\n      trashHasBeenSuccessfullyEmptied: 'Papierkorb wurde erfolgreich geleert.',\n      turnOffRecentCardHighlighting: 'Hervorhebung neuer Karten ausschalten',\n      typeNameToConfirm: 'Namen zur Bestätigung eingeben.',\n      typeTitleToConfirm: 'Titel zur Bestätigung eingeben.',\n      unlinkSso_title: 'SSO-Verknüpfung aufheben',\n      unsavedChanges: 'Ungespeicherte Änderungen',\n      uploadFailedFileIsTooBig: 'Upload fehlgeschlagen: Datei ist zu groß.',\n      uploadFailedNotEnoughStorageSpace: 'Upload fehlgeschlagen: Nicht genügend Speicherplatz.',\n      uploadedImages: 'Hochgeladene Bilder',\n      url: 'URL',\n      useSecureConnection: 'Sichere Verbindung verwenden',\n      userActions_title: 'Benutzeraktionen',\n      userAddedCardToList: '<0>{{user}}</0> hat <2>{{card}}</2> zu {{list}} hinzugefügt',\n      userAddedThisCardToList: '<0>{{user}}</0> hat diese Karte hinzugefügt zu {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> hat {{addedUser}} zu <4>{{card}}</4> hinzugefügt',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> hat {{addedUser}} zu dieser Karte hinzugefügt',\n      userAddedYouToCard: '<0>{{user}}</0> hat Sie zu <2>{{card}}</2> hinzugefügt',\n      userCompletedTaskOnCard: '<0>{{user}}</0> hat {{task}} auf <4>{{card}}</4> abgeschlossen',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> hat {{task}} auf dieser Karte abgeschlossen',\n      userJoinedCard: '<0>{{user}}</0> ist <2>{{card}}</2> beigetreten',\n      userJoinedThisCard: '<0>{{user}}</0> ist dieser Karte beigetreten',\n      userLeftCard: '<0>{{user}}</0> hat <2>{{card}}</2> verlassen',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> hat einen neuen Kommentar verfasst: «{{comment}}» in <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> hat diese Karte verlassen',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> hat {{task}} auf <4>{{card}}</4> als unvollständig markiert',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> hat {{task}} auf dieser Karte als unvollständig markiert',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> hat Sie in einem Kommentar «{{comment}}» auf <2>{{card}}</2> erwähnt',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> bewegte <2>{{card}}</2> von {{fromList}} nach {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> bewegte diese Karte von {{fromList}} nach {{toList}}',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0> hat {{removedUser}} von <4>{{card}}</4> entfernt',\n      userRemovedUserFromThisCard:\n        '<0>{{actorUser}}</0> hat {{removedUser}} von dieser Karte entfernt',\n      username: 'Benutzername',\n      users: 'Benutzer',\n      viewer: 'Betrachter',\n      viewers: 'Betrachter',\n      visualTaskManagementWithLists: 'Visuelle Aufgabenverwaltung mit Listen.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Was ist neu',\n      withoutBaseGroup: 'Ohne Basisgruppe',\n      writeComment: 'Kommentar verfassen...',\n    },\n\n    action: {\n      activateUser: 'Benutzer aktivieren',\n      activateUser_title: 'Benutzer aktivieren',\n      addAnotherCard: 'Karte hinzufügen',\n      addAnotherList: 'Liste hinzufügen',\n      addAnotherTask: 'Aufgabe hinzufügen',\n      addCard: 'Karte hinzufügen',\n      addCard_title: 'Karte hinzufügen',\n      addComment: 'Kommentar hinzufügen',\n      addCustomField: 'Datenfeld hinzufügen',\n      addCustomFieldGroup: 'Feldgruppe hinzufügen',\n      addList: 'Liste hinzufügen',\n      addMember: 'Mitglied hinzufügen',\n      addMoreDetailedDescription: 'Eine detaillierte Beschreibung hinzufügen',\n      addTask: 'Aufgabe hinzufügen',\n      addTaskList: 'Aufgaben hinzufügen',\n      addToCard: 'Zu Karte hinzufügen',\n      addUser: 'Benutzer hinzufügen',\n      addWebhook: 'Webhook hinzufügen',\n      archive: 'Archivieren',\n      archiveCard: 'Karte archivieren',\n      archiveCard_title: 'Karte archivieren',\n      archiveCards: 'Karten archivieren',\n      archiveCards_title: 'Karten archivieren',\n      assignAsOwner: 'Als Eigentümer zuweisen',\n      cancel: 'Abbrechen',\n      copy: 'Kopieren',\n      copyCard_title: 'Karte Kopieren',\n      createApiKey: 'API-Schlüssel erstellen',\n      createBoard: 'Arbeitsbereich erstellen',\n      createCustomFieldGroup: 'Feldgruppe erstellen',\n      createFile: 'Datei erstellen',\n      createLabel: 'Label erstellen',\n      createNewLabel: 'Neues Label erstellen',\n      createProject: 'Projekt erstellen',\n      cut: 'Ausschneiden',\n      cutCard_title: 'Karte Ausschneiden',\n      deactivateUser: 'Benutzer deaktivieren',\n      deactivateUser_title: 'Benutzer deaktivieren',\n      delete: 'Löschen',\n      deleteApiKey: 'API-Schlüssel löschen',\n      deleteAttachment: 'Anhang löschen',\n      deleteAvatar: 'Avatar löschen',\n      deleteBackgroundImage: 'Hintergrundbild löschen',\n      deleteBoard: 'Arbeitsbereich löschen',\n      deleteBoard_title: 'Arbeitsbereich löschen',\n      deleteCard: 'Karte löschen',\n      deleteCardForever: 'Karte endgültig löschen',\n      deleteCard_title: 'Karte löschen',\n      deleteComment: 'Kommentar löschen',\n      deleteCustomField: 'Datenfeld löschen',\n      deleteCustomFieldGroup: 'Feldgruppe löschen',\n      deleteForever_title: 'Endgültig löschen',\n      deleteGroup: 'Gruppe löschen',\n      deleteLabel: 'Label löschen',\n      deleteList: 'Liste löschen',\n      deleteList_title: 'Liste löschen',\n      deleteNotificationService: 'Benachrichtigungsdienst löschen',\n      deleteProject: 'Projekt löschen',\n      deleteProject_title: 'Projekt löschen',\n      deleteTask: 'Aufgabe löschen',\n      deleteTaskList: 'Aufgaben löschen',\n      deleteTask_title: 'Aufgabe löschen',\n      deleteUser: 'Benutzer löschen',\n      deleteUser_title: 'Benutzer löschen',\n      deleteWebhook: 'Webhook löschen',\n      dismissAll: 'Alle verwerfen',\n      download: 'Herunterladen',\n      duplicateCard_title: 'Karte duplizieren',\n      edit: 'Bearbeiten',\n      editColor_title: 'Farbe bearbeiten',\n      editDescription_title: 'Beschreibung ändern',\n      editDueDate_title: 'Fälligkeitsdatum bearbeiten',\n      editEmail_title: 'E-Mail-Adresse bearbeiten',\n      editGroup: 'Gruppe bearbeiten',\n      editInformation_title: 'Informationen bearbeiten',\n      editPassword_title: 'Passwort ändern',\n      editPermissions: 'Berechtigungen ändern',\n      editRole_title: 'Rolle zuweisen',\n      editStopwatch_title: 'Stoppuhr bearbeiten',\n      editTitle_title: 'Titel bearbeiten',\n      editType_title: 'Typ ändern ',\n      editUsername_title: 'Benutzername ändern',\n      emptyTrash: 'Papierkorb leeren',\n      emptyTrash_title: 'Papierkorb leeren',\n      import: 'Import',\n      join: 'Beitreten',\n      leave: 'Verlassen',\n      leaveBoard: 'Arbeitsbereich verlassen',\n      leaveProject: 'Projekt verlassen',\n      logOut_title: 'Ausloggen',\n      makeCover_title: 'Als Vorschau festlegen',\n      makeProjectPrivate: 'Projekt privat machen',\n      makeProjectPrivate_title: 'Projekt privat machen',\n      makeProjectShared: 'Projekt freigeben',\n      makeProjectShared_title: 'Projekt freigeben',\n      move: 'Verschieben',\n      moveCard_title: 'Karte bewegen',\n      moveList_title: 'Liste verschieben',\n      regenerateApiKey: 'API-Schlüssel neu generieren',\n      remove: 'Löschen',\n      removeAssignee: 'Zuständigen entfernen',\n      removeColor: 'Farbe löschen',\n      removeCover_title: 'Vorschau löschen',\n      removeFromBoard: 'Vom Arbeitsbereich entfernen',\n      removeFromProject: 'Vom Projekt entfernen',\n      removeManager: 'Projektleiter entfernen',\n      removeMember: 'Mitglied entfernen',\n      restoreToList: 'Wiederherstellen in {{list}}',\n      returnToBoard: 'Zurück zum Arbeitsbereich',\n      save: 'Speichern',\n      sendTestEmail: 'Test-E-Mail senden',\n      showActive: 'Aktive anzeigen',\n      showAllAttachments: 'Alle Anhänge anzeigen ({{hidden}} versteckt)',\n      showCardsWithThisUser: 'Karten mit diesem Benutzer zeigen',\n      showDeactivated: 'Deaktivierte anzeigen',\n      showFewerAttachments: 'Weniger Anhänge anzeigen',\n      showLess: 'Weniger anzeigen',\n      showMore: 'Mehr anzeigen',\n      sortList_title: 'Liste sortieren',\n      start: 'Start',\n      stop: 'Stopp',\n      subscribe: 'Abonnieren',\n      unlinkSso: 'SSO-Verknüpfung aufheben',\n      unlinkSso_title: 'SSO-Verknüpfung aufheben',\n      unsubscribe: 'De-abonnieren',\n      uploadNewAvatar: 'Neuen Avatar hochladen',\n      uploadNewImage: 'Neues Bild hochladen',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/de-DE/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'de-DE',\n  country: 'de',\n  name: 'Deutsch',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/de-DE/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Maximale Anzahl aktiver Benutzer erreicht',\n      adminLoginRequiredToInitializeInstance:\n        'Admin-Anmeldung erforderlich zur Initialisierung der Instanz',\n      emailAlreadyInUse: 'E-mail Adresse wird bereits benutzt',\n      emailOrUsername: 'E-Mail-Adresse oder Benutzername',\n      invalidCredentials: 'Ungültige Anmeldeinformationen',\n      invalidEmailOrUsername: 'Ungültige E-Mail-Adresse oder Benutzername',\n      invalidPassword: 'Ungültiges Passwort',\n      logIn_title: 'Anmelden',\n      noInternetConnection: 'Keine Internetverbindung',\n      or: 'Oder',\n      pageNotFound_title: 'Seite nicht gefunden',\n      password: 'Passwort',\n      poweredByPlanka: 'Powered by <1>PLANKA</1>',\n      serverConnectionFailed: 'Serververbindung fehlgeschlagen',\n      unknownError: 'Unbekannter Fehler, bitte später erneut versuchen',\n      useSingleSignOn: 'Einmalige Anmeldung (SSO) verwenden',\n      usernameAlreadyInUse: 'Benutzername wird bereits verwendet',\n      whoops_title: 'Hoppla!',\n    },\n\n    action: {\n      cancelAndClose: 'Abbrechen und schließen',\n      continue: 'Fortfahren',\n      debugSso: 'SSO debuggen',\n      goBack: 'Zurück gehen',\n      goHome: 'Zur Startseite',\n      logIn: 'Einloggen',\n      logInWithSso: 'Einloggen mit SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/de-DE/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Dies ist ein Text ohne Titel.\\nSowohl der Titel als auch der Text\\nkönnen fett, kursiv, farbig,\\ndurchgestrichen und unterstrichen hervorgehoben werden.\",\n    \"text-with-head\": \"Dies ist ein Text mit einem Titel.\\nSowohl der Titel als auch der Text\\nkönnen fett, kursiv, farbig,\\ndurchgestrichen und unterstrichen hervorgehoben werden.\",\n    \"heading\": \"Titel\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Fehler im Markdown-Editor\",\n    \"settings_wysiwyg\": \"Visueller Editor (WYSIWYG)\",\n    \"settings_markup\": \"Markdown-Auszeichnung\",\n    \"markup_placeholder\": \"Markdown-Auszeichnung eingeben...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Entfernen\",\n    \"empty_option\": \"Keine Übereinstimmungen gefunden\",\n    \"show_line_numbers\": \"Zeilennummerierung\"\n  },\n  \"common\": {\n    \"delete\": \"Löschen\",\n    \"edit\": \"Bearbeiten\",\n    \"toolbar_action_disabled\": \"Inkompatibles Markup-Element\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Abbrechen\",\n    \"common_action_submit\": \"Absenden\",\n    \"common_action_upload\": \"Auswählen\",\n    \"common_tab_attach\": \"Vom Gerät hinzufügen\",\n    \"common_tab_link\": \"Per Link hinzufügen\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Größe, px\",\n    \"image_name\": \"Titel\",\n    \"image_link_href\": \"Bildlink\",\n    \"image_link_href_help\": \"Adresse, zu der der Bildlink führt.\",\n    \"image_alt\": \"Alternativtext\",\n    \"image_alt_help\": \"Der Alternativtext wird angezeigt, wenn das Bild nicht geladen werden kann.\",\n    \"image_upload_help\": \"JPEG-, GIF- oder PNG-Bild mit maximal 1 MB.\",\n    \"image_upload_failed\": \"Bild konnte nicht hinzugefügt werden\",\n    \"image_size_width\": \"Breite\",\n    \"image_size_height\": \"Höhe\",\n    \"link_url_help\": \"Adresse, zu der der Link führt.\",\n    \"link_text\": \"Linktext\",\n    \"link_text_help\": \"Text, der als Link angezeigt wird.\",\n    \"link_open_help\": \"Link in neuem Tab öffnen\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Überschrift\",\n    \"header_hint\": \"# Dein Text\",\n    \"italic_title\": \"Kursiv\",\n    \"italic_hint\": \"_Dein Text_\",\n    \"bold_title\": \"Fett\",\n    \"bold_hint\": \"**Dein Text**\",\n    \"strikethrough_title\": \"Durchgestrichen\",\n    \"strikethrough_hint\": \"~~Dein Text~~\",\n    \"blockquote_title\": \"Blockzitat\",\n    \"blockquote_hint\": \"> Dein Text\",\n    \"code_title\": \"Code\",\n    \"code_hint\": \"```Dein Text```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[Dein Text](URL)\",\n    \"image_title\": \"Bild\",\n    \"image_hint\": \"![Dein Text](URL)\",\n    \"list_title\": \"Listenelement\",\n    \"list_hint\": \"- Dein Text\",\n    \"numbered-list_title\": \"Nummerierte Liste\",\n    \"numbered-list_hint\": \"1. Dein Text\",\n    \"documentation\": \"Dokumentation\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Fett\",\n    \"code\": \"Code\",\n    \"code_inline\": \"Inline-Code\",\n    \"codeblock\": \"Codeblock\",\n    \"colorify\": \"Textfarbe\",\n    \"colorify__color_blue\": \"Blau\",\n    \"colorify__color_default\": \"Standard\",\n    \"colorify__color_gray\": \"Grau\",\n    \"colorify__color_green\": \"Grün\",\n    \"colorify__color_orange\": \"Orange\",\n    \"colorify__color_red\": \"Rot\",\n    \"colorify__color_violet\": \"Violett\",\n    \"colorify__color_yellow\": \"Gelb\",\n    \"colorify__group_text\": \"Text\",\n    \"cut\": \"Ausschneiden\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emojis können im WYSIWYG-Editor hinzugefügt oder manuell mit Markup eingefügt werden\",\n    \"heading\": \"Überschrift\",\n    \"heading1\": \"Überschrift 1\",\n    \"heading2\": \"Überschrift 2\",\n    \"heading3\": \"Überschrift 3\",\n    \"heading4\": \"Überschrift 4\",\n    \"heading5\": \"Überschrift 5\",\n    \"heading6\": \"Überschrift 6\",\n    \"hrule\": \"Trennlinie\",\n    \"image\": \"Bild\",\n    \"italic\": \"Kursiv\",\n    \"link\": \"Link\",\n    \"list\": \"Liste\",\n    \"list__action_lift\": \"Element anheben\",\n    \"list__action_sink\": \"Element einrücken\",\n    \"list_action_disabled\": \"Widerspricht der Logik der Liste\",\n    \"mark\": \"Markiert\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Weitere Aktionen\",\n    \"note\": \"Hinweis\",\n    \"olist\": \"Geordnete Liste\",\n    \"quote\": \"Zitat\",\n    \"redo\": \"Wiederherstellen\",\n    \"strike\": \"Durchgestrichen\",\n    \"table\": \"Tabelle\",\n    \"text\": \"Text\",\n    \"ulist\": \"Aufzählungsliste\",\n    \"underline\": \"Unterstrichen\",\n    \"undo\": \"Rückgängig\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Tippe / für Slashbefehle...\",\n    \"checkbox\": \"Aufgabenbeschreibung eingeben...\",\n    \"deflist_term\": \"Begriff\",\n    \"deflist_desc\": \"Definitionsbeschreibung\",\n    \"heading\": \"Überschrift\",\n    \"cut_title\": \"Titel\",\n    \"cut_content\": \"Inhalt, der beim Klicken angezeigt wird\",\n    \"note_title\": \"Titel\",\n    \"note_content\": \"Hinweisinhalt\",\n    \"table_cell\": \"Zelleninhalt\",\n    \"select_filter\": \"Sprachen durchsuchen...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Groß-/Kleinschreibung beachten\",\n    \"label_whole-word\": \"Ganzes Wort\",\n    \"title\": \"Im Code suchen\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Nicht gefunden\"\n  },\n  \"widgets\": {\n    \"image\": \"Bild hinzufügen\",\n    \"link\": \"Link hinzufügen\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Hinweis\",\n    \"tip\": \"Tipp\",\n    \"warning\": \"Warnung\",\n    \"alert\": \"Alarm\",\n    \"remove\": \"Entfernen\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Spalte davor einfügen\",\n    \"column.add.after\": \"Spalte danach einfügen\",\n    \"column.remove\": \"Spalte entfernen\",\n    \"column.remove.multiple\": \"Spalten entfernen\",\n    \"row.add.before\": \"Zeile davor einfügen\",\n    \"row.add.after\": \"Zeile danach einfügen\",\n    \"row.remove\": \"Zeile entfernen\",\n    \"row.remove.multiple\": \"Zeilen entfernen\",\n    \"cells.clear\": \"Zellen leeren\",\n    \"table.remove\": \"Tabelle entfernen\",\n    \"table.menu.cell.align.left\": \"Zelleninhalt linksbündig ausrichten\",\n    \"table.menu.cell.align.right\": \"Zelleninhalt rechtsbündig ausrichten\",\n    \"table.menu.cell.align.center\": \"Zelleninhalt zentrieren\",\n    \"table.menu.row.add\": \"Zeile danach einfügen\",\n    \"table.menu.row.remove\": \"Zeile entfernen\",\n    \"table.menu.column.add\": \"Spalte danach einfügen\",\n    \"table.menu.column.remove\": \"Spalte entfernen\",\n    \"table.menu.convert.yfm\": \"In YFM-Tabelle umwandeln\",\n    \"table.menu.table.remove\": \"Tabelle entfernen\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/el-GR/core.js",
    "content": "import dateFns from 'date-fns/locale/el';\nimport timeAgo from 'javascript-time-ago/locale/el';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'P',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'at' p\",\n    fullDate: 'd MMM y',\n    fullDateTime: \"d MMMM y 'at' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Σχετικά με την εφαρμογή',\n      aboutPlanka_title: 'Σχετικά με το PLANKA',\n      accessToken: 'Διακριτικό πρόσβασης',\n      account: 'Λογαριασμός',\n      actions: 'Ενέργειες',\n      activateUser_title: 'Ενεργοποίηση χρήστη',\n      active: 'Ενεργός',\n      addAttachment_title: 'Προσθήκη συνημμένου',\n      addCustomFieldGroup_title: 'Προσθήκη ομάδας προσαρμοσμένων πεδίων',\n      addCustomField_title: 'Προσθήκη προσαρμοσμένου πεδίου',\n      addManager_title: 'Προσθήκη διαχειριστή',\n      addMember_title: 'Προσθήκη μέλους',\n      addTaskList_title: 'Προσθήκη λίστας εργασιών',\n      addUser_title: 'Προσθήκη χρήστη',\n      admin: 'Διαχειριστής',\n      administration: 'Διαχείριση',\n      all: 'Όλα',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Όλες οι αλλαγές θα αποθηκευτούν αυτόματα<br />όταν αποκατασταθεί η σύνδεση.',\n      alphabetically: 'Αλφαβητικά',\n      alwaysDisplayCardCreator: 'Πάντα εμφάνιση δημιουργού κάρτας',\n      apiKeyCreated_title: 'Δημιουργήθηκε κλειδί API',\n      apiKey_title: 'Κλειδί API',\n      archive: 'Αρχειοθέτηση',\n      archiveCard_title: 'Αρχειοθέτηση κάρτας',\n      archiveCards_title: 'Αρχειοθέτηση καρτών',\n      areYouSureYouWantToActivateThisUser:\n        'Είστε σίγουροι ότι θέλετε να ενεργοποιήσετε αυτόν τον χρήστη;',\n      areYouSureYouWantToArchiveCards: 'Είστε σίγουροι ότι θέλετε να αρχειοθετήσετε τις κάρτες;',\n      areYouSureYouWantToArchiveThisCard:\n        'Είστε σίγουροι ότι θέλετε να αρχειοθετήσετε αυτήν την κάρτα;',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Είστε σίγουροι ότι θέλετε να ορίσετε αυτόν τον διαχειριστή έργου ως ιδιοκτήτη;',\n      areYouSureYouWantToDeactivateThisUser:\n        'Είστε σίγουροι ότι θέλετε να απενεργοποιήσετε αυτόν τον χρήστη;',\n      areYouSureYouWantToDeleteThisApiKey:\n        'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το κλειδί API;',\n      areYouSureYouWantToDeleteThisAttachment:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το συνημμένο;',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτήν την εικόνα φόντου;',\n      areYouSureYouWantToDeleteThisBoard:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον πίνακα;',\n      areYouSureYouWantToDeleteThisCard: 'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτήν την κάρτα;',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτήν την κάρτα οριστικά;',\n      areYouSureYouWantToDeleteThisComment:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το σχόλιο;',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το προσαρμοσμένο πεδίο;',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτήν την ομάδα προσαρμοσμένων πεδίων;',\n      areYouSureYouWantToDeleteThisLabel:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτήν την ετικέτα;',\n      areYouSureYouWantToDeleteThisList:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτήν τη λίστα; Όλες οι κάρτες θα μετακινηθούν στον κάδο.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτήν την υπηρεσία ειδοποιήσεων;',\n      areYouSureYouWantToDeleteThisProject: 'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το έργο;',\n      areYouSureYouWantToDeleteThisTask:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτήν την εργασία;',\n      areYouSureYouWantToDeleteThisTaskList:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτήν τη λίστα εργασιών;',\n      areYouSureYouWantToDeleteThisUser:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον χρήστη;',\n      areYouSureYouWantToDeleteThisWebhook:\n        'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το webhook;',\n      areYouSureYouWantToEmptyTrash: 'Είστε σίγουροι ότι θέλετε να αδειάσετε τον κάδο;',\n      areYouSureYouWantToLeaveBoard: 'Είστε σίγουροι ότι θέλετε να αποχωρήσετε από τον πίνακα;',\n      areYouSureYouWantToLeaveProject: 'Είστε σίγουροι ότι θέλετε να αποχωρήσετε από το έργο;',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Είστε σίγουροι ότι θέλετε να κάνετε αυτό το έργο ιδιωτικό;',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Είστε σίγουροι ότι θέλετε να κάνετε αυτό το έργο κοινόχρηστο;',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Είστε βέβαιοι ότι θέλετε να αναδημιουργήσετε αυτό το κλειδί API; Το προηγούμενο κλειδί δεν θα λειτουργεί πλέον.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Είστε σίγουροι ότι θέλετε να αφαιρέσετε αυτόν τον διαχειριστή από το έργο;',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Είστε σίγουροι ότι θέλετε να αφαιρέσετε αυτό το μέλος από τον πίνακα;',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Είστε σίγουροι ότι θέλετε να αποσυνδέσετε το SSO από αυτόν τον χρήστη; Αυτό θα επιτρέψει στον χρήστη να συνδεθεί με κωδικό πρόσβασης.',\n      assignAsOwner_title: 'Ορισμός ως ιδιοκτήτης',\n      atLeastOneListMustBePresent: 'Πρέπει να υπάρχει τουλάχιστον μία λίστα',\n      attachment: 'Συνημμένο',\n      attachments: 'Συνημμένα',\n      authentication: 'Ταυτοποίηση',\n      background: 'Φόντο',\n      baseCustomFields_title: 'Βασικά προσαρμοσμένα πεδία',\n      baseGroup: 'Βασική ομάδα',\n      board: 'Πίνακας',\n      boardActions_title: 'Ενέργειες πίνακα',\n      boardNotFound_title: 'Ο πίνακας δεν βρέθηκε',\n      boardSubscribed: 'Εγγραφή στον πίνακα',\n      boardUser: 'Χρήστης πίνακα',\n      byCreationTime: 'Κατά χρόνο δημιουργίας',\n      byDefault: 'Από προεπιλογή',\n      byDueDate: 'Κατά ημερομηνία λήξης',\n      canBeInvitedToWorkInBoards: 'Μπορεί να προσκληθεί να εργαστεί σε πίνακες.',\n      canComment: 'Μπορεί να σχολιάσει',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Μπορεί να δημιουργήσει δικά του έργα και να προσκληθεί να εργαστεί σε άλλα.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Μπορεί να επεξεργαστεί τη διάταξη του πίνακα και να αναθέσει μέλη σε κάρτες.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Μπορεί να διαχειριστεί τις ρυθμίσεις του συστήματος και να λειτουργεί ως ιδιοκτήτης έργου.',\n      canOnlyViewBoard: 'Μπορεί μόνο να δει τον πίνακα.',\n      cardActions_title: 'Ενέργειες κάρτας',\n      cardNotFound_title: 'Η κάρτα δεν βρέθηκε',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Οι κάρτες σε αυτήν τη λίστα είναι διαθέσιμες σε όλα τα μέλη του πίνακα.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Οι κάρτες σε αυτήν τη λίστα είναι ολοκληρωμένες και έτοιμες για αρχειοθέτηση.',\n      cardsOnThisListAreReadyToBeWorkedOn:\n        'Οι κάρτες σε αυτήν τη λίστα είναι έτοιμες για επεξεργασία.',\n      clickHereOrRefreshPageToUpdate: '<0>Κάντε κλικ εδώ</0> ή ανανεώστε τη σελίδα για ενημέρωση.',\n      clientHostnameInEhlo: 'Όνομα κεντρικού υπολογιστή πελάτη στο EHLO',\n      closed: 'Κλειστό',\n      color: 'Χρώμα',\n      comments: 'Σχόλια',\n      contentExceedsLimit: 'Το περιεχόμενο υπερβαίνει το όριο {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Το περιεχόμενο αυτού του συνημμένου είναι πολύ μεγάλο για εμφάνιση.',\n      copy_inline: 'αντίγραφο',\n      createBoard_title: 'Δημιουργία πίνακα',\n      createCustomFieldGroup_title: 'Δημιουργία ομάδας προσαρμοσμένων πεδίων',\n      createLabel_title: 'Δημιουργία ετικέτας',\n      createNewOneOrSelectExistingOne: 'Δημιουργήστε νέο ή επιλέξτε<br />υπάρχον.',\n      createProject_title: 'Δημιουργία έργου',\n      createTextFile_title: 'Δημιουργία αρχείου κειμένου',\n      creator: 'Δημιουργός',\n      currentPassword: 'Τρέχων κωδικός',\n      currentUser: 'Τρέχων χρήστης',\n      customFieldGroup_title: 'Ομάδα προσαρμοσμένων πεδίων',\n      customFieldGroups_title: 'Ομάδες προσαρμοσμένων πεδίων',\n      customField_title: 'Προσαρμοσμένο πεδίο',\n      customFields_title: 'Προσαρμοσμένα πεδία',\n      customerPanel_title: 'Πίνακας πελάτη',\n      dangerZone_title: 'Επικίνδυνη ζώνη',\n      date: 'Ημερομηνία',\n      deactivateUser_title: 'Απενεργοποίηση χρήστη',\n      defaultCardType_title: 'Προεπιλεγμένος τύπος κάρτας',\n      defaultFrom: 'Προεπιλεγμένο \"από\"',\n      defaultView_title: 'Προεπιλεγμένη προβολή',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Διαγράψτε όλους τους πίνακες για να μπορέσετε να διαγράψετε αυτό το έργο',\n      deleteApiKey_title: 'Διαγραφή κλειδιού API',\n      deleteAttachment_title: 'Διαγραφή συνημμένου',\n      deleteBackgroundImage_title: 'Διαγραφή εικόνας φόντου',\n      deleteBoard_title: 'Διαγραφή πίνακα',\n      deleteCardForever_title: 'Διαγραφή κάρτας οριστικά',\n      deleteCard_title: 'Διαγραφή κάρτας',\n      deleteComment_title: 'Διαγραφή σχολίου',\n      deleteCustomFieldGroup_title: 'Διαγραφή ομάδας προσαρμοσμένων πεδίων',\n      deleteCustomField_title: 'Διαγραφή προσαρμοσμένου πεδίου',\n      deleteLabel_title: 'Διαγραφή ετικέτας',\n      deleteList_title: 'Διαγραφή λίστας',\n      deleteNotificationService_title: 'Διαγραφή υπηρεσίας ειδοποιήσεων',\n      deleteProject_title: 'Διαγραφή έργου',\n      deleteTaskList_title: 'Διαγραφή λίστας εργασιών',\n      deleteTask_title: 'Διαγραφή εργασίας',\n      deleteUser_title: 'Διαγραφή χρήστη',\n      deleteWebhook_title: 'Διαγραφή webhook',\n      deletedUser_title: 'Διαγραμμένος χρήστης',\n      description: 'Περιγραφή',\n      display: 'Εμφάνιση',\n      displayCardAges: 'Εμφάνιση ηλικίας καρτών',\n      dropFileToUpload: 'Σύρετε το αρχείο για μεταφόρτωση',\n      dueDate_title: 'Ημερομηνία λήξης',\n      dynamicAndUnevenlySpacedLayout: 'Δυναμική και άνισα κατανεμημένη διάταξη.',\n      editAttachment_title: 'Επεξεργασία συνημμένου',\n      editAvatar_title: 'Επεξεργασία avatar',\n      editColor_title: 'Επεξεργασία χρώματος',\n      editCustomFieldGroup_title: 'Επεξεργασία ομάδας προσαρμοσμένων πεδίων',\n      editCustomField_title: 'Επεξεργασία προσαρμοσμένου πεδίου',\n      editDueDate_title: 'Επεξεργασία ημερομηνίας λήξης',\n      editEmail_title: 'Επεξεργασία e-mail',\n      editInformation_title: 'Επεξεργασία πληροφοριών',\n      editLabel_title: 'Επεξεργασία ετικέτας',\n      editPassword_title: 'Επεξεργασία κωδικού',\n      editPermissions_title: 'Επεξεργασία δικαιωμάτων',\n      editRole_title: 'Επεξεργασία ρόλου',\n      editStopwatch_title: 'Επεξεργασία χρονομέτρου',\n      editType_title: 'Επεξεργασία τύπου',\n      editUsername_title: 'Επεξεργασία ονόματος χρήστη',\n      editor: 'Επεξεργαστής',\n      editors: 'Επεξεργαστές',\n      email: 'E-mail',\n      emptyTrash_title: 'Άδειασμα κάδου',\n      enterCardTitle: 'Εισάγετε τίτλο κάρτας...',\n      enterDescription: 'Εισάγετε περιγραφή...',\n      enterFilename: 'Εισάγετε όνομα αρχείου',\n      enterListTitle: 'Εισάγετε τίτλο λίστας...',\n      enterTaskDescription: 'Εισάγετε περιγραφή εργασίας...',\n      events: 'Γεγονότα',\n      excludedEvents: 'Εξαιρούμενα γεγονότα',\n      expandTaskListsByDefault: 'Επέκταση λιστών εργασιών από προεπιλογή',\n      filterByLabels_title: 'Φιλτράρισμα κατά ετικέτες',\n      filterByMembers_title: 'Φιλτράρισμα κατά μέλη',\n      forPersonalProjects: 'Για προσωπικά έργα.',\n      forTeamBasedProjects: 'Για έργα βασισμένα σε ομάδες.',\n      fromComputer_title: 'Από υπολογιστή',\n      fromTrello: 'Από Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Το πλήρες κλειδί είναι κρυφό για λόγους ασφαλείας. Αναδημιουργήστε το για να δημιουργήσετε ένα νέο.',\n      general: 'Γενικά',\n      gradients: 'Διαβαθμίσεις',\n      grid: 'Πλέγμα',\n      hideCompletedTasks: 'Απόκρυψη ολοκληρωμένων εργασιών',\n      hideFromProjectListAndFavorites: 'Απόκρυψη από τη λίστα έργων και τα αγαπημένα',\n      host: 'Κεντρικός υπολογιστής',\n      hours: 'Ώρες',\n      identity: 'Ταυτότητα',\n      importBoard_title: 'Εισαγωγή πίνακα',\n      information: 'Πληροφορίες',\n      invalidCurrentPassword: 'Μη έγκυρος τρέχων κωδικός',\n      kanban: 'Kanban',\n      labels: 'Ετικέτες',\n      language: 'Γλώσσα',\n      leaveBoard_title: 'Αποχώρηση από τον πίνακα',\n      leaveProject_title: 'Αποχώρηση από το έργο',\n      limitCardTypesToDefaultOne: 'Περιορισμός τύπων καρτών στον προεπιλεγμένο',\n      linkToCard: 'Σύνδεσμος στην κάρτα',\n      list: 'Λίστα',\n      listActions_title: 'Ενέργειες λίστας',\n      lists: 'Λίστες',\n      makeProjectPrivate_title: 'Κάντε το έργο ιδιωτικό',\n      makeProjectShared_title: 'Κάντε το έργο κοινόχρηστο',\n      managers: 'Διαχειριστές',\n      memberActions_title: 'Ενέργειες μέλους',\n      members: 'Μέλη',\n      minutes: 'Λεπτά',\n      moreActions: 'Περισσότερες ενέργειες',\n      moreActions_title: 'Περισσότερες ενέργειες',\n      moveCard_title: 'Μετακίνηση κάρτας',\n      moveList_title: 'Μετακίνηση λίστας',\n      myOwn_title: 'Δικά μου',\n      name: 'Όνομα',\n      newEmail: 'Νέο e-mail',\n      newPassword: 'Νέος κωδικός',\n      newUsername: 'Νέο όνομα χρήστη',\n      newVersionAvailable: 'Διαθέσιμη νέα έκδοση',\n      newestFirst: 'Νεότερα πρώτα',\n      noApiKeyCreated: 'Δεν έχει δημιουργηθεί κλειδί API.',\n      noBoards: 'Δεν υπάρχουν πίνακες',\n      noCardsFound: 'Δεν βρέθηκαν κάρτες.',\n      noConnectionToServer: 'Δεν υπάρχει σύνδεση με τον διακομιστή',\n      noLists: 'Δεν υπάρχουν λίστες',\n      noProjects: 'Δεν υπάρχουν έργα',\n      noUnreadNotifications: 'Δεν υπάρχουν μη αναγνωσμένες ειδοποιήσεις.',\n      notifications: 'Ειδοποιήσεις',\n      oldestFirst: 'Παλαιότερα πρώτα',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Μόνο ένας διαχειριστής πρέπει να παραμείνει για να γίνει αυτό το έργο ιδιωτικό',\n      openBoard_title: 'Άνοιγμα πίνακα',\n      optional_inline: 'προαιρετικό',\n      organization: 'Οργάνωση',\n      others: 'Άλλοι',\n      passwordIsSet: 'Ο κωδικός πρόσβασης έχει οριστεί',\n      phone: 'Τηλέφωνο',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'Το PLANKA χρησιμοποιεί το <1><0>Apprise</0></1> για να στέλνει ειδοποιήσεις σε πάνω από 100 δημοφιλείς υπηρεσίες.',\n      port: 'Θύρα',\n      preferences: 'Προτιμήσεις',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Συμβουλή: πατήστε Ctrl-V (Cmd-V σε Mac) για να προσθέσετε συνημμένο από το πρόχειρο.',\n      private: 'Ιδιωτικό',\n      project: 'Έργο',\n      projectNotFound_title: 'Το έργο δεν βρέθηκε',\n      projectOwner: 'Ιδιοκτήτης έργου',\n      referenceDataAndKnowledgeStorage: 'Αποθήκευση δεδομένων και γνώσης αναφοράς.',\n      regenerateApiKey_title: 'Αναδημιουργία κλειδιού API',\n      rejectUnauthorizedTlsCertificates: 'Απόρριψη μη εξουσιοδοτημένων πιστοποιητικών TLS',\n      removeManager_title: 'Αφαίρεση διαχειριστή',\n      removeMember_title: 'Αφαίρεση μέλους',\n      role: 'Ρόλος',\n      saveThisKeyItWillNotBeShownAgain: 'Αποθηκεύστε αυτό το κλειδί — δεν θα εμφανιστεί ξανά!',\n      searchCards: 'Αναζήτηση καρτών...',\n      searchCustomFieldGroups: 'Αναζήτηση ομάδων προσαρμοσμένων πεδίων...',\n      searchCustomFields: 'Αναζήτηση προσαρμοσμένων πεδίων...',\n      searchLabels: 'Αναζήτηση ετικετών...',\n      searchLists: 'Αναζήτηση λιστών...',\n      searchMembers: 'Αναζήτηση μελών...',\n      searchProjects: 'Αναζήτηση έργων...',\n      searchUsers: 'Αναζήτηση χρηστών...',\n      seconds: 'Δευτερόλεπτα',\n      selectAssignee_title: 'Επιλογή υπευθύνου',\n      selectBoard: 'Επιλογή πίνακα',\n      selectList: 'Επιλογή λίστας',\n      selectListToRestoreThisCard: 'Επιλέξτε λίστα για επαναφορά αυτής της κάρτας',\n      selectOrder_title: 'Επιλογή σειράς',\n      selectPermissions_title: 'Επιλογή δικαιωμάτων',\n      selectProject: 'Επιλογή έργου',\n      selectRole_title: 'Επιλογή ρόλου',\n      selectType_title: 'Επιλογή τύπου',\n      sequentialDisplayOfCards: 'Διαδοχική εμφάνιση καρτών.',\n      settings: 'Ρυθμίσεις',\n      shared: 'Κοινόχρηστο',\n      sharedWithMe_title: 'Κοινόχρηστο με εμένα',\n      showOnFrontOfCard: 'Εμφάνιση στο μπροστινό μέρος της κάρτας',\n      smtp: 'SMTP',\n      sortList_title: 'Ταξινόμηση λίστας',\n      sourceCardIsNoLongerAvailableForCopying:\n        'Η κάρτα πηγής δεν είναι πλέον διαθέσιμη για αντιγραφή.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'Η κάρτα πηγής δεν είναι πλέον διαθέσιμη για μετακίνηση.',\n      stopwatch: 'Χρονόμετρο',\n      story: 'Ιστορία',\n      subscribeToCardWhenCommenting: 'Εγγραφή στην κάρτα κατά τη σχολιασμό',\n      subscribeToMyOwnCardsByDefault: 'Αυτόματη εγγραφή στις δικές μου κάρτες',\n      taskActions_title: 'Ενέργειες εργασίας',\n      taskAssignmentAndProjectCompletion: 'Ανάθεση εργασίας και ολοκλήρωση έργου.',\n      taskListActions_title: 'Ενέργειες λίστας εργασιών',\n      taskList_title: 'Λίστα εργασιών',\n      team: 'Ομάδα',\n      termsOfService_title: 'Όροι χρήσης',\n      testLog_title: 'Αρχείο καταγραφής δοκιμών',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'Δεν υπάρχει διαθέσιμη προεπισκόπηση για αυτό το συνημμένο.',\n      time: 'Ώρα',\n      title: 'Τίτλος',\n      trash: 'Κάδος',\n      trashHasBeenSuccessfullyEmptied: 'Ο κάδος αδειάστηκε με επιτυχία.',\n      turnOffRecentCardHighlighting: 'Απενεργοποίηση επισήμανσης πρόσφατων καρτών',\n      typeNameToConfirm: 'Πληκτρολογήστε το όνομα για επιβεβαίωση.',\n      typeTitleToConfirm: 'Πληκτρολογήστε τον τίτλο για επιβεβαίωση.',\n      unlinkSso_title: 'Αποσύνδεση SSO',\n      unsavedChanges: 'Μη αποθηκευμένες αλλαγές',\n      uploadFailedFileIsTooBig: 'Η μεταφόρτωση απέτυχε: το αρχείο είναι πολύ μεγάλο.',\n      uploadFailedNotEnoughStorageSpace:\n        'Η μεταφόρτωση απέτυχε: δεν υπάρχει αρκετός χώρος αποθήκευσης.',\n      uploadedImages: 'Μεταφορτωμένες εικόνες',\n      url: 'URL',\n      useSecureConnection: 'Χρήση ασφαλούς σύνδεσης',\n      userActions_title: 'Ενέργειες χρήστη',\n      userAddedCardToList: '<0>{{user}}</0> πρόσθεσε <2>{{card}}</2> στη λίστα {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> πρόσθεσε αυτήν την κάρτα στη λίστα {{list}}',\n      userAddedUserToCard:\n        '<0>{{actorUser}}</0> πρόσθεσε τον/την {{addedUser}} στην <4>{{card}}</4>',\n      userAddedUserToThisCard:\n        '<0>{{actorUser}}</0> πρόσθεσε τον/την {{addedUser}} σε αυτήν την κάρτα',\n      userAddedYouToCard: '<0>{{user}}</0> σας πρόσθεσε στην <2>{{card}}</2>',\n      userCompletedTaskOnCard:\n        '<0>{{user}}</0> ολοκλήρωσε την εργασία {{task}} στην <4>{{card}}</4>',\n      userCompletedTaskOnThisCard:\n        '<0>{{user}}</0> ολοκλήρωσε την εργασία {{task}} σε αυτήν την κάρτα',\n      userJoinedCard: '<0>{{user}}</0> προστέθηκε στην <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> προστέθηκε σε αυτήν την κάρτα',\n      userLeftCard: '<0>{{user}}</0> αποχώρησε από την <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> άφησε νέο σχόλιο «{{comment}}» στην <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> αποχώρησε από αυτήν την κάρτα',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> σημείωσε την εργασία {{task}} ως μη ολοκληρωμένη στην <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> σημείωσε την εργασία {{task}} ως μη ολοκληρωμένη σε αυτήν την κάρτα',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> σας ανέφερε σε σχόλιο «{{comment}}» στην <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> μετέφερε την <2>{{card}}</2> από τη λίστα {{fromList}} στη λίστα {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> μετέφερε αυτήν την κάρτα από τη λίστα {{fromList}} στη λίστα {{toList}}',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0> αφαίρεσε τον/την {{removedUser}} από την <4>{{card}}</4>',\n      userRemovedUserFromThisCard:\n        '<0>{{actorUser}}</0> αφαίρεσε τον/την {{removedUser}} από αυτήν την κάρτα',\n      username: 'Όνομα χρήστη',\n      users: 'Χρήστες',\n      viewer: 'Θεατής',\n      viewers: 'Θεατές',\n      visualTaskManagementWithLists: 'Οπτική διαχείριση εργασιών με λίστες.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Τι νέο υπάρχει',\n      withoutBaseGroup: 'Χωρίς βασική ομάδα',\n      writeComment: 'Γράψτε ένα σχόλιο...',\n    },\n\n    action: {\n      activateUser: 'Ενεργοποίηση χρήστη',\n      activateUser_title: 'Ενεργοποίηση χρήστη',\n      addAnotherCard: 'Προσθήκη άλλης κάρτας',\n      addAnotherList: 'Προσθήκη άλλης λίστας',\n      addAnotherTask: 'Προσθήκη άλλης εργασίας',\n      addCard: 'Προσθήκη κάρτας',\n      addCard_title: 'Προσθήκη κάρτας',\n      addComment: 'Προσθήκη σχολίου',\n      addCustomField: 'Προσθήκη προσαρμοσμένου πεδίου',\n      addCustomFieldGroup: 'Προσθήκη ομάδας προσαρμοσμένων πεδίων',\n      addList: 'Προσθήκη λίστας',\n      addMember: 'Προσθήκη μέλους',\n      addMoreDetailedDescription: 'Προσθήκη πιο λεπτομερούς περιγραφής',\n      addTask: 'Προσθήκη εργασίας',\n      addTaskList: 'Προσθήκη λίστας εργασιών',\n      addToCard: 'Προσθήκη στην κάρτα',\n      addUser: 'Προσθήκη χρήστη',\n      addWebhook: 'Προσθήκη webhook',\n      archive: 'Αρχειοθέτηση',\n      archiveCard: 'Αρχειοθέτηση κάρτας',\n      archiveCard_title: 'Αρχειοθέτηση κάρτας',\n      archiveCards: 'Αρχειοθέτηση καρτών',\n      archiveCards_title: 'Αρχειοθέτηση καρτών',\n      assignAsOwner: 'Ορισμός ως ιδιοκτήτης',\n      cancel: 'Ακύρωση',\n      copy: 'Αντιγραφή',\n      copyCard_title: 'Αντιγραφή κάρτας',\n      createApiKey: 'Δημιουργία κλειδιού API',\n      createBoard: 'Δημιουργία πίνακα',\n      createCustomFieldGroup: 'Δημιουργία ομάδας προσαρμοσμένων πεδίων',\n      createFile: 'Δημιουργία αρχείου',\n      createLabel: 'Δημιουργία ετικέτας',\n      createNewLabel: 'Δημιουργία νέας ετικέτας',\n      createProject: 'Δημιουργία έργου',\n      cut: 'Αποκοπή',\n      cutCard_title: 'Αποκοπή κάρτας',\n      deactivateUser: 'Απενεργοποίηση χρήστη',\n      deactivateUser_title: 'Απενεργοποίηση χρήστη',\n      delete: 'Διαγραφή',\n      deleteApiKey: 'Διαγραφή κλειδιού API',\n      deleteAttachment: 'Διαγραφή συνημμένου',\n      deleteAvatar: 'Διαγραφή avatar',\n      deleteBackgroundImage: 'Διαγραφή εικόνας φόντου',\n      deleteBoard: 'Διαγραφή πίνακα',\n      deleteBoard_title: 'Διαγραφή πίνακα',\n      deleteCard: 'Διαγραφή κάρτας',\n      deleteCardForever: 'Διαγραφή κάρτας οριστικά',\n      deleteCard_title: 'Διαγραφή κάρτας',\n      deleteComment: 'Διαγραφή σχολίου',\n      deleteCustomField: 'Διαγραφή προσαρμοσμένου πεδίου',\n      deleteCustomFieldGroup: 'Διαγραφή ομάδας προσαρμοσμένων πεδίων',\n      deleteForever_title: 'Διαγραφή οριστικά',\n      deleteGroup: 'Διαγραφή ομάδας',\n      deleteLabel: 'Διαγραφή ετικέτας',\n      deleteList: 'Διαγραφή λίστας',\n      deleteList_title: 'Διαγραφή λίστας',\n      deleteNotificationService: 'Διαγραφή υπηρεσίας ειδοποιήσεων',\n      deleteProject: 'Διαγραφή έργου',\n      deleteProject_title: 'Διαγραφή έργου',\n      deleteTask: 'Διαγραφή εργασίας',\n      deleteTaskList: 'Διαγραφή λίστας εργασιών',\n      deleteTask_title: 'Διαγραφή εργασίας',\n      deleteUser: 'Διαγραφή χρήστη',\n      deleteUser_title: 'Διαγραφή χρήστη',\n      deleteWebhook: 'Διαγραφή webhook',\n      dismissAll: 'Απόρριψη όλων',\n      download: 'Λήψη',\n      duplicateCard_title: 'Διπλασιασμός κάρτας',\n      edit: 'Επεξεργασία',\n      editColor_title: 'Επεξεργασία χρώματος',\n      editDescription_title: 'Επεξεργασία περιγραφής',\n      editDueDate_title: 'Επεξεργασία ημερομηνίας λήξης',\n      editEmail_title: 'Επεξεργασία e-mail',\n      editGroup: 'Επεξεργασία ομάδας',\n      editInformation_title: 'Επεξεργασία πληροφοριών',\n      editPassword_title: 'Επεξεργασία κωδικού',\n      editPermissions: 'Επεξεργασία δικαιωμάτων',\n      editRole_title: 'Επεξεργασία ρόλου',\n      editStopwatch_title: 'Επεξεργασία χρονομέτρου',\n      editTitle_title: 'Επεξεργασία τίτλου',\n      editType_title: 'Επεξεργασία τύπου',\n      editUsername_title: 'Επεξεργασία ονόματος χρήστη',\n      emptyTrash: 'Άδειασμα κάδου',\n      emptyTrash_title: 'Άδειασμα κάδου',\n      import: 'Εισαγωγή',\n      join: 'Συμμετοχή',\n      leave: 'Αποχώρηση',\n      leaveBoard: 'Αποχώρηση από τον πίνακα',\n      leaveProject: 'Αποχώρηση από το έργο',\n      logOut_title: 'Αποσύνδεση',\n      makeCover_title: 'Ορισμός ως εξώφυλλο',\n      makeProjectPrivate: 'Κάντε το έργο ιδιωτικό',\n      makeProjectPrivate_title: 'Κάντε το έργο ιδιωτικό',\n      makeProjectShared: 'Κάντε το έργο κοινόχρηστο',\n      makeProjectShared_title: 'Κάντε το έργο κοινόχρηστο',\n      move: 'Μετακίνηση',\n      moveCard_title: 'Μετακίνηση κάρτας',\n      moveList_title: 'Μετακίνηση λίστας',\n      regenerateApiKey: 'Αναδημιουργία κλειδιού API',\n      remove: 'Αφαίρεση',\n      removeAssignee: 'Αφαίρεση υπευθύνου',\n      removeColor: 'Αφαίρεση χρώματος',\n      removeCover_title: 'Αφαίρεση εξωφύλλου',\n      removeFromBoard: 'Αφαίρεση από τον πίνακα',\n      removeFromProject: 'Αφαίρεση από το έργο',\n      removeManager: 'Αφαίρεση διαχειριστή',\n      removeMember: 'Αφαίρεση μέλους',\n      restoreToList: 'Επαναφορά στη {{list}}',\n      returnToBoard: 'Επιστροφή στον πίνακα',\n      save: 'Αποθήκευση',\n      sendTestEmail: 'Αποστολή δοκιμαστικού email',\n      showActive: 'Εμφάνιση ενεργών',\n      showAllAttachments: 'Εμφάνιση όλων των συνημμένων ({{hidden}} κρυφά)',\n      showCardsWithThisUser: 'Εμφάνιση καρτών με αυτόν τον χρήστη',\n      showDeactivated: 'Εμφάνιση απενεργοποιημένων',\n      showFewerAttachments: 'Εμφάνιση λιγότερων συνημμένων',\n      showLess: 'Εμφάνιση λιγότερων',\n      showMore: 'Εμφάνιση περισσότερων',\n      sortList_title: 'Ταξινόμηση λίστας',\n      start: 'Έναρξη',\n      stop: 'Διακοπή',\n      subscribe: 'Εγγραφή',\n      unlinkSso: 'Αποσύνδεση SSO',\n      unlinkSso_title: 'Αποσύνδεση SSO',\n      unsubscribe: 'Απεγγραφή',\n      uploadNewAvatar: 'Μεταφόρτωση νέου avatar',\n      uploadNewImage: 'Μεταφόρτωση νέας εικόνας',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/el-GR/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'el-GR',\n  country: 'gr',\n  name: 'Ελληνικά',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/el-GR/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Έχει επιτευχθεί το όριο ενεργών χρηστών',\n      adminLoginRequiredToInitializeInstance:\n        'Απαιτείται σύνδεση διαχειριστή για την αρχικοποίηση της εφαρμογής',\n      emailAlreadyInUse: 'Το e-mail χρησιμοποιείται ήδη',\n      emailOrUsername: 'E-mail ή όνομα χρήστη',\n      invalidCredentials: 'Μη έγκυρα στοιχεία σύνδεσης',\n      invalidEmailOrUsername: 'Μη έγκυρο e-mail ή όνομα χρήστη',\n      invalidPassword: 'Μη έγκυρος κωδικός',\n      logIn_title: 'Σύνδεση',\n      noInternetConnection: 'Δεν υπάρχει σύνδεση στο διαδίκτυο',\n      or: 'Ή',\n      pageNotFound_title: 'Η σελίδα δεν βρέθηκε',\n      password: 'Κωδικός',\n      poweredByPlanka: 'Με την υποστήριξη του <1>PLANKA</1>',\n      serverConnectionFailed: 'Αποτυχία σύνδεσης με τον διακομιστή',\n      unknownError: 'Άγνωστο σφάλμα, δοκιμάστε ξανά αργότερα',\n      useSingleSignOn: 'Χρήση Single Sign-On',\n      usernameAlreadyInUse: 'Το όνομα χρήστη χρησιμοποιείται ήδη',\n      whoops_title: 'Ωχ!',\n    },\n\n    action: {\n      cancelAndClose: 'Ακύρωση και κλείσιμο',\n      continue: 'Συνέχεια',\n      debugSso: 'Αποσφαλμάτωση SSO',\n      goBack: 'Επιστροφή',\n      goHome: 'Αρχική σελίδα',\n      logIn: 'Σύνδεση',\n      logInWithSso: 'Σύνδεση με SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/el-GR/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Αυτό είναι ένα κείμενο χωρίς τίτλο.\\nΤόσο ο τίτλος όσο και το κείμενο\\nμπορούν να επισημανθούν με έντονα, πλάγια, χρώμα,\\nδιαγραφή και υπογράμμιση.\",\n    \"text-with-head\": \"Αυτό είναι ένα κείμενο με τίτλο.\\nΤόσο ο τίτλος όσο και το κείμενο\\nμπορούν να επισημανθούν με έντονα, πλάγια, χρώμα,\\nδιαγραφή και υπογράμμιση.\",\n    \"heading\": \"Τίτλος\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Σφάλμα στον επεξεργαστή markdown\",\n    \"settings_wysiwyg\": \"Οπτικός επεξεργαστής (wysiwyg)\",\n    \"settings_markup\": \"Σήμανση markdown\",\n    \"markup_placeholder\": \"Εισάγετε σήμανση markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Αφαίρεση\",\n    \"empty_option\": \"Δεν βρέθηκαν αποτελέσματα\",\n    \"show_line_numbers\": \"Αρίθμηση γραμμών\"\n  },\n  \"common\": {\n    \"delete\": \"Διαγραφή\",\n    \"edit\": \"Επεξεργασία\",\n    \"toolbar_action_disabled\": \"Μη συμβατό στοιχείο σήμανσης\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Ακύρωση\",\n    \"common_action_submit\": \"Υποβολή\",\n    \"common_action_upload\": \"Επιλογή\",\n    \"common_tab_attach\": \"Προσθήκη από συσκευή\",\n    \"common_tab_link\": \"Προσθήκη μέσω συνδέσμου\",\n    \"common_link\": \"Σύνδεσμος\",\n    \"common_sizes\": \"Μέγεθος, px\",\n    \"image_name\": \"Τίτλος\",\n    \"image_link_href\": \"Σύνδεσμος εικόνας\",\n    \"image_link_href_help\": \"Η διεύθυνση στην οποία οδηγεί ο σύνδεσμος της εικόνας.\",\n    \"image_alt\": \"Εναλλακτικό κείμενο\",\n    \"image_alt_help\": \"Το εναλλακτικό κείμενο εμφανίζεται αν η εικόνα δεν μπορεί να φορτωθεί.\",\n    \"image_upload_help\": \"Εικόνα JPEG, GIF ή PNG έως 1 MB.\",\n    \"image_upload_failed\": \"Αποτυχία προσθήκης εικόνας\",\n    \"image_size_width\": \"Πλάτος\",\n    \"image_size_height\": \"Ύψος\",\n    \"link_url_help\": \"Η διεύθυνση στην οποία οδηγεί ο σύνδεσμος.\",\n    \"link_text\": \"Κείμενο συνδέσμου\",\n    \"link_text_help\": \"Το κείμενο που εμφανίζεται ως σύνδεσμος.\",\n    \"link_open_help\": \"Άνοιγμα του συνδέσμου σε νέα καρτέλα\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Επικεφαλίδα\",\n    \"header_hint\": \"# Το κείμενό σας\",\n    \"italic_title\": \"Πλάγια\",\n    \"italic_hint\": \"_Το κείμενό σας_\",\n    \"bold_title\": \"Έντονα\",\n    \"bold_hint\": \"**Το κείμενό σας**\",\n    \"strikethrough_title\": \"Διαγραφή\",\n    \"strikethrough_hint\": \"~~Το κείμενό σας~~\",\n    \"blockquote_title\": \"Παράθεση\",\n    \"blockquote_hint\": \"> Το κείμενό σας\",\n    \"code_title\": \"Κώδικας\",\n    \"code_hint\": \"```Το κείμενό σας```\",\n    \"link_title\": \"Σύνδεσμος\",\n    \"link_hint\": \"[Το κείμενό σας](url)\",\n    \"image_title\": \"Εικόνα\",\n    \"image_hint\": \"![Το κείμενό σας](url)\",\n    \"list_title\": \"Στοιχείο λίστας\",\n    \"list_hint\": \"- Το κείμενό σας\",\n    \"numbered-list_title\": \"Αριθμημένη λίστα\",\n    \"numbered-list_hint\": \"1. Το κείμενό σας\",\n    \"documentation\": \"Τεκμηρίωση\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Έντονα\",\n    \"code\": \"Κώδικας\",\n    \"code_inline\": \"Ενσωματωμένος κώδικας\",\n    \"codeblock\": \"Μπλοκ κώδικα\",\n    \"colorify\": \"Χρώμα κειμένου\",\n    \"colorify__color_blue\": \"Μπλε\",\n    \"colorify__color_default\": \"Προεπιλογή\",\n    \"colorify__color_gray\": \"Γκρι\",\n    \"colorify__color_green\": \"Πράσινο\",\n    \"colorify__color_orange\": \"Πορτοκαλί\",\n    \"colorify__color_red\": \"Κόκκινο\",\n    \"colorify__color_violet\": \"Μωβ\",\n    \"colorify__color_yellow\": \"Κίτρινο\",\n    \"colorify__group_text\": \"Κείμενο\",\n    \"cut\": \"Αποκοπή\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Τα emoji μπορούν να προστεθούν στο WYSIWYG ή χειροκίνητα με σήμανση\",\n    \"heading\": \"Επικεφαλίδα\",\n    \"heading1\": \"Επικεφαλίδα 1\",\n    \"heading2\": \"Επικεφαλίδα 2\",\n    \"heading3\": \"Επικεφαλίδα 3\",\n    \"heading4\": \"Επικεφαλίδα 4\",\n    \"heading5\": \"Επικεφαλίδα 5\",\n    \"heading6\": \"Επικεφαλίδα 6\",\n    \"hrule\": \"Διαχωριστική γραμμή\",\n    \"image\": \"Εικόνα\",\n    \"italic\": \"Πλάγια\",\n    \"link\": \"Σύνδεσμος\",\n    \"list\": \"Λίστα\",\n    \"list__action_lift\": \"Ανύψωση στοιχείου\",\n    \"list__action_sink\": \"Υποβιβασμός στοιχείου\",\n    \"list_action_disabled\": \"Αντίκειται στη λογική της λίστας\",\n    \"mark\": \"Επισήμανση\",\n    \"mono\": \"Μονοδιάστημα\",\n    \"more_action\": \"Περισσότερες ενέργειες\",\n    \"note\": \"Σημείωση\",\n    \"olist\": \"Αριθμημένη λίστα\",\n    \"quote\": \"Παράθεση\",\n    \"redo\": \"Επαναφορά\",\n    \"strike\": \"Διαγραφή\",\n    \"table\": \"Πίνακας\",\n    \"text\": \"Κείμενο\",\n    \"ulist\": \"Λίστα κουκκίδων\",\n    \"underline\": \"Υπογράμμιση\",\n    \"undo\": \"Αναίρεση\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Πληκτρολογήστε / για να χρησιμοποιήσετε εντολές...\",\n    \"checkbox\": \"Εισάγετε περιγραφή εργασίας...\",\n    \"deflist_term\": \"Όρος\",\n    \"deflist_desc\": \"Περιγραφή ορισμού\",\n    \"heading\": \"Επικεφαλίδα\",\n    \"cut_title\": \"Τίτλος\",\n    \"cut_content\": \"Περιεχόμενο που θα εμφανιστεί με κλικ\",\n    \"note_title\": \"Τίτλος\",\n    \"note_content\": \"Περιεχόμενο σημείωσης\",\n    \"table_cell\": \"Περιεχόμενο κελιού\",\n    \"select_filter\": \"Αναζήτηση γλωσσών...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Διάκριση πεζών-κεφαλαίων\",\n    \"label_whole-word\": \"Ολόκληρη λέξη\",\n    \"title\": \"Αναζήτηση στον κώδικα\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Δεν βρέθηκε\"\n  },\n  \"widgets\": {\n    \"image\": \"Προσθήκη εικόνας\",\n    \"link\": \"Προσθήκη συνδέσμου\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Σημείωση\",\n    \"tip\": \"Συμβουλή\",\n    \"warning\": \"Προειδοποίηση\",\n    \"alert\": \"Συναγερμός\",\n    \"remove\": \"Αφαίρεση\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Προσθήκη στήλης πριν\",\n    \"column.add.after\": \"Προσθήκη στήλης μετά\",\n    \"column.remove\": \"Αφαίρεση στήλης\",\n    \"column.remove.multiple\": \"Αφαίρεση στηλών\",\n    \"row.add.before\": \"Προσθήκη γραμμής πριν\",\n    \"row.add.after\": \"Προσθήκη γραμμής μετά\",\n    \"row.remove\": \"Αφαίρεση γραμμής\",\n    \"row.remove.multiple\": \"Αφαίρεση γραμμών\",\n    \"cells.clear\": \"Εκκαθάριση κελιών\",\n    \"table.remove\": \"Αφαίρεση πίνακα\",\n    \"table.menu.cell.align.left\": \"Στοίχιση κελιού αριστερά\",\n    \"table.menu.cell.align.right\": \"Στοίχιση κελιού δεξιά\",\n    \"table.menu.cell.align.center\": \"Στοίχιση κελιού στο κέντρο\",\n    \"table.menu.row.add\": \"Προσθήκη γραμμής μετά\",\n    \"table.menu.row.remove\": \"Αφαίρεση γραμμής\",\n    \"table.menu.column.add\": \"Προσθήκη στήλης μετά\",\n    \"table.menu.column.remove\": \"Αφαίρεση στήλης\",\n    \"table.menu.convert.yfm\": \"Μετατροπή σε πίνακα YFM\",\n    \"table.menu.table.remove\": \"Αφαίρεση πίνακα\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/en-GB/core.js",
    "content": "import dateFns from 'date-fns/locale/en-GB';\nimport timeAgo from 'javascript-time-ago/locale/en-GB';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'P',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'at' p\",\n    fullDate: 'd MMM y',\n    fullDateTime: \"d MMMM y 'at' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'About the App',\n      aboutPlanka_title: 'About PLANKA',\n      accessToken: 'Access token',\n      account: 'Account',\n      actions: 'Actions',\n      activateUser_title: 'Activate User',\n      active: 'Active',\n      addAttachment_title: 'Add Attachment',\n      addCustomFieldGroup_title: 'Add Custom Field Group',\n      addCustomField_title: 'Add Custom Field',\n      addManager_title: 'Add Manager',\n      addMember_title: 'Add Member',\n      addTaskList_title: 'Add Task List',\n      addUser_title: 'Add User',\n      admin: 'Admin',\n      administration: 'Administration',\n      all: 'All',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'All changes will be automatically saved<br />after connection restored.',\n      alphabetically: 'Alphabetically',\n      alwaysDisplayCardCreator: 'Always display card creator',\n      apiKeyCreated_title: 'API Key Created',\n      apiKey_title: 'API Key',\n      archive: 'Archive',\n      archiveCard_title: 'Archive Card',\n      archiveCards_title: 'Archive Cards',\n      areYouSureYouWantToActivateThisUser: 'Are you sure you want to activate this user?',\n      areYouSureYouWantToArchiveCards: 'Are you sure you want to archive cards?',\n      areYouSureYouWantToArchiveThisCard: 'Are you sure you want to archive this card?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Are you sure you want to assign this project manager as owner?',\n      areYouSureYouWantToDeactivateThisUser: 'Are you sure you want to deactivate this user?',\n      areYouSureYouWantToDeleteThisApiKey: 'Are you sure you want to delete this API key?',\n      areYouSureYouWantToDeleteThisAttachment: 'Are you sure you want to delete this attachment?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Are you sure you want to delete this background image?',\n      areYouSureYouWantToDeleteThisBoard: 'Are you sure you want to delete this board?',\n      areYouSureYouWantToDeleteThisCard: 'Are you sure you want to delete this card?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Are you sure you want to delete this card forever?',\n      areYouSureYouWantToDeleteThisComment: 'Are you sure you want to delete this comment?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Are you sure you want to delete this custom field?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Are you sure you want to delete this custom field group?',\n      areYouSureYouWantToDeleteThisLabel: 'Are you sure you want to delete this label?',\n      areYouSureYouWantToDeleteThisList:\n        'Are you sure you want to delete this list? All cards will be moved to trash.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Are you sure you want to delete this notification service?',\n      areYouSureYouWantToDeleteThisProject: 'Are you sure you want to delete this project?',\n      areYouSureYouWantToDeleteThisTask: 'Are you sure you want to delete this task?',\n      areYouSureYouWantToDeleteThisTaskList: 'Are you sure you want to delete this task list?',\n      areYouSureYouWantToDeleteThisUser: 'Are you sure you want to delete this user?',\n      areYouSureYouWantToDeleteThisWebhook: 'Are you sure you want to delete this webhook?',\n      areYouSureYouWantToEmptyTrash: 'Are you sure you want to empty the trash?',\n      areYouSureYouWantToLeaveBoard: 'Are you sure you want to leave the board?',\n      areYouSureYouWantToLeaveProject: 'Are you sure you want to leave the project?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Are you sure you want to make this project private?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Are you sure you want to make this project shared?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Are you sure you want to regenerate this API key? The previous key will no longer work.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Are you sure you want to remove this manager from the project?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Are you sure you want to remove this member from the board?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Are you sure you want to unlink SSO from this user? This will allow the user to log in with a password.',\n      assignAsOwner_title: 'Assign As Owner',\n      atLeastOneListMustBePresent: 'At least one list must be present',\n      attachment: 'Attachment',\n      attachments: 'Attachments',\n      authentication: 'Authentication',\n      background: 'Background',\n      baseCustomFields_title: 'Base Custom Fields',\n      baseGroup: 'Base group',\n      board: 'Board',\n      boardActions_title: 'Board Actions',\n      boardNotFound_title: 'Board Not Found',\n      boardSubscribed: 'Board subscribed',\n      boardUser: 'Board user',\n      byCreationTime: 'By creation time',\n      byDefault: 'By default',\n      byDueDate: 'By due date',\n      canBeInvitedToWorkInBoards: 'Can be invited to work in boards.',\n      canComment: 'Can comment',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Can create own projects and be invited to work in others.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Can edit the board layout and assign members to cards.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Can manage the system wide settings and act as project owner.',\n      canOnlyViewBoard: 'Can only view the board.',\n      cardActions_title: 'Card Actions',\n      cardNotFound_title: 'Card Not Found',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Cards on this list are available to all board members.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Cards on this list are complete and ready to be archived.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Cards on this list are ready to be worked on.',\n      clickHereOrRefreshPageToUpdate: '<0>Click here</0> or refresh the page to update.',\n      clientHostnameInEhlo: 'Client hostname in EHLO',\n      closed: 'Closed',\n      color: 'Color',\n      comments: 'Comments',\n      contentExceedsLimit: 'Content exceeds {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay: 'Content of this attachment is too big to display.',\n      copy_inline: 'copy',\n      createBoard_title: 'Create Board',\n      createCustomFieldGroup_title: 'Create Custom Field Group',\n      createLabel_title: 'Create Label',\n      createNewOneOrSelectExistingOne: 'Create a new one or select<br />an existing one.',\n      createProject_title: 'Create Project',\n      createTextFile_title: 'Create Text File',\n      creator: 'Creator',\n      currentPassword: 'Current password',\n      currentUser: 'Current user',\n      customFieldGroup_title: 'Custom Field Group',\n      customFieldGroups_title: 'Custom Field Groups',\n      customField_title: 'Custom Field',\n      customFields_title: 'Custom Fields',\n      customerPanel_title: 'Customer Panel',\n      dangerZone_title: 'Danger Zone',\n      date: 'Date',\n      deactivateUser_title: 'Deactivate User',\n      defaultCardType_title: 'Default Card Type',\n      defaultFrom: 'Default \"from\"',\n      defaultView_title: 'Default View',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Delete all boards to be able to delete this project',\n      deleteApiKey_title: 'Delete API Key',\n      deleteAttachment_title: 'Delete Attachment',\n      deleteBackgroundImage_title: 'Delete Background Image',\n      deleteBoard_title: 'Delete Board',\n      deleteCardForever_title: 'Delete Card Forever',\n      deleteCard_title: 'Delete Card',\n      deleteComment_title: 'Delete Comment',\n      deleteCustomFieldGroup_title: 'Delete Custom Field Group',\n      deleteCustomField_title: 'Delete Custom Field',\n      deleteLabel_title: 'Delete Label',\n      deleteList_title: 'Delete List',\n      deleteNotificationService_title: 'Delete Notification Service',\n      deleteProject_title: 'Delete Project',\n      deleteTaskList_title: 'Delete Task List',\n      deleteTask_title: 'Delete Task',\n      deleteUser_title: 'Delete User',\n      deleteWebhook_title: 'Delete Webhook',\n      deletedUser_title: 'Deleted User',\n      description: 'Description',\n      display: 'Display',\n      displayCardAges: 'Display card ages',\n      dropFileToUpload: 'Drop file to upload',\n      dueDate_title: 'Due Date',\n      dynamicAndUnevenlySpacedLayout: 'Dynamic and unevenly spaced layout.',\n      editAttachment_title: 'Edit Attachment',\n      editAvatar_title: 'Edit Avatar',\n      editColor_title: 'Edit Color',\n      editCustomFieldGroup_title: 'Edit Custom Field Group',\n      editCustomField_title: 'Edit Custom Field',\n      editDueDate_title: 'Edit Due Date',\n      editEmail_title: 'Edit E-mail',\n      editInformation_title: 'Edit Information',\n      editLabel_title: 'Edit Label',\n      editPassword_title: 'Edit Password',\n      editPermissions_title: 'Edit Permissions',\n      editRole_title: 'Edit Role',\n      editStopwatch_title: 'Edit Stopwatch',\n      editType_title: 'Edit Type',\n      editUsername_title: 'Edit Username',\n      editor: 'Editor',\n      editors: 'Editors',\n      email: 'E-mail',\n      emptyTrash_title: 'Empty Trash',\n      enterCardTitle: 'Enter card title...',\n      enterDescription: 'Enter description...',\n      enterFilename: 'Enter filename',\n      enterListTitle: 'Enter list title...',\n      enterTaskDescription: 'Enter task description...',\n      events: 'Events',\n      excludedEvents: 'Excluded events',\n      expandTaskListsByDefault: 'Expand task lists by default',\n      filterByLabels_title: 'Filter By Labels',\n      filterByMembers_title: 'Filter By Members',\n      forPersonalProjects: 'For personal projects.',\n      forTeamBasedProjects: 'For team-based projects.',\n      fromComputer_title: 'From Computer',\n      fromTrello: 'From Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'The full key is hidden for security reasons. Regenerate it to create a new one.',\n      general: 'General',\n      gradients: 'Gradients',\n      grid: 'Grid',\n      hideCompletedTasks: 'Hide completed tasks',\n      hideFromProjectListAndFavorites: 'Hide from project list and favorites',\n      host: 'Host',\n      hours: 'Hours',\n      identity: 'Identity',\n      importBoard_title: 'Import Board',\n      information: 'Information',\n      invalidCurrentPassword: 'Invalid current password',\n      kanban: 'Kanban',\n      labels: 'Labels',\n      language: 'Language',\n      leaveBoard_title: 'Leave Board',\n      leaveProject_title: 'Leave Project',\n      limitCardTypesToDefaultOne: 'Limit card types to default one',\n      linkToCard: 'Link to card',\n      list: 'List',\n      listActions_title: 'List Actions',\n      lists: 'Lists',\n      makeProjectPrivate_title: 'Make Project Private',\n      makeProjectShared_title: 'Make Project Shared',\n      managers: 'Managers',\n      memberActions_title: 'Member Actions',\n      members: 'Members',\n      minutes: 'Minutes',\n      moreActions: 'More actions',\n      moreActions_title: 'More Actions',\n      moveCard_title: 'Move Card',\n      moveList_title: 'Move List',\n      myOwn_title: 'My Own',\n      name: 'Name',\n      newEmail: 'New e-mail',\n      newPassword: 'New password',\n      newUsername: 'New username',\n      newVersionAvailable: 'New version available',\n      newestFirst: 'Newest first',\n      noApiKeyCreated: 'No API key created.',\n      noBoards: 'No boards',\n      noCardsFound: 'No cards found.',\n      noConnectionToServer: 'No connection to server',\n      noLists: 'No lists',\n      noProjects: 'No projects',\n      noUnreadNotifications: 'No unread notifications.',\n      notifications: 'Notifications',\n      oldestFirst: 'Oldest first',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Only one manager should remain to make this project private',\n      openBoard_title: 'Open Board',\n      optional_inline: 'optional',\n      organization: 'Organization',\n      others: 'Others',\n      passwordIsSet: 'Password is set',\n      phone: 'Phone',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA uses <1><0>Apprise</0></1> to send notifications to over 100 popular services.',\n      port: 'Port',\n      preferences: 'Preferences',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Tip: press Ctrl-V (Cmd-V on Mac) to add an attachment from the clipboard.',\n      private: 'Private',\n      project: 'Project',\n      projectNotFound_title: 'Project Not Found',\n      projectOwner: 'Project owner',\n      referenceDataAndKnowledgeStorage: 'Reference data and knowledge storage.',\n      regenerateApiKey_title: 'Regenerate API Key',\n      rejectUnauthorizedTlsCertificates: 'Reject unauthorized TLS certificates',\n      removeManager_title: 'Remove Manager',\n      removeMember_title: 'Remove Member',\n      role: 'Role',\n      saveThisKeyItWillNotBeShownAgain: 'Save this key — it will not be shown again!',\n      searchCards: 'Search cards...',\n      searchCustomFieldGroups: 'Search custom field groups...',\n      searchCustomFields: 'Search custom fields...',\n      searchLabels: 'Search labels...',\n      searchLists: 'Search lists...',\n      searchMembers: 'Search members...',\n      searchProjects: 'Search projects...',\n      searchUsers: 'Search users...',\n      seconds: 'Seconds',\n      selectAssignee_title: 'Select Assignee',\n      selectBoard: 'Select board',\n      selectList: 'Select list',\n      selectListToRestoreThisCard: 'Select list to restore this card',\n      selectOrder_title: 'Select Order',\n      selectPermissions_title: 'Select Permissions',\n      selectProject: 'Select project',\n      selectRole_title: 'Select Role',\n      selectType_title: 'Select Type',\n      sequentialDisplayOfCards: 'Sequential display of cards.',\n      settings: 'Settings',\n      shared: 'Shared',\n      sharedWithMe_title: 'Shared With Me',\n      showOnFrontOfCard: 'Show on front of card',\n      smtp: 'SMTP',\n      sortList_title: 'Sort List',\n      sourceCardIsNoLongerAvailableForCopying: 'Source card is no longer available for copying.',\n      sourceCardIsNoLongerAvailableForMoving: 'Source card is no longer available for moving.',\n      stopwatch: 'Stopwatch',\n      story: 'Story',\n      subscribeToCardWhenCommenting: 'Subscribe to card when commenting',\n      subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default',\n      taskActions_title: 'Task Actions',\n      taskAssignmentAndProjectCompletion: 'Task assignment and project completion.',\n      taskListActions_title: 'Task List Actions',\n      taskList_title: 'Task List',\n      team: 'Team',\n      termsOfService_title: 'Terms of Service',\n      testLog_title: 'Test Log',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'There is no preview available for this attachment.',\n      time: 'Time',\n      title: 'Title',\n      trash: 'Trash',\n      trashHasBeenSuccessfullyEmptied: 'Trash has been successfully emptied.',\n      turnOffRecentCardHighlighting: 'Turn off recent card highlighting',\n      typeNameToConfirm: 'Type the name to confirm.',\n      typeTitleToConfirm: 'Type the title to confirm.',\n      unlinkSso_title: 'Unlink SSO',\n      unsavedChanges: 'Unsaved changes',\n      uploadFailedFileIsTooBig: 'Upload failed: File is too big.',\n      uploadFailedNotEnoughStorageSpace: 'Upload failed: Not enough storage space.',\n      uploadedImages: 'Uploaded images',\n      url: 'URL',\n      useSecureConnection: 'Use secure connection',\n      userActions_title: 'User Actions',\n      userAddedCardToList: '<0>{{user}}</0> added <2>{{card}}</2> to {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> added this card to {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> added {{addedUser}} to <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> added {{addedUser}} to this card',\n      userAddedYouToCard: '<0>{{user}}</0> added you to <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> completed {{task}} on <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> completed {{task}} on this card',\n      userJoinedCard: '<0>{{user}}</0> joined <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> joined this card',\n      userLeftCard: '<0>{{user}}</0> left <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> left a new comment «{{comment}}» to <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> left this card',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> marked {{task}} incomplete on <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard: '<0>{{user}}</0> marked {{task}} incomplete on this card',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> mentioned you in a comment «{{comment}}» on <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> moved <2>{{card}}</2> from {{fromList}} to {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> moved this card from {{fromList}} to {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> removed {{removedUser}} from <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> removed {{removedUser}} from this card',\n      username: 'Username',\n      users: 'Users',\n      viewer: 'Viewer',\n      viewers: 'Viewers',\n      visualTaskManagementWithLists: 'Visual task management with lists.',\n      webhooks: 'Webhooks',\n      whatsNew_title: \"What's New\",\n      withoutBaseGroup: 'Without base group',\n      writeComment: 'Write a comment...',\n    },\n\n    action: {\n      activateUser: 'Activate user',\n      activateUser_title: 'Activate User',\n      addAnotherCard: 'Add another card',\n      addAnotherList: 'Add another list',\n      addAnotherTask: 'Add another task',\n      addCard: 'Add card',\n      addCard_title: 'Add Card',\n      addComment: 'Add comment',\n      addCustomField: 'Add custom field',\n      addCustomFieldGroup: 'Add custom field group',\n      addList: 'Add list',\n      addMember: 'Add member',\n      addMoreDetailedDescription: 'Add more detailed description',\n      addTask: 'Add task',\n      addTaskList: 'Add task list',\n      addToCard: 'Add to card',\n      addUser: 'Add user',\n      addWebhook: 'Add webhook',\n      archive: 'Archive',\n      archiveCard: 'Archive card',\n      archiveCard_title: 'Archive Card',\n      archiveCards: 'Archive cards',\n      archiveCards_title: 'Archive Cards',\n      assignAsOwner: 'Assign as owner',\n      cancel: 'Cancel',\n      copy: 'Copy',\n      copyCard_title: 'Copy Card',\n      createApiKey: 'Create API key',\n      createBoard: 'Create board',\n      createCustomFieldGroup: 'Create custom field group',\n      createFile: 'Create file',\n      createLabel: 'Create label',\n      createNewLabel: 'Create new label',\n      createProject: 'Create project',\n      cut: 'Cut',\n      cutCard_title: 'Cut Card',\n      deactivateUser: 'Deactivate user',\n      deactivateUser_title: 'Deactivate User',\n      delete: 'Delete',\n      deleteApiKey: 'Delete API key',\n      deleteAttachment: 'Delete attachment',\n      deleteAvatar: 'Delete avatar',\n      deleteBackgroundImage: 'Delete background image',\n      deleteBoard: 'Delete board',\n      deleteBoard_title: 'Delete Board',\n      deleteCard: 'Delete card',\n      deleteCardForever: 'Delete card forever',\n      deleteCard_title: 'Delete Card',\n      deleteComment: 'Delete comment',\n      deleteCustomField: 'Delete custom field',\n      deleteCustomFieldGroup: 'Delete custom field group',\n      deleteForever_title: 'Delete Forever',\n      deleteGroup: 'Delete group',\n      deleteLabel: 'Delete label',\n      deleteList: 'Delete list',\n      deleteList_title: 'Delete List',\n      deleteNotificationService: 'Delete notification service',\n      deleteProject: 'Delete project',\n      deleteProject_title: 'Delete Project',\n      deleteTask: 'Delete task',\n      deleteTaskList: 'Delete task list',\n      deleteTask_title: 'Delete Task',\n      deleteUser: 'Delete user',\n      deleteUser_title: 'Delete User',\n      deleteWebhook: 'Delete webhook',\n      dismissAll: 'Dismiss all',\n      download: 'Download',\n      duplicateCard_title: 'Duplicate Card',\n      edit: 'Edit',\n      editColor_title: 'Edit Color',\n      editDescription_title: 'Edit Description',\n      editDueDate_title: 'Edit Due Date',\n      editEmail_title: 'Edit E-mail',\n      editGroup: 'Edit group',\n      editInformation_title: 'Edit Information',\n      editPassword_title: 'Edit Password',\n      editPermissions: 'Edit permissions',\n      editRole_title: 'Edit Role',\n      editStopwatch_title: 'Edit Stopwatch',\n      editTitle_title: 'Edit Title',\n      editType_title: 'Edit Type',\n      editUsername_title: 'Edit Username',\n      emptyTrash: 'Empty trash',\n      emptyTrash_title: 'Empty Trash',\n      import: 'Import',\n      join: 'Join',\n      leave: 'Leave',\n      leaveBoard: 'Leave board',\n      leaveProject: 'Leave project',\n      logOut_title: 'Log Out',\n      makeCover_title: 'Make Cover',\n      makeProjectPrivate: 'Make project private',\n      makeProjectPrivate_title: 'Make Project Private',\n      makeProjectShared: 'Make project shared',\n      makeProjectShared_title: 'Make Project Shared',\n      move: 'Move',\n      moveCard_title: 'Move Card',\n      moveList_title: 'Move List',\n      regenerateApiKey: 'Regenerate API key',\n      remove: 'Remove',\n      removeAssignee: 'Remove assignee',\n      removeColor: 'Remove color',\n      removeCover_title: 'Remove Cover',\n      removeFromBoard: 'Remove from board',\n      removeFromProject: 'Remove from project',\n      removeManager: 'Remove manager',\n      removeMember: 'Remove member',\n      restoreToList: 'Restore to {{list}}',\n      returnToBoard: 'Return to board',\n      save: 'Save',\n      sendTestEmail: 'Send test email',\n      showActive: 'Show active',\n      showAllAttachments: 'Show all attachments ({{hidden}} hidden)',\n      showCardsWithThisUser: 'Show cards with this user',\n      showDeactivated: 'Show deactivated',\n      showFewerAttachments: 'Show fewer attachments',\n      showLess: 'Show less',\n      showMore: 'Show more',\n      sortList_title: 'Sort List',\n      start: 'Start',\n      stop: 'Stop',\n      subscribe: 'Subscribe',\n      unlinkSso: 'Unlink SSO',\n      unlinkSso_title: 'Unlink SSO',\n      unsubscribe: 'Unsubscribe',\n      uploadNewAvatar: 'Upload new avatar',\n      uploadNewImage: 'Upload new image',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/en-GB/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'en-GB',\n  country: 'gb',\n  name: 'English',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/en-GB/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Active users limit reached',\n      adminLoginRequiredToInitializeInstance: 'Admin login required to initialize instance',\n      emailAlreadyInUse: 'E-mail already in use',\n      emailOrUsername: 'E-mail or username',\n      invalidCredentials: 'Invalid credentials',\n      invalidEmailOrUsername: 'Invalid e-mail or username',\n      invalidPassword: 'Invalid password',\n      logIn_title: 'Log In',\n      noInternetConnection: 'No internet connection',\n      or: 'Or',\n      pageNotFound_title: 'Page Not Found',\n      password: 'Password',\n      poweredByPlanka: 'Powered by <1>PLANKA</1>',\n      serverConnectionFailed: 'Server connection failed',\n      unknownError: 'Unknown error, try again later',\n      useSingleSignOn: 'Use single sign-on',\n      usernameAlreadyInUse: 'Username already in use',\n      whoops_title: 'Whoops!',\n    },\n\n    action: {\n      cancelAndClose: 'Cancel and close',\n      continue: 'Continue',\n      debugSso: 'Debug SSO',\n      goBack: 'Go back',\n      goHome: 'Go home',\n      logIn: 'Log in',\n      logInWithSso: 'Log in with SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/en-GB/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"This is a text without a title.\\nBoth the title and the text\\ncan be highlighted in bold, italic, color,\\nstrikethrough, and underline.\",\n    \"text-with-head\": \"This is a text with a title.\\nBoth the title and the text\\ncan be highlighted in bold, italic, color,\\nstrikethrough, and underline.\",\n    \"heading\": \"Title\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Error in markdown editor\",\n    \"settings_wysiwyg\": \"Visual editor (wysiwyg)\",\n    \"settings_markup\": \"Markdown markup\",\n    \"markup_placeholder\": \"Enter markdown markup...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Remove\",\n    \"empty_option\": \"No matches found\",\n    \"show_line_numbers\": \"Line numbers\"\n  },\n  \"common\": {\n    \"delete\": \"Delete\",\n    \"edit\": \"Edit\",\n    \"toolbar_action_disabled\": \"Incompatible markup element\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Cancel\",\n    \"common_action_submit\": \"Submit\",\n    \"common_action_upload\": \"Select\",\n    \"common_tab_attach\": \"Add from device\",\n    \"common_tab_link\": \"Add by link\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Size, px\",\n    \"image_name\": \"Title\",\n    \"image_link_href\": \"Image link\",\n    \"image_link_href_help\": \"Adress the image link leads to.\",\n    \"image_alt\": \"Alt text\",\n    \"image_alt_help\": \"Alt text is displayed if the image cannot be loaded.\",\n    \"image_upload_help\": \"JPEG, GIF or PNG image no larger than 1 MB.\",\n    \"image_upload_failed\": \"Failed to add image\",\n    \"image_size_width\": \"Width\",\n    \"image_size_height\": \"Height\",\n    \"link_url_help\": \"Address the link leads to.\",\n    \"link_text\": \"Link text\",\n    \"link_text_help\": \"Text displayed as a link.\",\n    \"link_open_help\": \"Open the link in a new tab\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Header\",\n    \"header_hint\": \"# Your text\",\n    \"italic_title\": \"Italic\",\n    \"italic_hint\": \"_Your text_\",\n    \"bold_title\": \"Bold\",\n    \"bold_hint\": \"**Your text**\",\n    \"strikethrough_title\": \"Strikethrough\",\n    \"strikethrough_hint\": \"~~Your text~~\",\n    \"blockquote_title\": \"Blockquote\",\n    \"blockquote_hint\": \"> Your text\",\n    \"code_title\": \"Code\",\n    \"code_hint\": \"```Your text```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[Your text](url)\",\n    \"image_title\": \"Image\",\n    \"image_hint\": \"![Your text](url)\",\n    \"list_title\": \"List item\",\n    \"list_hint\": \"- Your text\",\n    \"numbered-list_title\": \"Numbered list\",\n    \"numbered-list_hint\": \"1. Your text\",\n    \"documentation\": \"Documentation\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Bold\",\n    \"code\": \"Code\",\n    \"code_inline\": \"Inline code\",\n    \"codeblock\": \"Code block\",\n    \"colorify\": \"Text color\",\n    \"colorify__color_blue\": \"Blue\",\n    \"colorify__color_default\": \"Default\",\n    \"colorify__color_gray\": \"Gray\",\n    \"colorify__color_green\": \"Green\",\n    \"colorify__color_orange\": \"Orange\",\n    \"colorify__color_red\": \"Red\",\n    \"colorify__color_violet\": \"Violet\",\n    \"colorify__color_yellow\": \"Yellow\",\n    \"colorify__group_text\": \"Text\",\n    \"cut\": \"Cut\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emojis can be added in WYSIWYG or manually with markup\",\n    \"heading\": \"Heading\",\n    \"heading1\": \"Heading 1\",\n    \"heading2\": \"Heading 2\",\n    \"heading3\": \"Heading 3\",\n    \"heading4\": \"Heading 4\",\n    \"heading5\": \"Heading 5\",\n    \"heading6\": \"Heading 6\",\n    \"hrule\": \"Separator\",\n    \"image\": \"Image\",\n    \"italic\": \"Italic\",\n    \"link\": \"Link\",\n    \"list\": \"List\",\n    \"list__action_lift\": \"Lift item\",\n    \"list__action_sink\": \"Sink item\",\n    \"list_action_disabled\": \"Contradicts logic of the list\",\n    \"mark\": \"Marked\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"More action\",\n    \"note\": \"Note\",\n    \"olist\": \"Ordered list\",\n    \"quote\": \"Quote\",\n    \"redo\": \"Redo\",\n    \"strike\": \"Strikethrough\",\n    \"table\": \"Table\",\n    \"text\": \"Text\",\n    \"ulist\": \"Bullet list\",\n    \"underline\": \"Underline\",\n    \"undo\": \"Undo\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Type / to use slash commands...\",\n    \"checkbox\": \"Enter task description...\",\n    \"deflist_term\": \"Term\",\n    \"deflist_desc\": \"Definition description\",\n    \"heading\": \"Heading\",\n    \"cut_title\": \"Title\",\n    \"cut_content\": \"Content to be displayed on click\",\n    \"note_title\": \"Title\",\n    \"note_content\": \"Note content\",\n    \"table_cell\": \"Cell content\",\n    \"select_filter\": \"Search languages...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Case sensitive\",\n    \"label_whole-word\": \"Whole word\",\n    \"title\": \"Search in code\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Not found\"\n  },\n  \"widgets\": {\n    \"image\": \"Add image\",\n    \"link\": \"Add link\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Note\",\n    \"tip\": \"Tip\",\n    \"warning\": \"Warning\",\n    \"alert\": \"Alert\",\n    \"remove\": \"Remove\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Add column before\",\n    \"column.add.after\": \"Add column after\",\n    \"column.remove\": \"Remove column\",\n    \"column.remove.multiple\": \"Remove columns\",\n    \"row.add.before\": \"Add row before\",\n    \"row.add.after\": \"Add row after\",\n    \"row.remove\": \"Remove row\",\n    \"row.remove.multiple\": \"Remove rows\",\n    \"cells.clear\": \"Clear cells\",\n    \"table.remove\": \"Remove table\",\n    \"table.menu.cell.align.left\": \"Align cell content to the left\",\n    \"table.menu.cell.align.right\": \"Align cell content to the right\",\n    \"table.menu.cell.align.center\": \"Align cell content to the center\",\n    \"table.menu.row.add\": \"Add row after\",\n    \"table.menu.row.remove\": \"Remove row\",\n    \"table.menu.column.add\": \"Add column after\",\n    \"table.menu.column.remove\": \"Remove column\",\n    \"table.menu.convert.yfm\": \"Convert to YFM table\",\n    \"table.menu.table.remove\": \"Remove table\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/en-US/core.js",
    "content": "import markdownEditor from './markdown-editor.json';\n\nexport default {\n  markdownEditor,\n\n  format: {\n    date: 'M/d/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'MMM d',\n    longDateTime: \"MMMM d 'at' p\",\n    fullDate: 'MMM d, y',\n    fullDateTime: \"MMMM d, y 'at' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'About the App',\n      aboutPlanka_title: 'About PLANKA',\n      accessToken: 'Access token',\n      account: 'Account',\n      actions: 'Actions',\n      activateUser_title: 'Activate User',\n      active: 'Active',\n      addAttachment_title: 'Add Attachment',\n      addCustomFieldGroup_title: 'Add Custom Field Group',\n      addCustomField_title: 'Add Custom Field',\n      addManager_title: 'Add Manager',\n      addMember_title: 'Add Member',\n      addTaskList_title: 'Add Task List',\n      addUser_title: 'Add User',\n      admin: 'Admin',\n      administration: 'Administration',\n      all: 'All',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'All changes will be automatically saved<br />after connection restored.',\n      alphabetically: 'Alphabetically',\n      alwaysDisplayCardCreator: 'Always display card creator',\n      apiKeyCreated_title: 'API Key Created',\n      apiKey_title: 'API Key',\n      archive: 'Archive',\n      archiveCard_title: 'Archive Card',\n      archiveCards_title: 'Archive Cards',\n      areYouSureYouWantToActivateThisUser: 'Are you sure you want to activate this user?',\n      areYouSureYouWantToArchiveCards: 'Are you sure you want to archive cards?',\n      areYouSureYouWantToArchiveThisCard: 'Are you sure you want to archive this card?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Are you sure you want to assign this project manager as owner?',\n      areYouSureYouWantToDeactivateThisUser: 'Are you sure you want to deactivate this user?',\n      areYouSureYouWantToDeleteThisApiKey: 'Are you sure you want to delete this API key?',\n      areYouSureYouWantToDeleteThisAttachment: 'Are you sure you want to delete this attachment?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Are you sure you want to delete this background image?',\n      areYouSureYouWantToDeleteThisBoard: 'Are you sure you want to delete this board?',\n      areYouSureYouWantToDeleteThisCard: 'Are you sure you want to delete this card?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Are you sure you want to delete this card forever?',\n      areYouSureYouWantToDeleteThisComment: 'Are you sure you want to delete this comment?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Are you sure you want to delete this custom field?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Are you sure you want to delete this custom field group?',\n      areYouSureYouWantToDeleteThisLabel: 'Are you sure you want to delete this label?',\n      areYouSureYouWantToDeleteThisList:\n        'Are you sure you want to delete this list? All cards will be moved to trash.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Are you sure you want to delete this notification service?',\n      areYouSureYouWantToDeleteThisProject: 'Are you sure you want to delete this project?',\n      areYouSureYouWantToDeleteThisTask: 'Are you sure you want to delete this task?',\n      areYouSureYouWantToDeleteThisTaskList: 'Are you sure you want to delete this task list?',\n      areYouSureYouWantToDeleteThisUser: 'Are you sure you want to delete this user?',\n      areYouSureYouWantToDeleteThisWebhook: 'Are you sure you want to delete this webhook?',\n      areYouSureYouWantToEmptyTrash: 'Are you sure you want to empty the trash?',\n      areYouSureYouWantToLeaveBoard: 'Are you sure you want to leave the board?',\n      areYouSureYouWantToLeaveProject: 'Are you sure you want to leave the project?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Are you sure you want to make this project private?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Are you sure you want to make this project shared?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Are you sure you want to regenerate this API key? The previous key will no longer work.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Are you sure you want to remove this manager from the project?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Are you sure you want to remove this member from the board?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Are you sure you want to unlink SSO from this user? This will allow the user to log in with a password.',\n      assignAsOwner_title: 'Assign As Owner',\n      atLeastOneListMustBePresent: 'At least one list must be present',\n      attachment: 'Attachment',\n      attachments: 'Attachments',\n      authentication: 'Authentication',\n      background: 'Background',\n      baseCustomFields_title: 'Base Custom Fields',\n      baseGroup: 'Base group',\n      board: 'Board',\n      boardActions_title: 'Board Actions',\n      boardNotFound_title: 'Board Not Found',\n      boardSubscribed: 'Board subscribed',\n      boardUser: 'Board user',\n      byCreationTime: 'By creation time',\n      byDefault: 'By default',\n      byDueDate: 'By due date',\n      canBeInvitedToWorkInBoards: 'Can be invited to work in boards.',\n      canComment: 'Can comment',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Can create own projects and be invited to work in others.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Can edit the board layout and assign members to cards.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Can manage the system wide settings and act as project owner.',\n      canOnlyViewBoard: 'Can only view the board.',\n      cardActions_title: 'Card Actions',\n      cardNotFound_title: 'Card Not Found',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Cards on this list are available to all board members.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Cards on this list are complete and ready to be archived.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Cards on this list are ready to be worked on.',\n      clickHereOrRefreshPageToUpdate: '<0>Click here</0> or refresh the page to update.',\n      clientHostnameInEhlo: 'Client hostname in EHLO',\n      closed: 'Closed',\n      color: 'Color',\n      comments: 'Comments',\n      contentExceedsLimit: 'Content exceeds {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay: 'Content of this attachment is too big to display.',\n      copy_inline: 'copy',\n      createBoard_title: 'Create Board',\n      createCustomFieldGroup_title: 'Create Custom Field Group',\n      createLabel_title: 'Create Label',\n      createNewOneOrSelectExistingOne: 'Create a new one or select<br />an existing one.',\n      createProject_title: 'Create Project',\n      createTextFile_title: 'Create Text File',\n      creator: 'Creator',\n      currentPassword: 'Current password',\n      currentUser: 'Current user',\n      customFieldGroup_title: 'Custom Field Group',\n      customFieldGroups_title: 'Custom Field Groups',\n      customField_title: 'Custom Field',\n      customFields_title: 'Custom Fields',\n      customerPanel_title: 'Customer Panel',\n      dangerZone_title: 'Danger Zone',\n      date: 'Date',\n      deactivateUser_title: 'Deactivate User',\n      defaultCardType_title: 'Default Card Type',\n      defaultFrom: 'Default \"from\"',\n      defaultView_title: 'Default View',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Delete all boards to be able to delete this project',\n      deleteApiKey_title: 'Delete API Key',\n      deleteAttachment_title: 'Delete Attachment',\n      deleteBackgroundImage_title: 'Delete Background Image',\n      deleteBoard_title: 'Delete Board',\n      deleteCardForever_title: 'Delete Card Forever',\n      deleteCard_title: 'Delete Card',\n      deleteComment_title: 'Delete Comment',\n      deleteCustomFieldGroup_title: 'Delete Custom Field Group',\n      deleteCustomField_title: 'Delete Custom Field',\n      deleteLabel_title: 'Delete Label',\n      deleteList_title: 'Delete List',\n      deleteNotificationService_title: 'Delete Notification Service',\n      deleteProject_title: 'Delete Project',\n      deleteTaskList_title: 'Delete Task List',\n      deleteTask_title: 'Delete Task',\n      deleteUser_title: 'Delete User',\n      deleteWebhook_title: 'Delete Webhook',\n      deletedUser_title: 'Deleted User',\n      description: 'Description',\n      display: 'Display',\n      displayCardAges: 'Display card ages',\n      dropFileToUpload: 'Drop file to upload',\n      dueDate_title: 'Due Date',\n      dynamicAndUnevenlySpacedLayout: 'Dynamic and unevenly spaced layout.',\n      editAttachment_title: 'Edit Attachment',\n      editAvatar_title: 'Edit Avatar',\n      editColor_title: 'Edit Color',\n      editCustomFieldGroup_title: 'Edit Custom Field Group',\n      editCustomField_title: 'Edit Custom Field',\n      editDueDate_title: 'Edit Due Date',\n      editEmail_title: 'Edit E-mail',\n      editInformation_title: 'Edit Information',\n      editLabel_title: 'Edit Label',\n      editPassword_title: 'Edit Password',\n      editPermissions_title: 'Edit Permissions',\n      editRole_title: 'Edit Role',\n      editStopwatch_title: 'Edit Stopwatch',\n      editType_title: 'Edit Type',\n      editUsername_title: 'Edit Username',\n      editor: 'Editor',\n      editors: 'Editors',\n      email: 'E-mail',\n      emptyTrash_title: 'Empty Trash',\n      enterCardTitle: 'Enter card title...',\n      enterDescription: 'Enter description...',\n      enterFilename: 'Enter filename',\n      enterListTitle: 'Enter list title...',\n      enterTaskDescription: 'Enter task description...',\n      events: 'Events',\n      excludedEvents: 'Excluded events',\n      expandTaskListsByDefault: 'Expand task lists by default',\n      filterByLabels_title: 'Filter By Labels',\n      filterByMembers_title: 'Filter By Members',\n      forPersonalProjects: 'For personal projects.',\n      forTeamBasedProjects: 'For team-based projects.',\n      fromComputer_title: 'From Computer',\n      fromTrello: 'From Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'The full key is hidden for security reasons. Regenerate it to create a new one.',\n      general: 'General',\n      gradients: 'Gradients',\n      grid: 'Grid',\n      hideCompletedTasks: 'Hide completed tasks',\n      hideFromProjectListAndFavorites: 'Hide from project list and favorites',\n      host: 'Host',\n      hours: 'Hours',\n      identity: 'Identity',\n      importBoard_title: 'Import Board',\n      information: 'Information',\n      invalidCurrentPassword: 'Invalid current password',\n      kanban: 'Kanban',\n      labels: 'Labels',\n      language: 'Language',\n      leaveBoard_title: 'Leave Board',\n      leaveProject_title: 'Leave Project',\n      limitCardTypesToDefaultOne: 'Limit card types to default one',\n      linkToCard: 'Link to card',\n      list: 'List',\n      listActions_title: 'List Actions',\n      lists: 'Lists',\n      makeProjectPrivate_title: 'Make Project Private',\n      makeProjectShared_title: 'Make Project Shared',\n      managers: 'Managers',\n      memberActions_title: 'Member Actions',\n      members: 'Members',\n      minutes: 'Minutes',\n      moreActions: 'More actions',\n      moreActions_title: 'More Actions',\n      moveCard_title: 'Move Card',\n      moveList_title: 'Move List',\n      myOwn_title: 'My Own',\n      name: 'Name',\n      newEmail: 'New e-mail',\n      newPassword: 'New password',\n      newUsername: 'New username',\n      newVersionAvailable: 'New version available',\n      newestFirst: 'Newest first',\n      noApiKeyCreated: 'No API key created.',\n      noBoards: 'No boards',\n      noCardsFound: 'No cards found.',\n      noConnectionToServer: 'No connection to server',\n      noLists: 'No lists',\n      noProjects: 'No projects',\n      noUnreadNotifications: 'No unread notifications.',\n      notifications: 'Notifications',\n      oldestFirst: 'Oldest first',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Only one manager should remain to make this project private',\n      openBoard_title: 'Open Board',\n      optional_inline: 'optional',\n      organization: 'Organization',\n      others: 'Others',\n      passwordIsSet: 'Password is set',\n      phone: 'Phone',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA uses <1><0>Apprise</0></1> to send notifications to over 100 popular services.',\n      port: 'Port',\n      preferences: 'Preferences',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Tip: press Ctrl-V (Cmd-V on Mac) to add an attachment from the clipboard.',\n      private: 'Private',\n      project: 'Project',\n      projectNotFound_title: 'Project Not Found',\n      projectOwner: 'Project owner',\n      referenceDataAndKnowledgeStorage: 'Reference data and knowledge storage.',\n      regenerateApiKey_title: 'Regenerate API Key',\n      rejectUnauthorizedTlsCertificates: 'Reject unauthorized TLS certificates',\n      removeManager_title: 'Remove Manager',\n      removeMember_title: 'Remove Member',\n      role: 'Role',\n      saveThisKeyItWillNotBeShownAgain: 'Save this key — it will not be shown again!',\n      searchCards: 'Search cards...',\n      searchCustomFieldGroups: 'Search custom field groups...',\n      searchCustomFields: 'Search custom fields...',\n      searchLabels: 'Search labels...',\n      searchLists: 'Search lists...',\n      searchMembers: 'Search members...',\n      searchProjects: 'Search projects...',\n      searchUsers: 'Search users...',\n      seconds: 'Seconds',\n      selectAssignee_title: 'Select Assignee',\n      selectBoard: 'Select board',\n      selectList: 'Select list',\n      selectListToRestoreThisCard: 'Select list to restore this card',\n      selectOrder_title: 'Select Order',\n      selectPermissions_title: 'Select Permissions',\n      selectProject: 'Select project',\n      selectRole_title: 'Select Role',\n      selectType_title: 'Select Type',\n      sequentialDisplayOfCards: 'Sequential display of cards.',\n      settings: 'Settings',\n      shared: 'Shared',\n      sharedWithMe_title: 'Shared With Me',\n      showOnFrontOfCard: 'Show on front of card',\n      smtp: 'SMTP',\n      sortList_title: 'Sort List',\n      sourceCardIsNoLongerAvailableForCopying: 'Source card is no longer available for copying.',\n      sourceCardIsNoLongerAvailableForMoving: 'Source card is no longer available for moving.',\n      stopwatch: 'Stopwatch',\n      story: 'Story',\n      subscribeToCardWhenCommenting: 'Subscribe to card when commenting',\n      subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default',\n      taskActions_title: 'Task Actions',\n      taskAssignmentAndProjectCompletion: 'Task assignment and project completion.',\n      taskListActions_title: 'Task List Actions',\n      taskList_title: 'Task List',\n      team: 'Team',\n      termsOfService_title: 'Terms of Service',\n      testLog_title: 'Test Log',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'There is no preview available for this attachment.',\n      time: 'Time',\n      title: 'Title',\n      trash: 'Trash',\n      trashHasBeenSuccessfullyEmptied: 'Trash has been successfully emptied.',\n      turnOffRecentCardHighlighting: 'Turn off recent card highlighting',\n      typeNameToConfirm: 'Type the name to confirm.',\n      typeTitleToConfirm: 'Type the title to confirm.',\n      unlinkSso_title: 'Inlink SSO',\n      unsavedChanges: 'Unsaved changes',\n      uploadFailedFileIsTooBig: 'Upload failed: File is too big.',\n      uploadFailedNotEnoughStorageSpace: 'Upload failed: Not enough storage space.',\n      uploadedImages: 'Uploaded images',\n      url: 'URL',\n      useSecureConnection: 'Use secure connection',\n      userActions_title: 'User Actions',\n      userAddedCardToList: '<0>{{user}}</0> added <2>{{card}}</2> to {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> added this card to {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> added {{addedUser}} to <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> added {{addedUser}} to this card',\n      userAddedYouToCard: '<0>{{user}}</0> added you to <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> completed {{task}} on <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> completed {{task}} on this card',\n      userJoinedCard: '<0>{{user}}</0> joined <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> joined this card',\n      userLeftCard: '<0>{{user}}</0> left <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> left a new comment «{{comment}}» to <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> left this card',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> marked {{task}} incomplete on <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard: '<0>{{user}}</0> marked {{task}} incomplete on this card',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> mentioned you in a comment «{{comment}}» on <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> moved <2>{{card}}</2> from {{fromList}} to {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> moved this card from {{fromList}} to {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> removed {{removedUser}} from <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> removed {{removedUser}} from this card',\n      username: 'Username',\n      users: 'Users',\n      viewer: 'Viewer',\n      viewers: 'Viewers',\n      visualTaskManagementWithLists: 'Visual task management with lists.',\n      webhooks: 'Webhooks',\n      whatsNew_title: \"What's New\",\n      withoutBaseGroup: 'Without base group',\n      writeComment: 'Write a comment...',\n    },\n\n    action: {\n      activateUser: 'Activate user',\n      activateUser_title: 'Activate User',\n      addAnotherCard: 'Add another card',\n      addAnotherList: 'Add another list',\n      addAnotherTask: 'Add another task',\n      addCard: 'Add card',\n      addCard_title: 'Add Card',\n      addComment: 'Add comment',\n      addCustomField: 'Add custom field',\n      addCustomFieldGroup: 'Add custom field group',\n      addList: 'Add list',\n      addMember: 'Add member',\n      addMoreDetailedDescription: 'Add more detailed description',\n      addTask: 'Add task',\n      addTaskList: 'Add task list',\n      addToCard: 'Add to card',\n      addUser: 'Add user',\n      addWebhook: 'Add webhook',\n      archive: 'Archive',\n      archiveCard: 'Archive card',\n      archiveCard_title: 'Archive Card',\n      archiveCards: 'Archive cards',\n      archiveCards_title: 'Archive Cards',\n      assignAsOwner: 'Assign as owner',\n      cancel: 'Cancel',\n      copy: 'Copy',\n      copyCard_title: 'Copy Card',\n      createApiKey: 'Create API key',\n      createBoard: 'Create board',\n      createCustomFieldGroup: 'Create custom field group',\n      createFile: 'Create file',\n      createLabel: 'Create label',\n      createNewLabel: 'Create new label',\n      createProject: 'Create project',\n      cut: 'Cut',\n      cutCard_title: 'Cut Card',\n      deactivateUser: 'Deactivate user',\n      deactivateUser_title: 'Deactivate User',\n      delete: 'Delete',\n      deleteApiKey: 'Delete API key',\n      deleteAttachment: 'Delete attachment',\n      deleteAvatar: 'Delete avatar',\n      deleteBackgroundImage: 'Delete background image',\n      deleteBoard: 'Delete board',\n      deleteBoard_title: 'Delete Board',\n      deleteCard: 'Delete card',\n      deleteCardForever: 'Delete card forever',\n      deleteCard_title: 'Delete Card',\n      deleteComment: 'Delete comment',\n      deleteCustomField: 'Delete custom field',\n      deleteCustomFieldGroup: 'Delete custom field group',\n      deleteForever_title: 'Delete Forever',\n      deleteGroup: 'Delete group',\n      deleteLabel: 'Delete label',\n      deleteList: 'Delete list',\n      deleteList_title: 'Delete List',\n      deleteNotificationService: 'Delete notification service',\n      deleteProject: 'Delete project',\n      deleteProject_title: 'Delete Project',\n      deleteTask: 'Delete task',\n      deleteTaskList: 'Delete task list',\n      deleteTask_title: 'Delete Task',\n      deleteUser: 'Delete user',\n      deleteUser_title: 'Delete User',\n      deleteWebhook: 'Delete webhook',\n      dismissAll: 'Dismiss all',\n      download: 'Download',\n      duplicateCard_title: 'Duplicate Card',\n      edit: 'Edit',\n      editColor_title: 'Edit Color',\n      editDescription_title: 'Edit Description',\n      editDueDate_title: 'Edit Due Date',\n      editEmail_title: 'Edit E-mail',\n      editGroup: 'Edit group',\n      editInformation_title: 'Edit Information',\n      editPassword_title: 'Edit Password',\n      editPermissions: 'Edit permissions',\n      editRole_title: 'Edit Role',\n      editStopwatch_title: 'Edit Stopwatch',\n      editTitle_title: 'Edit Title',\n      editType_title: 'Edit Type',\n      editUsername_title: 'Edit Username',\n      emptyTrash: 'Empty trash',\n      emptyTrash_title: 'Empty Trash',\n      import: 'Import',\n      join: 'Join',\n      leave: 'Leave',\n      leaveBoard: 'Leave board',\n      leaveProject: 'Leave project',\n      logOut_title: 'Log Out',\n      makeCover_title: 'Make Cover',\n      makeProjectPrivate: 'Make project private',\n      makeProjectPrivate_title: 'Make Project Private',\n      makeProjectShared: 'Make project shared',\n      makeProjectShared_title: 'Make Project Shared',\n      move: 'Move',\n      moveCard_title: 'Move Card',\n      moveList_title: 'Move List',\n      regenerateApiKey: 'Regenerate API key',\n      remove: 'Remove',\n      removeAssignee: 'Remove assignee',\n      removeColor: 'Remove color',\n      removeCover_title: 'Remove Cover',\n      removeFromBoard: 'Remove from board',\n      removeFromProject: 'Remove from project',\n      removeManager: 'Remove manager',\n      removeMember: 'Remove member',\n      restoreToList: 'Restore to {{list}}',\n      returnToBoard: 'Return to board',\n      save: 'Save',\n      sendTestEmail: 'Send test email',\n      showActive: 'Show active',\n      showAllAttachments: 'Show all attachments ({{hidden}} hidden)',\n      showCardsWithThisUser: 'Show cards with this user',\n      showDeactivated: 'Show deactivated',\n      showFewerAttachments: 'Show fewer attachments',\n      showLess: 'Show less',\n      showMore: 'Show more',\n      sortList_title: 'Sort List',\n      start: 'Start',\n      stop: 'Stop',\n      subscribe: 'Subscribe',\n      unlinkSso: 'Unlink SSO',\n      unlinkSso_title: 'Unlink SSO',\n      unsubscribe: 'Unsubscribe',\n      uploadNewAvatar: 'Upload new avatar',\n      uploadNewImage: 'Upload new image',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/en-US/index.js",
    "content": "import merge from 'lodash/merge';\n\nimport login from './login';\nimport core from './core';\n\nexport default {\n  language: 'en-US',\n  country: 'us',\n  name: 'English',\n  embeddedLocale: merge(login, core),\n};\n"
  },
  {
    "path": "client/src/locales/en-US/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Active users limit reached',\n      adminLoginRequiredToInitializeInstance: 'Admin login required to initialize instance',\n      emailAlreadyInUse: 'E-mail already in use',\n      emailOrUsername: 'E-mail or username',\n      invalidCredentials: 'Invalid credentials',\n      invalidEmailOrUsername: 'Invalid e-mail or username',\n      invalidPassword: 'Invalid password',\n      logIn_title: 'Log In',\n      noInternetConnection: 'No internet connection',\n      or: 'Or',\n      pageNotFound_title: 'Page Not Found',\n      password: 'Password',\n      poweredByPlanka: 'Powered by <1>PLANKA</1>',\n      serverConnectionFailed: 'Server connection failed',\n      unknownError: 'Unknown error, try again later',\n      useSingleSignOn: 'Use single sign-on',\n      usernameAlreadyInUse: 'Username already in use',\n      whoops_title: 'Whoops!',\n    },\n\n    action: {\n      cancelAndClose: 'Cancel and close',\n      continue: 'Continue',\n      debugSso: 'Debug SSO',\n      goBack: 'Go back',\n      goHome: 'Go home',\n      logIn: 'Log in',\n      logInWithSso: 'Log in with SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/en-US/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"This is a text without a title.\\nBoth the title and the text\\ncan be highlighted in bold, italic, color,\\nstrikethrough, and underline.\",\n    \"text-with-head\": \"This is a text with a title.\\nBoth the title and the text\\ncan be highlighted in bold, italic, color,\\nstrikethrough, and underline.\",\n    \"heading\": \"Title\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Error in markdown editor\",\n    \"settings_wysiwyg\": \"Visual editor (wysiwyg)\",\n    \"settings_markup\": \"Markdown markup\",\n    \"markup_placeholder\": \"Enter markdown markup...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Remove\",\n    \"empty_option\": \"No matches found\",\n    \"show_line_numbers\": \"Line numbers\"\n  },\n  \"common\": {\n    \"delete\": \"Delete\",\n    \"edit\": \"Edit\",\n    \"toolbar_action_disabled\": \"Incompatible markup element\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Cancel\",\n    \"common_action_submit\": \"Submit\",\n    \"common_action_upload\": \"Select\",\n    \"common_tab_attach\": \"Add from device\",\n    \"common_tab_link\": \"Add by link\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Size, px\",\n    \"image_name\": \"Title\",\n    \"image_link_href\": \"Image link\",\n    \"image_link_href_help\": \"Adress the image link leads to.\",\n    \"image_alt\": \"Alt text\",\n    \"image_alt_help\": \"Alt text is displayed if the image cannot be loaded.\",\n    \"image_upload_help\": \"JPEG, GIF or PNG image no larger than 1 MB.\",\n    \"image_upload_failed\": \"Failed to add image\",\n    \"image_size_width\": \"Width\",\n    \"image_size_height\": \"Height\",\n    \"link_url_help\": \"Address the link leads to.\",\n    \"link_text\": \"Link text\",\n    \"link_text_help\": \"Text displayed as a link.\",\n    \"link_open_help\": \"Open the link in a new tab\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Header\",\n    \"header_hint\": \"# Your text\",\n    \"italic_title\": \"Italic\",\n    \"italic_hint\": \"_Your text_\",\n    \"bold_title\": \"Bold\",\n    \"bold_hint\": \"**Your text**\",\n    \"strikethrough_title\": \"Strikethrough\",\n    \"strikethrough_hint\": \"~~Your text~~\",\n    \"blockquote_title\": \"Blockquote\",\n    \"blockquote_hint\": \"> Your text\",\n    \"code_title\": \"Code\",\n    \"code_hint\": \"```Your text```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[Your text](url)\",\n    \"image_title\": \"Image\",\n    \"image_hint\": \"![Your text](url)\",\n    \"list_title\": \"List item\",\n    \"list_hint\": \"- Your text\",\n    \"numbered-list_title\": \"Numbered list\",\n    \"numbered-list_hint\": \"1. Your text\",\n    \"documentation\": \"Documentation\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Bold\",\n    \"code\": \"Code\",\n    \"code_inline\": \"Inline code\",\n    \"codeblock\": \"Code block\",\n    \"colorify\": \"Text color\",\n    \"colorify__color_blue\": \"Blue\",\n    \"colorify__color_default\": \"Default\",\n    \"colorify__color_gray\": \"Gray\",\n    \"colorify__color_green\": \"Green\",\n    \"colorify__color_orange\": \"Orange\",\n    \"colorify__color_red\": \"Red\",\n    \"colorify__color_violet\": \"Violet\",\n    \"colorify__color_yellow\": \"Yellow\",\n    \"colorify__group_text\": \"Text\",\n    \"cut\": \"Cut\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emojis can be added in WYSIWYG or manually with markup\",\n    \"heading\": \"Heading\",\n    \"heading1\": \"Heading 1\",\n    \"heading2\": \"Heading 2\",\n    \"heading3\": \"Heading 3\",\n    \"heading4\": \"Heading 4\",\n    \"heading5\": \"Heading 5\",\n    \"heading6\": \"Heading 6\",\n    \"hrule\": \"Separator\",\n    \"image\": \"Image\",\n    \"italic\": \"Italic\",\n    \"link\": \"Link\",\n    \"list\": \"List\",\n    \"list__action_lift\": \"Lift item\",\n    \"list__action_sink\": \"Sink item\",\n    \"list_action_disabled\": \"Contradicts logic of the list\",\n    \"mark\": \"Marked\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"More action\",\n    \"note\": \"Note\",\n    \"olist\": \"Ordered list\",\n    \"quote\": \"Quote\",\n    \"redo\": \"Redo\",\n    \"strike\": \"Strikethrough\",\n    \"table\": \"Table\",\n    \"text\": \"Text\",\n    \"ulist\": \"Bullet list\",\n    \"underline\": \"Underline\",\n    \"undo\": \"Undo\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Type / to use slash commands...\",\n    \"checkbox\": \"Enter task description...\",\n    \"deflist_term\": \"Term\",\n    \"deflist_desc\": \"Definition description\",\n    \"heading\": \"Heading\",\n    \"cut_title\": \"Title\",\n    \"cut_content\": \"Content to be displayed on click\",\n    \"note_title\": \"Title\",\n    \"note_content\": \"Note content\",\n    \"table_cell\": \"Cell content\",\n    \"select_filter\": \"Search languages...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Case sensitive\",\n    \"label_whole-word\": \"Whole word\",\n    \"title\": \"Search in code\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Not found\"\n  },\n  \"widgets\": {\n    \"image\": \"Add image\",\n    \"link\": \"Add link\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Note\",\n    \"tip\": \"Tip\",\n    \"warning\": \"Warning\",\n    \"alert\": \"Alert\",\n    \"remove\": \"Remove\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Add column before\",\n    \"column.add.after\": \"Add column after\",\n    \"column.remove\": \"Remove column\",\n    \"column.remove.multiple\": \"Remove columns\",\n    \"row.add.before\": \"Add row before\",\n    \"row.add.after\": \"Add row after\",\n    \"row.remove\": \"Remove row\",\n    \"row.remove.multiple\": \"Remove rows\",\n    \"cells.clear\": \"Clear cells\",\n    \"table.remove\": \"Remove table\",\n    \"table.menu.cell.align.left\": \"Align cell content to the left\",\n    \"table.menu.cell.align.right\": \"Align cell content to the right\",\n    \"table.menu.cell.align.center\": \"Align cell content to the center\",\n    \"table.menu.row.add\": \"Add row after\",\n    \"table.menu.row.remove\": \"Remove row\",\n    \"table.menu.column.add\": \"Add column after\",\n    \"table.menu.column.remove\": \"Remove column\",\n    \"table.menu.convert.yfm\": \"Convert to YFM table\",\n    \"table.menu.table.remove\": \"Remove table\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/es-ES/core.js",
    "content": "import dateFns from 'date-fns/locale/es';\nimport timeAgo from 'javascript-time-ago/locale/es';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd/M/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d 'de' MMMM 'a las' p\",\n    fullDate: 'd MMM y',\n    fullDateTime: \"d 'de' MMMM 'de' y 'a las' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Acerca de la aplicación',\n      aboutPlanka_title: 'Acerca de PLANKA',\n      accessToken: 'Token de acceso',\n      account: 'Cuenta',\n      actions: 'Acciones',\n      activateUser_title: 'Activar usuario',\n      active: 'Activo',\n      addAttachment_title: 'Añadir archivo adjunto',\n      addCustomFieldGroup_title: 'Añadir grupo de campos personalizados',\n      addCustomField_title: 'Añadir campo personalizado',\n      addManager_title: 'Añadir gestor',\n      addMember_title: 'Añadir miembro',\n      addTaskList_title: 'Añadir lista de tareas',\n      addUser_title: 'Añadir usuario',\n      admin: 'Administrador',\n      administration: 'Administración',\n      all: 'Todo',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Todos los cambios se guardarán automáticamente<br />cuando se restablezca la conexión.',\n      alphabetically: 'Alfabéticamente',\n      alwaysDisplayCardCreator: 'Mostrar siempre el creador de la tarjeta',\n      apiKeyCreated_title: 'Clave API creada',\n      apiKey_title: 'Clave API',\n      archive: 'Archivar',\n      archiveCard_title: 'Archivar tarjeta',\n      archiveCards_title: 'Archivar tarjetas',\n      areYouSureYouWantToActivateThisUser: '¿Estás seguro de que quieres activar este usuario?',\n      areYouSureYouWantToArchiveCards: '¿Estás seguro de que quieres archivar las tarjetas?',\n      areYouSureYouWantToArchiveThisCard: '¿Estás seguro de que quieres archivar esta tarjeta?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        '¿Estás seguro de que quieres asignar este gestor de proyecto como propietario?',\n      areYouSureYouWantToDeactivateThisUser:\n        '¿Estás seguro de que quieres desactivar este usuario?',\n      areYouSureYouWantToDeleteThisApiKey: '¿Estás seguro de que quieres eliminar esta clave API?',\n      areYouSureYouWantToDeleteThisAttachment:\n        '¿Estás seguro de que quieres eliminar este archivo adjunto?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        '¿Estás seguro de que quieres eliminar esta imagen de fondo?',\n      areYouSureYouWantToDeleteThisBoard: '¿Estás seguro de que quieres eliminar este tablero?',\n      areYouSureYouWantToDeleteThisCard: '¿Estás seguro de que quieres eliminar esta tarjeta?',\n      areYouSureYouWantToDeleteThisCardForever:\n        '¿Estás seguro de que quieres eliminar esta tarjeta para siempre?',\n      areYouSureYouWantToDeleteThisComment:\n        '¿Estás seguro de que quieres eliminar este comentario?',\n      areYouSureYouWantToDeleteThisCustomField:\n        '¿Estás seguro de que quieres eliminar este campo personalizado?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        '¿Estás seguro de que quieres eliminar este grupo de campos personalizados?',\n      areYouSureYouWantToDeleteThisLabel: '¿Estás seguro de que quieres eliminar esta etiqueta?',\n      areYouSureYouWantToDeleteThisList:\n        '¿Estás seguro de que quieres eliminar esta lista? Todas las tarjetas se moverán a la papelera.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        '¿Estás seguro de que quieres eliminar este servicio de notificación?',\n      areYouSureYouWantToDeleteThisProject: '¿Estás seguro de que quieres eliminar este proyecto?',\n      areYouSureYouWantToDeleteThisTask: '¿Estás seguro de que quieres eliminar esta tarea?',\n      areYouSureYouWantToDeleteThisTaskList:\n        '¿Estás seguro de que quieres eliminar esta lista de tareas?',\n      areYouSureYouWantToDeleteThisUser: '¿Estás seguro de que quieres eliminar este usuario?',\n      areYouSureYouWantToDeleteThisWebhook: '¿Estás seguro de que quieres eliminar este webhook?',\n      areYouSureYouWantToEmptyTrash: '¿Estás seguro de que quieres vaciar la papelera?',\n      areYouSureYouWantToLeaveBoard: '¿Estás seguro de que quieres abandonar el tablero?',\n      areYouSureYouWantToLeaveProject: '¿Estás seguro de que quieres abandonar el proyecto?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        '¿Estás seguro de que quieres hacer este proyecto privado?',\n      areYouSureYouWantToMakeThisProjectShared:\n        '¿Estás seguro de que quieres hacer este proyecto compartido?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        '¿Estás seguro de que quieres regenerar esta clave API? La clave anterior ya no funcionará.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        '¿Estás seguro de que quieres eliminar este gestor del proyecto?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        '¿Estás seguro de que quieres eliminar este miembro del tablero?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        '¿Estás seguro de que quieres desvincular SSO de este usuario? Esto permitirá que el usuario inicie sesión con una contraseña.',\n      assignAsOwner_title: 'Asignar como propietario',\n      atLeastOneListMustBePresent: 'Debe existir al menos una lista',\n      attachment: 'Archivo adjunto',\n      attachments: 'Archivos adjuntos',\n      authentication: 'Autenticación',\n      background: 'Fondo',\n      baseCustomFields_title: 'Campos personalizados base',\n      baseGroup: 'Grupo base',\n      board: 'Tablero',\n      boardActions_title: 'Acciones del tablero',\n      boardNotFound_title: 'Tablero no encontrado',\n      boardSubscribed: 'Tablero suscrito',\n      boardUser: 'Usuario del tablero',\n      byCreationTime: 'Por fecha de creación',\n      byDefault: 'Por defecto',\n      byDueDate: 'Por fecha de vencimiento',\n      canBeInvitedToWorkInBoards: 'Puede ser invitado a trabajar en tableros.',\n      canComment: 'Puede comentar',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Puede crear proyectos propios y ser invitado a trabajar en otros.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Puede editar el diseño del tablero y asignar miembros a las tarjetas.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Puede gestionar la configuración general del sistema y actuar como propietario del proyecto.',\n      canOnlyViewBoard: 'Solo puede ver el tablero.',\n      cardActions_title: 'Acciones de la tarjeta',\n      cardNotFound_title: 'Tarjeta no encontrada',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Las tarjetas de esta lista están disponibles para todos los miembros del tablero.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Las tarjetas de esta lista están completas y listas para ser archivadas.',\n      cardsOnThisListAreReadyToBeWorkedOn:\n        'Las tarjetas de esta lista están listas para trabajar en ellas.',\n      clickHereOrRefreshPageToUpdate: '<0>Haz clic aquí</0> o actualiza la página para actualizar.',\n      clientHostnameInEhlo: 'Nombre del host del cliente en EHLO',\n      closed: 'Cerrado',\n      color: 'Color',\n      comments: 'Comentarios',\n      contentExceedsLimit: 'El contenido excede {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'El contenido de este archivo adjunto es demasiado grande para mostrarse.',\n      copy_inline: 'copia',\n      createBoard_title: 'Crear tablero',\n      createCustomFieldGroup_title: 'Crear grupo de campos personalizados',\n      createLabel_title: 'Crear etiqueta',\n      createNewOneOrSelectExistingOne: 'Crea uno nuevo o selecciona<br />uno existente.',\n      createProject_title: 'Crear proyecto',\n      createTextFile_title: 'Crear archivo de texto',\n      creator: 'Creador',\n      currentPassword: 'Contraseña actual',\n      currentUser: 'Usuario actual',\n      customFieldGroup_title: 'Grupo de campos personalizados',\n      customFieldGroups_title: 'Grupos de campos personalizados',\n      customField_title: 'Campo personalizado',\n      customFields_title: 'Campos personalizados',\n      customerPanel_title: 'Panel del cliente',\n      dangerZone_title: 'Zona de peligro',\n      date: 'Fecha',\n      deactivateUser_title: 'Desactivar usuario',\n      defaultCardType_title: 'Tipo de tarjeta por defecto',\n      defaultFrom: '\"De\" por defecto',\n      defaultView_title: 'Vista por defecto',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Elimina todos los tableros para poder eliminar este proyecto',\n      deleteApiKey_title: 'Eliminar clave API',\n      deleteAttachment_title: 'Eliminar archivo adjunto',\n      deleteBackgroundImage_title: 'Eliminar imagen de fondo',\n      deleteBoard_title: 'Eliminar tablero',\n      deleteCardForever_title: 'Eliminar tarjeta para siempre',\n      deleteCard_title: 'Eliminar tarjeta',\n      deleteComment_title: 'Eliminar comentario',\n      deleteCustomFieldGroup_title: 'Eliminar grupo de campos personalizados',\n      deleteCustomField_title: 'Eliminar campo personalizado',\n      deleteLabel_title: 'Eliminar etiqueta',\n      deleteList_title: 'Eliminar lista',\n      deleteNotificationService_title: 'Eliminar servicio de notificación',\n      deleteProject_title: 'Eliminar proyecto',\n      deleteTaskList_title: 'Eliminar lista de tareas',\n      deleteTask_title: 'Eliminar tarea',\n      deleteUser_title: 'Eliminar usuario',\n      deleteWebhook_title: 'Eliminar webhook',\n      deletedUser_title: 'Usuario eliminado',\n      description: 'Descripción',\n      display: 'Mostrar',\n      displayCardAges: 'Mostrar antigüedad de las tarjetas',\n      dropFileToUpload: 'Arrastra archivo para subir',\n      dueDate_title: 'Fecha de vencimiento',\n      dynamicAndUnevenlySpacedLayout: 'Diseño dinámico y con espaciado irregular.',\n      editAttachment_title: 'Editar archivo adjunto',\n      editAvatar_title: 'Editar avatar',\n      editColor_title: 'Editar color',\n      editCustomFieldGroup_title: 'Editar grupo de campos personalizados',\n      editCustomField_title: 'Editar campo personalizado',\n      editDueDate_title: 'Editar fecha de vencimiento',\n      editEmail_title: 'Editar correo electrónico',\n      editInformation_title: 'Editar información',\n      editLabel_title: 'Editar etiqueta',\n      editPassword_title: 'Editar contraseña',\n      editPermissions_title: 'Editar permisos',\n      editRole_title: 'Editar rol',\n      editStopwatch_title: 'Editar cronómetro',\n      editType_title: 'Editar tipo',\n      editUsername_title: 'Editar nombre de usuario',\n      editor: 'Editor',\n      editors: 'Editores',\n      email: 'Correo electrónico',\n      emptyTrash_title: 'Vaciar papelera',\n      enterCardTitle: 'Introduce el título de la tarjeta...',\n      enterDescription: 'Introduce una descripción...',\n      enterFilename: 'Introduce el nombre del archivo',\n      enterListTitle: 'Introduce el título de la lista...',\n      enterTaskDescription: 'Introduce la descripción de la tarea...',\n      events: 'Eventos',\n      excludedEvents: 'Eventos excluidos',\n      expandTaskListsByDefault: 'Expandir listas de tareas por defecto',\n      filterByLabels_title: 'Filtrar por etiquetas',\n      filterByMembers_title: 'Filtrar por miembros',\n      forPersonalProjects: 'Para proyectos personales.',\n      forTeamBasedProjects: 'Para proyectos en equipo.',\n      fromComputer_title: 'Desde el ordenador',\n      fromTrello: 'Desde Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'La clave completa está oculta por razones de seguridad. Regénérala para crear una nueva.',\n      general: 'General',\n      gradients: 'Degradados',\n      grid: 'Cuadrícula',\n      hideCompletedTasks: 'Ocultar tareas completadas',\n      hideFromProjectListAndFavorites: 'Ocultar de la lista de proyectos y favoritos',\n      host: 'Host',\n      hours: 'Horas',\n      identity: 'Identidad',\n      importBoard_title: 'Importar tablero',\n      information: 'Información',\n      invalidCurrentPassword: 'Contraseña actual incorrecta',\n      kanban: 'Kanban',\n      labels: 'Etiquetas',\n      language: 'Idioma',\n      leaveBoard_title: 'Abandonar tablero',\n      leaveProject_title: 'Abandonar proyecto',\n      limitCardTypesToDefaultOne: 'Limitar tipos de tarjeta al predeterminado',\n      linkToCard: 'Enlace a la tarjeta',\n      list: 'Lista',\n      listActions_title: 'Acciones de la lista',\n      lists: 'Listas',\n      makeProjectPrivate_title: 'Hacer proyecto privado',\n      makeProjectShared_title: 'Hacer proyecto compartido',\n      managers: 'Gestores',\n      memberActions_title: 'Acciones del miembro',\n      members: 'Miembros',\n      minutes: 'Minutos',\n      moreActions: 'Más acciones',\n      moreActions_title: 'Más acciones',\n      moveCard_title: 'Mover tarjeta',\n      moveList_title: 'Mover lista',\n      myOwn_title: 'Mis propios',\n      name: 'Nombre',\n      newEmail: 'Nuevo correo electrónico',\n      newPassword: 'Nueva contraseña',\n      newUsername: 'Nuevo nombre de usuario',\n      newVersionAvailable: 'Nueva versión disponible',\n      newestFirst: 'Más recientes primero',\n      noApiKeyCreated: 'No se ha creado ninguna clave API.',\n      noBoards: 'Sin tableros',\n      noCardsFound: 'No se encontraron tarjetas.',\n      noConnectionToServer: 'Sin conexión al servidor',\n      noLists: 'Sin listas',\n      noProjects: 'Sin proyectos',\n      noUnreadNotifications: 'Sin notificaciones sin leer.',\n      notifications: 'Notificaciones',\n      oldestFirst: 'Más antiguos primero',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Solo debe quedar un gestor para hacer este proyecto privado',\n      openBoard_title: 'Abrir tablero',\n      optional_inline: 'opcional',\n      organization: 'Organización',\n      others: 'Otros',\n      passwordIsSet: 'La contraseña está establecida',\n      phone: 'Teléfono',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA usa <1><0>Apprise</0></1> para enviar notificaciones a más de 100 servicios populares.',\n      port: 'Puerto',\n      preferences: 'Preferencias',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Consejo: pulsa Ctrl-V (Cmd-V en Mac) para añadir un archivo adjunto desde el portapapeles.',\n      private: 'Privado',\n      project: 'Proyecto',\n      projectNotFound_title: 'Proyecto no encontrado',\n      projectOwner: 'Propietario del proyecto',\n      referenceDataAndKnowledgeStorage: 'Almacenamiento de datos de referencia y conocimiento.',\n      regenerateApiKey_title: 'Regenerar clave API',\n      rejectUnauthorizedTlsCertificates: 'Rechazar certificados TLS no autorizados',\n      removeManager_title: 'Eliminar gestor',\n      removeMember_title: 'Eliminar miembro',\n      role: 'Rol',\n      saveThisKeyItWillNotBeShownAgain: '¡Guarda esta clave, no se mostrará de nuevo!',\n      searchCards: 'Buscar tarjetas...',\n      searchCustomFieldGroups: 'Buscar grupos de campos personalizados...',\n      searchCustomFields: 'Buscar campos personalizados...',\n      searchLabels: 'Buscar etiquetas...',\n      searchLists: 'Buscar listas...',\n      searchMembers: 'Buscar miembros...',\n      searchProjects: 'Buscar proyectos...',\n      searchUsers: 'Buscar usuarios...',\n      seconds: 'Segundos',\n      selectAssignee_title: 'Seleccionar asignado',\n      selectBoard: 'Seleccionar tablero',\n      selectList: 'Seleccionar lista',\n      selectListToRestoreThisCard: 'Selecciona una lista para restaurar esta tarjeta',\n      selectOrder_title: 'Seleccionar orden',\n      selectPermissions_title: 'Seleccionar permisos',\n      selectProject: 'Seleccionar proyecto',\n      selectRole_title: 'Seleccionar rol',\n      selectType_title: 'Seleccionar tipo',\n      sequentialDisplayOfCards: 'Visualización secuencial de tarjetas.',\n      settings: 'Configuración',\n      shared: 'Compartido',\n      sharedWithMe_title: 'Compartido conmigo',\n      showOnFrontOfCard: 'Mostrar en el frente de la tarjeta',\n      smtp: 'SMTP',\n      sortList_title: 'Ordenar lista',\n      sourceCardIsNoLongerAvailableForCopying:\n        'La tarjeta de origen ya no está disponible para copiar.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'La tarjeta de origen ya no está disponible para mover.',\n      stopwatch: 'Cronómetro',\n      story: 'Historia',\n      subscribeToCardWhenCommenting: 'Suscribirse a la tarjeta al comentar',\n      subscribeToMyOwnCardsByDefault: 'Suscribirse a mis propias tarjetas por defecto',\n      taskActions_title: 'Acciones de la tarea',\n      taskAssignmentAndProjectCompletion: 'Asignación de tareas y finalización del proyecto.',\n      taskListActions_title: 'Acciones de la lista de tareas',\n      taskList_title: 'Lista de tareas',\n      team: 'Equipo',\n      termsOfService_title: 'Términos de servicio',\n      testLog_title: 'Registro de prueba',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'No hay vista previa disponible para este archivo adjunto.',\n      time: 'Hora',\n      title: 'Título',\n      trash: 'Papelera',\n      trashHasBeenSuccessfullyEmptied: 'La papelera se ha vaciado correctamente.',\n      turnOffRecentCardHighlighting: 'Desactivar resaltado de tarjetas recientes',\n      typeNameToConfirm: 'Escribe el nombre para confirmar.',\n      typeTitleToConfirm: 'Escribe el título para confirmar.',\n      unlinkSso_title: 'Desvinculación de SSO',\n      unsavedChanges: 'Cambios sin guardar',\n      uploadFailedFileIsTooBig: 'Error al subir: El archivo es demasiado grande.',\n      uploadFailedNotEnoughStorageSpace:\n        'Error al subir: No hay suficiente espacio de almacenamiento.',\n      uploadedImages: 'Imágenes subidas',\n      url: 'URL',\n      useSecureConnection: 'Usar conexión segura',\n      userActions_title: 'Acciones del usuario',\n      userAddedCardToList: '<0>{{user}}</0> añadió <2>{{card}}</2> a {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> añadió esta tarjeta a {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> añadió a {{addedUser}} a <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> añadió a {{addedUser}} a esta tarjeta',\n      userAddedYouToCard: '<0>{{user}}</0> te añadió a <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> completó {{task}} en <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> completó {{task}} en esta tarjeta',\n      userJoinedCard: '<0>{{user}}</0> se unió a <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> se unió a esta tarjeta',\n      userLeftCard: '<0>{{user}}</0> abandonó <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> dejó un nuevo comentario «{{comment}}» en <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> abandonó esta tarjeta',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> marcó {{task}} como incompleta en <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> marcó {{task}} como incompleta en esta tarjeta',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> te mencionó en un comentario «{{comment}}» en <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> movió <2>{{card}}</2> de {{fromList}} a {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> movió esta tarjeta de {{fromList}} a {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> eliminó a {{removedUser}} de <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> eliminó a {{removedUser}} de esta tarjeta',\n      username: 'Nombre de usuario',\n      users: 'Usuarios',\n      viewer: 'Observador',\n      viewers: 'Observadores',\n      visualTaskManagementWithLists: 'Gestión visual de tareas con listas.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Novedades',\n      withoutBaseGroup: 'Sin grupo base',\n      writeComment: 'Escribe un comentario...',\n    },\n\n    action: {\n      activateUser: 'Activar usuario',\n      activateUser_title: 'Activar usuario',\n      addAnotherCard: 'Añadir otra tarjeta',\n      addAnotherList: 'Añadir otra lista',\n      addAnotherTask: 'Añadir otra tarea',\n      addCard: 'Añadir tarjeta',\n      addCard_title: 'Añadir tarjeta',\n      addComment: 'Añadir comentario',\n      addCustomField: 'Añadir campo personalizado',\n      addCustomFieldGroup: 'Añadir grupo de campos personalizados',\n      addList: 'Añadir lista',\n      addMember: 'Añadir miembro',\n      addMoreDetailedDescription: 'Añadir una descripción más detallada',\n      addTask: 'Añadir tarea',\n      addTaskList: 'Añadir lista de tareas',\n      addToCard: 'Añadir a tarjeta',\n      addUser: 'Añadir usuario',\n      addWebhook: 'Añadir webhook',\n      archive: 'Archivar',\n      archiveCard: 'Archivar tarjeta',\n      archiveCard_title: 'Archivar tarjeta',\n      archiveCards: 'Archivar tarjetas',\n      archiveCards_title: 'Archivar tarjetas',\n      assignAsOwner: 'Asignar como propietario',\n      cancel: 'Cancelar',\n      copy: 'Copiar',\n      copyCard_title: 'Copiar tarjeta',\n      createApiKey: 'Crear clave API',\n      createBoard: 'Crear tablero',\n      createCustomFieldGroup: 'Crear grupo de campos personalizados',\n      createFile: 'Crear archivo',\n      createLabel: 'Crear etiqueta',\n      createNewLabel: 'Crear nueva etiqueta',\n      createProject: 'Crear proyecto',\n      cut: 'Cortar',\n      cutCard_title: 'Cortar tarjeta',\n      deactivateUser: 'Desactivar usuario',\n      deactivateUser_title: 'Desactivar usuario',\n      delete: 'Eliminar',\n      deleteApiKey: 'Eliminar clave API',\n      deleteAttachment: 'Eliminar archivo adjunto',\n      deleteAvatar: 'Eliminar avatar',\n      deleteBackgroundImage: 'Eliminar imagen de fondo',\n      deleteBoard: 'Eliminar tablero',\n      deleteBoard_title: 'Eliminar tablero',\n      deleteCard: 'Eliminar tarjeta',\n      deleteCardForever: 'Eliminar tarjeta para siempre',\n      deleteCard_title: 'Eliminar tarjeta',\n      deleteComment: 'Eliminar comentario',\n      deleteCustomField: 'Eliminar campo personalizado',\n      deleteCustomFieldGroup: 'Eliminar grupo de campos personalizados',\n      deleteForever_title: 'Eliminar para siempre',\n      deleteGroup: 'Eliminar grupo',\n      deleteLabel: 'Eliminar etiqueta',\n      deleteList: 'Eliminar lista',\n      deleteList_title: 'Eliminar lista',\n      deleteNotificationService: 'Eliminar servicio de notificación',\n      deleteProject: 'Eliminar proyecto',\n      deleteProject_title: 'Eliminar proyecto',\n      deleteTask: 'Eliminar tarea',\n      deleteTaskList: 'Eliminar lista de tareas',\n      deleteTask_title: 'Eliminar tarea',\n      deleteUser: 'Eliminar usuario',\n      deleteUser_title: 'Eliminar usuario',\n      deleteWebhook: 'Eliminar webhook',\n      dismissAll: 'Descartar todo',\n      download: 'Descargar',\n      duplicateCard_title: 'Duplicar tarjeta',\n      edit: 'Editar',\n      editColor_title: 'Editar color',\n      editDescription_title: 'Editar descripción',\n      editDueDate_title: 'Editar fecha de vencimiento',\n      editEmail_title: 'Editar correo electrónico',\n      editGroup: 'Editar grupo',\n      editInformation_title: 'Editar información',\n      editPassword_title: 'Editar contraseña',\n      editPermissions: 'Editar permisos',\n      editRole_title: 'Editar rol',\n      editStopwatch_title: 'Editar cronómetro',\n      editTitle_title: 'Editar título',\n      editType_title: 'Editar tipo',\n      editUsername_title: 'Editar nombre de usuario',\n      emptyTrash: 'Vaciar papelera',\n      emptyTrash_title: 'Vaciar papelera',\n      import: 'Importar',\n      join: 'Unirse',\n      leave: 'Abandonar',\n      leaveBoard: 'Abandonar tablero',\n      leaveProject: 'Abandonar proyecto',\n      logOut_title: 'Cerrar sesión',\n      makeCover_title: 'Hacer portada',\n      makeProjectPrivate: 'Hacer proyecto privado',\n      makeProjectPrivate_title: 'Hacer proyecto privado',\n      makeProjectShared: 'Hacer proyecto compartido',\n      makeProjectShared_title: 'Hacer proyecto compartido',\n      move: 'Mover',\n      moveCard_title: 'Mover tarjeta',\n      moveList_title: 'Mover lista',\n      regenerateApiKey: 'Regenerar clave API',\n      remove: 'Eliminar',\n      removeAssignee: 'Eliminar asignado',\n      removeColor: 'Eliminar color',\n      removeCover_title: 'Eliminar portada',\n      removeFromBoard: 'Eliminar del tablero',\n      removeFromProject: 'Eliminar del proyecto',\n      removeManager: 'Eliminar gestor',\n      removeMember: 'Eliminar miembro',\n      restoreToList: 'Restaurar a {{list}}',\n      returnToBoard: 'Volver al tablero',\n      save: 'Guardar',\n      sendTestEmail: 'Enviar correo de prueba',\n      showActive: 'Mostrar activos',\n      showAllAttachments: 'Mostrar todos los archivos adjuntos ({{hidden}} ocultos)',\n      showCardsWithThisUser: 'Mostrar tarjetas con este usuario',\n      showDeactivated: 'Mostrar desactivados',\n      showFewerAttachments: 'Mostrar menos archivos adjuntos',\n      showLess: 'Mostrar menos',\n      showMore: 'Mostrar más',\n      sortList_title: 'Ordenar lista',\n      start: 'Iniciar',\n      stop: 'Detener',\n      subscribe: 'Suscribirse',\n      unlinkSso: 'Desvincular SSO',\n      unlinkSso_title: 'Desvincular SSO',\n      unsubscribe: 'Cancelar suscripción',\n      uploadNewAvatar: 'Subir nuevo avatar',\n      uploadNewImage: 'Subir nueva imagen',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/es-ES/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'es-ES',\n  country: 'es',\n  name: 'Español',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/es-ES/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Se ha alcanzado el límite de usuarios activos',\n      adminLoginRequiredToInitializeInstance:\n        'Se requiere inicio de sesión de administrador para inicializar la instancia',\n      emailAlreadyInUse: 'Correo electrónico ya en uso',\n      emailOrUsername: 'Correo electrónico o nombre de usuario',\n      invalidCredentials: 'Credenciales no válidas',\n      invalidEmailOrUsername: 'Correo electrónico o nombre de usuario no válido',\n      invalidPassword: 'Contraseña no válida',\n      logIn_title: 'Iniciar sesión',\n      noInternetConnection: 'Sin conexión a internet',\n      or: 'O',\n      pageNotFound_title: 'Página no encontrada',\n      password: 'Contraseña',\n      poweredByPlanka: 'Desarrollado con <1>PLANKA</1>',\n      serverConnectionFailed: 'Error de conexión con el servidor',\n      unknownError: 'Error desconocido, inténtalo más tarde',\n      useSingleSignOn: 'Usar inicio de sesión único',\n      usernameAlreadyInUse: 'Nombre de usuario ya en uso',\n      whoops_title: '¡Ups!',\n    },\n\n    action: {\n      cancelAndClose: 'Cancelar y cerrar',\n      continue: 'Continuar',\n      debugSso: 'Depurar SSO',\n      goBack: 'Volver',\n      goHome: 'Ir al inicio',\n      logIn: 'Iniciar sesión',\n      logInWithSso: 'Iniciar sesión con SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/es-ES/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Este es un texto sin título.\\nTanto el título como el texto\\npueden resaltarse en negrita, cursiva, color,\\ntachado y subrayado.\",\n    \"text-with-head\": \"Este es un texto con título.\\nTanto el título como el texto\\npueden resaltarse en negrita, cursiva, color,\\ntachado y subrayado.\",\n    \"heading\": \"Título\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Error en el editor de markdown\",\n    \"settings_wysiwyg\": \"Editor visual (wysiwyg)\",\n    \"settings_markup\": \"Marcado markdown\",\n    \"markup_placeholder\": \"Introduce marcado markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Eliminar\",\n    \"empty_option\": \"No se encontraron coincidencias\",\n    \"show_line_numbers\": \"Numeración de líneas\"\n  },\n  \"common\": {\n    \"delete\": \"Eliminar\",\n    \"edit\": \"Editar\",\n    \"toolbar_action_disabled\": \"Elemento de marcado incompatible\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Cancelar\",\n    \"common_action_submit\": \"Enviar\",\n    \"common_action_upload\": \"Seleccionar\",\n    \"common_tab_attach\": \"Añadir desde dispositivo\",\n    \"common_tab_link\": \"Añadir por enlace\",\n    \"common_link\": \"Enlace\",\n    \"common_sizes\": \"Tamaño, px\",\n    \"image_name\": \"Título\",\n    \"image_link_href\": \"Enlace de imagen\",\n    \"image_link_href_help\": \"Dirección a la que lleva el enlace de la imagen.\",\n    \"image_alt\": \"Texto alternativo\",\n    \"image_alt_help\": \"El texto alternativo se muestra si la imagen no puede cargarse.\",\n    \"image_upload_help\": \"Imagen JPEG, GIF o PNG no mayor de 1 MB.\",\n    \"image_upload_failed\": \"Error al añadir imagen\",\n    \"image_size_width\": \"Ancho\",\n    \"image_size_height\": \"Alto\",\n    \"link_url_help\": \"Dirección a la que lleva el enlace.\",\n    \"link_text\": \"Texto del enlace\",\n    \"link_text_help\": \"Texto mostrado como enlace.\",\n    \"link_open_help\": \"Abrir el enlace en una nueva pestaña\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Encabezado\",\n    \"header_hint\": \"# Tu texto\",\n    \"italic_title\": \"Cursiva\",\n    \"italic_hint\": \"_Tu texto_\",\n    \"bold_title\": \"Negrita\",\n    \"bold_hint\": \"**Tu texto**\",\n    \"strikethrough_title\": \"Tachado\",\n    \"strikethrough_hint\": \"~~Tu texto~~\",\n    \"blockquote_title\": \"Cita\",\n    \"blockquote_hint\": \"> Tu texto\",\n    \"code_title\": \"Código\",\n    \"code_hint\": \"```Tu texto```\",\n    \"link_title\": \"Enlace\",\n    \"link_hint\": \"[Tu texto](url)\",\n    \"image_title\": \"Imagen\",\n    \"image_hint\": \"![Tu texto](url)\",\n    \"list_title\": \"Elemento de lista\",\n    \"list_hint\": \"- Tu texto\",\n    \"numbered-list_title\": \"Lista numerada\",\n    \"numbered-list_hint\": \"1. Tu texto\",\n    \"documentation\": \"Documentación\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Negrita\",\n    \"code\": \"Código\",\n    \"code_inline\": \"Código en línea\",\n    \"codeblock\": \"Bloque de código\",\n    \"colorify\": \"Color de texto\",\n    \"colorify__color_blue\": \"Azul\",\n    \"colorify__color_default\": \"Por defecto\",\n    \"colorify__color_gray\": \"Gris\",\n    \"colorify__color_green\": \"Verde\",\n    \"colorify__color_orange\": \"Naranja\",\n    \"colorify__color_red\": \"Rojo\",\n    \"colorify__color_violet\": \"Violeta\",\n    \"colorify__color_yellow\": \"Amarillo\",\n    \"colorify__group_text\": \"Texto\",\n    \"cut\": \"Cortar\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Los emojis se pueden añadir en WYSIWYG o manualmente con marcado\",\n    \"heading\": \"Encabezado\",\n    \"heading1\": \"Encabezado 1\",\n    \"heading2\": \"Encabezado 2\",\n    \"heading3\": \"Encabezado 3\",\n    \"heading4\": \"Encabezado 4\",\n    \"heading5\": \"Encabezado 5\",\n    \"heading6\": \"Encabezado 6\",\n    \"hrule\": \"Separador\",\n    \"image\": \"Imagen\",\n    \"italic\": \"Cursiva\",\n    \"link\": \"Enlace\",\n    \"list\": \"Lista\",\n    \"list__action_lift\": \"Elevar elemento\",\n    \"list__action_sink\": \"Bajar elemento\",\n    \"list_action_disabled\": \"Contradice la lógica de la lista\",\n    \"mark\": \"Marcado\",\n    \"mono\": \"Monoespaciado\",\n    \"more_action\": \"Más acciones\",\n    \"note\": \"Nota\",\n    \"olist\": \"Lista ordenada\",\n    \"quote\": \"Cita\",\n    \"redo\": \"Rehacer\",\n    \"strike\": \"Tachado\",\n    \"table\": \"Tabla\",\n    \"text\": \"Texto\",\n    \"ulist\": \"Lista con viñetas\",\n    \"underline\": \"Subrayado\",\n    \"undo\": \"Deshacer\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Escribe / para usar comandos de barra...\",\n    \"checkbox\": \"Introduce descripción de la tarea...\",\n    \"deflist_term\": \"Término\",\n    \"deflist_desc\": \"Descripción de la definición\",\n    \"heading\": \"Encabezado\",\n    \"cut_title\": \"Título\",\n    \"cut_content\": \"Contenido a mostrar al hacer clic\",\n    \"note_title\": \"Título\",\n    \"note_content\": \"Contenido de la nota\",\n    \"table_cell\": \"Contenido de la celda\",\n    \"select_filter\": \"Buscar idiomas...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Distinguir mayúsculas\",\n    \"label_whole-word\": \"Palabra completa\",\n    \"title\": \"Buscar en el código\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"No encontrado\"\n  },\n  \"widgets\": {\n    \"image\": \"Añadir imagen\",\n    \"link\": \"Añadir enlace\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Información\",\n    \"tip\": \"Consejo\",\n    \"warning\": \"Advertencia\",\n    \"alert\": \"Alerta\",\n    \"remove\": \"Eliminar\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Añadir columna antes\",\n    \"column.add.after\": \"Añadir columna después\",\n    \"column.remove\": \"Eliminar columna\",\n    \"column.remove.multiple\": \"Eliminar columnas\",\n    \"row.add.before\": \"Añadir fila antes\",\n    \"row.add.after\": \"Añadir fila después\",\n    \"row.remove\": \"Eliminar fila\",\n    \"row.remove.multiple\": \"Eliminar filas\",\n    \"cells.clear\": \"Limpiar celdas\",\n    \"table.remove\": \"Eliminar tabla\",\n    \"table.menu.cell.align.left\": \"Alinear contenido de la celda a la izquierda\",\n    \"table.menu.cell.align.right\": \"Alinear contenido de la celda a la derecha\",\n    \"table.menu.cell.align.center\": \"Centrar contenido de la celda\",\n    \"table.menu.row.add\": \"Añadir fila después\",\n    \"table.menu.row.remove\": \"Eliminar fila\",\n    \"table.menu.column.add\": \"Añadir columna después\",\n    \"table.menu.column.remove\": \"Eliminar columna\",\n    \"table.menu.convert.yfm\": \"Convertir a tabla YFM\",\n    \"table.menu.table.remove\": \"Eliminar tabla\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/et-EE/core.js",
    "content": "import dateFns from 'date-fns/locale/et';\nimport timeAgo from 'javascript-time-ago/locale/et';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'M/d/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'MMM d',\n    longDateTime: \"MMMM d 'at' p\",\n    fullDate: 'MMM d, y',\n    fullDateTime: \"MMMM d, y 'at' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Rakenduse kohta',\n      aboutPlanka_title: 'PLANKA kohta',\n      accessToken: 'Juurdepääsuluba',\n      account: 'Konto',\n      actions: 'Tegevused',\n      activateUser_title: 'Aktiveeri kasutaja',\n      active: 'Aktiivne',\n      addAttachment_title: 'Lisa manus',\n      addCustomFieldGroup_title: 'Lisa kohandatud väljade grupp',\n      addCustomField_title: 'Lisa kohandatud väli',\n      addManager_title: 'Lisa haldur',\n      addMember_title: 'Lisa liige',\n      addTaskList_title: 'Lisa ülesannete nimekiri',\n      addUser_title: 'Lisa kasutaja',\n      admin: 'Administraator',\n      administration: 'Administreerimine',\n      all: 'Kõik',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Kõik muudatused salvestatakse automaatselt<br />pärast ühenduse taastamist.',\n      alphabetically: 'Tähestiku järgi',\n      alwaysDisplayCardCreator: 'Näita alati kaardi loojat',\n      apiKeyCreated_title: 'API võti loodud',\n      apiKey_title: 'API võti',\n      archive: 'Arhiveeri',\n      archiveCard_title: 'Arhiveeri kaart',\n      archiveCards_title: 'Arhiveeri kaardid',\n      areYouSureYouWantToActivateThisUser: 'Oled kindel, et soovid seda kasutajat aktiveerida?',\n      areYouSureYouWantToArchiveCards: 'Oled kindel, et soovid kaarte arhiveerida?',\n      areYouSureYouWantToArchiveThisCard: 'Oled kindel, et soovid seda kaarti arhiveerida?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Oled kindel, et soovid seda projektihaldurit omanikuks määrata?',\n      areYouSureYouWantToDeactivateThisUser: 'Oled kindel, et soovid seda kasutajat deaktiveerida?',\n      areYouSureYouWantToDeleteThisApiKey: 'Kas olete kindel, et soovite seda API võtit kustutada?',\n      areYouSureYouWantToDeleteThisAttachment: 'Oled kindel, et soovid seda manusi kustutada?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Oled kindel, et soovid seda taustapilla kustutada?',\n      areYouSureYouWantToDeleteThisBoard: 'Oled kindel, et soovid seda tahveli kustutada?',\n      areYouSureYouWantToDeleteThisCard: 'Oled kindel, et soovid seda kaarti kustutada?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Oled kindel, et soovid seda kaarti jäädavalt kustutada?',\n      areYouSureYouWantToDeleteThisComment: 'Oled kindel, et soovid seda kommentaari kustutada?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Oled kindel, et soovid seda kohandatud väli kustutada?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Oled kindel, et soovid seda kohandatud väljade gruppi kustutada?',\n      areYouSureYouWantToDeleteThisLabel: 'Oled kindel, et soovid seda silti kustutada?',\n      areYouSureYouWantToDeleteThisList:\n        'Oled kindel, et soovid seda nimekirja kustutada? Kõik kaardid liigutatakse prügikasti.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Oled kindel, et soovid seda teavitusteenuse kustutada?',\n      areYouSureYouWantToDeleteThisProject: 'Oled kindel, et soovid seda projekti kustutada?',\n      areYouSureYouWantToDeleteThisTask: 'Oled kindel, et soovid seda ülesannet kustutada?',\n      areYouSureYouWantToDeleteThisTaskList:\n        'Oled kindel, et soovid seda ülesannete nimekirja kustutada?',\n      areYouSureYouWantToDeleteThisUser: 'Oled kindel, et soovid seda kasutaja kustutada?',\n      areYouSureYouWantToDeleteThisWebhook: 'Oled kindel, et soovid seda webhookki kustutada?',\n      areYouSureYouWantToEmptyTrash: 'Oled kindel, et soovid prügikasti tühjendada?',\n      areYouSureYouWantToLeaveBoard: 'Oled kindel, et soovid lahku tahvlilt?',\n      areYouSureYouWantToLeaveProject: 'Oled kindel, et soovid lahku projektist?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Oled kindel, et soovid seda projekti privaatseks muuta?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Oled kindel, et soovid seda projekti jagatavaks määrata?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Kas olete kindel, et soovite seda API võtit taastada? Eelmine võti ei tööta enam.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Oled kindel, et soovid seda haldurit projektist eemaldada?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Oled kindel, et soovid seda liiget tahvlilt eemaldada?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Kas olete kindel, et soovite selle kasutaja SSO sidet eemaldada? See võimaldab kasutajal sisse logida parooliga.',\n      assignAsOwner_title: 'Määra omanikuks',\n      atLeastOneListMustBePresent: 'Vähemalt üks nimekiri peab olemas olema',\n      attachment: 'Manus',\n      attachments: 'Manused',\n      authentication: 'Autentimine',\n      background: 'Taust',\n      baseCustomFields_title: 'Põhiväljad',\n      baseGroup: 'Põhiklass',\n      board: 'Tahvel',\n      boardActions_title: 'Tahvelitegevused',\n      boardNotFound_title: 'Tahvel ei leitud',\n      boardSubscribed: 'Tahvel tellitud',\n      boardUser: 'Tahvelkasutaja',\n      byCreationTime: 'Kõrvaliku järgi',\n      byDefault: 'Vaikimisi',\n      byDueDate: 'Kõrvaliku järgi',\n      canBeInvitedToWorkInBoards: 'Saab kutsuda tööle tahvlid.',\n      canComment: 'Saab kommenteerida',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Saab luua oma projekte ja kutsuda tööle teised.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Saab tahveli kujundust ja liikmeid kaarte määrama.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Saab hallata süsteemi üldseadeid ja mõjutada projekti omanikut.',\n      canOnlyViewBoard: 'Saab vaid tahveli vaadata.',\n      cardActions_title: 'Kaardi tegevused',\n      cardNotFound_title: 'Kaart ei leitud',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Kaardid sellel nimekirjal on saadaval kõigile tahvelkasutajatele.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Kaardid sellel nimekirjal on täidetud ja valmis arhiveerimiseks.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Kaardid sellel nimekirjal on valmis tööle.',\n      clickHereOrRefreshPageToUpdate: '<0>Klõpsa siia</0> või uuendage lehte.',\n      clientHostnameInEhlo: 'Kliendi hostinimi EHLO-s',\n      closed: 'Suletud',\n      color: 'Värv',\n      comments: 'Kommentaarid',\n      contentExceedsLimit: 'Sisu ületab {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay: 'Sisu seda manusi ei õnnestu kuvada.',\n      copy_inline: 'koopia',\n      createBoard_title: 'Loo tahvel',\n      createCustomFieldGroup_title: 'Loo kohandatud väljade grupp',\n      createLabel_title: 'Loo silt',\n      createNewOneOrSelectExistingOne: 'Loo uus või vali<br />olemasolev.',\n      createProject_title: 'Loo projekt',\n      createTextFile_title: 'Loo tekstifail',\n      creator: 'Looja',\n      currentPassword: 'Praegune parool',\n      currentUser: 'Praegune kasutaja',\n      customFieldGroup_title: 'Kohandatud väljade grupp',\n      customFieldGroups_title: 'Kohandatud väljade grupid',\n      customField_title: 'Kohandatud väli',\n      customFields_title: 'Kohandatud väljad',\n      customerPanel_title: 'Kliendi paneel',\n      dangerZone_title: 'Ohtliku ala',\n      date: 'Kuupäev',\n      deactivateUser_title: 'Deaktiveeri kasutaja',\n      defaultCardType_title: 'Vaikimisi kaardi tüüp',\n      defaultFrom: 'Vaikimisi \"saatja\"',\n      defaultView_title: 'Vaikimisi vaade',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Kustuta kõik tahvlid, et seda projekti kustutada',\n      deleteApiKey_title: 'Kustuta API võti',\n      deleteAttachment_title: 'Kustuta manus',\n      deleteBackgroundImage_title: 'Kustuta taustapilt',\n      deleteBoard_title: 'Kustuta tahvel',\n      deleteCardForever_title: 'Kustuta kaart jäädavalt',\n      deleteCard_title: 'Kustuta kaart',\n      deleteComment_title: 'Kustuta kommentaar',\n      deleteCustomFieldGroup_title: 'Kustuta kohandatud väljade grupp',\n      deleteCustomField_title: 'Kustuta kohandatud väli',\n      deleteLabel_title: 'Kustuta silt',\n      deleteList_title: 'Kustuta nimekiri',\n      deleteNotificationService_title: 'Kustuta teavitusteenus',\n      deleteProject_title: 'Kustuta projekt',\n      deleteTaskList_title: 'Kustuta ülesannete nimekiri',\n      deleteTask_title: 'Kustuta ülesanne',\n      deleteUser_title: 'Kustuta kasutaja',\n      deleteWebhook_title: 'Kustuta webhook',\n      deletedUser_title: 'Kustutatud kasutaja',\n      description: 'Kirjeldus',\n      display: 'Kuva',\n      displayCardAges: 'Näita kaartide vanust',\n      dropFileToUpload: 'Lase faili üleslaadida',\n      dueDate_title: 'Tähtaeg',\n      dynamicAndUnevenlySpacedLayout: 'Dünaamiline ja eirastega kujundus.',\n      editAttachment_title: 'Muuda manus',\n      editAvatar_title: 'Muuda avatar',\n      editColor_title: 'Muuda värvi',\n      editCustomFieldGroup_title: 'Muuda kohandatud väljade gruppi',\n      editCustomField_title: 'Muuda kohandatud väli',\n      editDueDate_title: 'Muuda tähtaega',\n      editEmail_title: 'Muuda e-posti',\n      editInformation_title: 'Muuda infot',\n      editLabel_title: 'Muuda silt',\n      editPassword_title: 'Muuda parooli',\n      editPermissions_title: 'Muuda õigusi',\n      editRole_title: 'Muuda rolli',\n      editStopwatch_title: 'Muuda stopperit',\n      editType_title: 'Muuda tüüpi',\n      editUsername_title: 'Muuda kasutajanime',\n      editor: 'Redaktor',\n      editors: 'Redaktorid',\n      email: 'E-post',\n      emptyTrash_title: 'Tühjenda prügikast',\n      enterCardTitle: 'Sisestage kaardi pealkiri...',\n      enterDescription: 'Sisestage kirjeldus...',\n      enterFilename: 'Sisestage failinimi',\n      enterListTitle: 'Sisestage nimekiri pealkiri...',\n      enterTaskDescription: 'Sisestage ülesande kirjeldus...',\n      events: 'Sündmused',\n      excludedEvents: 'Välistatud sündmused',\n      expandTaskListsByDefault: 'Laienda ülesannete nimekirjad vaikimisi',\n      filterByLabels_title: 'Filtreeri siltide järgi',\n      filterByMembers_title: 'Filtreeri liikmete järgi',\n      forPersonalProjects: 'Inimlikuks projektideks.',\n      forTeamBasedProjects: 'Töögrupi põhised projektid.',\n      fromComputer_title: 'Arvutist',\n      fromTrello: 'Trellost',\n      fullKeyIsHiddenForSecurityReasons:\n        'Täielik võti on turvakaalutlustel peidetud. Taasta see, et luua uus.',\n      general: 'Üldine',\n      gradients: 'Gradiendid',\n      grid: 'Grill',\n      hideCompletedTasks: 'Peida lõpetatud ülesanded',\n      hideFromProjectListAndFavorites: 'Peida projektiloendist ja lemmikutest',\n      host: 'Host',\n      hours: 'Tunnid',\n      identity: 'Identiteet',\n      importBoard_title: 'Impordi tahvel',\n      information: 'Informatsioon',\n      invalidCurrentPassword: 'Vale praegune parool',\n      kanban: 'Kanban',\n      labels: 'Sildid',\n      language: 'Keel',\n      leaveBoard_title: 'Lahku tahvlilt',\n      leaveProject_title: 'Lahku projektist',\n      limitCardTypesToDefaultOne: 'Piirata kaardi tüüpe vaikimisi üheks',\n      linkToCard: 'Link kaardile',\n      list: 'Nimekiri',\n      listActions_title: 'Nimekiri tegevused',\n      lists: 'Nimekirjad',\n      makeProjectPrivate_title: 'Muuda projekt privaatseks',\n      makeProjectShared_title: 'Muuda projekt jagatavaks',\n      managers: 'Haldurid',\n      memberActions_title: 'Liikme tegevused',\n      members: 'Liikmed',\n      minutes: 'Minutid',\n      moreActions: 'Rohkem tegevusi',\n      moreActions_title: 'Rohkem tegevusi',\n      moveCard_title: 'Liiguta kaart',\n      moveList_title: 'Liiguta nimekiri',\n      myOwn_title: 'Minu omanik',\n      name: 'Nimi',\n      newEmail: 'Uus e-post',\n      newPassword: 'Uus parool',\n      newUsername: 'Uus kasutajanimi',\n      newVersionAvailable: 'Uus versioon saadaval',\n      newestFirst: 'Kõige uuem',\n      noApiKeyCreated: 'API võtit pole loodud.',\n      noBoards: 'Tahvleid pole',\n      noCardsFound: 'Kaarte ei leitud.',\n      noConnectionToServer: 'Ühendust serveriga ei leitud',\n      noLists: 'Nimekirju pole',\n      noProjects: 'Projekte pole',\n      noUnreadNotifications: 'Lugemata teavitused.',\n      notifications: 'Teavitused',\n      oldestFirst: 'Kõige vanem',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Ainult üks haldur peaks jääma, et muuta see projekt privaatseks',\n      openBoard_title: 'Ava tahvel',\n      optional_inline: 'valikuline',\n      organization: 'Organisatsioon',\n      others: 'Teised',\n      passwordIsSet: 'Parool on määratud',\n      phone: 'Telefon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA kasutab <1><0>Apprise</0></1> teavitusteenuse, et teavitada üle 100 populaarset teenust.',\n      port: 'Port',\n      preferences: 'Eelistused',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        \"Näpunäide: vajutage Ctrl-V (Cmd-V Mac'il) manuse lisamiseks kleebist.\",\n      private: 'Privaatne',\n      project: 'Projekt',\n      projectNotFound_title: 'Projekt ei leitud',\n      projectOwner: 'Projekti omanik',\n      referenceDataAndKnowledgeStorage: 'Viideandmete ja teadmise salvestamiseks.',\n      regenerateApiKey_title: 'Taasta API võti',\n      rejectUnauthorizedTlsCertificates: 'Lükka tagasi volitamata TLS-sertifikaadid',\n      removeManager_title: 'Eemalda haldur',\n      removeMember_title: 'Eemalda liige',\n      role: 'Roll',\n      saveThisKeyItWillNotBeShownAgain: 'Salvesta see võti — seda ei näidata enam!',\n      searchCards: 'Kaartide otsimine...',\n      searchCustomFieldGroups: 'Kohandatud väljade gruppide otsimine...',\n      searchCustomFields: 'Kohandatud väljade otsimine...',\n      searchLabels: 'Siltide otsimine...',\n      searchLists: 'Nimekiri otsimine...',\n      searchMembers: 'Liikmete otsimine...',\n      searchProjects: 'Projektide otsimine...',\n      searchUsers: 'Kasutajate otsimine...',\n      seconds: 'Sekundid',\n      selectAssignee_title: 'Vali vastutaja',\n      selectBoard: 'Vali tahvel',\n      selectList: 'Vali nimekiri',\n      selectListToRestoreThisCard: 'Vali nimekiri, et seda kaart taastada',\n      selectOrder_title: 'Vali järjekord',\n      selectPermissions_title: 'Vali õigused',\n      selectProject: 'Vali projekt',\n      selectRole_title: 'Vali roll',\n      selectType_title: 'Vali tüüp',\n      sequentialDisplayOfCards: 'Järjestikune kaardi kuvamine.',\n      settings: 'Seaded',\n      shared: 'Jagatud',\n      sharedWithMe_title: 'Jagatud minuga',\n      showOnFrontOfCard: 'Kuva kaardi ees',\n      smtp: 'SMTP',\n      sortList_title: 'Nimekiri sorteerimine',\n      sourceCardIsNoLongerAvailableForCopying: 'Lähtekaart ei ole enam kopeerimiseks saadaval.',\n      sourceCardIsNoLongerAvailableForMoving: 'Lähtekaart ei ole enam liigutamiseks saadaval.',\n      stopwatch: 'Stopper',\n      story: 'Kirjeldus',\n      subscribeToCardWhenCommenting: 'Telli kaart, kui kommenteerida',\n      subscribeToMyOwnCardsByDefault: 'Telli oma kaardid vaikimisi',\n      taskActions_title: 'Ülesande tegevused',\n      taskAssignmentAndProjectCompletion: 'Ülesande määramine ja projekti lõpetamine.',\n      taskListActions_title: 'Ülesannete nimekiri tegevused',\n      taskList_title: 'Ülesanne nimekiri',\n      team: 'Töögrupp',\n      termsOfService_title: 'Teenuse tingimused',\n      testLog_title: 'Testilogi',\n      thereIsNoPreviewAvailableForThisAttachment: 'Selle manusi eelvaadet pole saadaval.',\n      time: 'Aeg',\n      title: 'Pealkiri',\n      trash: 'Prügikast',\n      trashHasBeenSuccessfullyEmptied: 'Prügikast on edukalt tühjendatud.',\n      turnOffRecentCardHighlighting: 'Lülita välja hiljuti kuvatud kaardi üleminek',\n      typeNameToConfirm: 'Sisestage nimi, et kinnitada.',\n      typeTitleToConfirm: 'Sisestage pealkiri, et kinnitada.',\n      unlinkSso_title: 'SSO sidemete eemaldamine',\n      unsavedChanges: 'Muudetud andmed',\n      uploadFailedFileIsTooBig: 'Üleslaadimine ebaõnnestus: fail on liiga suur.',\n      uploadFailedNotEnoughStorageSpace:\n        'Üleslaadimine ebaõnnestus: pole piisavalt salvestusruumi.',\n      uploadedImages: 'Laaditud pildid',\n      url: 'URL',\n      useSecureConnection: 'Kasuta turvalist ühendust',\n      userActions_title: 'Kasutaja tegevused',\n      userAddedCardToList: '<0>{{user}}</0> lisas <2>{{card}}</2> nimekirjaan {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> lisas selle kaardi nimekirjaan {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> lisas {{addedUser}} kaardile <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> lisas {{addedUser}} selle kaardi',\n      userAddedYouToCard: '<0>{{user}}</0> lisas sind kaardile <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> täideti {{task}} kaardil <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> täideti {{task}} selle kaardil',\n      userJoinedCard: '<0>{{user}}</0> liitus <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> liitus selle kaardiga',\n      userLeftCard: '<0>{{user}}</0> lahkus <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> jäi uus kommentaar «{{comment}}» kaardile <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> lahkus selle kaardist',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> märgis {{task}} täitmata kaardil <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard: '<0>{{user}}</0> märgis {{task}} täitmata selle kaardil',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> mainis sind kommentaaris «{{comment}}» kaardil <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> liigutas <2>{{card}}</2> nimekirjast {{fromList}} nimekirjaan {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> liigutas selle kaardi nimekirjast {{fromList}} nimekirjaan {{toList}}',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0> eemaldas {{removedUser}} kaardilt <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> eemaldas {{removedUser}} selle kaardist',\n      username: 'Kasutajanimi',\n      users: 'Kasutajad',\n      viewer: 'Vaataja',\n      viewers: 'Vaatajad',\n      visualTaskManagementWithLists: 'Visual tööülesande haldamine nimekirjade abil.',\n      webhooks: 'Webhookid',\n      whatsNew_title: 'Mis on uut',\n      withoutBaseGroup: 'Ilma põhiklassita',\n      writeComment: 'Kirjuta kommentaar...',\n    },\n\n    action: {\n      activateUser: 'Aktiveeri kasutaja',\n      activateUser_title: 'Aktiveeri kasutaja',\n      addAnotherCard: 'Lisa veel kaart',\n      addAnotherList: 'Lisa veel nimekiri',\n      addAnotherTask: 'Lisa veel ülesanne',\n      addCard: 'Lisa kaart',\n      addCard_title: 'Lisa kaart',\n      addComment: 'Lisa kommentaar',\n      addCustomField: 'Lisa kohandatud väli',\n      addCustomFieldGroup: 'Lisa kohandatud väljade grupp',\n      addList: 'Lisa nimekiri',\n      addMember: 'Lisa liige',\n      addMoreDetailedDescription: 'Lisa üksikasjalikum kirjeldus',\n      addTask: 'Lisa ülesanne',\n      addTaskList: 'Lisa ülesannete nimekiri',\n      addToCard: 'Lisa kaardile',\n      addUser: 'Lisa kasutaja',\n      addWebhook: 'Lisa webhook',\n      archive: 'Arhiveeri',\n      archiveCard: 'Arhiveeri kaart',\n      archiveCard_title: 'Arhiveeri kaart',\n      archiveCards: 'Arhiveeri kaardid',\n      archiveCards_title: 'Arhiveeri kaardid',\n      assignAsOwner: 'Määra omanikuks',\n      cancel: 'Tühista',\n      copy: 'Kopeeri',\n      copyCard_title: 'Kopeeri kaart',\n      createApiKey: 'Loo API võti',\n      createBoard: 'Loo tahvel',\n      createCustomFieldGroup: 'Loo kohandatud väljade grupp',\n      createFile: 'Loo fail',\n      createLabel: 'Loo silt',\n      createNewLabel: 'Loo uus silt',\n      createProject: 'Loo projekt',\n      cut: 'Lõika',\n      cutCard_title: 'Lõika kaart',\n      deactivateUser: 'Deaktiveeri kasutaja',\n      deactivateUser_title: 'Deaktiveeri kasutaja',\n      delete: 'Kustuta',\n      deleteApiKey: 'Kustuta API võti',\n      deleteAttachment: 'Kustuta manus',\n      deleteAvatar: 'Kustuta avatar',\n      deleteBackgroundImage: 'Kustuta taustapilt',\n      deleteBoard: 'Kustuta tahvel',\n      deleteBoard_title: 'Kustuta tahvel',\n      deleteCard: 'Kustuta kaart',\n      deleteCardForever: 'Kustuta kaart jäädavalt',\n      deleteCard_title: 'Kustuta kaart',\n      deleteComment: 'Kustuta kommentaar',\n      deleteCustomField: 'Kustuta kohandatud väli',\n      deleteCustomFieldGroup: 'Kustuta kohandatud väljade grupp',\n      deleteForever_title: 'Kustuta jäädavalt',\n      deleteGroup: 'Kustuta grupp',\n      deleteLabel: 'Kustuta silt',\n      deleteList: 'Kustuta nimekiri',\n      deleteList_title: 'Kustuta nimekiri',\n      deleteNotificationService: 'Kustuta teavitusteenus',\n      deleteProject: 'Kustuta projekt',\n      deleteProject_title: 'Kustuta projekt',\n      deleteTask: 'Kustuta ülesanne',\n      deleteTaskList: 'Kustuta ülesannete nimekiri',\n      deleteTask_title: 'Kustuta ülesanne',\n      deleteUser: 'Kustuta kasutaja',\n      deleteUser_title: 'Kustuta kasutaja',\n      deleteWebhook: 'Kustuta webhook',\n      dismissAll: 'Eemalda kõik',\n      download: 'Laadi alla',\n      duplicateCard_title: 'Duplikaardi loomine',\n      edit: 'Muuda',\n      editColor_title: 'Muuda värvi',\n      editDescription_title: 'Muuda kirjeldust',\n      editDueDate_title: 'Muuda tähtaega',\n      editEmail_title: 'Muuda e-posti',\n      editGroup: 'Muuda gruppi',\n      editInformation_title: 'Muuda infot',\n      editPassword_title: 'Muuda parooli',\n      editPermissions: 'Muuda õigusi',\n      editRole_title: 'Muuda rolli',\n      editStopwatch_title: 'Muuda stopperit',\n      editTitle_title: 'Muuda pealkirja',\n      editType_title: 'Muuda tüüpi',\n      editUsername_title: 'Muuda kasutajanime',\n      emptyTrash: 'Tühjenda prügikast',\n      emptyTrash_title: 'Tühjenda prügikast',\n      import: 'Impordi',\n      join: 'Liitu',\n      leave: 'Lahku',\n      leaveBoard: 'Lahku tahvlilt',\n      leaveProject: 'Lahku projektist',\n      logOut_title: 'Logi välja',\n      makeCover_title: 'Määra kaaneks',\n      makeProjectPrivate: 'Muuda projekt privaatseks',\n      makeProjectPrivate_title: 'Muuda projekt privaatseks',\n      makeProjectShared: 'Muuda projekt jagatavaks',\n      makeProjectShared_title: 'Muuda projekt jagatavaks',\n      move: 'Liiguta',\n      moveCard_title: 'Liiguta kaart',\n      moveList_title: 'Liiguta nimekiri',\n      regenerateApiKey: 'Taasta API võti',\n      remove: 'Eemalda',\n      removeAssignee: 'Eemalda vastutaja',\n      removeColor: 'Eemalda värv',\n      removeCover_title: 'Eemalda kaas',\n      removeFromBoard: 'Eemalda tahvlilt',\n      removeFromProject: 'Eemalda projektist',\n      removeManager: 'Eemalda haldur',\n      removeMember: 'Eemalda liige',\n      restoreToList: 'Taasta nimekirja {{list}}',\n      returnToBoard: 'Tagasi tahvlile',\n      save: 'Salvesta',\n      sendTestEmail: 'Saada test e-kiri',\n      showActive: 'Näita aktiivseid',\n      showAllAttachments: 'Näita kõiki manuseid ({{hidden}} peidetud)',\n      showCardsWithThisUser: 'Näita selle kasutajaga kaarte',\n      showDeactivated: 'Näita deaktiveerituid',\n      showFewerAttachments: 'Näita vähem manuseid',\n      showLess: 'Näita vähem',\n      showMore: 'Näita rohkem',\n      sortList_title: 'Sorteeri nimekiri',\n      start: 'Alusta',\n      stop: 'Peata',\n      subscribe: 'Telli',\n      unlinkSso: 'Eemalda SSO side',\n      unlinkSso_title: 'Eemalda SSO side',\n      unsubscribe: 'Tühista tellimus',\n      uploadNewAvatar: 'Laadi üles uus avatar',\n      uploadNewImage: 'Laadi üles uus pilt',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/et-EE/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'et-EE',\n  country: 'ee',\n  name: 'Eesti',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/et-EE/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Aktiivsete kasutajate limiit on täis',\n      adminLoginRequiredToInitializeInstance:\n        'Administraatori sisselogimine on vajalik rakenduse käivitamiseks',\n      emailAlreadyInUse: 'E-post on juba kasutusel',\n      emailOrUsername: 'E-post või kasutajanimi',\n      invalidCredentials: 'Vale kasutajanimi või parool',\n      invalidEmailOrUsername: 'Vale e-post või kasutajanimi',\n      invalidPassword: 'Vale parool',\n      logIn_title: 'Logi sisse',\n      noInternetConnection: 'Internetiühendus puudub',\n      or: 'Või',\n      pageNotFound_title: 'Lehte ei leitud',\n      password: 'Parool',\n      poweredByPlanka: 'Töötab <1>PLANKA</1> platvormil',\n      serverConnectionFailed: 'Serveriühendus ebaõnnestus',\n      unknownError: 'Tundmatu viga, proovi hiljem uuesti',\n      useSingleSignOn: 'Kasuta ühekordset sisselogimist',\n      usernameAlreadyInUse: 'Kasutajanimi on juba kasutusel',\n      whoops_title: 'Ups!',\n    },\n\n    action: {\n      cancelAndClose: 'Tühista ja sulge',\n      continue: 'Jätka',\n      debugSso: 'Siluda SSO',\n      goBack: 'Tagasi',\n      goHome: 'Koju',\n      logIn: 'Logi sisse',\n      logInWithSso: 'Logi sisse SSO-ga',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/et-EE/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"See on tekst ilma pealkirjata.\\nNii pealkirja kui ka teksti\\nsaab esile tõsta paksus, kaldkirjas, värvilisena,\\nläbikriipsutatuna ja allajoonituna.\",\n    \"text-with-head\": \"See on tekst koos pealkirjaga.\\nNii pealkirja kui ka teksti\\nsaab esile tõsta paksus, kaldkirjas, värvilisena,\\nläbikriipsutatuna ja allajoonituna.\",\n    \"heading\": \"Pealkiri\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Viga markdown-redaktoris\",\n    \"settings_wysiwyg\": \"Visuaalne redaktor (wysiwyg)\",\n    \"settings_markup\": \"Markdown märgistus\",\n    \"markup_placeholder\": \"Sisesta markdown märgistus...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Eemalda\",\n    \"empty_option\": \"Vastavusi ei leitud\",\n    \"show_line_numbers\": \"Ridade nummerdamine\"\n  },\n  \"common\": {\n    \"delete\": \"Kustuta\",\n    \"edit\": \"Muuda\",\n    \"toolbar_action_disabled\": \"Ühildumatu märgistus\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Tühista\",\n    \"common_action_submit\": \"Saada\",\n    \"common_action_upload\": \"Vali\",\n    \"common_tab_attach\": \"Lisa seadmest\",\n    \"common_tab_link\": \"Lisa lingi kaudu\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Suurus, px\",\n    \"image_name\": \"Pealkiri\",\n    \"image_link_href\": \"Pildi link\",\n    \"image_link_href_help\": \"Aadress, kuhu pildi link viib.\",\n    \"image_alt\": \"Alternatiivtekst\",\n    \"image_alt_help\": \"Alternatiivtekst kuvatakse, kui pilti ei saa laadida.\",\n    \"image_upload_help\": \"JPEG, GIF või PNG pilt kuni 1 MB.\",\n    \"image_upload_failed\": \"Pildi lisamine ebaõnnestus\",\n    \"image_size_width\": \"Laius\",\n    \"image_size_height\": \"Kõrgus\",\n    \"link_url_help\": \"Aadress, kuhu link viib.\",\n    \"link_text\": \"Lingitekst\",\n    \"link_text_help\": \"Tekst, mida kuvatakse lingina.\",\n    \"link_open_help\": \"Ava link uues vahekaardis\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Pealkiri\",\n    \"header_hint\": \"# Sinu tekst\",\n    \"italic_title\": \"Kaldkiri\",\n    \"italic_hint\": \"_Sinu tekst_\",\n    \"bold_title\": \"Paks\",\n    \"bold_hint\": \"**Sinu tekst**\",\n    \"strikethrough_title\": \"Läbikriipsutus\",\n    \"strikethrough_hint\": \"~~Sinu tekst~~\",\n    \"blockquote_title\": \"Tsitaat\",\n    \"blockquote_hint\": \"> Sinu tekst\",\n    \"code_title\": \"Kood\",\n    \"code_hint\": \"```Sinu tekst```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[Sinu tekst](url)\",\n    \"image_title\": \"Pilt\",\n    \"image_hint\": \"![Sinu tekst](url)\",\n    \"list_title\": \"Loendi element\",\n    \"list_hint\": \"- Sinu tekst\",\n    \"numbered-list_title\": \"Nummerdatud loend\",\n    \"numbered-list_hint\": \"1. Sinu tekst\",\n    \"documentation\": \"Dokumentatsioon\",\n    \"documentation_link\": \"https://diplodoc.com/docs/et/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Paks\",\n    \"code\": \"Kood\",\n    \"code_inline\": \"Reakood\",\n    \"codeblock\": \"Koodiplokk\",\n    \"colorify\": \"Teksti värv\",\n    \"colorify__color_blue\": \"Sinine\",\n    \"colorify__color_default\": \"Vaikimisi\",\n    \"colorify__color_gray\": \"Hall\",\n    \"colorify__color_green\": \"Roheline\",\n    \"colorify__color_orange\": \"Oranž\",\n    \"colorify__color_red\": \"Punane\",\n    \"colorify__color_violet\": \"Violetne\",\n    \"colorify__color_yellow\": \"Kollane\",\n    \"colorify__group_text\": \"Tekst\",\n    \"cut\": \"Lõika\",\n    \"emoji\": \"Emotikon\",\n    \"emoji__hint\": \"Emotikoone saab lisada WYSIWYG-is või käsitsi märgistusega\",\n    \"heading\": \"Pealkiri\",\n    \"heading1\": \"Pealkiri 1\",\n    \"heading2\": \"Pealkiri 2\",\n    \"heading3\": \"Pealkiri 3\",\n    \"heading4\": \"Pealkiri 4\",\n    \"heading5\": \"Pealkiri 5\",\n    \"heading6\": \"Pealkiri 6\",\n    \"hrule\": \"Eraldaja\",\n    \"image\": \"Pilt\",\n    \"italic\": \"Kaldkiri\",\n    \"link\": \"Link\",\n    \"list\": \"Loend\",\n    \"list__action_lift\": \"Tõsta üles\",\n    \"list__action_sink\": \"Langeta alla\",\n    \"list_action_disabled\": \"Loogikaga vastuolus\",\n    \"mark\": \"Märgitud\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Rohkem toiminguid\",\n    \"note\": \"Märkus\",\n    \"olist\": \"Järjestatud loend\",\n    \"quote\": \"Tsitaat\",\n    \"redo\": \"Tee uuesti\",\n    \"strike\": \"Läbikriipsutus\",\n    \"table\": \"Tabel\",\n    \"text\": \"Tekst\",\n    \"ulist\": \"Punktloend\",\n    \"underline\": \"Allajoonitud\",\n    \"undo\": \"Võta tagasi\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Kirjuta /, et kasutada käsklusi...\",\n    \"checkbox\": \"Sisesta ülesande kirjeldus...\",\n    \"deflist_term\": \"Mõiste\",\n    \"deflist_desc\": \"Mõiste kirjeldus\",\n    \"heading\": \"Pealkiri\",\n    \"cut_title\": \"Pealkiri\",\n    \"cut_content\": \"Sisu, mida kuvatakse klõpsamisel\",\n    \"note_title\": \"Pealkiri\",\n    \"note_content\": \"Märkuse sisu\",\n    \"table_cell\": \"Lahtri sisu\",\n    \"select_filter\": \"Otsi keeli...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Tõstutundlik\",\n    \"label_whole-word\": \"Terve sõna\",\n    \"title\": \"Otsi koodist\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Ei leitud\"\n  },\n  \"widgets\": {\n    \"image\": \"Lisa pilt\",\n    \"link\": \"Lisa link\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Märkus\",\n    \"tip\": \"Nipp\",\n    \"warning\": \"Hoiatus\",\n    \"alert\": \"Häire\",\n    \"remove\": \"Eemalda\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Lisa veerg enne\",\n    \"column.add.after\": \"Lisa veerg pärast\",\n    \"column.remove\": \"Eemalda veerg\",\n    \"column.remove.multiple\": \"Eemalda veerud\",\n    \"row.add.before\": \"Lisa rida enne\",\n    \"row.add.after\": \"Lisa rida pärast\",\n    \"row.remove\": \"Eemalda rida\",\n    \"row.remove.multiple\": \"Eemalda read\",\n    \"cells.clear\": \"Tühjenda lahtrid\",\n    \"table.remove\": \"Eemalda tabel\",\n    \"table.menu.cell.align.left\": \"Joonda lahtri sisu vasakule\",\n    \"table.menu.cell.align.right\": \"Joonda lahtri sisu paremale\",\n    \"table.menu.cell.align.center\": \"Joonda lahtri sisu keskele\",\n    \"table.menu.row.add\": \"Lisa rida pärast\",\n    \"table.menu.row.remove\": \"Eemalda rida\",\n    \"table.menu.column.add\": \"Lisa veerg pärast\",\n    \"table.menu.column.remove\": \"Eemalda veerg\",\n    \"table.menu.convert.yfm\": \"Konverteeri YFM-tabeliks\",\n    \"table.menu.table.remove\": \"Eemalda tabel\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/fa-IR/core.js",
    "content": "import dateFns from 'date-fns/locale/fa-IR';\nimport timeAgo from 'javascript-time-ago/locale/fa';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'M/d/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'MMM d',\n    longDateTime: \"MMMM d 'at' p\",\n    fullDate: 'MMM d, y',\n    fullDateTime: \"MMMM d, y 'at' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'درباره برنامه',\n      aboutPlanka_title: 'درباره PLANKA',\n      accessToken: 'رمز دسترسی',\n      account: 'حساب کاربری',\n      actions: 'اقدامات',\n      activateUser_title: 'فعال کردن کاربر',\n      active: 'فعال',\n      addAttachment_title: 'اضافه کردن پیوست',\n      addCustomFieldGroup_title: 'اضافه کردن گروه فیلد سفارشی',\n      addCustomField_title: 'اضافه کردن فیلد سفارشی',\n      addManager_title: 'اضافه کردن مدیر',\n      addMember_title: 'اضافه کردن عضو',\n      addTaskList_title: 'اضافه کردن لیست وظایف',\n      addUser_title: 'اضافه کردن کاربر',\n      admin: 'مدیر',\n      administration: 'مدیریت',\n      all: 'همه',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'تمام تغییرات به صورت خودکار ذخیره می‌شوند<br />بعد از بازیابی ارتباط.',\n      alphabetically: 'بر اساس حروف الفبا',\n      alwaysDisplayCardCreator: 'همیشه سازنده کارت را نمایش بده',\n      apiKeyCreated_title: 'کلید API ایجاد شد',\n      apiKey_title: 'کلید API',\n      archive: 'آرشیو',\n      archiveCard_title: 'آرشیو کارت',\n      archiveCards_title: 'آرشیو کارت‌ها',\n      areYouSureYouWantToActivateThisUser: 'آیا مطمئن هستید که می‌خواهید این کاربر را فعال کنید؟',\n      areYouSureYouWantToArchiveCards: 'آیا مطمئن هستید که می‌خواهید کارت‌ها را آرشیو کنید؟',\n      areYouSureYouWantToArchiveThisCard: 'آیا مطمئن هستید که می‌خواهید این کارت را آرشیو کنید؟',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'آیا مطمئن هستید که می‌خواهید این مدیر پروژه را به عنوان مالک تعیین کنید؟',\n      areYouSureYouWantToDeactivateThisUser:\n        'آیا مطمئن هستید که می‌خواهید این کاربر را غیرفعال کنید؟',\n      areYouSureYouWantToDeleteThisApiKey: 'آیا مطمئن هستید که می‌خواهید این کلید API را حذف کنید؟',\n      areYouSureYouWantToDeleteThisAttachment:\n        'آیا مطمئن هستید که می‌خواهید این پیوست را حذف کنید؟',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'آیا مطمئن هستید که می‌خواهید این تصویر پس‌زمینه را حذف کنید؟',\n      areYouSureYouWantToDeleteThisBoard: 'آیا مطمئن هستید که می‌خواهید این برد را حذف کنید؟',\n      areYouSureYouWantToDeleteThisCard: 'آیا مطمئن هستید که می‌خواهید این کارت را حذف کنید؟',\n      areYouSureYouWantToDeleteThisCardForever:\n        'آیا مطمئن هستید که می‌خواهید این کارت را برای همیشه حذف کنید؟',\n      areYouSureYouWantToDeleteThisComment: 'آیا مطمئن هستید که می‌خواهید این نظر را حذف کنید؟',\n      areYouSureYouWantToDeleteThisCustomField:\n        'آیا مطمئن هستید که می‌خواهید این فیلد سفارشی را حذف کنید؟',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'آیا مطمئن هستید که می‌خواهید این گروه فیلد سفارشی را حذف کنید؟',\n      areYouSureYouWantToDeleteThisLabel: 'آیا مطمئن هستید که می‌خواهید این برچسب را حذف کنید؟',\n      areYouSureYouWantToDeleteThisList:\n        'آیا مطمئن هستید که می‌خواهید این لیست را حذف کنید؟ همه کارت‌ها به سطل زباله منتقل خواهند شد.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'آیا مطمئن هستید که می‌خواهید این سرویس اعلان را حذف کنید؟',\n      areYouSureYouWantToDeleteThisProject: 'آیا مطمئن هستید که می‌خواهید این پروژه را حذف کنید؟',\n      areYouSureYouWantToDeleteThisTask: 'آیا مطمئن هستید که می‌خواهید این وظیفه را حذف کنید؟',\n      areYouSureYouWantToDeleteThisTaskList:\n        'آیا مطمئن هستید که می‌خواهید این لیست وظایف را حذف کنید؟',\n      areYouSureYouWantToDeleteThisUser: 'آیا مطمئن هستید که می‌خواهید این کاربر را حذف کنید؟',\n      areYouSureYouWantToDeleteThisWebhook: 'آیا مطمئن هستید که می‌خواهید این وب‌هوک را حذف کنید؟',\n      areYouSureYouWantToEmptyTrash: 'آیا مطمئن هستید که می‌خواهید سطل زباله را خالی کنید؟',\n      areYouSureYouWantToLeaveBoard: 'آیا مطمئن هستید که می‌خواهید از برد خارج شوید؟',\n      areYouSureYouWantToLeaveProject: 'آیا مطمئن هستید که می‌خواهید از پروژه خارج شوید؟',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'آیا مطمئن هستید که می‌خواهید این پروژه را خصوصی کنید؟',\n      areYouSureYouWantToMakeThisProjectShared:\n        'آیا مطمئن هستید که می‌خواهید این پروژه را به اشتراک بگذارید؟',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'آیا مطمئن هستید که می‌خواهید این کلید API را بازسازی کنید؟ کلید قبلی دیگر کار نخواهد کرد.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'آیا مطمئن هستید که می‌خواهید این مدیر را از پروژه حذف کنید؟',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'آیا مطمئن هستید که می‌خواهید این عضو را از برد حذف کنید؟',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'آیا مطمئن هستید که می‌خواهید پیوند SSO را از این کاربر لغو کنید؟ این به کاربر اجازه می‌دهد با رمز عبور وارد شود.',\n      assignAsOwner_title: 'تعیین به عنوان مالک',\n      atLeastOneListMustBePresent: 'حداقل یک لیست باید وجود داشته باشد',\n      attachment: 'پیوست',\n      attachments: 'پیوست‌ها',\n      authentication: 'احراز هویت',\n      background: 'پس‌زمینه',\n      baseCustomFields_title: 'فیلدهای سفارشی پایه',\n      baseGroup: 'گروه پایه',\n      board: 'برد',\n      boardActions_title: 'اقدامات برد',\n      boardNotFound_title: 'برد یافت نشد',\n      boardSubscribed: 'برد مشترک شد',\n      boardUser: 'کاربر برد',\n      byCreationTime: 'بر اساس زمان ایجاد',\n      byDefault: 'به طور پیش‌فرض',\n      byDueDate: 'بر اساس تاریخ سررسید',\n      canBeInvitedToWorkInBoards: 'می‌تواند برای کار در بردها دعوت شود.',\n      canComment: 'می‌تواند نظر بدهد',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'می‌تواند پروژه‌های خود را ایجاد کند و برای کار در پروژه‌های دیگران دعوت شود.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'می‌تواند طرح‌بندی برد را ویرایش کند و اعضا را به کارت‌ها اختصاص دهد.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'می‌تواند تنظیمات سیستم را مدیریت کند و به عنوان مالک پروژه عمل کند.',\n      canOnlyViewBoard: 'فقط می‌تواند برد را مشاهده کند.',\n      cardActions_title: 'اقدامات کارت',\n      cardNotFound_title: 'کارت یافت نشد',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'کارت‌های این لیست برای همه اعضای برد در دسترس هستند.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'کارت‌های این لیست کامل شده‌اند و آماده آرشیو هستند.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'کارت‌های این لیست آماده کار هستند.',\n      clickHereOrRefreshPageToUpdate:\n        '<0>اینجا کلیک کنید</0> یا صفحه را تازه کنید تا به‌روزرسانی شود.',\n      clientHostnameInEhlo: 'نام میزبان کلاینت در EHLO',\n      closed: 'بسته',\n      color: 'رنگ',\n      comments: 'نظرات',\n      contentExceedsLimit: 'محتوا از {{limit}} تجاوز کرده است',\n      contentOfThisAttachmentIsTooBigToDisplay: 'محتوای این پیوست برای نمایش خیلی بزرگ است.',\n      copy_inline: 'کپی',\n      createBoard_title: 'ایجاد برد',\n      createCustomFieldGroup_title: 'ایجاد گروه فیلد سفارشی',\n      createLabel_title: 'ایجاد برچسب',\n      createNewOneOrSelectExistingOne: 'یک جدید ایجاد کنید یا<br />یکی موجود را انتخاب کنید.',\n      createProject_title: 'ایجاد پروژه',\n      createTextFile_title: 'ایجاد فایل متنی',\n      creator: 'سازنده',\n      currentPassword: 'رمز عبور فعلی',\n      currentUser: 'کاربر فعلی',\n      customFieldGroup_title: 'گروه فیلد سفارشی',\n      customFieldGroups_title: 'گروه‌های فیلد سفارشی',\n      customField_title: 'فیلد سفارشی',\n      customFields_title: 'فیلدهای سفارشی',\n      customerPanel_title: 'پنل مشتری',\n      dangerZone_title: 'منطقه خطر',\n      date: 'تاریخ',\n      deactivateUser_title: 'غیرفعال کردن کاربر',\n      defaultCardType_title: 'نوع کارت پیش‌فرض',\n      defaultFrom: 'پیش‌فرض از',\n      defaultView_title: 'نمای پیش‌فرض',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'همه بردها را حذف کنید تا بتوانید این پروژه را حذف کنید',\n      deleteApiKey_title: 'حذف کلید API',\n      deleteAttachment_title: 'حذف پیوست',\n      deleteBackgroundImage_title: 'حذف تصویر پس‌زمینه',\n      deleteBoard_title: 'حذف برد',\n      deleteCardForever_title: 'حذف کارت برای همیشه',\n      deleteCard_title: 'حذف کارت',\n      deleteComment_title: 'حذف نظر',\n      deleteCustomFieldGroup_title: 'حذف گروه فیلد سفارشی',\n      deleteCustomField_title: 'حذف فیلد سفارشی',\n      deleteLabel_title: 'حذف برچسب',\n      deleteList_title: 'حذف لیست',\n      deleteNotificationService_title: 'حذف سرویس اعلان',\n      deleteProject_title: 'حذف پروژه',\n      deleteTaskList_title: 'حذف لیست وظایف',\n      deleteTask_title: 'حذف وظیفه',\n      deleteUser_title: 'حذف کاربر',\n      deleteWebhook_title: 'حذف وب‌هوک',\n      deletedUser_title: 'کاربر حذف شده',\n      description: 'توضیحات',\n      display: 'نمایش',\n      displayCardAges: 'نمایش سن کارت‌ها',\n      dropFileToUpload: 'فایل را برای آپلود بکشید',\n      dueDate_title: 'تاریخ سررسید',\n      dynamicAndUnevenlySpacedLayout: 'طرح‌بندی پویا و نامتقارن.',\n      editAttachment_title: 'ویرایش پیوست',\n      editAvatar_title: 'ویرایش آواتار',\n      editColor_title: 'ویرایش رنگ',\n      editCustomFieldGroup_title: 'ویرایش گروه فیلد سفارشی',\n      editCustomField_title: 'ویرایش فیلد سفارشی',\n      editDueDate_title: 'ویرایش تاریخ سررسید',\n      editEmail_title: 'ویرایش ایمیل',\n      editInformation_title: 'ویرایش اطلاعات',\n      editLabel_title: 'ویرایش برچسب',\n      editPassword_title: 'ویرایش رمز عبور',\n      editPermissions_title: 'ویرایش دسترسی‌ها',\n      editRole_title: 'ویرایش نقش',\n      editStopwatch_title: 'ویرایش کرنومتر',\n      editType_title: 'ویرایش نوع',\n      editUsername_title: 'ویرایش نام کاربری',\n      editor: 'ویرایشگر',\n      editors: 'ویرایشگران',\n      email: 'ایمیل',\n      emptyTrash_title: 'خالی کردن سطل زباله',\n      enterCardTitle: 'عنوان کارت را وارد کنید...',\n      enterDescription: 'توضیحات را وارد کنید...',\n      enterFilename: 'نام فایل را وارد کنید',\n      enterListTitle: 'عنوان لیست را وارد کنید...',\n      enterTaskDescription: 'توضیحات وظیفه را وارد کنید...',\n      events: 'رویدادها',\n      excludedEvents: 'رویدادهای مستثنی',\n      expandTaskListsByDefault: 'به طور پیش‌فرض لیست وظایف را باز کن',\n      filterByLabels_title: 'فیلتر بر اساس برچسب‌ها',\n      filterByMembers_title: 'فیلتر بر اساس اعضا',\n      forPersonalProjects: 'برای پروژه‌های شخصی.',\n      forTeamBasedProjects: 'برای پروژه‌های تیمی.',\n      fromComputer_title: 'از کامپیوتر',\n      fromTrello: 'از Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'کلید کامل به دلایل امنیتی مخفی است. آن را بازسازی کنید تا یک کلید جدید ایجاد شود.',\n      general: 'عمومی',\n      gradients: 'گرادیان‌ها',\n      grid: 'شبکه',\n      hideCompletedTasks: 'مخفی کردن وظایف تکمیل شده',\n      hideFromProjectListAndFavorites: 'مخفی کردن از لیست پروژه‌ها و علاقه‌مندی‌ها',\n      host: 'میزبان',\n      hours: 'ساعت‌ها',\n      identity: 'هویت',\n      importBoard_title: 'وارد کردن برد',\n      information: 'اطلاعات',\n      invalidCurrentPassword: 'رمز عبور فعلی نامعتبر است',\n      kanban: 'کانبان',\n      labels: 'برچسب‌ها',\n      language: 'زبان',\n      leaveBoard_title: 'ترک برد',\n      leaveProject_title: 'ترک پروژه',\n      limitCardTypesToDefaultOne: 'محدود کردن انواع کارت به نوع پیش‌فرض',\n      linkToCard: 'لینک به کارت',\n      list: 'لیست',\n      listActions_title: 'اقدامات لیست',\n      lists: 'لیست‌ها',\n      makeProjectPrivate_title: 'خصوصی کردن پروژه',\n      makeProjectShared_title: 'به اشتراک گذاشتن پروژه',\n      managers: 'مدیران',\n      memberActions_title: 'اقدامات عضو',\n      members: 'اعضا',\n      minutes: 'دقیقه‌ها',\n      moreActions: 'اقدامات بیشتر',\n      moreActions_title: 'اقدامات بیشتر',\n      moveCard_title: 'انتقال کارت',\n      moveList_title: 'انتقال لیست',\n      myOwn_title: 'مال خودم',\n      name: 'نام',\n      newEmail: 'ایمیل جدید',\n      newPassword: 'رمز عبور جدید',\n      newUsername: 'نام کاربری جدید',\n      newVersionAvailable: 'نسخه جدید موجود است',\n      newestFirst: 'جدیدترین اول',\n      noApiKeyCreated: 'هیچ کلید API ایجاد نشده است.',\n      noBoards: 'بردی وجود ندارد',\n      noCardsFound: 'کارتی یافت نشد.',\n      noConnectionToServer: 'ارتباط با سرور قطع است',\n      noLists: 'لیستی وجود ندارد',\n      noProjects: 'پروژه‌ای وجود ندارد',\n      noUnreadNotifications: 'اعلان خوانده نشده‌ای وجود ندارد.',\n      notifications: 'اعلان‌ها',\n      oldestFirst: 'قدیمی‌ترین اول',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'برای خصوصی کردن این پروژه باید فقط یک مدیر باقی بماند',\n      openBoard_title: 'باز کردن برد',\n      optional_inline: 'اختیاری',\n      organization: 'سازمان',\n      others: 'دیگران',\n      passwordIsSet: 'رمز عبور تنظیم شده است',\n      phone: 'تلفن',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'پلانکا از <1><0>Apprise</0></1> برای ارسال اعلان به بیش از ۱۰۰ سرویس محبوب استفاده می‌کند.',\n      port: 'پورت',\n      preferences: 'ترجیحات',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'نکته: با فشردن Ctrl-V (Cmd-V در مک) می‌توانید پیوست را از کلیپ بورد اضافه کنید.',\n      private: 'خصوصی',\n      project: 'پروژه',\n      projectNotFound_title: 'پروژه یافت نشد',\n      projectOwner: 'مالک پروژه',\n      referenceDataAndKnowledgeStorage: 'ذخیره‌سازی داده‌های مرجع و دانش.',\n      regenerateApiKey_title: 'بازسازی کلید API',\n      rejectUnauthorizedTlsCertificates: 'رد کردن گواهی‌نامه‌های TLS غیرمجاز',\n      removeManager_title: 'حذف مدیر',\n      removeMember_title: 'حذف عضو',\n      role: 'نقش',\n      saveThisKeyItWillNotBeShownAgain: 'این کلید را ذخیره کنید — دیگر نشان داده نخواهد شد!',\n      searchCards: 'جستجوی کارت‌ها...',\n      searchCustomFieldGroups: 'جستجوی گروه‌های فیلد سفارشی...',\n      searchCustomFields: 'جستجوی فیلدهای سفارشی...',\n      searchLabels: 'جستجوی برچسب‌ها...',\n      searchLists: 'جستجوی لیست‌ها...',\n      searchMembers: 'جستجوی اعضا...',\n      searchProjects: 'جستجوی پروژه‌ها...',\n      searchUsers: 'جستجوی کاربران...',\n      seconds: 'ثانیه‌ها',\n      selectAssignee_title: 'انتخاب مسئول',\n      selectBoard: 'انتخاب برد',\n      selectList: 'انتخاب لیست',\n      selectListToRestoreThisCard: 'لیستی را برای بازگردانی این کارت انتخاب کنید',\n      selectOrder_title: 'انتخاب ترتیب',\n      selectPermissions_title: 'انتخاب دسترسی‌ها',\n      selectProject: 'انتخاب پروژه',\n      selectRole_title: 'انتخاب نقش',\n      selectType_title: 'انتخاب نوع',\n      sequentialDisplayOfCards: 'نمایش متوالی کارت‌ها.',\n      settings: 'تنظیمات',\n      shared: 'به اشتراک گذاشته شده',\n      sharedWithMe_title: 'با من به اشتراک گذاشته شده',\n      showOnFrontOfCard: 'نمایش در جلوی کارت',\n      smtp: 'SMTP',\n      sortList_title: 'مرتب‌سازی لیست',\n      sourceCardIsNoLongerAvailableForCopying: 'کارت مبدا دیگر برای کپی کردن در دسترس نیست.',\n      sourceCardIsNoLongerAvailableForMoving: 'کارت مبدا دیگر برای انتقال در دسترس نیست.',\n      stopwatch: 'کرنومتر',\n      story: 'داستان',\n      subscribeToCardWhenCommenting: 'هنگام نظر دادن به کارت مشترک شو',\n      subscribeToMyOwnCardsByDefault: 'به طور پیش‌فرض به کارت‌های خودم مشترک شوم',\n      taskActions_title: 'اقدامات وظیفه',\n      taskAssignmentAndProjectCompletion: 'تخصیص وظیفه و تکمیل پروژه.',\n      taskListActions_title: 'اقدامات لیست وظایف',\n      taskList_title: 'لیست وظایف',\n      team: 'تیم',\n      termsOfService_title: 'شرایط خدمات',\n      testLog_title: 'گزارش تست',\n      thereIsNoPreviewAvailableForThisAttachment: 'پیش نمایشی برای این پیوست موجود نیست.',\n      time: 'زمان',\n      title: 'عنوان',\n      trash: 'سطل زباله',\n      trashHasBeenSuccessfullyEmptied: 'سطل زباله با موفقیت خالی شد.',\n      turnOffRecentCardHighlighting: 'خاموش کردن برجسته‌سازی کارت‌های اخیر',\n      typeNameToConfirm: 'نام را برای تأیید تایپ کنید.',\n      typeTitleToConfirm: 'عنوان را برای تأیید تایپ کنید.',\n      unlinkSso_title: 'لغو پیوند SSO',\n      unsavedChanges: 'تغییرات ذخیره نشده',\n      uploadFailedFileIsTooBig: 'آپلود ناموفق: فایل خیلی بزرگ است.',\n      uploadFailedNotEnoughStorageSpace: 'آپلود ناموفق: فضای ذخیره‌سازی کافی نیست.',\n      uploadedImages: 'تصاویر آپلود شده',\n      url: 'آدرس',\n      useSecureConnection: 'استفاده از اتصال امن',\n      userActions_title: 'اقدامات کاربر',\n      userAddedCardToList: '<0>{{user}}</0> <2>{{card}}</2> را به {{list}} اضافه کرد',\n      userAddedThisCardToList: '<0>{{user}}</0> این کارت را به {{list}} اضافه کرد',\n      userAddedUserToCard: '<0>{{actorUser}}</0> {{addedUser}} را به <4>{{card}}</4> اضافه کرد',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> {{addedUser}} را به این کارت اضافه کرد',\n      userAddedYouToCard: '<0>{{user}}</0> شما را به <2>{{card}}</2> اضافه کرد',\n      userCompletedTaskOnCard: '<0>{{user}}</0> {{task}} را در <4>{{card}}</4> تکمیل کرد',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> {{task}} را در این کارت تکمیل کرد',\n      userJoinedCard: '<0>{{user}}</0> به <2>{{card}}</2> پیوست',\n      userJoinedThisCard: '<0>{{user}}</0> به این کارت پیوست',\n      userLeftCard: '<0>{{user}}</0> <2>{{card}}</2> را ترک کرد',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> نظر جدید «{{comment}}» را به <2>{{card}}</2> اضافه کرد',\n      userLeftThisCard: '<0>{{user}}</0> این کارت را ترک کرد',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> {{task}} را در <4>{{card}}</4> ناتمام علامت‌گذاری کرد',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> {{task}} را در این کارت ناتمام علامت‌گذاری کرد',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> شما را در نظر «{{comment}}» در <2>{{card}}</2> ذکر کرد',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> <2>{{card}}</2> را از {{fromList}} به {{toList}} منتقل کرد',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> این کارت را از {{fromList}} به {{toList}} منتقل کرد',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> {{removedUser}} را از <4>{{card}}</4> حذف کرد',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> {{removedUser}} را از این کارت حذف کرد',\n      username: 'نام کاربری',\n      users: 'کاربران',\n      viewer: 'بیننده',\n      viewers: 'بینندگان',\n      visualTaskManagementWithLists: 'مدیریت بصری وظایف با لیست‌ها.',\n      webhooks: 'وب‌هوک‌ها',\n      whatsNew_title: 'چه چیز جدیدی',\n      withoutBaseGroup: 'بدون گروه پایه',\n      writeComment: 'نظر بنویسید...',\n    },\n\n    action: {\n      activateUser: 'فعال کردن کاربر',\n      activateUser_title: 'فعال کردن کاربر',\n      addAnotherCard: 'اضافه کردن کارت دیگر',\n      addAnotherList: 'اضافه کردن لیست دیگر',\n      addAnotherTask: 'اضافه کردن وظیفه دیگر',\n      addCard: 'اضافه کردن کارت',\n      addCard_title: 'اضافه کردن کارت',\n      addComment: 'اضافه کردن نظر',\n      addCustomField: 'اضافه کردن فیلد سفارشی',\n      addCustomFieldGroup: 'اضافه کردن گروه فیلد سفارشی',\n      addList: 'اضافه کردن لیست',\n      addMember: 'اضافه کردن عضو',\n      addMoreDetailedDescription: 'اضافه کردن توضیحات بیشتر',\n      addTask: 'اضافه کردن وظیفه',\n      addTaskList: 'اضافه کردن لیست وظایف',\n      addToCard: 'اضافه کردن به کارت',\n      addUser: 'اضافه کردن کاربر',\n      addWebhook: 'اضافه کردن وب‌هوک',\n      archive: 'آرشیو',\n      archiveCard: 'آرشیو کارت',\n      archiveCard_title: 'آرشیو کارت',\n      archiveCards: 'آرشیو کارت‌ها',\n      archiveCards_title: 'آرشیو کارت‌ها',\n      assignAsOwner: 'تعیین به عنوان مالک',\n      cancel: 'لغو',\n      copy: 'کپی',\n      copyCard_title: 'کپی کارت',\n      createApiKey: 'ایجاد کلید API',\n      createBoard: 'ایجاد برد',\n      createCustomFieldGroup: 'ایجاد گروه فیلد سفارشی',\n      createFile: 'ایجاد فایل',\n      createLabel: 'ایجاد برچسب',\n      createNewLabel: 'ایجاد برچسب جدید',\n      createProject: 'ایجاد پروژه',\n      cut: 'برش',\n      cutCard_title: 'برش کارت',\n      deactivateUser: 'غیرفعال کردن کاربر',\n      deactivateUser_title: 'غیرفعال کردن کاربر',\n      delete: 'حذف',\n      deleteApiKey: 'حذف کلید API',\n      deleteAttachment: 'حذف پیوست',\n      deleteAvatar: 'حذف آواتار',\n      deleteBackgroundImage: 'حذف تصویر پس‌زمینه',\n      deleteBoard: 'حذف برد',\n      deleteBoard_title: 'حذف برد',\n      deleteCard: 'حذف کارت',\n      deleteCardForever: 'حذف کارت برای همیشه',\n      deleteCard_title: 'حذف کارت',\n      deleteComment: 'حذف نظر',\n      deleteCustomField: 'حذف فیلد سفارشی',\n      deleteCustomFieldGroup: 'حذف گروه فیلد سفارشی',\n      deleteForever_title: 'حذف برای همیشه',\n      deleteGroup: 'حذف گروه',\n      deleteLabel: 'حذف برچسب',\n      deleteList: 'حذف لیست',\n      deleteList_title: 'حذف لیست',\n      deleteNotificationService: 'حذف سرویس اعلان',\n      deleteProject: 'حذف پروژه',\n      deleteProject_title: 'حذف پروژه',\n      deleteTask: 'حذف وظیفه',\n      deleteTaskList: 'حذف لیست وظایف',\n      deleteTask_title: 'حذف وظیفه',\n      deleteUser: 'حذف کاربر',\n      deleteUser_title: 'حذف کاربر',\n      deleteWebhook: 'حذف وب‌هوک',\n      dismissAll: 'رد کردن همه',\n      download: 'دانلود',\n      duplicateCard_title: 'تکرار کارت',\n      edit: 'ویرایش',\n      editColor_title: 'ویرایش رنگ',\n      editDescription_title: 'ویرایش توضیحات',\n      editDueDate_title: 'ویرایش تاریخ سررسید',\n      editEmail_title: 'ویرایش ایمیل',\n      editGroup: 'ویرایش گروه',\n      editInformation_title: 'ویرایش اطلاعات',\n      editPassword_title: 'ویرایش رمز عبور',\n      editPermissions: 'ویرایش دسترسی‌ها',\n      editRole_title: 'ویرایش نقش',\n      editStopwatch_title: 'ویرایش کرنومتر',\n      editTitle_title: 'ویرایش عنوان',\n      editType_title: 'ویرایش نوع',\n      editUsername_title: 'ویرایش نام کاربری',\n      emptyTrash: 'خالی کردن سطل زباله',\n      emptyTrash_title: 'خالی کردن سطل زباله',\n      import: 'وارد کردن',\n      join: 'پیوستن',\n      leave: 'ترک کردن',\n      leaveBoard: 'ترک برد',\n      leaveProject: 'ترک پروژه',\n      logOut_title: 'خروج',\n      makeCover_title: 'ایجاد کاور',\n      makeProjectPrivate: 'خصوصی کردن پروژه',\n      makeProjectPrivate_title: 'خصوصی کردن پروژه',\n      makeProjectShared: 'به اشتراک گذاشتن پروژه',\n      makeProjectShared_title: 'به اشتراک گذاشتن پروژه',\n      move: 'انتقال',\n      moveCard_title: 'انتقال کارت',\n      moveList_title: 'انتقال لیست',\n      regenerateApiKey: 'بازسازی کلید API',\n      remove: 'حذف',\n      removeAssignee: 'حذف مسئول',\n      removeColor: 'حذف رنگ',\n      removeCover_title: 'حذف کاور',\n      removeFromBoard: 'حذف از برد',\n      removeFromProject: 'حذف از پروژه',\n      removeManager: 'حذف مدیر',\n      removeMember: 'حذف عضو',\n      restoreToList: 'بازگردانی به {{list}}',\n      returnToBoard: 'بازگشت به برد',\n      save: 'ذخیره',\n      sendTestEmail: 'ارسال ایمیل تست',\n      showActive: 'نمایش فعال‌ها',\n      showAllAttachments: 'نمایش همه پیوست‌ها ({{hidden}} مخفی)',\n      showCardsWithThisUser: 'نمایش کارت‌های این کاربر',\n      showDeactivated: 'نمایش غیرفعال‌ها',\n      showFewerAttachments: 'نمایش کمتر پیوست‌ها',\n      showLess: 'نمایش کمتر',\n      showMore: 'نمایش بیشتر',\n      sortList_title: 'مرتب‌سازی لیست',\n      start: 'شروع',\n      stop: 'توقف',\n      subscribe: 'مشترک شدن',\n      unlinkSso: 'لغو پیوند SSO',\n      unlinkSso_title: 'لغو پیوند SSO',\n      unsubscribe: 'لغو اشتراک',\n      uploadNewAvatar: 'آپلود آواتار جدید',\n      uploadNewImage: 'آپلود تصویر جدید',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/fa-IR/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'fa-IR',\n  country: 'ir',\n  name: 'فارسی',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/fa-IR/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'حد کاربران فعال به پایان رسیده است',\n      adminLoginRequiredToInitializeInstance: 'ورود مدیر برای راه‌اندازی نمونه مورد نیاز است',\n      emailAlreadyInUse: 'ایمیل قبلا استفاده شده است',\n      emailOrUsername: 'ایمیل یا نام کاربری',\n      invalidCredentials: 'اطلاعات ورود نامعتبر',\n      invalidEmailOrUsername: 'ایمیل یا نام کاربری نامعتبر است',\n      invalidPassword: 'رمز عبور نامعتبر است',\n      logIn_title: 'ورود',\n      noInternetConnection: 'بدون اتصال به اینترنت',\n      or: 'یا',\n      pageNotFound_title: 'صفحه یافت نشد',\n      password: 'رمز عبور',\n      poweredByPlanka: 'قدرت گرفته از <1>PLANKA</1>',\n      serverConnectionFailed: 'اتصال به سرور ناموفق بود',\n      unknownError: 'خطای ناشناخته، بعداً دوباره تلاش کنید',\n      useSingleSignOn: 'استفاده از ورود یکپارچه',\n      usernameAlreadyInUse: 'نام کاربری قبلا استفاده شده است',\n      whoops_title: 'اوه!',\n    },\n\n    action: {\n      cancelAndClose: 'لغو و بستن',\n      continue: 'ادامه',\n      debugSso: 'اشکال‌زدایی SSO',\n      goBack: 'بازگشت',\n      goHome: 'رفتن به خانه',\n      logIn: 'ورود',\n      logInWithSso: 'ورود با SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/fa-IR/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"این متنی بدون عنوان است.\\nهم عنوان و هم متن\\nمی‌توانند با پررنگ، کج، رنگ،\\nخط خورده و زیرخط برجسته شوند.\",\n    \"text-with-head\": \"این متنی با عنوان است.\\nهم عنوان و هم متن\\nمی‌توانند با پررنگ، کج، رنگ،\\nخط خورده و زیرخط برجسته شوند.\",\n    \"heading\": \"عنوان\"\n  },\n  \"bundle\": {\n    \"error-title\": \"خطا در ویرایشگر markdown\",\n    \"settings_wysiwyg\": \"ویرایشگر بصری (wysiwyg)\",\n    \"settings_markup\": \"نشانه‌گذاری markdown\",\n    \"markup_placeholder\": \"نشانه‌گذاری markdown را وارد کنید...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"حذف\",\n    \"empty_option\": \"هیچ تطبیقی یافت نشد\",\n    \"show_line_numbers\": \"شماره‌گذاری خطوط\"\n  },\n  \"common\": {\n    \"delete\": \"حذف\",\n    \"edit\": \"ویرایش\",\n    \"toolbar_action_disabled\": \"عنصر نشانه‌گذاری ناسازگار\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"لغو\",\n    \"common_action_submit\": \"ارسال\",\n    \"common_action_upload\": \"انتخاب\",\n    \"common_tab_attach\": \"افزودن از دستگاه\",\n    \"common_tab_link\": \"افزودن با لینک\",\n    \"common_link\": \"لینک\",\n    \"common_sizes\": \"اندازه، px\",\n    \"image_name\": \"عنوان\",\n    \"image_link_href\": \"لینک تصویر\",\n    \"image_link_href_help\": \"آدرسی که لینک تصویر به آن منتهی می‌شود.\",\n    \"image_alt\": \"متن جایگزین\",\n    \"image_alt_help\": \"متن جایگزین در صورت عدم بارگذاری تصویر نمایش داده می‌شود.\",\n    \"image_upload_help\": \"تصویر JPEG، GIF یا PNG با حجم کمتر از 1 مگابایت.\",\n    \"image_upload_failed\": \"افزودن تصویر ناموفق بود\",\n    \"image_size_width\": \"عرض\",\n    \"image_size_height\": \"ارتفاع\",\n    \"link_url_help\": \"آدرسی که لینک به آن منتهی می‌شود.\",\n    \"link_text\": \"متن لینک\",\n    \"link_text_help\": \"متنی که به عنوان لینک نمایش داده می‌شود.\",\n    \"link_open_help\": \"باز کردن لینک در تب جدید\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"سرتیتر\",\n    \"header_hint\": \"# متن شما\",\n    \"italic_title\": \"کج\",\n    \"italic_hint\": \"_متن شما_\",\n    \"bold_title\": \"پررنگ\",\n    \"bold_hint\": \"**متن شما**\",\n    \"strikethrough_title\": \"خط خورده\",\n    \"strikethrough_hint\": \"~~متن شما~~\",\n    \"blockquote_title\": \"نقل قول\",\n    \"blockquote_hint\": \"> متن شما\",\n    \"code_title\": \"کد\",\n    \"code_hint\": \"```متن شما```\",\n    \"link_title\": \"لینک\",\n    \"link_hint\": \"[متن شما](url)\",\n    \"image_title\": \"تصویر\",\n    \"image_hint\": \"![متن شما](url)\",\n    \"list_title\": \"آیتم فهرست\",\n    \"list_hint\": \"- متن شما\",\n    \"numbered-list_title\": \"فهرست شماره‌دار\",\n    \"numbered-list_hint\": \"1. متن شما\",\n    \"documentation\": \"مستندات\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"پررنگ\",\n    \"code\": \"کد\",\n    \"code_inline\": \"کد درون‌خطی\",\n    \"codeblock\": \"بلوک کد\",\n    \"colorify\": \"رنگ متن\",\n    \"colorify__color_blue\": \"آبی\",\n    \"colorify__color_default\": \"پیش‌فرض\",\n    \"colorify__color_gray\": \"خاکستری\",\n    \"colorify__color_green\": \"سبز\",\n    \"colorify__color_orange\": \"نارنجی\",\n    \"colorify__color_red\": \"قرمز\",\n    \"colorify__color_violet\": \"بنفش\",\n    \"colorify__color_yellow\": \"زرد\",\n    \"colorify__group_text\": \"متن\",\n    \"cut\": \"برش\",\n    \"emoji\": \"شکلک\",\n    \"emoji__hint\": \"شکلک‌ها را می‌توان در WYSIWYG یا به صورت دستی با نشانه‌گذاری اضافه کرد\",\n    \"heading\": \"عنوان\",\n    \"heading1\": \"عنوان 1\",\n    \"heading2\": \"عنوان 2\",\n    \"heading3\": \"عنوان 3\",\n    \"heading4\": \"عنوان 4\",\n    \"heading5\": \"عنوان 5\",\n    \"heading6\": \"عنوان 6\",\n    \"hrule\": \"جداکننده\",\n    \"image\": \"تصویر\",\n    \"italic\": \"کج\",\n    \"link\": \"لینک\",\n    \"list\": \"فهرست\",\n    \"list__action_lift\": \"بالا بردن آیتم\",\n    \"list__action_sink\": \"پایین بردن آیتم\",\n    \"list_action_disabled\": \"با منطق فهرست در تضاد است\",\n    \"mark\": \"علامت‌گذاری شده\",\n    \"mono\": \"تک‌فاصله\",\n    \"more_action\": \"اقدام بیشتر\",\n    \"note\": \"یادداشت\",\n    \"olist\": \"فهرست مرتب\",\n    \"quote\": \"نقل قول\",\n    \"redo\": \"انجام مجدد\",\n    \"strike\": \"خط خورده\",\n    \"table\": \"جدول\",\n    \"text\": \"متن\",\n    \"ulist\": \"فهرست گلوله‌ای\",\n    \"underline\": \"زیرخط\",\n    \"undo\": \"واگرد\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"/ را تایپ کنید تا از دستورات اسلش استفاده کنید...\",\n    \"checkbox\": \"توضیح وظیفه را وارد کنید...\",\n    \"deflist_term\": \"اصطلاح\",\n    \"deflist_desc\": \"توضیح تعریف\",\n    \"heading\": \"عنوان\",\n    \"cut_title\": \"عنوان\",\n    \"cut_content\": \"محتوایی که با کلیک نمایش داده می‌شود\",\n    \"note_title\": \"عنوان\",\n    \"note_content\": \"محتوای یادداشت\",\n    \"table_cell\": \"محتوای سلول\",\n    \"select_filter\": \"جستجوی زبان‌ها...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"حساس به حروف کوچک و بزرگ\",\n    \"label_whole-word\": \"کلمه کامل\",\n    \"title\": \"جستجو در کد\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"یافت نشد\"\n  },\n  \"widgets\": {\n    \"image\": \"افزودن تصویر\",\n    \"link\": \"افزودن لینک\"\n  },\n  \"yfm-note\": {\n    \"info\": \"یادداشت\",\n    \"tip\": \"نکته\",\n    \"warning\": \"هشدار\",\n    \"alert\": \"اخطار\",\n    \"remove\": \"حذف\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"افزودن ستون قبل\",\n    \"column.add.after\": \"افزودن ستون بعد\",\n    \"column.remove\": \"حذف ستون\",\n    \"column.remove.multiple\": \"حذف ستون‌ها\",\n    \"row.add.before\": \"افزودن ردیف قبل\",\n    \"row.add.after\": \"افزودن ردیف بعد\",\n    \"row.remove\": \"حذف ردیف\",\n    \"row.remove.multiple\": \"حذف ردیف‌ها\",\n    \"cells.clear\": \"پاک کردن سلول‌ها\",\n    \"table.remove\": \"حذف جدول\",\n    \"table.menu.cell.align.left\": \"تراز محتوای سلول به چپ\",\n    \"table.menu.cell.align.right\": \"تراز محتوای سلول به راست\",\n    \"table.menu.cell.align.center\": \"تراز محتوای سلول به مرکز\",\n    \"table.menu.row.add\": \"افزودن ردیف بعد\",\n    \"table.menu.row.remove\": \"حذف ردیف\",\n    \"table.menu.column.add\": \"افزودن ستون بعد\",\n    \"table.menu.column.remove\": \"حذف ستون\",\n    \"table.menu.convert.yfm\": \"تبدیل به جدول YFM\",\n    \"table.menu.table.remove\": \"حذف جدول\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/fi-FI/core.js",
    "content": "import dateFns from 'date-fns/locale/fi';\nimport timeAgo from 'javascript-time-ago/locale/fi';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd.M.yyyy',\n    time: 'H.mm',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd. MMM',\n    longDateTime: \"d. MMMM 'klo' H.mm\",\n    fullDate: 'd. MMM y',\n    fullDateTime: \"d. MMMM y 'klo' H.mm\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Tietoa sovelluksesta',\n      aboutPlanka_title: 'Tietoa PLANKAsta',\n      accessToken: 'Käyttöoikeustunnus',\n      account: 'Tili',\n      actions: 'Toiminnot',\n      activateUser_title: 'Aktivoi käyttäjä',\n      active: 'Aktiivinen',\n      addAttachment_title: 'Lisää liite',\n      addCustomFieldGroup_title: 'Lisää mukautettujen kenttien ryhmä',\n      addCustomField_title: 'Lisää mukautettu kenttä',\n      addManager_title: 'Lisää ylläpitäjä',\n      addMember_title: 'Lisää jäsen',\n      addTaskList_title: 'Lisää tehtävälista',\n      addUser_title: 'Lisää käyttäjä',\n      admin: 'Ylläpitäjä',\n      administration: 'Hallinta',\n      all: 'Kaikki',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Kaikki muutokset tallennetaan automaattisesti<br />yhteyden palautuessa.',\n      alphabetically: 'Aakkosjärjestyksessä',\n      alwaysDisplayCardCreator: 'Näytä aina kortin luoja',\n      apiKeyCreated_title: 'API-avain luotu',\n      apiKey_title: 'API-avain',\n      archive: 'Arkistoi',\n      archiveCard_title: 'Arkistoi kortti',\n      archiveCards_title: 'Arkistoi kortit',\n      areYouSureYouWantToActivateThisUser: 'Haluatko varmasti aktivoida tämän käyttäjän?',\n      areYouSureYouWantToArchiveCards: 'Haluatko varmasti arkistoida kortit?',\n      areYouSureYouWantToArchiveThisCard: 'Haluatko varmasti arkistoida tämän kortin?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Haluatko varmasti asettaa tämän projektipäällikön omistajaksi?',\n      areYouSureYouWantToDeactivateThisUser: 'Haluatko varmasti poistaa tämän käyttäjän käytöstä?',\n      areYouSureYouWantToDeleteThisApiKey: 'Haluatko varmasti poistaa tämän API-avaimen?',\n      areYouSureYouWantToDeleteThisAttachment: 'Haluatko varmasti poistaa tämän liitteen?',\n      areYouSureYouWantToDeleteThisBackgroundImage: 'Haluatko varmasti poistaa tämän taustakuvan?',\n      areYouSureYouWantToDeleteThisBoard: 'Haluatko varmasti poistaa tämän taulun?',\n      areYouSureYouWantToDeleteThisCard: 'Haluatko varmasti poistaa tämän kortin?',\n      areYouSureYouWantToDeleteThisCardForever: 'Haluatko varmasti poistaa tämän kortin pysyvästi?',\n      areYouSureYouWantToDeleteThisComment: 'Haluatko varmasti poistaa tämän kommentin?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Haluatko varmasti poistaa tämän mukautetun kentän?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Haluatko varmasti poistaa tämän mukautettujen kenttien ryhmän?',\n      areYouSureYouWantToDeleteThisLabel: 'Haluatko varmasti poistaa tämän tunnisteen?',\n      areYouSureYouWantToDeleteThisList:\n        'Haluatko varmasti poistaa tämän listan? Kaikki kortit siirretään roskakoriin.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Haluatko varmasti poistaa tämän ilmoituspalvelun?',\n      areYouSureYouWantToDeleteThisProject: 'Haluatko varmasti poistaa tämän projektin?',\n      areYouSureYouWantToDeleteThisTask: 'Haluatko varmasti poistaa tämän tehtävän?',\n      areYouSureYouWantToDeleteThisTaskList: 'Haluatko varmasti poistaa tämän tehtävälistan?',\n      areYouSureYouWantToDeleteThisUser: 'Haluatko varmasti poistaa tämän käyttäjän?',\n      areYouSureYouWantToDeleteThisWebhook: 'Haluatko varmasti poistaa tämän webhookin?',\n      areYouSureYouWantToEmptyTrash: 'Haluatko varmasti tyhjentää roskakorin?',\n      areYouSureYouWantToLeaveBoard: 'Haluatko varmasti poistua taululta?',\n      areYouSureYouWantToLeaveProject: 'Haluatko varmasti poistua projektista?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Haluatko varmasti tehdä tästä projektista yksityisen?',\n      areYouSureYouWantToMakeThisProjectShared: 'Haluatko varmasti jakaa tämän projektin?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Haluatko varmasti luoda uuden API-avaimen? Edellinen avain ei enää toimi.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Haluatko varmasti poistaa tämän ylläpitäjän projektista?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Haluatko varmasti poistaa tämän jäsenen taululta?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Haluatko varmasti poistaa SSO-yhteyden tältä käyttäjältä? Tämä sallii käyttäjän kirjautua sisään salasanalla.',\n      assignAsOwner_title: 'Aseta omistajaksi',\n      atLeastOneListMustBePresent: 'Vähintään yksi lista vaaditaan',\n      attachment: 'Liite',\n      attachments: 'Liitteet',\n      authentication: 'Tunnistautuminen',\n      background: 'Tausta',\n      baseCustomFields_title: 'Perusmukautetut kentät',\n      baseGroup: 'Perusryhmä',\n      board: 'Taulu',\n      boardActions_title: 'Taulun toiminnot',\n      boardNotFound_title: 'Taulua ei löytynyt',\n      boardSubscribed: 'Taulu tilattu',\n      boardUser: 'Taulun käyttäjä',\n      byCreationTime: 'Luontiajan mukaan',\n      byDefault: 'Oletuksena',\n      byDueDate: 'Määräajan mukaan',\n      canBeInvitedToWorkInBoards: 'Voidaan kutsua työskentelemään tauluihin.',\n      canComment: 'Voi kommentoida',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Voi luoda omia projekteja ja tulla kutsutuksi muihin.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Voi muokata taulun asettelua ja lisätä jäseniä kortteihin.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Voi hallita järjestelmän asetuksia ja toimia projektin omistajana.',\n      canOnlyViewBoard: 'Voi vain tarkastella taulua.',\n      cardActions_title: 'Kortin toiminnot',\n      cardNotFound_title: 'Korttia ei löytynyt',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Tämän listan kortit ovat kaikkien taulun jäsenten saatavilla.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Tämän listan kortit ovat valmiita ja voidaan arkistoida.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Tämän listan kortit ovat valmiita työstettäväksi.',\n      clickHereOrRefreshPageToUpdate: '<0>Päivitä tästä</0> tai lataa sivu uudelleen.',\n      clientHostnameInEhlo: 'Asiakkaan isäntänimi EHLO:ssa',\n      closed: 'Suljettu',\n      color: 'Väri',\n      comments: 'Kommentit',\n      contentExceedsLimit: 'Sisältö ylittää rajan {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay: 'Liitteen sisältö on liian suuri näytettäväksi.',\n      copy_inline: 'kopio',\n      createBoard_title: 'Luo taulu',\n      createCustomFieldGroup_title: 'Luo mukautettujen kenttien ryhmä',\n      createLabel_title: 'Luo tunniste',\n      createNewOneOrSelectExistingOne: 'Luo uusi tai valitse<br />olemassa oleva.',\n      createProject_title: 'Luo projekti',\n      createTextFile_title: 'Luo tekstitiedosto',\n      creator: 'Luoja',\n      currentPassword: 'Nykyinen salasana',\n      currentUser: 'Nykyinen käyttäjä',\n      customFieldGroup_title: 'Mukautettujen kenttien ryhmä',\n      customFieldGroups_title: 'Mukautettujen kenttien ryhmät',\n      customField_title: 'Mukautettu kenttä',\n      customFields_title: 'Mukautetut kentät',\n      customerPanel_title: 'Asiakaspaneeli',\n      dangerZone_title: 'Vaaravyöhyke',\n      date: 'Päivämäärä',\n      deactivateUser_title: 'Poista käyttäjä käytöstä',\n      defaultCardType_title: 'Oletuskorttityyppi',\n      defaultFrom: 'Oletus \"lähettäjä\"',\n      defaultView_title: 'Oletusnäkymä',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Poista kaikki taulut, jotta voit poistaa tämän projektin',\n      deleteApiKey_title: 'Poista API-avain',\n      deleteAttachment_title: 'Poista liite',\n      deleteBackgroundImage_title: 'Poista taustakuva',\n      deleteBoard_title: 'Poista taulu',\n      deleteCardForever_title: 'Poista kortti pysyvästi',\n      deleteCard_title: 'Poista kortti',\n      deleteComment_title: 'Poista kommentti',\n      deleteCustomFieldGroup_title: 'Poista mukautettujen kenttien ryhmä',\n      deleteCustomField_title: 'Poista mukautettu kenttä',\n      deleteLabel_title: 'Poista tunniste',\n      deleteList_title: 'Poista lista',\n      deleteNotificationService_title: 'Poista ilmoituspalvelu',\n      deleteProject_title: 'Poista projekti',\n      deleteTaskList_title: 'Poista tehtävälista',\n      deleteTask_title: 'Poista tehtävä',\n      deleteUser_title: 'Poista käyttäjä',\n      deleteWebhook_title: 'Poista webhook',\n      deletedUser_title: 'Poistettu käyttäjä',\n      description: 'Kuvaus',\n      display: 'Näyttö',\n      displayCardAges: 'Näytä korttien ikä',\n      dropFileToUpload: 'Pudota tiedosto ladattavaksi',\n      dueDate_title: 'Määräpäivä',\n      dynamicAndUnevenlySpacedLayout: 'Dynaaminen ja epätasaisesti jaettu asettelu.',\n      editAttachment_title: 'Muokkaa liitettä',\n      editAvatar_title: 'Muokkaa avatar',\n      editColor_title: 'Muokkaa väriä',\n      editCustomFieldGroup_title: 'Muokkaa mukautettujen kenttien ryhmää',\n      editCustomField_title: 'Muokkaa mukautettua kenttää',\n      editDueDate_title: 'Muokkaa määräpäivää',\n      editEmail_title: 'Muokkaa sähköpostia',\n      editInformation_title: 'Muokkaa tietoja',\n      editLabel_title: 'Muokkaa tunnistetta',\n      editPassword_title: 'Muokkaa salasanaa',\n      editPermissions_title: 'Muokkaa oikeuksia',\n      editRole_title: 'Muokkaa roolia',\n      editStopwatch_title: 'Muokkaa ajastinta',\n      editType_title: 'Muokkaa tyyppiä',\n      editUsername_title: 'Muokkaa käyttäjänimeä',\n      editor: 'Muokkain',\n      editors: 'Muokkaajat',\n      email: 'Sähköposti',\n      emptyTrash_title: 'Tyhjennä roskakori',\n      enterCardTitle: 'Syötä kortin otsikko...',\n      enterDescription: 'Syötä kuvaus...',\n      enterFilename: 'Syötä tiedostonimi',\n      enterListTitle: 'Syötä listan otsikko...',\n      enterTaskDescription: 'Syötä tehtävän kuvaus...',\n      events: 'Tapahtumat',\n      excludedEvents: 'Poissuljetut tapahtumat',\n      expandTaskListsByDefault: 'Laajenna tehtävälistat oletuksena',\n      filterByLabels_title: 'Suodata tunnisteiden mukaan',\n      filterByMembers_title: 'Suodata jäsenten mukaan',\n      forPersonalProjects: 'Henkilökohtaisiin projekteihin.',\n      forTeamBasedProjects: 'Tiimipohjaisiin projekteihin.',\n      fromComputer_title: 'Tietokoneelta',\n      fromTrello: 'Trellosta',\n      fullKeyIsHiddenForSecurityReasons:\n        'Koko avain on piilotettu turvallisuussyistä. Luo se uudelleen saadaksesi uuden.',\n      general: 'Yleinen',\n      gradients: 'Liukuvärit',\n      grid: 'Ruudukko',\n      hideCompletedTasks: 'Piilota valmiit tehtävät',\n      hideFromProjectListAndFavorites: 'Piilota projektilistasta ja suosikeista',\n      host: 'Isäntä',\n      hours: 'Tunnit',\n      identity: 'Henkilöllisyys',\n      importBoard_title: 'Tuo taulu',\n      information: 'Tiedot',\n      invalidCurrentPassword: 'Virheellinen nykyinen salasana',\n      kanban: 'Kanban',\n      labels: 'Tunnisteet',\n      language: 'Kieli',\n      leaveBoard_title: 'Poistu taululta',\n      leaveProject_title: 'Poistu projektista',\n      limitCardTypesToDefaultOne: 'Rajoita korttityypit oletukseen',\n      linkToCard: 'Linkki korttiin',\n      list: 'Lista',\n      listActions_title: 'Listan toiminnot',\n      lists: 'Listat',\n      makeProjectPrivate_title: 'Tee projektista yksityinen',\n      makeProjectShared_title: 'Jaa projekti',\n      managers: 'Ylläpitäjät',\n      memberActions_title: 'Jäsenen toiminnot',\n      members: 'Jäsenet',\n      minutes: 'Minuutit',\n      moreActions: 'Lisää toimintoja',\n      moreActions_title: 'Lisää toimintoja',\n      moveCard_title: 'Siirrä kortti',\n      moveList_title: 'Siirrä lista',\n      myOwn_title: 'Omat',\n      name: 'Nimi',\n      newEmail: 'Uusi sähköposti',\n      newPassword: 'Uusi salasana',\n      newUsername: 'Uusi käyttäjänimi',\n      newVersionAvailable: 'Uusi versio saatavilla',\n      newestFirst: 'Uusimmat ensin',\n      noApiKeyCreated: 'API-avainta ei ole luotu.',\n      noBoards: 'Ei tauluja',\n      noCardsFound: 'Kortteja ei löytynyt.',\n      noConnectionToServer: 'Ei yhteyttä palvelimeen',\n      noLists: 'Ei listoja',\n      noProjects: 'Ei projekteja',\n      noUnreadNotifications: 'Ei lukemattomia ilmoituksia.',\n      notifications: 'Ilmoitukset',\n      oldestFirst: 'Vanhimmat ensin',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Vain yhden ylläpitäjän tulisi jäädä tehdäkseen tästä projektista yksityisen',\n      openBoard_title: 'Avaa taulu',\n      optional_inline: 'valinnainen',\n      organization: 'Organisaatio',\n      others: 'Muut',\n      passwordIsSet: 'Salasana on asetettu',\n      phone: 'Puhelin',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA käyttää <1><0>Apprise</0></1> lähettääkseen ilmoituksia yli 100 suosittuun palveluun.',\n      port: 'Portti',\n      preferences: 'Asetukset',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Vinkki: paina Ctrl-V (tai Cmd-V Macilla) lisätäksesi liitteen leikepöydältä.',\n      private: 'Yksityinen',\n      project: 'Projekti',\n      projectNotFound_title: 'Projektia ei löytynyt',\n      projectOwner: 'Projektin omistaja',\n      referenceDataAndKnowledgeStorage: 'Viitetiedot ja tietovarasto.',\n      regenerateApiKey_title: 'Luo API-avain uudelleen',\n      rejectUnauthorizedTlsCertificates: 'Hylkää valtuuttamattomat TLS-varmenteet',\n      removeManager_title: 'Poista ylläpitäjä',\n      removeMember_title: 'Poista jäsen',\n      role: 'Rooli',\n      saveThisKeyItWillNotBeShownAgain: 'Tallenna tämä avain — sitä ei näytetä uudelleen!',\n      searchCards: 'Etsi kortteja...',\n      searchCustomFieldGroups: 'Etsi mukautettujen kenttien ryhmiä...',\n      searchCustomFields: 'Etsi mukautettuja kenttiä...',\n      searchLabels: 'Etsi tunnisteita...',\n      searchLists: 'Etsi listoja...',\n      searchMembers: 'Etsi jäseniä...',\n      searchProjects: 'Etsi projekteja...',\n      searchUsers: 'Etsi käyttäjiä...',\n      seconds: 'Sekunnit',\n      selectAssignee_title: 'Valitse vastuuhenkilö',\n      selectBoard: 'Valitse taulu',\n      selectList: 'Valitse lista',\n      selectListToRestoreThisCard: 'Valitse lista palauttaaksesi tämän kortin',\n      selectOrder_title: 'Valitse järjestys',\n      selectPermissions_title: 'Valitse oikeudet',\n      selectProject: 'Valitse projekti',\n      selectRole_title: 'Valitse rooli',\n      selectType_title: 'Valitse tyyppi',\n      sequentialDisplayOfCards: 'Korttien peräkkäinen näyttö.',\n      settings: 'Asetukset',\n      shared: 'Jaettu',\n      sharedWithMe_title: 'Jaettu kanssani',\n      showOnFrontOfCard: 'Näytä kortin etupuolella',\n      smtp: 'SMTP',\n      sortList_title: 'Lajittele lista',\n      sourceCardIsNoLongerAvailableForCopying:\n        'Lähdekortti ei ole enää saatavilla kopiointia varten.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'Lähdekortti ei ole enää saatavilla siirtämistä varten.',\n      stopwatch: 'Ajastin',\n      story: 'Tarina',\n      subscribeToCardWhenCommenting: 'Tilaa kortti kommentoidessa',\n      subscribeToMyOwnCardsByDefault: 'Tilaa omat kortit oletuksena',\n      taskActions_title: 'Tehtävän toiminnot',\n      taskAssignmentAndProjectCompletion: 'Tehtävien jako ja projektin valmistuminen.',\n      taskListActions_title: 'Tehtävälistan toiminnot',\n      taskList_title: 'Tehtävälista',\n      team: 'Tiimi',\n      termsOfService_title: 'Käyttöehdot',\n      testLog_title: 'Testiloki',\n      thereIsNoPreviewAvailableForThisAttachment: 'Tälle liitteelle ei ole esikatselua saatavilla.',\n      time: 'Aika',\n      title: 'Otsikko',\n      trash: 'Roskakori',\n      trashHasBeenSuccessfullyEmptied: 'Roskakori tyhjennetty onnistuneesti.',\n      turnOffRecentCardHighlighting: 'Poista uusien korttien korostus',\n      typeNameToConfirm: 'Kirjoita nimi vahvistaaksesi.',\n      typeTitleToConfirm: 'Kirjoita otsikko vahvistaaksesi.',\n      unlinkSso_title: 'SSO-yhteyden poistaminen',\n      unsavedChanges: 'Tallentamattomat muutokset',\n      uploadFailedFileIsTooBig: 'Lataus epäonnistui: tiedosto on liian suuri.',\n      uploadFailedNotEnoughStorageSpace: 'Lataus epäonnistui: tallennustilaa ei ole tarpeeksi.',\n      uploadedImages: 'Ladatut kuvat',\n      url: 'URL',\n      useSecureConnection: 'Käytä turvallista yhteyttä',\n      userActions_title: 'Käyttäjän toiminnot',\n      userAddedCardToList: '<0>{{user}}</0> lisäsi <2>{{card}}</2> listaan {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> lisäsi tämän kortin listaan {{list}}',\n      userAddedUserToCard:\n        '<0>{{actorUser}}</0> lisäsi käyttäjän {{addedUser}} korttiin <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> lisäsi käyttäjän {{addedUser}} tähän korttiin',\n      userAddedYouToCard: '<0>{{user}}</0> lisäsi sinut korttiin <2>{{card}}</2>',\n      userCompletedTaskOnCard:\n        '<0>{{user}}</0> suoritti tehtävän {{task}} kortissa <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> suoritti tehtävän {{task}} tässä kortissa',\n      userJoinedCard: '<0>{{user}}</0> liittyi korttiin <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> liittyi tähän korttiin',\n      userLeftCard: '<0>{{user}}</0> poistui kortista <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> jätti uuden kommentin «{{comment}}» korttiin <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> poistui tästä kortista',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> merkitsi tehtävän {{task}} keskeneräiseksi kortissa <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> merkitsi tehtävän {{task}} keskeneräiseksi tässä kortissa',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> mainitsi sinut kommentissa «{{comment}}» kortissa <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> siirsi <2>{{card}}</2> listasta {{fromList}} listaan {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> siirsi tämän kortin listasta {{fromList}} listaan {{toList}}',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0> poisti käyttäjän {{removedUser}} kortista <4>{{card}}</4>',\n      userRemovedUserFromThisCard:\n        '<0>{{actorUser}}</0> poisti käyttäjän {{removedUser}} tästä kortista',\n      username: 'Käyttäjänimi',\n      users: 'Käyttäjät',\n      viewer: 'Katselija',\n      viewers: 'Katselijat',\n      visualTaskManagementWithLists: 'Visuaalinen tehtävien hallinta listoilla.',\n      webhooks: 'Webhookit',\n      whatsNew_title: 'Mitä uutta',\n      withoutBaseGroup: 'Ilman perusryhmää',\n      writeComment: 'Kirjoita kommentti...',\n    },\n\n    action: {\n      activateUser: 'Aktivoi käyttäjä',\n      activateUser_title: 'Aktivoi käyttäjä',\n      addAnotherCard: 'Lisää toinen kortti',\n      addAnotherList: 'Lisää toinen lista',\n      addAnotherTask: 'Lisää toinen tehtävä',\n      addCard: 'Lisää kortti',\n      addCard_title: 'Lisää kortti',\n      addComment: 'Lisää kommentti',\n      addCustomField: 'Lisää mukautettu kenttä',\n      addCustomFieldGroup: 'Lisää mukautettujen kenttien ryhmä',\n      addList: 'Lisää lista',\n      addMember: 'Lisää jäsen',\n      addMoreDetailedDescription: 'Lisää tarkempi kuvaus',\n      addTask: 'Lisää tehtävä',\n      addTaskList: 'Lisää tehtävälista',\n      addToCard: 'Lisää korttiin',\n      addUser: 'Lisää käyttäjä',\n      addWebhook: 'Lisää webhook',\n      archive: 'Arkistoi',\n      archiveCard: 'Arkistoi kortti',\n      archiveCard_title: 'Arkistoi kortti',\n      archiveCards: 'Arkistoi kortit',\n      archiveCards_title: 'Arkistoi kortit',\n      assignAsOwner: 'Aseta omistajaksi',\n      cancel: 'Peruuta',\n      copy: 'Kopioi',\n      copyCard_title: 'Kopioi kortti',\n      createApiKey: 'Luo API-avain',\n      createBoard: 'Luo taulu',\n      createCustomFieldGroup: 'Luo mukautettujen kenttien ryhmä',\n      createFile: 'Luo tiedosto',\n      createLabel: 'Luo tunniste',\n      createNewLabel: 'Luo uusi tunniste',\n      createProject: 'Luo projekti',\n      cut: 'Leikkaa',\n      cutCard_title: 'Leikkaa kortti',\n      deactivateUser: 'Poista käyttäjä käytöstä',\n      deactivateUser_title: 'Poista käyttäjä käytöstä',\n      delete: 'Poista',\n      deleteApiKey: 'Poista API-avain',\n      deleteAttachment: 'Poista liite',\n      deleteAvatar: 'Poista avatar',\n      deleteBackgroundImage: 'Poista taustakuva',\n      deleteBoard: 'Poista taulu',\n      deleteBoard_title: 'Poista taulu',\n      deleteCard: 'Poista kortti',\n      deleteCardForever: 'Poista kortti pysyvästi',\n      deleteCard_title: 'Poista kortti',\n      deleteComment: 'Poista kommentti',\n      deleteCustomField: 'Poista mukautettu kenttä',\n      deleteCustomFieldGroup: 'Poista mukautettujen kenttien ryhmä',\n      deleteForever_title: 'Poista pysyvästi',\n      deleteGroup: 'Poista ryhmä',\n      deleteLabel: 'Poista tunniste',\n      deleteList: 'Poista lista',\n      deleteList_title: 'Poista lista',\n      deleteNotificationService: 'Poista ilmoituspalvelu',\n      deleteProject: 'Poista projekti',\n      deleteProject_title: 'Poista projekti',\n      deleteTask: 'Poista tehtävä',\n      deleteTaskList: 'Poista tehtävälista',\n      deleteTask_title: 'Poista tehtävä',\n      deleteUser: 'Poista käyttäjä',\n      deleteUser_title: 'Poista käyttäjä',\n      deleteWebhook: 'Poista webhook',\n      dismissAll: 'Sulje kaikki',\n      download: 'Lataa',\n      duplicateCard_title: 'Monista kortti',\n      edit: 'Muokkaa',\n      editColor_title: 'Muokkaa väriä',\n      editDescription_title: 'Muokkaa kuvausta',\n      editDueDate_title: 'Muokkaa määräpäivää',\n      editEmail_title: 'Muokkaa sähköpostia',\n      editGroup: 'Muokkaa ryhmää',\n      editInformation_title: 'Muokkaa tietoja',\n      editPassword_title: 'Muokkaa salasanaa',\n      editPermissions: 'Muokkaa oikeuksia',\n      editRole_title: 'Muokkaa roolia',\n      editStopwatch_title: 'Muokkaa ajastinta',\n      editTitle_title: 'Muokkaa otsikkoa',\n      editType_title: 'Muokkaa tyyppiä',\n      editUsername_title: 'Muokkaa käyttäjänimeä',\n      emptyTrash: 'Tyhjennä roskakori',\n      emptyTrash_title: 'Tyhjennä roskakori',\n      import: 'Tuo',\n      join: 'Liity',\n      leave: 'Poistu',\n      leaveBoard: 'Poistu taululta',\n      leaveProject: 'Poistu projektista',\n      logOut_title: 'Kirjaudu ulos',\n      makeCover_title: 'Aseta kansikuvaksi',\n      makeProjectPrivate: 'Tee projektista yksityinen',\n      makeProjectPrivate_title: 'Tee projektista yksityinen',\n      makeProjectShared: 'Jaa projekti',\n      makeProjectShared_title: 'Jaa projekti',\n      move: 'Siirrä',\n      moveCard_title: 'Siirrä kortti',\n      moveList_title: 'Siirrä lista',\n      regenerateApiKey: 'Luo API-avain uudelleen',\n      remove: 'Poista',\n      removeAssignee: 'Poista vastuuhenkilö',\n      removeColor: 'Poista väri',\n      removeCover_title: 'Poista kansikuva',\n      removeFromBoard: 'Poista taululta',\n      removeFromProject: 'Poista projektista',\n      removeManager: 'Poista ylläpitäjä',\n      removeMember: 'Poista jäsen',\n      restoreToList: 'Palauta listaan {{list}}',\n      returnToBoard: 'Palaa tauluun',\n      save: 'Tallenna',\n      sendTestEmail: 'Lähetä testisähköposti',\n      showActive: 'Näytä aktiiviset',\n      showAllAttachments: 'Näytä kaikki liitteet ({{hidden}} piilotettu)',\n      showCardsWithThisUser: 'Näytä kortit, joissa tämä käyttäjä',\n      showDeactivated: 'Näytä poistetut käytöstä',\n      showFewerAttachments: 'Näytä vähemmän liitteitä',\n      showLess: 'Näytä vähemmän',\n      showMore: 'Näytä enemmän',\n      sortList_title: 'Lajittele lista',\n      start: 'Aloita',\n      stop: 'Lopeta',\n      subscribe: 'Tilaa',\n      unlinkSso: 'Poista SSO-yhteys',\n      unlinkSso_title: 'Poista SSO-yhteys',\n      unsubscribe: 'Peru tilaus',\n      uploadNewAvatar: 'Lataa uusi avatar',\n      uploadNewImage: 'Lataa uusi kuva',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/fi-FI/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'fi-FI',\n  country: 'fi',\n  name: 'Suomi',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/fi-FI/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Aktiivisten käyttäjien raja saavutettu',\n      adminLoginRequiredToInitializeInstance:\n        'Järjestelmänvalvojan kirjautuminen vaaditaan instanssin alustamiseksi',\n      emailAlreadyInUse: 'Sähköposti on jo käytössä',\n      emailOrUsername: 'Sähköposti tai käyttäjänimi',\n      invalidCredentials: 'Virheelliset tunnistetiedot',\n      invalidEmailOrUsername: 'Virheellinen sähköposti tai käyttäjänimi',\n      invalidPassword: 'Virheellinen salasana',\n      logIn_title: 'Kirjaudu sisään',\n      noInternetConnection: 'Ei internet-yhteyttä',\n      or: 'Tai',\n      pageNotFound_title: 'Sivua ei löytynyt',\n      password: 'Salasana',\n      poweredByPlanka: 'Käyttää <1>PLANKAa</1>',\n      serverConnectionFailed: 'Yhteys palvelimeen epäonnistui',\n      unknownError: 'Tuntematon virhe, yritä myöhemmin uudelleen',\n      useSingleSignOn: 'Käytä kertakirjautumista',\n      usernameAlreadyInUse: 'Käyttäjänimi on jo käytössä',\n      whoops_title: 'Hups!',\n    },\n\n    action: {\n      cancelAndClose: 'Peruuta ja sulje',\n      continue: 'Jatka',\n      debugSso: 'Korjaa SSO-virheitä',\n      goBack: 'Takaisin',\n      goHome: 'Kotiin',\n      logIn: 'Kirjaudu sisään',\n      logInWithSso: 'Kirjaudu SSO:lla',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/fi-FI/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Tämä on teksti ilman otsikkoa.\\nSekä otsikko että teksti\\nvoidaan korostaa lihavoidulla, kursiivilla, värillä,\\nyliviivauksella ja alleviivauksella.\",\n    \"text-with-head\": \"Tämä on teksti otsikolla.\\nSekä otsikko että teksti\\nvoidaan korostaa lihavoidulla, kursiivilla, värillä,\\nyliviivauksella ja alleviivauksella.\",\n    \"heading\": \"Otsikko\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Virhe markdown-editorissa\",\n    \"settings_wysiwyg\": \"Visuaalinen editori (wysiwyg)\",\n    \"settings_markup\": \"Markdown-merkintä\",\n    \"markup_placeholder\": \"Syötä markdown-merkintä...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Poista\",\n    \"empty_option\": \"Ei tuloksia löytynyt\",\n    \"show_line_numbers\": \"Rivinumerointi\"\n  },\n  \"common\": {\n    \"delete\": \"Poista\",\n    \"edit\": \"Muokkaa\",\n    \"toolbar_action_disabled\": \"Yhteensopimaton merkintäelementti\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Peruuta\",\n    \"common_action_submit\": \"Lähetä\",\n    \"common_action_upload\": \"Valitse\",\n    \"common_tab_attach\": \"Lisää laitteelta\",\n    \"common_tab_link\": \"Lisää linkillä\",\n    \"common_link\": \"Linkki\",\n    \"common_sizes\": \"Koko, px\",\n    \"image_name\": \"Otsikko\",\n    \"image_link_href\": \"Kuvan linkki\",\n    \"image_link_href_help\": \"Osoite, johon kuvalinkki johtaa.\",\n    \"image_alt\": \"Alt-teksti\",\n    \"image_alt_help\": \"Alt-teksti näytetään, jos kuvaa ei voida ladata.\",\n    \"image_upload_help\": \"JPEG-, GIF- tai PNG-kuva, enintään 1 Mt.\",\n    \"image_upload_failed\": \"Kuvan lisääminen epäonnistui\",\n    \"image_size_width\": \"Leveys\",\n    \"image_size_height\": \"Korkeus\",\n    \"link_url_help\": \"Osoite, johon linkki johtaa.\",\n    \"link_text\": \"Linkin teksti\",\n    \"link_text_help\": \"Teksti, joka näytetään linkkinä.\",\n    \"link_open_help\": \"Avaa linkki uuteen välilehteen\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Otsikko\",\n    \"header_hint\": \"# Tekstisi\",\n    \"italic_title\": \"Kursiivi\",\n    \"italic_hint\": \"_Tekstisi_\",\n    \"bold_title\": \"Lihavoitu\",\n    \"bold_hint\": \"**Tekstisi**\",\n    \"strikethrough_title\": \"Yliviivaus\",\n    \"strikethrough_hint\": \"~~Tekstisi~~\",\n    \"blockquote_title\": \"Lainaus\",\n    \"blockquote_hint\": \"> Tekstisi\",\n    \"code_title\": \"Koodi\",\n    \"code_hint\": \"```Tekstisi```\",\n    \"link_title\": \"Linkki\",\n    \"link_hint\": \"[Tekstisi](osoite)\",\n    \"image_title\": \"Kuva\",\n    \"image_hint\": \"![Tekstisi](osoite)\",\n    \"list_title\": \"Listan kohta\",\n    \"list_hint\": \"- Tekstisi\",\n    \"numbered-list_title\": \"Numeroitu lista\",\n    \"numbered-list_hint\": \"1. Tekstisi\",\n    \"documentation\": \"Dokumentaatio\",\n    \"documentation_link\": \"https://diplodoc.com/docs/fi/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Lihavoitu\",\n    \"code\": \"Koodi\",\n    \"code_inline\": \"Rivikoodi\",\n    \"codeblock\": \"Koodilohko\",\n    \"colorify\": \"Tekstin väri\",\n    \"colorify__color_blue\": \"Sininen\",\n    \"colorify__color_default\": \"Oletus\",\n    \"colorify__color_gray\": \"Harmaa\",\n    \"colorify__color_green\": \"Vihreä\",\n    \"colorify__color_orange\": \"Oranssi\",\n    \"colorify__color_red\": \"Punainen\",\n    \"colorify__color_violet\": \"Violetti\",\n    \"colorify__color_yellow\": \"Keltainen\",\n    \"colorify__group_text\": \"Teksti\",\n    \"cut\": \"Leikkaa\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emojit voi lisätä WYSIWYG:ssä tai käsin merkinnällä\",\n    \"heading\": \"Otsikko\",\n    \"heading1\": \"Otsikko 1\",\n    \"heading2\": \"Otsikko 2\",\n    \"heading3\": \"Otsikko 3\",\n    \"heading4\": \"Otsikko 4\",\n    \"heading5\": \"Otsikko 5\",\n    \"heading6\": \"Otsikko 6\",\n    \"hrule\": \"Erotin\",\n    \"image\": \"Kuva\",\n    \"italic\": \"Kursiivi\",\n    \"link\": \"Linkki\",\n    \"list\": \"Lista\",\n    \"list__action_lift\": \"Nosta kohta\",\n    \"list__action_sink\": \"Laske kohta\",\n    \"list_action_disabled\": \"Ristiriita listan logiikan kanssa\",\n    \"mark\": \"Merkitty\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Lisää toimintoja\",\n    \"note\": \"Huomautus\",\n    \"olist\": \"Järjestetty lista\",\n    \"quote\": \"Lainaus\",\n    \"redo\": \"Tee uudelleen\",\n    \"strike\": \"Yliviivaus\",\n    \"table\": \"Taulukko\",\n    \"text\": \"Teksti\",\n    \"ulist\": \"Luettelomerkki lista\",\n    \"underline\": \"Alleviivaus\",\n    \"undo\": \"Kumoa\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Kirjoita / käyttääksesi komentoja...\",\n    \"checkbox\": \"Syötä tehtävän kuvaus...\",\n    \"deflist_term\": \"Termi\",\n    \"deflist_desc\": \"Määritelmän kuvaus\",\n    \"heading\": \"Otsikko\",\n    \"cut_title\": \"Otsikko\",\n    \"cut_content\": \"Sisältö näytetään napsautettaessa\",\n    \"note_title\": \"Otsikko\",\n    \"note_content\": \"Huomautuksen sisältö\",\n    \"table_cell\": \"Solun sisältö\",\n    \"select_filter\": \"Etsi kieliä...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Kirjainkoko huomioidaan\",\n    \"label_whole-word\": \"Koko sana\",\n    \"title\": \"Etsi koodista\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Ei löytynyt\"\n  },\n  \"widgets\": {\n    \"image\": \"Lisää kuva\",\n    \"link\": \"Lisää linkki\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Huomautus\",\n    \"tip\": \"Vinkki\",\n    \"warning\": \"Varoitus\",\n    \"alert\": \"Hälytys\",\n    \"remove\": \"Poista\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Lisää sarake ennen\",\n    \"column.add.after\": \"Lisää sarake jälkeen\",\n    \"column.remove\": \"Poista sarake\",\n    \"column.remove.multiple\": \"Poista sarakkeet\",\n    \"row.add.before\": \"Lisää rivi ennen\",\n    \"row.add.after\": \"Lisää rivi jälkeen\",\n    \"row.remove\": \"Poista rivi\",\n    \"row.remove.multiple\": \"Poista rivit\",\n    \"cells.clear\": \"Tyhjennä solut\",\n    \"table.remove\": \"Poista taulukko\",\n    \"table.menu.cell.align.left\": \"Tasaa solun sisältö vasemmalle\",\n    \"table.menu.cell.align.right\": \"Tasaa solun sisältö oikealle\",\n    \"table.menu.cell.align.center\": \"Tasaa solun sisältö keskelle\",\n    \"table.menu.row.add\": \"Lisää rivi jälkeen\",\n    \"table.menu.row.remove\": \"Poista rivi\",\n    \"table.menu.column.add\": \"Lisää sarake jälkeen\",\n    \"table.menu.column.remove\": \"Poista sarake\",\n    \"table.menu.convert.yfm\": \"Muunna YFM-taulukoksi\",\n    \"table.menu.table.remove\": \"Poista taulukko\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/fr-FR/core.js",
    "content": "import dateFns from 'date-fns/locale/fr';\nimport timeAgo from 'javascript-time-ago/locale/fr';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'P',\n    time: 'HH:mm',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'à' p\",\n    fullDate: 'd MMM y',\n    fullDateTime: \"d MMMM y 'à' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: \"À propos de l'application\",\n      aboutPlanka_title: 'À propos de PLANKA',\n      accessToken: \"Jeton d'accès\",\n      account: 'Compte',\n      actions: 'Actions',\n      activateUser_title: \"Activer l'utilisateur\",\n      active: 'Actif',\n      addAttachment_title: 'Ajouter une pièce jointe',\n      addCustomFieldGroup_title: 'Ajouter un groupe de champs personnalisés',\n      addCustomField_title: 'Ajouter un champ personnalisé',\n      addManager_title: 'Ajouter un responsable',\n      addMember_title: 'Ajouter un membre',\n      addTaskList_title: 'Ajouter une liste de tâches',\n      addUser_title: 'Ajouter un utilisateur',\n      admin: 'Administrateur',\n      administration: 'Administration',\n      all: 'Tout',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Toutes les modifications seront automatiquement enregistrées<br />une fois la connexion rétablie.',\n      alphabetically: 'Alphabétique',\n      alwaysDisplayCardCreator: 'Toujours afficher le créateur de la carte',\n      apiKeyCreated_title: 'Clé API créée',\n      apiKey_title: 'Clé API',\n      archive: 'Archiver',\n      archiveCard_title: 'Archiver la carte',\n      archiveCards_title: 'Cartes archivées',\n      areYouSureYouWantToActivateThisUser: 'Etes-vous sûr de vouloir activer cet utilisateur ?',\n      areYouSureYouWantToArchiveCards: 'Etes-vous sûr de vouloir archiver les cartes ?',\n      areYouSureYouWantToArchiveThisCard: 'Etes-vous sûr de vouloir archiver cette carte ?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Êtes-vous sûr de vouloir attribuer ce responsable de projet comme propriétaire ?',\n      areYouSureYouWantToDeactivateThisUser:\n        'Etes-vous sûr de vouloir désactiver cet utilisateur ?',\n      areYouSureYouWantToDeleteThisApiKey: 'Êtes-vous sûr de vouloir supprimer cette clé API ?',\n      areYouSureYouWantToDeleteThisAttachment:\n        'Êtes-vous sûr de vouloir supprimer cette pièce jointe ?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Etes-vous sûr de vouloir supprimer cette image d’arrière-plan ?',\n      areYouSureYouWantToDeleteThisBoard: 'Êtes-vous sûr de vouloir supprimer ce tableau ?',\n      areYouSureYouWantToDeleteThisCard: 'Êtes-vous sûr de vouloir supprimer cette carte ?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Êtes-vous sûr de vouloir supprimer cette carte définitivement ?',\n      areYouSureYouWantToDeleteThisComment: 'Êtes-vous sûr de vouloir supprimer ce commentaire ?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Êtes-vous sûr de vouloir supprimer ce champ personnalisé ?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Êtes-vous sûr de vouloir supprimer ce groupe de champs personnalisés ?',\n      areYouSureYouWantToDeleteThisLabel: 'Êtes-vous sûr de vouloir supprimer cette étiquette ?',\n      areYouSureYouWantToDeleteThisList:\n        'Êtes-vous sûr de vouloir supprimer cette liste ? Toutes les cartes seront déplacées vers la corbeille.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Êtes-vous sûr de vouloir supprimer ce service de notification ?',\n      areYouSureYouWantToDeleteThisProject: 'Êtes-vous sûr de vouloir supprimer ce projet ?',\n      areYouSureYouWantToDeleteThisTask: 'Êtes-vous sûr de vouloir supprimer cette tâche ?',\n      areYouSureYouWantToDeleteThisTaskList:\n        'Êtes-vous sûr de vouloir supprimer cette liste de tâches ?',\n      areYouSureYouWantToDeleteThisUser: 'Êtes-vous sûr de vouloir supprimer cet utilisateur ?',\n      areYouSureYouWantToDeleteThisWebhook: 'Etes-vous sûr de vouloir supprimer ce webhook ?',\n      areYouSureYouWantToEmptyTrash: 'Etes-vous sûr de vouloir vider la corbeille ?',\n      areYouSureYouWantToLeaveBoard: 'Etes-vous sûr de vouloir quitter ce tableau ?',\n      areYouSureYouWantToLeaveProject: 'Êtes-vous sûr de vouloir quitter ce projet ?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Êtes-vous sûr de vouloir rendre ce projet privé ?',\n      areYouSureYouWantToMakeThisProjectShared:\n        \"Etes-vous sûr de vouloir transformer ce projet en projet d'équipe ?\",\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Êtes-vous sûr de vouloir régénérer cette clé API ? La clé précédente ne fonctionnera plus.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Êtes-vous sûr de vouloir supprimer ce responsable du projet ?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Êtes-vous sûr de vouloir supprimer ce membre du tableau ?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        \"Êtes-vous sûr de vouloir dissocier le SSO de cet utilisateur ? Cela permettra à l'utilisateur de se connecter avec un mot de passe.\",\n      assignAsOwner_title: 'Assigner comme propriétaire',\n      atLeastOneListMustBePresent: 'Au moins une liste doit être présente',\n      attachment: 'Pièce jointe',\n      attachments: 'Pièces jointes',\n      authentication: 'Authentification',\n      background: 'Arrière-plan',\n      baseCustomFields_title: 'Champs personnalisés de base',\n      baseGroup: 'Groupe de base',\n      board: 'Tableau',\n      boardActions_title: 'Actions du tableau',\n      boardNotFound_title: 'Carte non trouvée',\n      boardSubscribed: 'Abonné au tableau',\n      boardUser: 'Utilisateur de tableau',\n      byCreationTime: 'Par date de création',\n      byDefault: 'Par défaut',\n      byDueDate: 'Par date d’échéance',\n      canBeInvitedToWorkInBoards: 'Peut être invité à travailler dans des tableaux.',\n      canComment: 'Peut commenter',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Peut créer ses propres projets et être invité à travailler dans d’autres.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Peut modifier la disposition du tableau et attribuer des membres aux cartes.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Peut gérer les paramètres globaux du système et agir en tant que propriétaire de projet.',\n      canOnlyViewBoard: 'Peut uniquement voir le tableau.',\n      cardActions_title: 'Actions sur la carte',\n      cardNotFound_title: 'Carte non trouvée',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Les cartes de cette liste sont disponibles pour tous les membres du tableau.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Les cartes de cette liste sont terminées et prêtes à être archivées.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Les cartes de cette liste sont prêtes à être traitées.',\n      clickHereOrRefreshPageToUpdate:\n        '<0>Cliquez ici</0> ou rafraîchissez la page pour mettre à jour.',\n      clientHostnameInEhlo: \"Nom d'hôte du client dans EHLO\",\n      closed: 'Fermé',\n      color: 'Couleur',\n      comments: 'Commentaires',\n      contentExceedsLimit: 'Le contenu dépasse la limite {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Le contenu de cette pièce jointe est trop volumineux pour être affiché.',\n      copy_inline: 'copie',\n      createBoard_title: 'Créer un tableau',\n      createCustomFieldGroup_title: 'Créer un groupe de champs personnalisés',\n      createLabel_title: 'Créer une étiquette',\n      createNewOneOrSelectExistingOne: 'Créez-en un nouveau ou sélectionnez<br />un existant.',\n      createProject_title: 'Créer un projet',\n      createTextFile_title: 'Créer un fichier texte',\n      creator: 'Créateur',\n      currentPassword: 'Mot de passe actuel',\n      currentUser: 'Utilisateur actuel',\n      customFieldGroup_title: 'Groupe de champs personnalisés',\n      customFieldGroups_title: 'Groupes de champs personnalisés',\n      customField_title: 'Champ personnalisé',\n      customFields_title: 'Champs personnalisés',\n      customerPanel_title: 'Panneau client',\n      dangerZone_title: 'Zone dangereuse',\n      date: 'Date',\n      deactivateUser_title: 'Désactiver l’utilisateur',\n      defaultCardType_title: 'Type de carte par défaut',\n      defaultFrom: '\"De\" par défaut',\n      defaultView_title: 'Vue par défaut',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Supprimer tous les tableaux pour pouvoir supprimer ce projet.',\n      deleteApiKey_title: 'Supprimer la clé API',\n      deleteAttachment_title: 'Supprimer la pièce jointe',\n      deleteBackgroundImage_title: 'Supprimer l’image d’arrière-plan',\n      deleteBoard_title: 'Supprimer le tableau',\n      deleteCardForever_title: 'Supprimer la carte définitivement',\n      deleteCard_title: 'Supprimer la carte',\n      deleteComment_title: 'Supprimer le commentaire',\n      deleteCustomFieldGroup_title: 'Supprimer le groupe de champs personnalisés',\n      deleteCustomField_title: 'Supprimer le champ personnalisé',\n      deleteLabel_title: \"Supprimer l'étiquette\",\n      deleteList_title: 'Supprimer la liste',\n      deleteNotificationService_title: 'Supprimer le service de notification',\n      deleteProject_title: 'Supprimer le projet',\n      deleteTaskList_title: 'Supprimer la liste de tâches',\n      deleteTask_title: 'Supprimer la tâche',\n      deleteUser_title: \"Supprimer l'utilisateur\",\n      deleteWebhook_title: 'Supprimer le webhook',\n      deletedUser_title: 'Utilisateur supprimé',\n      description: 'Description',\n      display: 'Affichage',\n      displayCardAges: \"Afficher l'âge des cartes\",\n      dropFileToUpload: 'Déposer le fichier à télécharger',\n      dueDate_title: \"Date d'échéance\",\n      dynamicAndUnevenlySpacedLayout: 'Mise en page dynamique et inégalement espacée.',\n      editAttachment_title: 'Modifier la pièce jointe',\n      editAvatar_title: \"Modifier l'avatar\",\n      editColor_title: 'Modifier la couleur',\n      editCustomFieldGroup_title: 'Modifier le groupe de champs personnalisés',\n      editCustomField_title: 'Modifier le champ personnalisé',\n      editDueDate_title: \"Modifier la date d'échéance\",\n      editEmail_title: \"Modifier l'e-mail\",\n      editInformation_title: 'Modifier les informations',\n      editLabel_title: \"Modifier l'étiquette\",\n      editPassword_title: 'Modifier le mot de passe',\n      editPermissions_title: 'Modifier les permissions',\n      editRole_title: 'Modifier le rôle',\n      editStopwatch_title: 'Modifier la minuterie',\n      editType_title: 'Modifier le type',\n      editUsername_title: \"Modifier le nom d'utilisateur\",\n      editor: 'Éditeur',\n      editors: 'Éditeurs',\n      email: 'E-mail',\n      emptyTrash_title: 'Vider la corbeille',\n      enterCardTitle: 'Saisir le titre de la carte...',\n      enterDescription: 'Saisir la description...',\n      enterFilename: 'Saisir le nom du fichier',\n      enterListTitle: 'Saisie le titre de la liste...',\n      enterTaskDescription: 'Saisir la description de la tâche...',\n      events: 'Événements',\n      excludedEvents: 'Événements exclus',\n      expandTaskListsByDefault: 'Développer les listes de tâches par défaut',\n      filterByLabels_title: 'Filtrer par étiquettes',\n      filterByMembers_title: 'Filtrer par membres',\n      forPersonalProjects: 'Pour les projets personnels.',\n      forTeamBasedProjects: 'Pour les projets basés sur une équipe.',\n      fromComputer_title: \"Depuis l'ordinateur\",\n      fromTrello: 'Depuis Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'La clé complète est masquée pour des raisons de sécurité. Régénérez-la pour en créer une nouvelle.',\n      general: 'Général',\n      gradients: 'Dégradés',\n      grid: 'Grille',\n      hideCompletedTasks: 'Masquer les tâches terminées',\n      hideFromProjectListAndFavorites: 'Masquer de la liste des projets et des favoris',\n      host: 'Hôte',\n      hours: 'Heures',\n      identity: 'Identité',\n      importBoard_title: 'Importer un tableau',\n      information: 'Information',\n      invalidCurrentPassword: 'Mot de passe actuel invalide',\n      kanban: 'Kanban',\n      labels: 'Étiquettes',\n      language: 'Langue',\n      leaveBoard_title: 'Quitter le tableau',\n      leaveProject_title: 'Quitter le projet',\n      limitCardTypesToDefaultOne: 'Restreindre les types de cartes au type par défaut',\n      linkToCard: 'Lien vers la carte',\n      list: 'Lister',\n      listActions_title: 'Liste des actions',\n      lists: 'Listes',\n      makeProjectPrivate_title: 'Rendre le projet privé',\n      makeProjectShared_title: 'Transformer le projet en projet partagé',\n      managers: 'Responsables',\n      memberActions_title: 'Actions des membres',\n      members: 'Membres',\n      minutes: 'Minutes',\n      moreActions: \"Plus d'actions\",\n      moreActions_title: \"Plus d'actions\",\n      moveCard_title: 'Déplacer la carte',\n      moveList_title: 'Déplacer la liste',\n      myOwn_title: 'Mes projets privés',\n      name: 'Nom',\n      newEmail: 'Nouvel e-mail',\n      newPassword: 'Nouveau mot de passe',\n      newUsername: \"Nouveau nom d'utilisateur\",\n      newVersionAvailable: 'Une nouvelle version est disponible',\n      newestFirst: 'Le plus récent en premier',\n      noApiKeyCreated: 'Aucune clé API créée.',\n      noBoards: 'Pas de tableau',\n      noCardsFound: 'Aucune carte trouvée.',\n      noConnectionToServer: 'Pas de connexion au serveur',\n      noLists: 'Pas de liste',\n      noProjects: 'Pas de projet',\n      noUnreadNotifications: 'Aucune notification non lue.',\n      notifications: 'Notifications',\n      oldestFirst: 'Le plus ancien en premier',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Un seul responsable doit rester pour rendre ce projet privé',\n      openBoard_title: 'Ouvrir le tableau',\n      optional_inline: 'optionnel',\n      organization: 'Organisation',\n      others: 'Autres',\n      passwordIsSet: 'Mot de passe défini',\n      phone: 'Téléphone',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA utilise <1><0>Apprise</0></1> pour envoyer des notifications vers plus de 100 services populaires.',\n      port: 'Port',\n      preferences: 'Préférences',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Conseil: appuyer sur Ctrl-V (Cmd-V sur Mac) pour ajouter une pièce jointe depuis le presse-papiers',\n      private: 'Privé',\n      project: 'Projet',\n      projectNotFound_title: 'Projet introuvable',\n      projectOwner: 'Propriétaire de projet',\n      referenceDataAndKnowledgeStorage: 'Stockage de données de référence et de connaissances.',\n      regenerateApiKey_title: 'Régénérer la clé API',\n      rejectUnauthorizedTlsCertificates: 'Rejeter les certificats TLS non autorisés',\n      removeManager_title: 'Supprimer le responsable',\n      removeMember_title: 'Supprimer le membre',\n      role: 'Rôle',\n      saveThisKeyItWillNotBeShownAgain: 'Enregistrez cette clé — elle ne sera plus affichée !',\n      searchCards: 'Rechercher une carte...',\n      searchCustomFieldGroups: 'Chercher un groupe de champs personnalisés...',\n      searchCustomFields: 'Chercher un champ personnalisé...',\n      searchLabels: 'Rechercher une étiquette...',\n      searchLists: 'Rechercher une liste...',\n      searchMembers: 'Rechercher un membre...',\n      searchProjects: 'Rechercher un projet...',\n      searchUsers: 'Rechercher un utilisateur...',\n      seconds: 'Secondes',\n      selectAssignee_title: 'Sélectionner un responsable',\n      selectBoard: 'Sélectionner un tableau',\n      selectList: 'Sélectionner une liste',\n      selectListToRestoreThisCard: 'Sélectionner une liste pour restaurer cette carte',\n      selectOrder_title: 'Sélectionner l’ordre',\n      selectPermissions_title: 'Sélectionner les permissions',\n      selectProject: 'Sélectionner un projet',\n      selectRole_title: 'Sélectionner un rôle',\n      selectType_title: 'Sélectionner un type',\n      sequentialDisplayOfCards: 'Affichage séquentiel des cartes.',\n      settings: 'Réglages',\n      shared: 'Partagé',\n      sharedWithMe_title: 'Partagé avec moi',\n      showOnFrontOfCard: 'Afficher sur le devant de la carte',\n      smtp: 'SMTP',\n      sortList_title: 'Trier la liste',\n      sourceCardIsNoLongerAvailableForCopying:\n        \"La carte source n'est plus disponible pour la copie.\",\n      sourceCardIsNoLongerAvailableForMoving:\n        \"La carte source n'est plus disponible pour le déplacement.\",\n      stopwatch: 'Minuteur',\n      story: 'Story',\n      subscribeToCardWhenCommenting: 'S’abonner à la carte lors de la rédaction d’un commentaire',\n      subscribeToMyOwnCardsByDefault: \"M'abonner à mes propres cartes par défaut\",\n      taskActions_title: 'Actions de tâche',\n      taskAssignmentAndProjectCompletion: 'Completion de tâches et d’un projet.',\n      taskListActions_title: 'Actions de la liste de tâches',\n      taskList_title: 'Liste de tâches',\n      team: \"Mes projets d'équipe\",\n      termsOfService_title: 'Conditions de service',\n      testLog_title: 'Journal de test',\n      thereIsNoPreviewAvailableForThisAttachment:\n        \"Il n'y a pas d'aperçu disponible pour cette pièce jointe.\",\n      time: 'Temps',\n      title: 'Titre',\n      trash: 'Corbeille',\n      trashHasBeenSuccessfullyEmptied: 'La corbeille a été vidée avec succès.',\n      turnOffRecentCardHighlighting: 'Désactiver la mise en évidence des cartes récentes',\n      typeNameToConfirm: 'Saissir le nom pour confirmer.',\n      typeTitleToConfirm: 'Saisir le titre pour confirmer.',\n      unlinkSso_title: 'Dissociation du SSO',\n      unsavedChanges: 'Modifications non enregistrées',\n      uploadFailedFileIsTooBig: 'Échec du téléchargement : fichier trop volumineux.',\n      uploadFailedNotEnoughStorageSpace:\n        'Échec du téléchargement : espace de stockage insuffisant.',\n      uploadedImages: 'Images téléchargées',\n      url: 'URL',\n      useSecureConnection: 'Utiliser une connexion sécurisée',\n      userActions_title: \"Actions de l'utilisateur\",\n      userAddedCardToList: '<0>{{user}}</0> a ajouté <2>{{card}}</2> à {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> a ajouté cette carte à {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> a ajouté {{addedUser}} à <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> a ajouté {{addedUser}} à cette carte',\n      userAddedYouToCard: '<0>{{user}}</0> vous a ajouté à <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> a finalisé {{task}} dans <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> a finalisé {{task}} dans cette carte',\n      userJoinedCard: '<0>{{user}}</0> a rejoint <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> a rejoint cette carte',\n      userLeftCard: '<0>{{user}}</0> a quitté <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> a laissé un nouveau commentaire «{{comment}}» à <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> a quitté cette carte',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> a marqué {{task}} comme incomplete dans <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> a marqué {{task}} comme incomplete dans cette carte',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> vous a mentionné dans un commentaire «{{comment}}» dans <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> a déplacé <2>{{card}}</2> de {{fromList}} vers {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> a déplacé cette carte de {{fromList}} vers {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> a retiré {{removedUser}} de <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> a retiré {{removedUser}} de cette carte',\n      username: \"Nom d'utilisateur\",\n      users: 'Utilisateurs',\n      viewer: 'Spectateur',\n      viewers: 'Spectateurs',\n      visualTaskManagementWithLists: 'Management visuel des tâches avec des listes.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Quoi de neuf',\n      withoutBaseGroup: 'Sans groupe de base',\n      writeComment: 'Écrire un commentaire...',\n    },\n    action: {\n      activateUser: 'Activer l’utilisateur',\n      activateUser_title: 'Activer l’utilisateur',\n      addAnotherCard: 'Ajouter une autre carte',\n      addAnotherList: 'Ajouter une autre liste',\n      addAnotherTask: 'Ajouter une autre tâche',\n      addCard: 'Ajouter une carte',\n      addCard_title: 'Ajouter une carte',\n      addComment: 'Ajouter un commentaire',\n      addCustomField: 'Ajouter un champ personnalisé',\n      addCustomFieldGroup: 'Ajouter un groupe de champs personnalisés',\n      addList: 'Ajouter la liste',\n      addMember: 'Ajouter un membre',\n      addMoreDetailedDescription: 'Ajouter une description plus détaillée',\n      addTask: 'Ajouter une tâche',\n      addTaskList: 'Ajouter une liste de tâches',\n      addToCard: 'Ajouter à la carte',\n      addUser: 'Ajouter un utilisateur',\n      addWebhook: 'Ajouter un webhook',\n      archive: 'Archiver',\n      archiveCard: 'Archiver la carte',\n      archiveCard_title: 'Archiver la carte',\n      archiveCards: 'Archiver les cartes',\n      archiveCards_title: 'Archiver les cartes',\n      assignAsOwner: 'Assigner comme propriétaire',\n      cancel: 'Annuler',\n      copy: 'Copier',\n      copyCard_title: 'Copier la carte',\n      createApiKey: 'Créer une clé API',\n      createBoard: 'Créer un tableau',\n      createCustomFieldGroup: 'Créer un groupe de champs personnalisés',\n      createFile: 'Créer un fichier',\n      createLabel: 'Créer une étiquette',\n      createNewLabel: 'Créer une nouvelle étiquette',\n      createProject: 'Créer un projet',\n      cut: 'Couper',\n      cutCard_title: 'Couper la carte',\n      deactivateUser: 'Désactiver l’utilisateur',\n      deactivateUser_title: 'Désactiver l’utilisateur',\n      delete: 'Supprimer',\n      deleteApiKey: 'Supprimer la clé API',\n      deleteAttachment: 'Supprimer la pièce jointe',\n      deleteAvatar: \"Supprimer l'avatar\",\n      deleteBackgroundImage: 'Supprimer l’image d’arrière-plan',\n      deleteBoard: 'Supprimer le tableau',\n      deleteBoard_title: 'Supprimer le tableau',\n      deleteCard: 'Supprimer la carte',\n      deleteCardForever: 'Supprimer la carte définitivement',\n      deleteCard_title: 'Supprimer la carte',\n      deleteComment: 'Supprimer le commentaire',\n      deleteCustomField: 'Supprimer le champ personnalisé',\n      deleteCustomFieldGroup: 'Supprimer le groupe de champs personnalisés',\n      deleteForever_title: 'Supprimer définitivement',\n      deleteGroup: 'Supprimer le groupe',\n      deleteLabel: \"Supprimer l'étiquette\",\n      deleteList: 'Supprimer la liste',\n      deleteList_title: 'Supprimer la liste',\n      deleteNotificationService: 'Supprimer le service de notification',\n      deleteProject: 'Supprimer le projet',\n      deleteProject_title: 'Supprimer le projet',\n      deleteTask: 'Supprimer la tâche',\n      deleteTaskList: 'Supprimer la liste de tâches',\n      deleteTask_title: 'Supprimer la tâche',\n      deleteUser: \"Supprimer l'utilisateur\",\n      deleteUser_title: 'Supprimer l’utilisateur',\n      deleteWebhook: 'Supprimer le webhook',\n      dismissAll: 'Tout rejeter',\n      download: 'Télécharger',\n      duplicateCard_title: 'Dupliquer la carte',\n      edit: 'Modifier',\n      editColor_title: 'Modifier la couleur',\n      editDescription_title: 'Modifier la description',\n      editDueDate_title: \"Modifier la date d'échéance\",\n      editEmail_title: \"Modifier l'e-mail\",\n      editGroup: 'Modifier le groupe',\n      editInformation_title: 'Modifier les informations',\n      editPassword_title: 'Modifier le mot de passe',\n      editPermissions: 'Modifier les permissions',\n      editRole_title: 'Modifier le rôle',\n      editStopwatch_title: 'Modifier la minuterie',\n      editTitle_title: 'Modifier le titre',\n      editType_title: 'Modifier le type',\n      editUsername_title: \"Modifier le nom d'utilisateur\",\n      emptyTrash: 'Vider la corbeille',\n      emptyTrash_title: 'Vider la corbeille',\n      import: 'Importer',\n      join: 'Rejoindre',\n      leave: 'Quitter',\n      leaveBoard: 'Quitter le tableau',\n      leaveProject: 'Quitter le projet',\n      logOut_title: 'Se déconnecter',\n      makeCover_title: 'Faire la couverture',\n      makeProjectPrivate: 'Rendre le projet privé',\n      makeProjectPrivate_title: 'Rendre le projet privé',\n      makeProjectShared: \"Transformer le projet en projet d'équipe\",\n      makeProjectShared_title: \"Transformer le projet en projet d'équipe\",\n      move: 'Déplacer',\n      moveCard_title: 'Déplacer la carte',\n      moveList_title: 'Déplacer la liste',\n      regenerateApiKey: 'Régénérer la clé API',\n      remove: 'Supprimer',\n      removeAssignee: 'Retirer le responsable',\n      removeColor: 'Supprimer la couleur',\n      removeCover_title: 'Supprimer la couverture',\n      removeFromBoard: 'Supprimer du tableau',\n      removeFromProject: 'Supprimer du projet',\n      removeManager: 'Supprimer le responsable',\n      removeMember: 'Supprimer le membre',\n      restoreToList: 'Restaurer dans {{list}}',\n      returnToBoard: 'Retourner au tableau',\n      save: 'Sauvegarder',\n      sendTestEmail: 'Envoyer un e-mail de test',\n      showActive: 'Voir les actifs',\n      showAllAttachments: 'Afficher toutes les pièces jointes ({{hidden}} masquées)',\n      showCardsWithThisUser: 'Voir les cartes avec cet utilisateur',\n      showDeactivated: 'Voir les désactivés',\n      showFewerAttachments: 'Afficher moins de pièces jointes',\n      showLess: 'Voir moins',\n      showMore: 'Voir plus',\n      sortList_title: 'Trier la liste',\n      start: 'Commencer',\n      stop: 'Arrêter',\n      subscribe: \"S'abonner\",\n      unlinkSso: 'Dissocier le SSO',\n      unlinkSso_title: 'Dissocier le SSO',\n      unsubscribe: 'Se désabonner',\n      uploadNewAvatar: 'Télécharger un nouvel avatar',\n      uploadNewImage: 'Télécharger une nouvelle image',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/fr-FR/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'fr-FR',\n  country: 'fr',\n  name: 'Français',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/fr-FR/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'La limite d’utilisateurs actifs a été atteinte',\n      adminLoginRequiredToInitializeInstance:\n        \"Connexion administrateur requise pour initialiser l'instance\",\n      emailAlreadyInUse: 'E-mail déjà utilisé',\n      emailOrUsername: \"E-mail ou nom d'utilisateur\",\n      invalidCredentials: 'Identifiants invalides',\n      invalidEmailOrUsername: \"E-mail ou nom d'utilisateur invalide\",\n      invalidPassword: 'Mot de passe invalide',\n      logIn_title: 'Se connecter',\n      noInternetConnection: 'Aucune connexion internet',\n      or: 'Ou',\n      pageNotFound_title: 'Page non trouvée',\n      password: 'Mot de passe',\n      poweredByPlanka: 'Propulsé par <1>PLANKA</1>',\n      serverConnectionFailed: 'Échec de la connexion au serveur',\n      unknownError: 'Erreur inconnue, réessayez plus tard',\n      useSingleSignOn: \"Utiliser l'authentification unique\",\n      usernameAlreadyInUse: \"Nom d'utilisateur déjà utilisé\",\n      whoops_title: 'Oups !',\n    },\n\n    action: {\n      cancelAndClose: 'Annuler et fermer',\n      continue: 'Continuer',\n      debugSso: 'Déboguer le SSO',\n      goBack: 'Retour',\n      goHome: \"Aller à l'accueil\",\n      logIn: 'Se connecter',\n      logInWithSso: \"Se connecter avec l'authentification unique\",\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/fr-FR/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Ceci est un texte sans titre.\\nLe titre et le texte\\npeuvent être mis en gras, italique, couleur,\\nbarré et souligné.\",\n    \"text-with-head\": \"Ceci est un texte avec un titre.\\nLe titre et le texte\\npeuvent être mis en gras, italique, couleur,\\nbarré et souligné.\",\n    \"heading\": \"Titre\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Erreur dans l'éditeur markdown\",\n    \"settings_wysiwyg\": \"Éditeur visuel (wysiwyg)\",\n    \"settings_markup\": \"Syntaxe markdown\",\n    \"markup_placeholder\": \"Entrez la syntaxe markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Supprimer\",\n    \"empty_option\": \"Aucune correspondance trouvée\",\n    \"show_line_numbers\": \"Numérotation des lignes\"\n  },\n  \"common\": {\n    \"delete\": \"Supprimer\",\n    \"edit\": \"Éditer\",\n    \"toolbar_action_disabled\": \"Élément de syntaxe incompatible\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Annuler\",\n    \"common_action_submit\": \"Envoyer\",\n    \"common_action_upload\": \"Sélectionner\",\n    \"common_tab_attach\": \"Ajouter depuis l'appareil\",\n    \"common_tab_link\": \"Ajouter par lien\",\n    \"common_link\": \"Lien\",\n    \"common_sizes\": \"Taille, px\",\n    \"image_name\": \"Titre\",\n    \"image_link_href\": \"Lien de l'image\",\n    \"image_link_href_help\": \"Adresse vers laquelle mène le lien de l'image.\",\n    \"image_alt\": \"Texte alternatif\",\n    \"image_alt_help\": \"Le texte alternatif s'affiche si l'image ne peut pas être chargée.\",\n    \"image_upload_help\": \"Image JPEG, GIF ou PNG de 1 Mo maximum.\",\n    \"image_upload_failed\": \"Échec de l'ajout de l'image\",\n    \"image_size_width\": \"Largeur\",\n    \"image_size_height\": \"Hauteur\",\n    \"link_url_help\": \"Adresse vers laquelle mène le lien.\",\n    \"link_text\": \"Texte du lien\",\n    \"link_text_help\": \"Texte affiché comme lien.\",\n    \"link_open_help\": \"Ouvrir le lien dans un nouvel onglet\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"En-tête\",\n    \"header_hint\": \"# Votre texte\",\n    \"italic_title\": \"Italique\",\n    \"italic_hint\": \"_Votre texte_\",\n    \"bold_title\": \"Gras\",\n    \"bold_hint\": \"**Votre texte**\",\n    \"strikethrough_title\": \"Barré\",\n    \"strikethrough_hint\": \"~~Votre texte~~\",\n    \"blockquote_title\": \"Citation\",\n    \"blockquote_hint\": \"> Votre texte\",\n    \"code_title\": \"Code\",\n    \"code_hint\": \"```Votre texte```\",\n    \"link_title\": \"Lien\",\n    \"link_hint\": \"[Votre texte](url)\",\n    \"image_title\": \"Image\",\n    \"image_hint\": \"![Votre texte](url)\",\n    \"list_title\": \"Élément de liste\",\n    \"list_hint\": \"- Votre texte\",\n    \"numbered-list_title\": \"Liste numérotée\",\n    \"numbered-list_hint\": \"1. Votre texte\",\n    \"documentation\": \"Documentation\",\n    \"documentation_link\": \"https://diplodoc.com/docs/fr/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Gras\",\n    \"code\": \"Code\",\n    \"code_inline\": \"Code en ligne\",\n    \"codeblock\": \"Bloc de code\",\n    \"colorify\": \"Couleur du texte\",\n    \"colorify__color_blue\": \"Bleu\",\n    \"colorify__color_default\": \"Par défaut\",\n    \"colorify__color_gray\": \"Gris\",\n    \"colorify__color_green\": \"Vert\",\n    \"colorify__color_orange\": \"Orange\",\n    \"colorify__color_red\": \"Rouge\",\n    \"colorify__color_violet\": \"Violet\",\n    \"colorify__color_yellow\": \"Jaune\",\n    \"colorify__group_text\": \"Texte\",\n    \"cut\": \"Couper\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Les emojis peuvent être ajoutés en WYSIWYG ou manuellement avec la syntaxe\",\n    \"heading\": \"En-tête\",\n    \"heading1\": \"En-tête 1\",\n    \"heading2\": \"En-tête 2\",\n    \"heading3\": \"En-tête 3\",\n    \"heading4\": \"En-tête 4\",\n    \"heading5\": \"En-tête 5\",\n    \"heading6\": \"En-tête 6\",\n    \"hrule\": \"Séparateur\",\n    \"image\": \"Image\",\n    \"italic\": \"Italique\",\n    \"link\": \"Lien\",\n    \"list\": \"Liste\",\n    \"list__action_lift\": \"Remonter l'élément\",\n    \"list__action_sink\": \"Descendre l'élément\",\n    \"list_action_disabled\": \"Contredit la logique de la liste\",\n    \"mark\": \"Marqué\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Plus d'actions\",\n    \"note\": \"Note\",\n    \"olist\": \"Liste ordonnée\",\n    \"quote\": \"Citation\",\n    \"redo\": \"Rétablir\",\n    \"strike\": \"Barré\",\n    \"table\": \"Tableau\",\n    \"text\": \"Texte\",\n    \"ulist\": \"Liste à puces\",\n    \"underline\": \"Souligné\",\n    \"undo\": \"Annuler\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Tapez / pour utiliser les commandes...\",\n    \"checkbox\": \"Entrez la description de la tâche...\",\n    \"deflist_term\": \"Terme\",\n    \"deflist_desc\": \"Description de la définition\",\n    \"heading\": \"En-tête\",\n    \"cut_title\": \"Titre\",\n    \"cut_content\": \"Contenu à afficher au clic\",\n    \"note_title\": \"Titre\",\n    \"note_content\": \"Contenu de la note\",\n    \"table_cell\": \"Contenu de la cellule\",\n    \"select_filter\": \"Rechercher des langues...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Sensible à la casse\",\n    \"label_whole-word\": \"Mot entier\",\n    \"title\": \"Rechercher dans le code\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Aucun résultat trouvé\"\n  },\n  \"widgets\": {\n    \"image\": \"Ajouter une image\",\n    \"link\": \"Ajouter un lien\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Note\",\n    \"tip\": \"Astuce\",\n    \"warning\": \"Avertissement\",\n    \"alert\": \"Alerte\",\n    \"remove\": \"Supprimer\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Ajouter une colonne avant\",\n    \"column.add.after\": \"Ajouter une colonne après\",\n    \"column.remove\": \"Supprimer la colonne\",\n    \"column.remove.multiple\": \"Supprimer les colonnes\",\n    \"row.add.before\": \"Ajouter une ligne avant\",\n    \"row.add.after\": \"Ajouter une ligne après\",\n    \"row.remove\": \"Supprimer la ligne\",\n    \"row.remove.multiple\": \"Supprimer les lignes\",\n    \"cells.clear\": \"Vider les cellules\",\n    \"table.remove\": \"Supprimer le tableau\",\n    \"table.menu.cell.align.left\": \"Aligner le contenu de la cellule à gauche\",\n    \"table.menu.cell.align.right\": \"Aligner le contenu de la cellule à droite\",\n    \"table.menu.cell.align.center\": \"Aligner le contenu de la cellule au centre\",\n    \"table.menu.row.add\": \"Ajouter une ligne après\",\n    \"table.menu.row.remove\": \"Supprimer la ligne\",\n    \"table.menu.column.add\": \"Ajouter une colonne après\",\n    \"table.menu.column.remove\": \"Supprimer la colonne\",\n    \"table.menu.convert.yfm\": \"Convertir en tableau YFM\",\n    \"table.menu.table.remove\": \"Supprimer le tableau\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/hu-HU/core.js",
    "content": "import dateFns from 'date-fns/locale/hu';\nimport timeAgo from 'javascript-time-ago/locale/hu';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'yyyy.M.d.',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'MMM. d.',\n    longDateTime: 'MMMM. d. p',\n    fullDate: 'y. MMM d.',\n    fullDateTime: 'y. MMMM d. p',\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Az alkalmazásról',\n      aboutPlanka_title: 'PLANKA-ról',\n      accessToken: 'Hozzáférési token',\n      account: 'Fiók',\n      actions: 'Műveletek',\n      activateUser_title: 'Felhasználó aktiválása',\n      active: 'Aktív',\n      addAttachment_title: 'Melléklet hozzáadása',\n      addCustomFieldGroup_title: 'Egyedi mezőcsoport hozzáadása',\n      addCustomField_title: 'Egyedi mező hozzáadása',\n      addManager_title: 'Menedzser hozzáadása',\n      addMember_title: 'Tag létrehozása',\n      addTaskList_title: 'Feladatlista hozzáadása',\n      addUser_title: 'Felhasználó hozzáadása',\n      admin: 'Adminisztrátor',\n      administration: 'Adminisztráció',\n      all: 'Összes',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Az összes változás automatikusan mentésre kerül<br />a kapcsolat helyreállása után.',\n      alphabetically: 'ABC sorrendben',\n      alwaysDisplayCardCreator: 'Mindig jelenjen meg a kártya készítője',\n      apiKeyCreated_title: 'API kulcs létrehozva',\n      apiKey_title: 'API kulcs',\n      archive: 'Archív',\n      archiveCard_title: 'Archív kártya',\n      archiveCards_title: 'Archív kártyák',\n      areYouSureYouWantToActivateThisUser: 'Biztosan aktiválja ezt a felhasználót?',\n      areYouSureYouWantToArchiveCards: 'Biztosan archiválja a kártyákat?',\n      areYouSureYouWantToArchiveThisCard: 'Biztosan archiválni szeretné ezt a kártyát?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Biztosan tulajdonosként jelöli ki ezt a projektmenedzsert?',\n      areYouSureYouWantToDeactivateThisUser: 'Biztosan deaktiválni szeretné ezt a felhasználót?',\n      areYouSureYouWantToDeleteThisApiKey: 'Biztosan törölni szeretné ezt az API kulcsot?',\n      areYouSureYouWantToDeleteThisAttachment: 'Biztosan törölni szeretné ezt a mellékletet?',\n      areYouSureYouWantToDeleteThisBackgroundImage: 'Biztosan törli ezt a háttérképet?',\n      areYouSureYouWantToDeleteThisBoard: 'Biztosan törölni szeretné ezt a táblát?',\n      areYouSureYouWantToDeleteThisCard: 'Biztosan törölni szeretné ezt a kártyát?',\n      areYouSureYouWantToDeleteThisCardForever: 'Biztosan végleg törli ezt a kártyát?',\n      areYouSureYouWantToDeleteThisComment: 'Biztosan törölni szeretné ezt a megjegyzést?',\n      areYouSureYouWantToDeleteThisCustomField: 'Biztosan törli ezt az egyedi mezőt?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup: 'Biztosan törli ezt az egyedi mezőcsoportot?',\n      areYouSureYouWantToDeleteThisLabel: 'Biztosan törölni szeretné ezt a címkét?',\n      areYouSureYouWantToDeleteThisList:\n        'Biztosan törölni szeretné ezt a listát? Minden kártya a lomtárba kerül.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Biztosan törli ezt az értesítési szolgáltatást?',\n      areYouSureYouWantToDeleteThisProject: 'Biztosan törölni szeretné ezt a projektet?',\n      areYouSureYouWantToDeleteThisTask: 'Biztosan törölni szeretné ezt a feladatot?',\n      areYouSureYouWantToDeleteThisTaskList: 'Biztosan törli ezt a feladatlistát?',\n      areYouSureYouWantToDeleteThisUser: 'Biztosan törölni szeretné ezt a felhasználót?',\n      areYouSureYouWantToDeleteThisWebhook: 'Biztosan törli ezt a webhookot?',\n      areYouSureYouWantToEmptyTrash: 'Biztosan kiüríti a lomtárat?',\n      areYouSureYouWantToLeaveBoard: 'Biztosan el akarja hagyni a táblát?',\n      areYouSureYouWantToLeaveProject: 'Biztosan el akarja hagyni a projektet?',\n      areYouSureYouWantToMakeThisProjectPrivate: 'Biztosan priváttá teszi ezt a projektet?',\n      areYouSureYouWantToMakeThisProjectShared: 'Biztosan megosztottá teszi ezt a projektet?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Biztosan újra szeretné generálni ezt az API kulcsot? Az előző kulcs többé nem fog működni.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Biztosan eltávolítja ezt a menedzsert a projektből?',\n      areYouSureYouWantToRemoveThisMemberFromBoard: 'Biztosan eltávolítja ezt a tagot a tábláról?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Biztosan le szeretné választani az SSO-t ettől a felhasználótól? Ez lehetővé teszi a felhasználó számára, hogy jelszóval jelentkezzen be.',\n      assignAsOwner_title: 'Hozzárendelés tulajdonosnak',\n      atLeastOneListMustBePresent: 'Legalább egy lista szükséges.',\n      attachment: 'Melléklet',\n      attachments: 'Mellékletek',\n      authentication: 'Hitelesítés',\n      background: 'Háttér',\n      baseCustomFields_title: 'Alap egyedi mezők',\n      baseGroup: 'Alapcsoport',\n      board: 'Tábla',\n      boardActions_title: 'Tábla műveletek',\n      boardNotFound_title: 'Tábla nem található',\n      boardSubscribed: 'Sikeresen feliratkozott a táblára',\n      boardUser: 'Tábla felhasználó',\n      byCreationTime: 'Létrehozási időpont alapján',\n      byDefault: 'Alapértelmezett',\n      byDueDate: 'Határidő szerint',\n      canBeInvitedToWorkInBoards: 'Meghívható táblákon való munkára.',\n      canComment: 'Hozzászólhat',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Létrehozhat saját projekteket, és meghívható másokéba.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Szerkesztheti a tábla elrendezését és tagokat rendelhet a kártyákhoz.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Kezelheti a rendszerszintű beállításokat és projekt tulajdonosként járhat el.',\n      canOnlyViewBoard: 'Csak megtekintheti a táblát.',\n      cardActions_title: 'Kártya műveletek',\n      cardNotFound_title: 'Kártya nem található',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'A listán lévő kártyák minden tábla tag számára elérhetők.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'A listán lévő kártyák készek és archiválásra várnak.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'A listán lévő kártyák készen állnak a munkára.',\n      clickHereOrRefreshPageToUpdate:\n        '<0>Kattintson ide</0> vagy frissítse az oldalt a frissítéshez.',\n      clientHostnameInEhlo: 'Kliens hostname az EHLO-ban',\n      closed: 'Lezárt',\n      color: 'Szín',\n      comments: 'Megjegyzések',\n      contentExceedsLimit: 'A tartalom meghaladja a(z) {{limit}} korlátot.',\n      contentOfThisAttachmentIsTooBigToDisplay: 'A melléklet tartalma túl nagy a megjelenítéshez.',\n      copy_inline: 'másolat',\n      createBoard_title: 'Tábla létrehozása',\n      createCustomFieldGroup_title: 'Egyedi mezőcsoport létrehozása',\n      createLabel_title: 'Címke létrehozása',\n      createNewOneOrSelectExistingOne:\n        'Hozzon létre egy újat, vagy válasszon ki<br />egy meglévőt.',\n      createProject_title: 'Projekt létrehozása',\n      createTextFile_title: 'Szövegfájl létrehozása',\n      creator: 'Létrehozta',\n      currentPassword: 'Jelenlegi jelszó',\n      currentUser: 'Jelenlegi felhasználó',\n      customFieldGroup_title: 'Egyedi mezőcsoport',\n      customFieldGroups_title: 'Egyedi mezőcsoportok',\n      customField_title: 'Egyedi mező',\n      customFields_title: 'Egyedi mezők',\n      customerPanel_title: 'Ügyfélpanel',\n      dangerZone_title: 'Veszélyzóna',\n      date: 'Dátum',\n      deactivateUser_title: 'Felhasználó inaktiválása',\n      defaultCardType_title: 'Alapértelmezett kártyatípus',\n      defaultFrom: 'Alapértelmezett \"feladó\"',\n      defaultView_title: 'Alapértelmezett nézet',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'A projekt törléséhez törölni kell az összes táblát.',\n      deleteApiKey_title: 'API kulcs törlése',\n      deleteAttachment_title: 'Melléklet törlése',\n      deleteBackgroundImage_title: 'Háttérkép törlése',\n      deleteBoard_title: 'Tábla törlése',\n      deleteCardForever_title: 'Kártya végleges törlése',\n      deleteCard_title: 'Kártya törlése',\n      deleteComment_title: 'Megjegyzés törlése',\n      deleteCustomFieldGroup_title: 'Egyedi mezőcsoport törlése',\n      deleteCustomField_title: 'Egyedi mező törlése',\n      deleteLabel_title: 'Címke törlése',\n      deleteList_title: 'Lista törlése',\n      deleteNotificationService_title: 'Értesítési szolgáltatás törlése',\n      deleteProject_title: 'Projekt törlése',\n      deleteTaskList_title: 'Feladatlista törlése',\n      deleteTask_title: 'Feladat törlése',\n      deleteUser_title: 'Felhasználó törlése',\n      deleteWebhook_title: 'Webhook törlése',\n      deletedUser_title: 'Törölt felhasználó',\n      description: 'Leírás',\n      display: 'Megjelenítés',\n      displayCardAges: 'Kártyák korának megjelenítése',\n      dropFileToUpload: 'Dobja ide a fájlt a feltöltéshez',\n      dueDate_title: 'Esedékesség dátuma',\n      dynamicAndUnevenlySpacedLayout: 'Dinamikus és asszimetrikus kiosztás.',\n      editAttachment_title: 'Melléklet szerkesztése',\n      editAvatar_title: 'Avatar szerkesztése',\n      editColor_title: 'Szín szerkesztése',\n      editCustomFieldGroup_title: 'Egyedi mezőcsoport szerkesztése',\n      editCustomField_title: 'Egyedi mező szerkesztése',\n      editDueDate_title: 'Esedékesség dátumának szerkesztése',\n      editEmail_title: 'E-mail szerkesztése',\n      editInformation_title: 'Információ szerkesztése',\n      editLabel_title: 'Címke szerkesztése',\n      editPassword_title: 'Jelszó módosítása',\n      editPermissions_title: 'Jogosultságok szerkesztése',\n      editRole_title: 'Szerepkör szerkesztése',\n      editStopwatch_title: 'Stopper szerkesztése',\n      editType_title: 'Típus szerkesztése',\n      editUsername_title: 'Felhasználónév szerkesztése',\n      editor: 'Szerkesztő',\n      editors: 'Szerkesztők',\n      email: 'E-mail',\n      emptyTrash_title: 'Lomtár ürítése',\n      enterCardTitle: 'Adja meg a kártya címét...',\n      enterDescription: 'Adja meg a leírást...',\n      enterFilename: 'Adja meg a fájl nevét',\n      enterListTitle: 'Adja meg a lista címét...',\n      enterTaskDescription: 'Adja meg a feladat leírását...',\n      events: 'Események',\n      excludedEvents: 'Kizárt események',\n      expandTaskListsByDefault: 'Feladatlisták alapértelmezett kibontása',\n      filterByLabels_title: 'Szűrés címkék alapján',\n      filterByMembers_title: 'Szűrés tagok alapján',\n      forPersonalProjects: 'Személyes projektekhez.',\n      forTeamBasedProjects: 'Csapatalapú projektekhez.',\n      fromComputer_title: 'Számítógépről',\n      fromTrello: 'Trello-ról',\n      fullKeyIsHiddenForSecurityReasons:\n        'A teljes kulcs biztonsági okokból rejtett. Generálja újra egy új létrehozásához.',\n      general: 'Általános',\n      gradients: 'Színátmenetek',\n      grid: 'Rács',\n      hideCompletedTasks: 'Befejezett feladatok elrejtése',\n      hideFromProjectListAndFavorites: 'Elrejtés a projektlistából és a kedvencekből',\n      host: 'Host',\n      hours: 'Órák',\n      identity: 'Személyazonosság',\n      importBoard_title: 'Tábla importálása',\n      information: 'Információ',\n      invalidCurrentPassword: 'Érvénytelen jelenlegi jelszó',\n      kanban: 'Kanban',\n      labels: 'Címkék',\n      language: 'Nyelv',\n      leaveBoard_title: 'Tábla elhagyása',\n      leaveProject_title: 'Projekt elhagyása',\n      limitCardTypesToDefaultOne: 'Kártyatípusok korlátozása az alapértelmezettre',\n      linkToCard: 'Hivatkozás a kártyára',\n      list: 'Lista',\n      listActions_title: 'Lista műveletek',\n      lists: 'Listák',\n      makeProjectPrivate_title: 'Projekt priváttá tétele',\n      makeProjectShared_title: 'Projekt megosztása',\n      managers: 'Menedzserek',\n      memberActions_title: 'Tag műveletek',\n      members: 'Tagok',\n      minutes: 'Percek',\n      moreActions: 'További műveletek',\n      moreActions_title: 'További műveletek',\n      moveCard_title: 'Kártya áthelyezése',\n      moveList_title: 'Lista áthelyezése',\n      myOwn_title: 'Saját',\n      name: 'Név',\n      newEmail: 'Új e-mail',\n      newPassword: 'Új jelszó',\n      newUsername: 'Új felhasználónév',\n      newVersionAvailable: 'Új verzió érhető el',\n      newestFirst: 'Újabbak előre',\n      noApiKeyCreated: 'Nincs API kulcs létrehozva.',\n      noBoards: 'Nincsenek táblák',\n      noCardsFound: 'Nem található kártya.',\n      noConnectionToServer: 'Nincs kapcsolat a szerverrel',\n      noLists: 'Nincsenek listák',\n      noProjects: 'Nincsenek projektek',\n      noUnreadNotifications: 'Nincsenek olvasatlan értesítések.',\n      notifications: 'Értesítések',\n      oldestFirst: 'Régebbi előre',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'A projekt priváttá tételéhez csak egy menedzser maradhat.',\n      openBoard_title: 'Tábla megnyitása',\n      optional_inline: 'opcionális',\n      organization: 'Szervezet',\n      others: 'Egyebek',\n      passwordIsSet: 'Jelszó beállítva',\n      phone: 'Telefon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'A PLANKA az <1><0>Apprise</0></1> szolgáltatást használja több mint 100 népszerű szolgáltatás értesítéseinek küldésére.',\n      port: 'Port',\n      preferences: 'Beállítások',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Tipp: nyomja meg a Ctrl-V (Cmd-V a Mac-en) billentyűkombinációt a vágólapról történő melléklet hozzáadásához.',\n      private: 'Privát',\n      project: 'Projekt',\n      projectNotFound_title: 'Projekt nem található',\n      projectOwner: 'Projekt tulajdonos',\n      referenceDataAndKnowledgeStorage: 'Referenciaadatok és tudástár.',\n      regenerateApiKey_title: 'API kulcs újragenerálása',\n      rejectUnauthorizedTlsCertificates: 'Jogosulatlan TLS tanúsítványok elutasítása',\n      removeManager_title: 'Menedzser eltávolítása',\n      removeMember_title: 'Tag eltávolítása',\n      role: 'Szerepkör',\n      saveThisKeyItWillNotBeShownAgain: 'Mentse el ezt a kulcsot — nem jelenik meg újra!',\n      searchCards: 'Kártyák keresése..',\n      searchCustomFieldGroups: 'Egyedi mezőcsoportok keresése...',\n      searchCustomFields: 'Egyedi mezők keresése...',\n      searchLabels: 'Címkék keresése...',\n      searchLists: 'Listák keresése...',\n      searchMembers: 'Tagok keresése...',\n      searchProjects: 'Projektek keresése...',\n      searchUsers: 'Felhasználók keresése...',\n      seconds: 'Másodpercek',\n      selectAssignee_title: 'Felelős kiválasztása',\n      selectBoard: 'Válassza ki a táblát',\n      selectList: 'Válassza ki a listát',\n      selectListToRestoreThisCard: 'Válasszon listát a kártya visszaállításához',\n      selectOrder_title: 'Sorrend kiválasztása',\n      selectPermissions_title: 'Jogosultságok kiválasztása',\n      selectProject: 'Válassza ki a projektet',\n      selectRole_title: 'Szerepkör kiválasztása',\n      selectType_title: 'Típus kiválasztása',\n      sequentialDisplayOfCards: 'Kártyák sorban történő megjelenítése.',\n      settings: 'Beállítások',\n      shared: 'Megosztott',\n      sharedWithMe_title: 'Velem megosztva',\n      showOnFrontOfCard: 'Megjelenítés a kártya borítóján',\n      smtp: 'SMTP',\n      sortList_title: 'Rendezés listában',\n      sourceCardIsNoLongerAvailableForCopying: 'A forráskártya már nem érhető el másoláshoz.',\n      sourceCardIsNoLongerAvailableForMoving: 'A forráskártya már nem érhető el áthelyezéshez.',\n      stopwatch: 'Stopper',\n      story: 'Story',\n      subscribeToCardWhenCommenting: 'Feliratkozás a kártyára kommenteléskor',\n      subscribeToMyOwnCardsByDefault: 'Alapértelmezés szerint feliratkozás a saját kártyáimra',\n      taskActions_title: 'Feladat műveletek',\n      taskAssignmentAndProjectCompletion: 'Feladatkiosztás és projektlezárás.',\n      taskListActions_title: 'Feladatlista műveletek',\n      taskList_title: 'Feladatlista',\n      team: 'Csapat',\n      termsOfService_title: 'Szolgáltatási feltételek',\n      testLog_title: 'Teszt napló',\n      thereIsNoPreviewAvailableForThisAttachment: 'Nincs elérhető előnézet ehhez a melléklethez.',\n      time: 'Idő',\n      title: 'Cím',\n      trash: 'Lomtár',\n      trashHasBeenSuccessfullyEmptied: 'A lomtár sikeresen kiürítve.',\n      turnOffRecentCardHighlighting: 'Legutóbb módosítótt kártyák kiemelésének kikapcsolása',\n      typeNameToConfirm: 'Írja be a nevet a megerősítéshez.',\n      typeTitleToConfirm: 'Írja be a címet a megerősítéshez.',\n      unlinkSso_title: 'SSO leválasztása',\n      unsavedChanges: 'Mentetlen változtatások',\n      uploadFailedFileIsTooBig: 'Feltöltési hiba: a fájl túl nagy.',\n      uploadFailedNotEnoughStorageSpace: 'Feltöltési hiba: nincs elég szabad tárhely.',\n      uploadedImages: 'Feltöltött képek',\n      url: 'URL',\n      useSecureConnection: 'Biztonságos kapcsolat használata',\n      userActions_title: 'Felhasználói műveletek',\n      userAddedCardToList:\n        '<0>{{user}}</0> hozzáadta a(z) <2>{{card}}</2> kártyát ehhez a listához: {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> hozzáadta ezt a kártyát a következőhöz: {{list}}',\n      userAddedUserToCard:\n        '<0>{{actorUser}}</0> hozzáadta {{addedUser}} felhasználót a(z) <4>{{card}}</4> kártyához',\n      userAddedUserToThisCard:\n        '<0>{{actorUser}}</0> hozzáadta {{addedUser}} felhasználót ehhez a kártyához',\n      userAddedYouToCard: '<0>{{user}}</0> hozzáadta Önt ehhez a kártyához: <2>{{card}}</2>',\n      userCompletedTaskOnCard:\n        '<0>{{user}}</0> befejezte a(z) {{task}} feladatot a(z) <4>{{card}}</4> kártyán',\n      userCompletedTaskOnThisCard:\n        '<0>{{user}}</0> befejezte a(z) {{task}} feladatot ezen a kártyán',\n      userJoinedCard: '<0>{{user}}</0> csatlakozott a(z) <2>{{card}}</2> kártyához',\n      userJoinedThisCard: '<0>{{user}}</0> csatlakozott ehhez a kártyához',\n      userLeftCard: '<0>{{user}}</0> kilépett a(z) <2>{{card}}</2> kártyáról',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> új kommentet hagyott itt: «{{comment}}» a következő kártyán: <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> kilépett erről a kártyáról',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> befejezetlennek jelölte a(z) {{task}} feladatot a(z) <4>{{card}}</4> kártyán',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> befejezetlennek jelölte a(z) {{task}} feladatot ezen a kártyán',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> megemlítette Önt a(z) «{{comment}}» megjegyzésben a(z) <2>{{card}}</2> kártyán',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> áthelyezte <2>{{card}}</2> kártyát innen: {{fromList}} ide: {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> áthelyezte ezt a kártyát innen: {{fromList}} ide: {{toList}}',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0> eltávolította {{removedUser}} felhasználót a(z) <4>{{card}}</4> kártyáról',\n      userRemovedUserFromThisCard:\n        '<0>{{actorUser}}</0> eltávolította {{removedUser}} felhasználót erről a kártyáról',\n      username: 'Felhasználónév',\n      users: 'Felhasználók',\n      viewer: 'Néző',\n      viewers: 'Megtekintők',\n      visualTaskManagementWithLists: 'Vizuális feladatkezelés listákkal.',\n      webhooks: 'Webhook-ok',\n      whatsNew_title: 'Újdonságok',\n      withoutBaseGroup: 'Alapcsoport nélkül',\n      writeComment: 'Írjon egy megjegyzést...',\n    },\n\n    action: {\n      activateUser: 'Felhasználó aktiválása',\n      activateUser_title: 'Felhasználó aktiválása',\n      addAnotherCard: 'Új kártya hozzáadása',\n      addAnotherList: 'Új lista hozzáadása',\n      addAnotherTask: 'Új feladat hozzáadása',\n      addCard: 'Kártya hozzáadása',\n      addCard_title: 'Kártya hozzáadása',\n      addComment: 'Megjegyzés hozzáadása',\n      addCustomField: 'Egyedi mező hozzáadása',\n      addCustomFieldGroup: 'Egyedi mezőcsoport hozzáadása',\n      addList: 'Lista hozzáadása',\n      addMember: 'Tag hozzáadása',\n      addMoreDetailedDescription: 'További részletes leírás hozzáadása',\n      addTask: 'Feladat hozzáadása',\n      addTaskList: 'Feladatlista hozzáadása',\n      addToCard: 'Kártyához hozzáadása',\n      addUser: 'Felhasználó hozzáadása',\n      addWebhook: 'Webhook hozzáadása',\n      archive: 'Archiválás',\n      archiveCard: 'Archív kártya',\n      archiveCard_title: 'Archív kártya',\n      archiveCards: 'Kártyák archiválása',\n      archiveCards_title: 'Archív kártyák',\n      assignAsOwner: 'Hozzárendelés tulajdonosnak',\n      cancel: 'Mégsem',\n      copy: 'Másolás',\n      copyCard_title: 'Kártya másolása',\n      createApiKey: 'API kulcs létrehozása',\n      createBoard: 'Tábla létrehozása',\n      createCustomFieldGroup: 'Egyedi mezőcsoport létrehozása',\n      createFile: 'Fájl létrehozása',\n      createLabel: 'Címke létrehozása',\n      createNewLabel: 'Új címke létrehozása',\n      createProject: 'Projekt létrehozása',\n      cut: 'Kivágás',\n      cutCard_title: 'Kártya kivágása',\n      deactivateUser: 'Felhasználó inaktiválása',\n      deactivateUser_title: 'Felhasználó inaktiválása',\n      delete: 'Törlés',\n      deleteApiKey: 'API kulcs törlése',\n      deleteAttachment: 'Melléklet törlése',\n      deleteAvatar: 'Avatar törlése',\n      deleteBackgroundImage: 'Háttérkép törlése',\n      deleteBoard: 'Tábla törlése',\n      deleteBoard_title: 'Tábla törlése',\n      deleteCard: 'Kártya törlése',\n      deleteCardForever: 'Kártya végleges törlése',\n      deleteCard_title: 'Kártya törlése',\n      deleteComment: 'Megjegyzés törlése',\n      deleteCustomField: 'Egyedi mező törlése',\n      deleteCustomFieldGroup: 'Egyedi mezőcsoport törlése',\n      deleteForever_title: 'Végleges törlés',\n      deleteGroup: 'Csoport törlése',\n      deleteLabel: 'Címke törlése',\n      deleteList: 'Lista törlése',\n      deleteList_title: 'Lista törlése',\n      deleteNotificationService: 'Értesítési szolgáltatás törlése',\n      deleteProject: 'Projekt törlése',\n      deleteProject_title: 'Projekt törlése',\n      deleteTask: 'Feladat törlése',\n      deleteTaskList: 'Feladatlista törlése',\n      deleteTask_title: 'Feladat törlése',\n      deleteUser: 'Felhasználó törlése',\n      deleteUser_title: 'Felhasználó törlése',\n      deleteWebhook: 'Webhook törlése',\n      dismissAll: 'Összes elvetése',\n      download: 'Letöltés',\n      duplicateCard_title: 'Kártya másolása',\n      edit: 'Szerkesztés',\n      editColor_title: 'Szín szerkesztése',\n      editDescription_title: 'Leírás szerkesztése',\n      editDueDate_title: 'Esedékesség dátumának szerkesztése',\n      editEmail_title: 'E-mail szerkesztése',\n      editGroup: 'Csoport szerkesztése',\n      editInformation_title: 'Információ szerkesztése',\n      editPassword_title: 'Jelszó módosítása',\n      editPermissions: 'Jogosultságok szerkesztése',\n      editRole_title: 'Jogkör szerkesztése',\n      editStopwatch_title: 'Stopper szerkesztése',\n      editTitle_title: 'Cím szerkesztése',\n      editType_title: 'Típus módosítása',\n      editUsername_title: 'Felhasználónév szerkesztése',\n      emptyTrash: 'Lomtár ürítése',\n      emptyTrash_title: 'Lomtár ürítése',\n      import: 'Importálás',\n      join: 'Csatlakozás',\n      leave: 'Kilépés',\n      leaveBoard: 'Tábla elhagyása',\n      leaveProject: 'Projekt elhagyása',\n      logOut_title: 'Kijelentkezés',\n      makeCover_title: 'Borító készítése',\n      makeProjectPrivate: 'Projekt priváttá tétele',\n      makeProjectPrivate_title: 'Projekt priváttá tétele',\n      makeProjectShared: 'Projekt megosztása',\n      makeProjectShared_title: 'Projekt megosztása',\n      move: 'Áthelyezés',\n      moveCard_title: 'Kártya áthelyezése',\n      moveList_title: 'Lista áthelyezése',\n      regenerateApiKey: 'API kulcs újragenerálása',\n      remove: 'Eltávolítás',\n      removeAssignee: 'Felelős eltávolítása',\n      removeColor: 'Szín eltávolítása',\n      removeCover_title: 'Borító eltávolítása',\n      removeFromBoard: 'Eltávolítás a tábláról',\n      removeFromProject: 'Eltávolítás a projektről',\n      removeManager: 'Menedzser eltávolítása',\n      removeMember: 'Tag eltávolítása',\n      restoreToList: 'Visszaállítás ide: {{list}}',\n      returnToBoard: 'Vissza a táblához',\n      save: 'Mentés',\n      sendTestEmail: 'Teszt e-mail küldése',\n      showActive: 'Aktívak megjelenítése',\n      showAllAttachments: 'Összes melléklet megjelenítése ({{hidden}} rejtve)',\n      showCardsWithThisUser: 'Kártyák megjelenítése ezzel a felhasználóval',\n      showDeactivated: 'Inaktívak megjelenítése',\n      showFewerAttachments: 'Kevesebb melléklet megjelenítése',\n      showLess: 'Kevesebb megjelenítése',\n      showMore: 'Továbbiak megjelenítése',\n      sortList_title: 'Rendezés listában',\n      start: 'Indítás',\n      stop: 'Megállítás',\n      subscribe: 'Feliratkozás',\n      unlinkSso: 'SSO leválasztása',\n      unlinkSso_title: 'SSO leválasztása',\n      unsubscribe: 'Leiratkozás',\n      uploadNewAvatar: 'Új avatar feltöltése',\n      uploadNewImage: 'Új kép feltöltése',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/hu-HU/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'hu-HU',\n  country: 'hu',\n  name: 'Magyar',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/hu-HU/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Aktív felhasználók korlátja elérve',\n      adminLoginRequiredToInitializeInstance:\n        'Rendszergazdai bejelentkezés szükséges a példány inicializálásához',\n      emailAlreadyInUse: 'Az e-mail cím már használatban van',\n      emailOrUsername: 'E-mail vagy felhasználó',\n      invalidCredentials: 'Érvénytelen hitelesítő adatok',\n      invalidEmailOrUsername: 'Érvénytelen e-mail vagy felhasználó',\n      invalidPassword: 'Érvénytelen jelszó',\n      logIn_title: 'Bejelentkezés',\n      noInternetConnection: 'Nincs internet kapcsolat',\n      or: 'Vagy',\n      pageNotFound_title: 'Az oldal nem található',\n      password: 'Jelszó',\n      poweredByPlanka: 'Működteti a <1>PLANKA</1>',\n      serverConnectionFailed: 'A szerverkapcsolat sikertelen',\n      unknownError: 'Ismeretlen hiba, próbáld meg később újra',\n      useSingleSignOn: 'Egyszeri bejelentkezés használata',\n      usernameAlreadyInUse: 'A felhasználónév már használatban van',\n      whoops_title: 'Hoppá!',\n    },\n\n    action: {\n      cancelAndClose: 'Mégse és bezárás',\n      continue: 'Folytatás',\n      debugSso: 'SSO hibakeresése',\n      goBack: 'Vissza',\n      goHome: 'Kezdőlapra',\n      logIn: 'Belépés',\n      logInWithSso: 'Belépés SSO-val',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/hu-HU/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Ez egy cím nélküli szöveg.\\nA cím és a szöveg\\nkijelölhető félkövérrel, dőlt betűvel, színnel,\\náthúzással és aláhúzással.\",\n    \"text-with-head\": \"Ez egy címmel ellátott szöveg.\\nA cím és a szöveg\\nkijelölhető félkövérrel, dőlt betűvel, színnel,\\náthúzással és aláhúzással.\",\n    \"heading\": \"Cím\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Hiba a markdown szerkesztőben\",\n    \"settings_wysiwyg\": \"Vizuális szerkesztő (wysiwyg)\",\n    \"settings_markup\": \"Markdown jelölés\",\n    \"markup_placeholder\": \"Írja be a markdown jelölést...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Eltávolítás\",\n    \"empty_option\": \"Nincs találat\",\n    \"show_line_numbers\": \"Sorszámozás\"\n  },\n  \"common\": {\n    \"delete\": \"Törlés\",\n    \"edit\": \"Szerkesztés\",\n    \"toolbar_action_disabled\": \"Nem kompatibilis jelölőelem\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Mégse\",\n    \"common_action_submit\": \"Küldés\",\n    \"common_action_upload\": \"Kiválasztás\",\n    \"common_tab_attach\": \"Hozzáadás eszközről\",\n    \"common_tab_link\": \"Hozzáadás linken keresztül\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Méret, px\",\n    \"image_name\": \"Cím\",\n    \"image_link_href\": \"Kép linkje\",\n    \"image_link_href_help\": \"A kép linkjének címe.\",\n    \"image_alt\": \"Alternatív szöveg\",\n    \"image_alt_help\": \"Az alternatív szöveg jelenik meg, ha a kép nem tölthető be.\",\n    \"image_upload_help\": \"JPEG, GIF vagy PNG kép, legfeljebb 1 MB méretben.\",\n    \"image_upload_failed\": \"A kép hozzáadása sikertelen\",\n    \"image_size_width\": \"Szélesség\",\n    \"image_size_height\": \"Magasság\",\n    \"link_url_help\": \"A link címe.\",\n    \"link_text\": \"Link szövege\",\n    \"link_text_help\": \"A linkként megjelenő szöveg.\",\n    \"link_open_help\": \"Link megnyitása új lapon\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Fejléc\",\n    \"header_hint\": \"# A szöveged\",\n    \"italic_title\": \"Dőlt\",\n    \"italic_hint\": \"_A szöveged_\",\n    \"bold_title\": \"Félkövér\",\n    \"bold_hint\": \"**A szöveged**\",\n    \"strikethrough_title\": \"Áthúzott\",\n    \"strikethrough_hint\": \"~~A szöveged~~\",\n    \"blockquote_title\": \"Idézet\",\n    \"blockquote_hint\": \"> A szöveged\",\n    \"code_title\": \"Kód\",\n    \"code_hint\": \"```A szöveged```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[A szöveged](url)\",\n    \"image_title\": \"Kép\",\n    \"image_hint\": \"![A szöveged](url)\",\n    \"list_title\": \"Listaelem\",\n    \"list_hint\": \"- A szöveged\",\n    \"numbered-list_title\": \"Számozott lista\",\n    \"numbered-list_hint\": \"1. A szöveged\",\n    \"documentation\": \"Dokumentáció\",\n    \"documentation_link\": \"https://diplodoc.com/docs/hu/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Félkövér\",\n    \"code\": \"Kód\",\n    \"code_inline\": \"Sorközi kód\",\n    \"codeblock\": \"Kódrészlet\",\n    \"colorify\": \"Szöveg színe\",\n    \"colorify__color_blue\": \"Kék\",\n    \"colorify__color_default\": \"Alapértelmezett\",\n    \"colorify__color_gray\": \"Szürke\",\n    \"colorify__color_green\": \"Zöld\",\n    \"colorify__color_orange\": \"Narancs\",\n    \"colorify__color_red\": \"Piros\",\n    \"colorify__color_violet\": \"Lila\",\n    \"colorify__color_yellow\": \"Sárga\",\n    \"colorify__group_text\": \"Szöveg\",\n    \"cut\": \"Kivágás\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emojik hozzáadhatók WYSIWYG-ben vagy manuálisan jelöléssel\",\n    \"heading\": \"Fejléc\",\n    \"heading1\": \"Fejléc 1\",\n    \"heading2\": \"Fejléc 2\",\n    \"heading3\": \"Fejléc 3\",\n    \"heading4\": \"Fejléc 4\",\n    \"heading5\": \"Fejléc 5\",\n    \"heading6\": \"Fejléc 6\",\n    \"hrule\": \"Elválasztó\",\n    \"image\": \"Kép\",\n    \"italic\": \"Dőlt\",\n    \"link\": \"Link\",\n    \"list\": \"Lista\",\n    \"list__action_lift\": \"Elem feljebb\",\n    \"list__action_sink\": \"Elem lejjebb\",\n    \"list_action_disabled\": \"Ellentmond a lista logikájának\",\n    \"mark\": \"Jelölt\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"További műveletek\",\n    \"note\": \"Jegyzet\",\n    \"olist\": \"Számozott lista\",\n    \"quote\": \"Idézet\",\n    \"redo\": \"Újra\",\n    \"strike\": \"Áthúzott\",\n    \"table\": \"Táblázat\",\n    \"text\": \"Szöveg\",\n    \"ulist\": \"Felsorolás\",\n    \"underline\": \"Aláhúzott\",\n    \"undo\": \"Visszavonás\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Írja be a / jelet parancsokhoz...\",\n    \"checkbox\": \"Írja be a feladat leírását...\",\n    \"deflist_term\": \"Kifejezés\",\n    \"deflist_desc\": \"Definíció leírása\",\n    \"heading\": \"Fejléc\",\n    \"cut_title\": \"Cím\",\n    \"cut_content\": \"Tartalom, amely kattintásra jelenik meg\",\n    \"note_title\": \"Cím\",\n    \"note_content\": \"Jegyzet tartalma\",\n    \"table_cell\": \"Cella tartalma\",\n    \"select_filter\": \"Nyelvek keresése...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Kis- és nagybetű érzékeny\",\n    \"label_whole-word\": \"Teljes szó\",\n    \"title\": \"Keresés a kódban\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Nincs találat\"\n  },\n  \"widgets\": {\n    \"image\": \"Kép hozzáadása\",\n    \"link\": \"Link hozzáadása\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Jegyzet\",\n    \"tip\": \"Tipp\",\n    \"warning\": \"Figyelmeztetés\",\n    \"alert\": \"Riasztás\",\n    \"remove\": \"Eltávolítás\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Oszlop hozzáadása elé\",\n    \"column.add.after\": \"Oszlop hozzáadása mögé\",\n    \"column.remove\": \"Oszlop eltávolítása\",\n    \"column.remove.multiple\": \"Oszlopok eltávolítása\",\n    \"row.add.before\": \"Sor hozzáadása elé\",\n    \"row.add.after\": \"Sor hozzáadása mögé\",\n    \"row.remove\": \"Sor eltávolítása\",\n    \"row.remove.multiple\": \"Sorok eltávolítása\",\n    \"cells.clear\": \"Cellák törlése\",\n    \"table.remove\": \"Táblázat eltávolítása\",\n    \"table.menu.cell.align.left\": \"Cella tartalmának balra igazítása\",\n    \"table.menu.cell.align.right\": \"Cella tartalmának jobbra igazítása\",\n    \"table.menu.cell.align.center\": \"Cella tartalmának középre igazítása\",\n    \"table.menu.row.add\": \"Sor hozzáadása mögé\",\n    \"table.menu.row.remove\": \"Sor eltávolítása\",\n    \"table.menu.column.add\": \"Oszlop hozzáadása mögé\",\n    \"table.menu.column.remove\": \"Oszlop eltávolítása\",\n    \"table.menu.convert.yfm\": \"Átalakítás YFM táblázattá\",\n    \"table.menu.table.remove\": \"Táblázat eltávolítása\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/id-ID/core.js",
    "content": "import dateFns from 'date-fns/locale/id';\nimport timeAgo from 'javascript-time-ago/locale/id';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'dd MMM yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'pada' p\",\n    fullDate: 'd MMM, y',\n    fullDateTime: \"d MMM, y 'pada' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Tentang aplikasi',\n      aboutPlanka_title: 'Tentang PLANKA',\n      accessToken: 'Token akses',\n      account: 'Akun',\n      actions: 'Tindakan',\n      activateUser_title: 'Aktifkan pengguna',\n      active: 'Aktif',\n      addAttachment_title: 'Tambah lampiran',\n      addCustomFieldGroup_title: 'Tambah grup bidang kustom',\n      addCustomField_title: 'Tambah bidang kustom',\n      addManager_title: 'Tambahkan manager',\n      addMember_title: 'Tambahkan anggota',\n      addTaskList_title: 'Tambah daftar tugas',\n      addUser_title: 'Tambahkan pengguna',\n      admin: 'Admin',\n      administration: 'Administrasi',\n      all: 'Semua',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Semua perubahan akan disimpan<br />setelah koneksi pulih.',\n      alphabetically: 'Berdasarkan abjad',\n      alwaysDisplayCardCreator: 'Selalu tampilkan pembuat kartu',\n      apiKeyCreated_title: 'Kunci API Dibuat',\n      apiKey_title: 'Kunci API',\n      archive: 'Arsip',\n      archiveCard_title: 'Arsipkan kartu',\n      archiveCards_title: 'Arsipkan kartu',\n      areYouSureYouWantToActivateThisUser: 'Apakah Anda yakin ingin mengaktifkan pengguna ini?',\n      areYouSureYouWantToArchiveCards: 'Apakah Anda yakin ingin mengarsipkan kartu-kartu ini?',\n      areYouSureYouWantToArchiveThisCard: 'Apakah Anda yakin ingin mengarsipkan kartu ini?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Apakah Anda yakin ingin menetapkan manajer proyek ini sebagai pemilik?',\n      areYouSureYouWantToDeactivateThisUser: 'Apakah Anda yakin ingin menonaktifkan pengguna ini?',\n      areYouSureYouWantToDeleteThisApiKey: 'Apakah Anda yakin ingin menghapus kunci API ini?',\n      areYouSureYouWantToDeleteThisAttachment: 'Apakah anda ingin menghapus lampiran ini?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Apakah Anda yakin ingin menghapus gambar latar belakang ini?',\n      areYouSureYouWantToDeleteThisBoard: 'Apakah anda ingin menghapus papan ini?',\n      areYouSureYouWantToDeleteThisCard: 'Apakah anda ingin menghapus kartu ini?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Apakah Anda yakin ingin menghapus kartu ini selamanya?',\n      areYouSureYouWantToDeleteThisComment: 'Apakah anda ingin menghapus komentar ini?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Apakah Anda yakin ingin menghapus bidang kustom ini?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Apakah Anda yakin ingin menghapus grup bidang kustom ini?',\n      areYouSureYouWantToDeleteThisLabel: 'Apakah anda ingin menghapus label ini?',\n      areYouSureYouWantToDeleteThisList:\n        'Apakah anda ingin menghapus daftar ini? Semua kartu akan dipindahkan ke sampah.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Apakah Anda yakin ingin menghapus layanan notifikasi ini?',\n      areYouSureYouWantToDeleteThisProject: 'Apakah anda ingin menghapus proyek ini?',\n      areYouSureYouWantToDeleteThisTask: 'Apakah anda ingin menghapus tugas ini?',\n      areYouSureYouWantToDeleteThisTaskList: 'Apakah Anda yakin ingin menghapus daftar tugas ini?',\n      areYouSureYouWantToDeleteThisUser: 'Apakah anda ingin menghapus pengguna ini?',\n      areYouSureYouWantToDeleteThisWebhook: 'Apakah Anda yakin ingin menghapus webhook ini?',\n      areYouSureYouWantToEmptyTrash: 'Apakah Anda yakin ingin mengosongkan sampah?',\n      areYouSureYouWantToLeaveBoard: 'Apakah anda ingin keluar dari papan ini?',\n      areYouSureYouWantToLeaveProject: 'Apakah anda ingin keluar dari proyek ini?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Apakah Anda yakin ingin menjadikan proyek ini pribadi?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Apakah Anda yakin ingin menjadikan proyek ini bersama?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Apakah Anda yakin ingin membuat ulang kunci API ini? Kunci sebelumnya tidak akan berfungsi lagi.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Apakah anda ingin menghapus manajer ini dari papan ini?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Apakah anda ingin menghapus anggota ini dari papan ini?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Apakah Anda yakin ingin memutuskan tautan SSO dari pengguna ini? Ini akan memungkinkan pengguna untuk masuk dengan kata sandi.',\n      assignAsOwner_title: 'Tetapkan sebagai pemilik',\n      atLeastOneListMustBePresent: 'Setidaknya satu daftar harus ada',\n      attachment: 'Lampiran',\n      attachments: 'Lampiran-lampiran',\n      authentication: 'Autentikasi',\n      background: 'Latar belakang',\n      baseCustomFields_title: 'Bidang kustom dasar',\n      baseGroup: 'Grup dasar',\n      board: 'Papan',\n      boardActions_title: 'Aksi papan',\n      boardNotFound_title: 'Papan tidak ditemukan',\n      boardSubscribed: 'Papan berlangganan',\n      boardUser: 'Pengguna papan',\n      byCreationTime: 'Berdasarkan waktu pembuatan',\n      byDefault: 'Secara default',\n      byDueDate: 'Berdasarkan tanggal jatuh tempo',\n      canBeInvitedToWorkInBoards: 'Dapat diundang untuk bekerja di papan.',\n      canComment: 'Bisa berkomentar',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Dapat membuat proyek sendiri dan diundang untuk bekerja di proyek lain.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Dapat mengedit tata letak papan dan menetapkan anggota ke kartu.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Dapat mengelola pengaturan sistem dan bertindak sebagai pemilik proyek.',\n      canOnlyViewBoard: 'Hanya dapat menglihat isi papan.',\n      cardActions_title: 'Aksi kartu',\n      cardNotFound_title: 'Kartu tidak ditemukan',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Kartu di daftar ini tersedia untuk semua anggota papan.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Kartu di daftar ini sudah selesai dan siap diarsipkan.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Kartu di daftar ini siap untuk dikerjakan.',\n      clickHereOrRefreshPageToUpdate:\n        '<0>Klik di sini</0> atau segarkan halaman untuk memperbarui.',\n      clientHostnameInEhlo: 'Nama host klien di EHLO',\n      closed: 'Ditutup',\n      color: 'Warna',\n      comments: 'Komentar',\n      contentExceedsLimit: 'Konten melebihi {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Konten lampiran ini terlalu besar untuk ditampilkan.',\n      copy_inline: 'salinan',\n      createBoard_title: 'Buat papan',\n      createCustomFieldGroup_title: 'Buat grup bidang kustom',\n      createLabel_title: 'Buat label',\n      createNewOneOrSelectExistingOne: 'Buat yang baru atau pilih<br />yang sudah ada.',\n      createProject_title: 'Buat proyek',\n      createTextFile_title: 'Buat berkas teks',\n      creator: 'Pembuat',\n      currentPassword: 'Kata sandi sekarang',\n      currentUser: 'Pengguna saat ini',\n      customFieldGroup_title: 'Grup bidang kustom',\n      customFieldGroups_title: 'Grup bidang kustom',\n      customField_title: 'Bidang kustom',\n      customFields_title: 'Bidang kustom',\n      customerPanel_title: 'Panel pelanggan',\n      dangerZone_title: 'Zona berbahaya',\n      date: 'Tanggal',\n      deactivateUser_title: 'Nonaktifkan pengguna',\n      defaultCardType_title: 'Tipe kartu default',\n      defaultFrom: 'Default dari',\n      defaultView_title: 'Tampilan default',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Hapus semua papan untuk dapat menghapus proyek ini',\n      deleteApiKey_title: 'Hapus Kunci API',\n      deleteAttachment_title: 'Hapus lampiran',\n      deleteBackgroundImage_title: 'Hapus gambar latar belakang',\n      deleteBoard_title: 'Hapus papan',\n      deleteCardForever_title: 'Hapus kartu selamanya',\n      deleteCard_title: 'Hapus kartu',\n      deleteComment_title: 'Hapus komentar',\n      deleteCustomFieldGroup_title: 'Hapus grup bidang kustom',\n      deleteCustomField_title: 'Hapus bidang kustom',\n      deleteLabel_title: 'Hapus label',\n      deleteList_title: 'Hapus daftar',\n      deleteNotificationService_title: 'Hapus layanan notifikasi',\n      deleteProject_title: 'Hapus proyek',\n      deleteTaskList_title: 'Hapus daftar tugas',\n      deleteTask_title: 'Hapus tugas',\n      deleteUser_title: 'Hapus pengguna',\n      deleteWebhook_title: 'Hapus webhook',\n      deletedUser_title: 'Pengguna yang dihapus',\n      description: 'Deskripsi',\n      display: 'Tampilan',\n      displayCardAges: 'Tampilkan usia kartu',\n      dropFileToUpload: 'Tarik berkas untuk menggungah',\n      dueDate_title: 'Tenggat waktu',\n      dynamicAndUnevenlySpacedLayout: 'Tata letak dinamis dan tidak merata.',\n      editAttachment_title: 'Ubah lampiran',\n      editAvatar_title: 'Ubah avatar',\n      editColor_title: 'Ubah warna',\n      editCustomFieldGroup_title: 'Ubah grup bidang kustom',\n      editCustomField_title: 'Ubah bidang kustom',\n      editDueDate_title: 'Ubah tenggat waktu',\n      editEmail_title: 'Ubah e-mail',\n      editInformation_title: 'Ubah informasi',\n      editLabel_title: 'Ubah label',\n      editPassword_title: 'Ubah kata sandi',\n      editPermissions_title: 'Ubah izin',\n      editRole_title: 'Ubah peran',\n      editStopwatch_title: 'Ubah stopwatch',\n      editType_title: 'Ubah tipe',\n      editUsername_title: 'Ubah username',\n      editor: 'Pengubah',\n      editors: 'Editor',\n      email: 'E-mail',\n      emptyTrash_title: 'Kosongkan sampah',\n      enterCardTitle: 'Masukkan judul kartu...',\n      enterDescription: 'Masukkan deskripsi...',\n      enterFilename: 'Masukkan nama berkas...',\n      enterListTitle: 'Masukkan judul daftar...',\n      enterTaskDescription: 'Masukkan deskripsi tugas...',\n      events: 'Peristiwa',\n      excludedEvents: 'Peristiwa yang dikecualikan',\n      expandTaskListsByDefault: 'Perluas daftar tugas secara default',\n      filterByLabels_title: 'Saring berdasarkan label',\n      filterByMembers_title: 'Saring berdasarkan anggota',\n      forPersonalProjects: 'Untuk proyek pribadi.',\n      forTeamBasedProjects: 'Untuk proyek berbasis tim.',\n      fromComputer_title: 'Dari komputer',\n      fromTrello: 'Dari Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Kunci lengkap disembunyikan karena alasan keamanan. Buat ulang untuk membuat yang baru.',\n      general: 'Umum',\n      gradients: 'Gradien',\n      grid: 'Kisi',\n      hideCompletedTasks: 'Sembunyikan tugas yang selesai',\n      hideFromProjectListAndFavorites: 'Sembunyikan dari daftar proyek dan favorit',\n      host: 'Host',\n      hours: 'Jam',\n      identity: 'Identitas',\n      importBoard_title: 'Impor papan',\n      information: 'Informasi',\n      invalidCurrentPassword: 'Kata sandi saat ini tidak valid',\n      kanban: 'Kanban',\n      labels: 'Label',\n      language: 'Bahasa',\n      leaveBoard_title: 'Keluar dari papan',\n      leaveProject_title: 'Keluar dari proyek',\n      limitCardTypesToDefaultOne: 'Batasi tipe kartu ke default',\n      linkToCard: 'Tautan ke kartu',\n      list: 'Daftar',\n      listActions_title: 'Aksi daftar',\n      lists: 'Daftar',\n      makeProjectPrivate_title: 'Jadikan proyek pribadi',\n      makeProjectShared_title: 'Jadikan proyek bersama',\n      managers: 'Manager',\n      memberActions_title: 'Aksi anggota',\n      members: 'Anggota',\n      minutes: 'Menit',\n      moreActions: 'Aksi lainnya',\n      moreActions_title: 'Aksi lainnya',\n      moveCard_title: 'Pindahkan kartu',\n      moveList_title: 'Pindahkan daftar',\n      myOwn_title: 'Milik saya',\n      name: 'Nama',\n      newEmail: 'E-mail baru',\n      newPassword: 'Kata sandi baru',\n      newUsername: 'Username baru',\n      newVersionAvailable: 'Versi baru tersedia',\n      newestFirst: 'Terbaru dulu',\n      noApiKeyCreated: 'Belum ada kunci API yang dibuat.',\n      noBoards: 'Tidak ada papan',\n      noCardsFound: 'Tidak ada kartu ditemukan.',\n      noConnectionToServer: 'Tidak ada koneksi ke server',\n      noLists: 'Tidak ada daftar',\n      noProjects: 'Tidak ada projek',\n      noUnreadNotifications: 'Tiada notifikasi yang belum dibaca.',\n      notifications: 'Notifikasi',\n      oldestFirst: 'Terlama dulu',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Hanya satu manajer yang harus tersisa untuk menjadikan proyek ini pribadi',\n      openBoard_title: 'Buka papan',\n      optional_inline: 'opsional',\n      organization: 'Organisasi',\n      others: 'Lainnya',\n      passwordIsSet: 'Kata sandi telah diatur',\n      phone: 'Ponsel',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA menggunakan <1><0>Apprise</0></1> untuk mengirim notifikasi ke lebih dari 100 layanan populer.',\n      port: 'Port',\n      preferences: 'Preferensi',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Tip: tekan Ctrl-V (Cmd-V di Mac) untuk menambahkan lampiran dari papan klip.',\n      private: 'Pribadi',\n      project: 'Proyek',\n      projectNotFound_title: 'Proyek tidak ditemukan',\n      projectOwner: 'Pemilik proyek',\n      referenceDataAndKnowledgeStorage: 'Penyimpanan data referensi dan pengetahuan.',\n      regenerateApiKey_title: 'Buat Ulang Kunci API',\n      rejectUnauthorizedTlsCertificates: 'Tolak sertifikat TLS yang tidak sah',\n      removeManager_title: 'Hapus manager',\n      removeMember_title: 'Hapus anggota',\n      role: 'Peran',\n      saveThisKeyItWillNotBeShownAgain: 'Simpan kunci ini — tidak akan ditampilkan lagi!',\n      searchCards: 'Cari kartu...',\n      searchCustomFieldGroups: 'Cari grup bidang kustom...',\n      searchCustomFields: 'Cari bidang kustom...',\n      searchLabels: 'Cari label...',\n      searchLists: 'Cari daftar...',\n      searchMembers: 'Cari anggota...',\n      searchProjects: 'Cari proyek...',\n      searchUsers: 'Cari pengguna...',\n      seconds: 'Detik',\n      selectAssignee_title: 'Pilih penerima tugas',\n      selectBoard: 'Pilih papan',\n      selectList: 'Pilih daftar',\n      selectListToRestoreThisCard: 'Pilih daftar untuk memulihkan kartu ini',\n      selectOrder_title: 'Pilih urutan',\n      selectPermissions_title: 'Pilih izin',\n      selectProject: 'Pilih proyek',\n      selectRole_title: 'Pilih peran',\n      selectType_title: 'Pilih tipe',\n      sequentialDisplayOfCards: 'Tampilan kartu berurutan.',\n      settings: 'Setelan',\n      shared: 'Bersama',\n      sharedWithMe_title: 'Dibagikan dengan saya',\n      showOnFrontOfCard: 'Tampilkan di depan kartu',\n      smtp: 'SMTP',\n      sortList_title: 'Urutkan daftar',\n      sourceCardIsNoLongerAvailableForCopying: 'Kartu sumber tidak lagi tersedia untuk disalin.',\n      sourceCardIsNoLongerAvailableForMoving: 'Kartu sumber tidak lagi tersedia untuk dipindahkan.',\n      stopwatch: 'Stopwatch',\n      story: 'Cerita',\n      subscribeToCardWhenCommenting: 'Berlangganan kartu saat berkomentar',\n      subscribeToMyOwnCardsByDefault: 'Berlangganan kartu saya sendiri secara default',\n      taskActions_title: 'Aksi tugas',\n      taskAssignmentAndProjectCompletion: 'Penugasan tugas dan penyelesaian proyek.',\n      taskListActions_title: 'Aksi daftar tugas',\n      taskList_title: 'Daftar tugas',\n      team: 'Tim',\n      termsOfService_title: 'Ketentuan layanan',\n      testLog_title: 'Log uji',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'Tidak ada pratinjau yang tersedia untuk lampiran ini.',\n      time: 'Waktu',\n      title: 'Judul',\n      trash: 'Sampah',\n      trashHasBeenSuccessfullyEmptied: 'Sampah telah berhasil dikosongkan.',\n      turnOffRecentCardHighlighting: 'Matikan penyorotan kartu terbaru',\n      typeNameToConfirm: 'Ketik nama untuk mengonfirmasi.',\n      typeTitleToConfirm: 'Ketik judul untuk mengonfirmasi.',\n      unlinkSso_title: 'Pemutusan tautan SSO',\n      unsavedChanges: 'Perubahan yang belum disimpan',\n      uploadFailedFileIsTooBig: 'Unggahan gagal - file terlalu besar.',\n      uploadFailedNotEnoughStorageSpace: 'Unggahan gagal - ruang penyimpanan tidak cukup.',\n      uploadedImages: 'Gambar yang diunggah',\n      url: 'URL',\n      useSecureConnection: 'Gunakan koneksi aman',\n      userActions_title: 'Aksi pengguna',\n      userAddedCardToList: '<0>{{user}}</0> menambahkan <2>{{card}}</2> ke {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> menambahkan kartu ini ke {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> menambahkan {{addedUser}} ke <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> menambahkan {{addedUser}} ke kartu ini',\n      userAddedYouToCard: '<0>{{user}}</0> menambahkan Anda ke <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> menyelesaikan {{task}} di <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> menyelesaikan {{task}} di kartu ini',\n      userJoinedCard: '<0>{{user}}</0> bergabung dengan <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> bergabung dengan kartu ini',\n      userLeftCard: '<0>{{user}}</0> meninggalkan <2>{{card}}</2>',\n      userLeftNewCommentToCard: '<0>{{user}}</0> mengomentari «{{comment}}» di <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> meninggalkan kartu ini',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> menandai {{task}} sebagai belum selesai di <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> menandai {{task}} sebagai belum selesai di kartu ini',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> menyebut Anda dalam komentar «{{comment}}» di <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> memindahkan <2>{{card}}</2> dari {{fromList}} ke {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> memindahkan kartu ini dari {{fromList}} ke {{toList}}',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0> menghapus {{removedUser}} dari <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> menghapus {{removedUser}} dari kartu ini',\n      username: 'Username',\n      users: 'Pengguna',\n      viewer: 'Penglihat',\n      viewers: 'Penglihat',\n      visualTaskManagementWithLists: 'Manajemen tugas visual dengan daftar.',\n      webhooks: 'Webhook',\n      whatsNew_title: 'Apa yang baru',\n      withoutBaseGroup: 'Tanpa grup dasar',\n      writeComment: 'Tuliskan komentar...',\n    },\n\n    action: {\n      activateUser: 'Aktifkan pengguna',\n      activateUser_title: 'Aktifkan pengguna',\n      addAnotherCard: 'Tambahkan kartu lain',\n      addAnotherList: 'Tambahkan daftar lain',\n      addAnotherTask: 'Tambahkan aksi lain',\n      addCard: 'Tambah kartu',\n      addCard_title: 'Tambah kartu',\n      addComment: 'Tambah komentar',\n      addCustomField: 'Tambah bidang kustom',\n      addCustomFieldGroup: 'Tambah grup bidang kustom',\n      addList: 'Tambah daftar',\n      addMember: 'Tambah anggota',\n      addMoreDetailedDescription: 'Tambahkan deskripsi yang lebih detail',\n      addTask: 'Tambah tugas',\n      addTaskList: 'Tambah daftar tugas',\n      addToCard: 'Tambahkan ke kartu',\n      addUser: 'Tambah pengguna',\n      addWebhook: 'Tambah webhook',\n      archive: 'Arsip',\n      archiveCard: 'Arsipkan kartu',\n      archiveCard_title: 'Arsipkan kartu',\n      archiveCards: 'Arsipkan kartu',\n      archiveCards_title: 'Arsipkan kartu',\n      assignAsOwner: 'Tetapkan sebagai pemilik',\n      cancel: 'Batal',\n      copy: 'Salin',\n      copyCard_title: 'Salin Kartu',\n      createApiKey: 'Buat kunci API',\n      createBoard: 'Tambah papan',\n      createCustomFieldGroup: 'Buat grup bidang kustom',\n      createFile: 'Tambah berkas',\n      createLabel: 'Tambah label',\n      createNewLabel: 'Tambah label baru',\n      createProject: 'Tambah proyek',\n      cut: 'Potong',\n      cutCard_title: 'Potong Kartu',\n      deactivateUser: 'Nonaktifkan pengguna',\n      deactivateUser_title: 'Nonaktifkan pengguna',\n      delete: 'Hapus',\n      deleteApiKey: 'Hapus kunci API',\n      deleteAttachment: 'Hapus lampiran',\n      deleteAvatar: 'Hapus avatar',\n      deleteBackgroundImage: 'Hapus gambar latar belakang',\n      deleteBoard: 'Hapus papan',\n      deleteBoard_title: 'Hapus papan',\n      deleteCard: 'Hapus kartu',\n      deleteCardForever: 'Hapus kartu selamanya',\n      deleteCard_title: 'Hapus kartu',\n      deleteComment: 'Hapus komentar',\n      deleteCustomField: 'Hapus bidang kustom',\n      deleteCustomFieldGroup: 'Hapus grup bidang kustom',\n      deleteForever_title: 'Hapus selamanya',\n      deleteGroup: 'Hapus grup',\n      deleteLabel: 'Hapus labek',\n      deleteList: 'Hapus daftar',\n      deleteList_title: 'Hapus daftar',\n      deleteNotificationService: 'Hapus layanan notifikasi',\n      deleteProject: 'Hapus proyek',\n      deleteProject_title: 'Hapus proyek',\n      deleteTask: 'Hapus tugas',\n      deleteTaskList: 'Hapus daftar tugas',\n      deleteTask_title: 'Hapus tugas',\n      deleteUser: 'Hapus pengguna',\n      deleteUser_title: 'Hapus pengguna',\n      deleteWebhook: 'Hapus webhook',\n      dismissAll: 'Tutup semua',\n      download: 'Unduh',\n      duplicateCard_title: 'Duplikat kartu',\n      edit: 'Ubah',\n      editColor_title: 'Ubah warna',\n      editDescription_title: 'Ubah deskripsi',\n      editDueDate_title: 'Ubah tenggat waktu',\n      editEmail_title: 'Ubah e-mail',\n      editGroup: 'Ubah grup',\n      editInformation_title: 'Ubah informasi',\n      editPassword_title: 'Ubah kata sandi',\n      editPermissions: 'Ubah izin',\n      editRole_title: 'Ubah peran',\n      editStopwatch_title: 'Ubah stopwatch',\n      editTitle_title: 'Ubah judul',\n      editType_title: 'Ubah tipe',\n      editUsername_title: 'Ubah username',\n      emptyTrash: 'Kosongkan sampah',\n      emptyTrash_title: 'Kosongkan sampah',\n      import: 'Impor',\n      join: 'Bergabung',\n      leave: 'Keluar',\n      leaveBoard: 'Keluar dari papan',\n      leaveProject: 'Keluar dari proyek',\n      logOut_title: 'Keluar',\n      makeCover_title: 'Buat cover',\n      makeProjectPrivate: 'Jadikan proyek pribadi',\n      makeProjectPrivate_title: 'Jadikan proyek pribadi',\n      makeProjectShared: 'Jadikan proyek bersama',\n      makeProjectShared_title: 'Jadikan proyek bersama',\n      move: 'Pindah',\n      moveCard_title: 'Pindahkan kartu',\n      moveList_title: 'Pindahkan daftar',\n      regenerateApiKey: 'Buat ulang kunci API',\n      remove: 'Hapus',\n      removeAssignee: 'Hapus penerima tugas',\n      removeColor: 'Hapus warna',\n      removeCover_title: 'Hapus cover',\n      removeFromBoard: 'Hapus dari papan',\n      removeFromProject: 'Hapus dari proyek',\n      removeManager: 'Hapus manager',\n      removeMember: 'Hapus papan',\n      restoreToList: 'Pulihkan ke {{list}}',\n      returnToBoard: 'Kembali ke papan',\n      save: 'Simpan',\n      sendTestEmail: 'Kirim email uji',\n      showActive: 'Tampilkan aktif',\n      showAllAttachments: 'Tampilkan semua lampiran ({{hidden}} tersembunyi)',\n      showCardsWithThisUser: 'Tampilkan kartu dengan pengguna ini',\n      showDeactivated: 'Tampilkan dinonaktifkan',\n      showFewerAttachments: 'Tampilkan lampiran lebih sedikit',\n      showLess: 'Tampilkan lebih sedikit',\n      showMore: 'Tampilkan lebih banyak',\n      sortList_title: 'Urutkan daftar',\n      start: 'Mulai',\n      stop: 'Berhenti',\n      subscribe: 'Berlanggan',\n      unlinkSso: 'Putuskan tautan SSO',\n      unlinkSso_title: 'Putuskan tautan SSO',\n      unsubscribe: 'Berhenti berlangganan',\n      uploadNewAvatar: 'Unggah avatar baru',\n      uploadNewImage: 'Unggah gambar baru',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/id-ID/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'id-ID',\n  country: 'id',\n  name: 'Bahasa Indonesia',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/id-ID/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Batas pengguna aktif tercapai',\n      adminLoginRequiredToInitializeInstance:\n        'Login admin diperlukan untuk menginisialisasi instance',\n      emailAlreadyInUse: 'E-mail telah digunakan',\n      emailOrUsername: 'E-mail atau username',\n      invalidCredentials: 'Kredensial tidak valid',\n      invalidEmailOrUsername: 'E-mail atau username salah',\n      invalidPassword: 'Kata sandi salah',\n      logIn_title: 'Masuk',\n      noInternetConnection: 'Tidak ada koneksi internet',\n      or: 'Atau',\n      pageNotFound_title: 'Halaman tidak ditemukan',\n      password: 'Kata sandi',\n      poweredByPlanka: 'Didukung oleh <1>PLANKA</1>',\n      serverConnectionFailed: 'Koneksi server gagal',\n      unknownError: 'Kesalahan tidak diketahui, coba lagi nanti.',\n      useSingleSignOn: 'Gunakan single sign-on',\n      usernameAlreadyInUse: 'Username telah digunakan',\n      whoops_title: 'Ups!',\n    },\n\n    action: {\n      cancelAndClose: 'Batal dan tutup',\n      continue: 'Lanjutkan',\n      debugSso: 'Debug SSO',\n      goBack: 'Kembali',\n      goHome: 'Ke beranda',\n      logIn: 'Masuk',\n      logInWithSso: 'Masuk dengan SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/id-ID/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Ini adalah teks tanpa judul.\\nBaik judul maupun teks\\ndapat disorot dengan tebal, miring, warna,\\ndicoret, dan digarisbawahi.\",\n    \"text-with-head\": \"Ini adalah teks dengan judul.\\nBaik judul maupun teks\\ndapat disorot dengan tebal, miring, warna,\\ndicoret, dan digarisbawahi.\",\n    \"heading\": \"Judul\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Kesalahan di editor markdown\",\n    \"settings_wysiwyg\": \"Editor visual (wysiwyg)\",\n    \"settings_markup\": \"Markup markdown\",\n    \"markup_placeholder\": \"Masukkan markup markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Hapus\",\n    \"empty_option\": \"Tidak ada hasil ditemukan\",\n    \"show_line_numbers\": \"Penomoran baris\"\n  },\n  \"common\": {\n    \"delete\": \"Hapus\",\n    \"edit\": \"Edit\",\n    \"toolbar_action_disabled\": \"Elemen markup tidak kompatibel\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Batal\",\n    \"common_action_submit\": \"Kirim\",\n    \"common_action_upload\": \"Pilih\",\n    \"common_tab_attach\": \"Tambahkan dari perangkat\",\n    \"common_tab_link\": \"Tambahkan melalui tautan\",\n    \"common_link\": \"Tautan\",\n    \"common_sizes\": \"Ukuran, px\",\n    \"image_name\": \"Judul\",\n    \"image_link_href\": \"Tautan gambar\",\n    \"image_link_href_help\": \"Alamat yang dituju tautan gambar.\",\n    \"image_alt\": \"Teks alternatif\",\n    \"image_alt_help\": \"Teks alternatif ditampilkan jika gambar tidak dapat dimuat.\",\n    \"image_upload_help\": \"Gambar JPEG, GIF, atau PNG tidak lebih dari 1 MB.\",\n    \"image_upload_failed\": \"Gagal menambahkan gambar\",\n    \"image_size_width\": \"Lebar\",\n    \"image_size_height\": \"Tinggi\",\n    \"link_url_help\": \"Alamat yang dituju tautan.\",\n    \"link_text\": \"Teks tautan\",\n    \"link_text_help\": \"Teks yang ditampilkan sebagai tautan.\",\n    \"link_open_help\": \"Buka tautan di tab baru\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Header\",\n    \"header_hint\": \"# Teks Anda\",\n    \"italic_title\": \"Miring\",\n    \"italic_hint\": \"_Teks Anda_\",\n    \"bold_title\": \"Tebal\",\n    \"bold_hint\": \"**Teks Anda**\",\n    \"strikethrough_title\": \"Coret\",\n    \"strikethrough_hint\": \"~~Teks Anda~~\",\n    \"blockquote_title\": \"Kutipan\",\n    \"blockquote_hint\": \"> Teks Anda\",\n    \"code_title\": \"Kode\",\n    \"code_hint\": \"```Teks Anda```\",\n    \"link_title\": \"Tautan\",\n    \"link_hint\": \"[Teks Anda](url)\",\n    \"image_title\": \"Gambar\",\n    \"image_hint\": \"![Teks Anda](url)\",\n    \"list_title\": \"Item daftar\",\n    \"list_hint\": \"- Teks Anda\",\n    \"numbered-list_title\": \"Daftar bernomor\",\n    \"numbered-list_hint\": \"1. Teks Anda\",\n    \"documentation\": \"Dokumentasi\",\n    \"documentation_link\": \"https://diplodoc.com/docs/id/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Tebal\",\n    \"code\": \"Kode\",\n    \"code_inline\": \"Kode sebaris\",\n    \"codeblock\": \"Blok kode\",\n    \"colorify\": \"Warna teks\",\n    \"colorify__color_blue\": \"Biru\",\n    \"colorify__color_default\": \"Default\",\n    \"colorify__color_gray\": \"Abu-abu\",\n    \"colorify__color_green\": \"Hijau\",\n    \"colorify__color_orange\": \"Oranye\",\n    \"colorify__color_red\": \"Merah\",\n    \"colorify__color_violet\": \"Ungu\",\n    \"colorify__color_yellow\": \"Kuning\",\n    \"colorify__group_text\": \"Teks\",\n    \"cut\": \"Potong\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emoji dapat ditambahkan di WYSIWYG atau secara manual dengan markup\",\n    \"heading\": \"Header\",\n    \"heading1\": \"Header 1\",\n    \"heading2\": \"Header 2\",\n    \"heading3\": \"Header 3\",\n    \"heading4\": \"Header 4\",\n    \"heading5\": \"Header 5\",\n    \"heading6\": \"Header 6\",\n    \"hrule\": \"Pemisah\",\n    \"image\": \"Gambar\",\n    \"italic\": \"Miring\",\n    \"link\": \"Tautan\",\n    \"list\": \"Daftar\",\n    \"list__action_lift\": \"Naikkan item\",\n    \"list__action_sink\": \"Turunkan item\",\n    \"list_action_disabled\": \"Bertentangan dengan logika daftar\",\n    \"mark\": \"Ditandai\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Aksi lainnya\",\n    \"note\": \"Catatan\",\n    \"olist\": \"Daftar urut\",\n    \"quote\": \"Kutipan\",\n    \"redo\": \"Ulangi\",\n    \"strike\": \"Coret\",\n    \"table\": \"Tabel\",\n    \"text\": \"Teks\",\n    \"ulist\": \"Daftar berpoin\",\n    \"underline\": \"Garis bawah\",\n    \"undo\": \"Batalkan\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Ketik / untuk menggunakan perintah...\",\n    \"checkbox\": \"Masukkan deskripsi tugas...\",\n    \"deflist_term\": \"Istilah\",\n    \"deflist_desc\": \"Deskripsi definisi\",\n    \"heading\": \"Header\",\n    \"cut_title\": \"Judul\",\n    \"cut_content\": \"Konten yang akan ditampilkan saat diklik\",\n    \"note_title\": \"Judul\",\n    \"note_content\": \"Konten catatan\",\n    \"table_cell\": \"Konten sel\",\n    \"select_filter\": \"Cari bahasa...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Peka huruf besar/kecil\",\n    \"label_whole-word\": \"Kata lengkap\",\n    \"title\": \"Cari di kode\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Tidak ditemukan\"\n  },\n  \"widgets\": {\n    \"image\": \"Tambahkan gambar\",\n    \"link\": \"Tambahkan tautan\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Catatan\",\n    \"tip\": \"Tips\",\n    \"warning\": \"Peringatan\",\n    \"alert\": \"Peringatan\",\n    \"remove\": \"Hapus\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Tambahkan kolom sebelum\",\n    \"column.add.after\": \"Tambahkan kolom setelah\",\n    \"column.remove\": \"Hapus kolom\",\n    \"column.remove.multiple\": \"Hapus kolom\",\n    \"row.add.before\": \"Tambahkan baris sebelum\",\n    \"row.add.after\": \"Tambahkan baris setelah\",\n    \"row.remove\": \"Hapus baris\",\n    \"row.remove.multiple\": \"Hapus baris\",\n    \"cells.clear\": \"Kosongkan sel\",\n    \"table.remove\": \"Hapus tabel\",\n    \"table.menu.cell.align.left\": \"Ratakan isi sel ke kiri\",\n    \"table.menu.cell.align.right\": \"Ratakan isi sel ke kanan\",\n    \"table.menu.cell.align.center\": \"Ratakan isi sel ke tengah\",\n    \"table.menu.row.add\": \"Tambahkan baris setelah\",\n    \"table.menu.row.remove\": \"Hapus baris\",\n    \"table.menu.column.add\": \"Tambahkan kolom setelah\",\n    \"table.menu.column.remove\": \"Hapus kolom\",\n    \"table.menu.convert.yfm\": \"Konversi ke tabel YFM\",\n    \"table.menu.table.remove\": \"Hapus tabel\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport keyBy from 'lodash/keyBy';\n\nimport arYE from './ar-YE';\nimport bgBG from './bg-BG';\nimport caES from './ca-ES';\nimport csCZ from './cs-CZ';\nimport daDK from './da-DK';\nimport deDE from './de-DE';\nimport elGR from './el-GR';\nimport enGB from './en-GB';\nimport enUS from './en-US';\nimport esES from './es-ES';\nimport etEE from './et-EE';\nimport faIR from './fa-IR';\nimport fiFI from './fi-FI';\nimport frFR from './fr-FR';\nimport huHU from './hu-HU';\nimport idID from './id-ID';\nimport itIT from './it-IT';\nimport jaJP from './ja-JP';\nimport koKR from './ko-KR';\nimport nlNL from './nl-NL';\nimport plPL from './pl-PL';\nimport ptBR from './pt-BR';\nimport ptPT from './pt-PT';\nimport roRO from './ro-RO';\nimport ruRU from './ru-RU';\nimport skSK from './sk-SK';\nimport srCyrlRS from './sr-Cyrl-RS';\nimport srLatnRS from './sr-Latn-RS';\nimport svSE from './sv-SE';\nimport trTR from './tr-TR';\nimport ukUA from './uk-UA';\nimport uzUZ from './uz-UZ';\nimport viVN from './vi-VN';\nimport zhCN from './zh-CN';\nimport zhTW from './zh-TW';\n\nconst locales = [\n  arYE,\n  bgBG,\n  caES,\n  csCZ,\n  daDK,\n  deDE,\n  elGR,\n  enGB,\n  enUS,\n  esES,\n  etEE,\n  faIR,\n  fiFI,\n  frFR,\n  huHU,\n  idID,\n  itIT,\n  jaJP,\n  koKR,\n  nlNL,\n  plPL,\n  ptBR,\n  ptPT,\n  roRO,\n  ruRU,\n  skSK,\n  srCyrlRS,\n  srLatnRS,\n  svSE,\n  trTR,\n  ukUA,\n  uzUZ,\n  viVN,\n  zhCN,\n  zhTW,\n];\n\nexport default locales;\n\nexport const languages = locales.map((locale) => locale.language);\n\nexport const embeddedLocales = locales.reduce(\n  (result, locale) => ({\n    ...result,\n    [locale.language]: locale.embeddedLocale,\n  }),\n  {},\n);\n\nexport const localeByLanguage = keyBy(locales, 'language');\n"
  },
  {
    "path": "client/src/locales/it-IT/core.js",
    "content": "import dateFns from 'date-fns/locale/it';\nimport timeAgo from 'javascript-time-ago/locale/it';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd/M/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'alle' p\",\n    fullDate: 'd MMM, y',\n    fullDateTime: \"d MMMM, y 'alle' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: \"Informazioni sull'app\",\n      aboutPlanka_title: 'Informazioni su PLANKA',\n      accessToken: 'Token di accesso',\n      account: 'Account',\n      actions: 'Azioni',\n      activateUser_title: 'Attiva utente',\n      active: 'Attivo',\n      addAttachment_title: 'Aggiungi allegato',\n      addCustomFieldGroup_title: 'Aggiungi campi personalizzati',\n      addCustomField_title: 'Aggiungi campo personalizzato',\n      addManager_title: 'Aggiungi manager',\n      addMember_title: 'Aggiungi membro',\n      addTaskList_title: 'Aggiungi lista di task',\n      addUser_title: 'Aggiungi utente',\n      admin: 'Amministratore',\n      administration: 'Amministrazione',\n      all: 'Tutto',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Tutte le modifiche verranno salvate<br />al ripristino della connessione.',\n      alphabetically: 'In ordine alfabetico',\n      alwaysDisplayCardCreator: 'Mostra sempre il creatore della scheda',\n      apiKeyCreated_title: 'Chiave API creata',\n      apiKey_title: 'Chiave API',\n      archive: 'Archivia',\n      archiveCard_title: 'Archivia scheda',\n      archiveCards_title: 'Archivia schede',\n      areYouSureYouWantToActivateThisUser: 'Sei sicuro di voler attivare questo utente?',\n      areYouSureYouWantToArchiveCards: 'Sei sicuro di voler archiviare le schede?',\n      areYouSureYouWantToArchiveThisCard: 'Sei sicuro di voler archiviare questa scheda?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Sei sicuro di voler assegnare questo project manager come proprietario?',\n      areYouSureYouWantToDeactivateThisUser: 'Sei sicuro di voler disattivare questo utente?',\n      areYouSureYouWantToDeleteThisApiKey: 'Sei sicuro di voler eliminare questa chiave API?',\n      areYouSureYouWantToDeleteThisAttachment: 'Sei sicuro di voler eliminare questo allegato?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Sei sicuro di voler eliminare questa immagine di sfondo?',\n      areYouSureYouWantToDeleteThisBoard: 'Sei sicuro di voler eliminare questa bacheca?',\n      areYouSureYouWantToDeleteThisCard: 'Sei sicuro di voler eliminare questa scheda?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Sei sicuro di voler eliminare questa scheda per sempre?',\n      areYouSureYouWantToDeleteThisComment: 'Sei sicuro di voler eliminare questo commento?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Sei sicuro di voler eliminare questo campo personalizzato?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Sei sicuro di voler eliminare questo campi personalizzati?',\n      areYouSureYouWantToDeleteThisLabel: 'Sei sicuro di voler eliminare questa etichetta?',\n      areYouSureYouWantToDeleteThisList:\n        'Sei sicuro di voler eliminare questa lista? Tutte le schede verranno spostate nel cestino.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Sei sicuro di voler eliminare questo servizio di notifica?',\n      areYouSureYouWantToDeleteThisProject: 'Sei sicuro di voler eliminare questo progetto?',\n      areYouSureYouWantToDeleteThisTask: 'Sei sicuro di voler eliminare questo task?',\n      areYouSureYouWantToDeleteThisTaskList: 'Sei sicuro di voler eliminare questa lista di task?',\n      areYouSureYouWantToDeleteThisUser: 'Sei sicuro di voler eliminare questo utente?',\n      areYouSureYouWantToDeleteThisWebhook: 'Sei sicuro di voler eliminare questo webhook?',\n      areYouSureYouWantToEmptyTrash: 'Sei sicuro di voler svuotare il cestino?',\n      areYouSureYouWantToLeaveBoard: 'Sei sicuro di voler abbandonare la bacheca?',\n      areYouSureYouWantToLeaveProject: 'Sei sicuro di voler abbandonare il progetto?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Sei sicuro di voler rendere questo progetto privato?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Sei sicuro di voler rendere questo progetto condiviso?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Sei sicuro di voler rigenerare questa chiave API? La chiave precedente non funzionerà più.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Sei sicuro di voler rimuovere questo amministratore dal progetto?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Sei sicuro di voler rimuovere questo membro dalla bacheca?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        \"Sei sicuro di voler scollegare SSO da questo utente? Questo permetterà all'utente di accedere con una password.\",\n      assignAsOwner_title: 'Assegna come proprietario',\n      atLeastOneListMustBePresent: 'Deve essere presente almeno una lista',\n      attachment: 'Allegato',\n      attachments: 'Allegati',\n      authentication: 'Autenticazione',\n      background: 'Sfondo',\n      baseCustomFields_title: 'Campi personalizzati base',\n      baseGroup: 'Gruppo base',\n      board: 'Bacheca',\n      boardActions_title: 'Azioni bacheca',\n      boardNotFound_title: 'Bacheca non trovata',\n      boardSubscribed: 'Iscritto alla bacheca',\n      boardUser: 'Utente della bacheca',\n      byCreationTime: 'Per data di creazione',\n      byDefault: 'Per impostazione predefinita',\n      byDueDate: 'Per data di scadenza',\n      canBeInvitedToWorkInBoards: 'Può essere invitato a lavorare in bacheche.',\n      canComment: 'Può commentare',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Può creare i propri progetti ed essere invitato a lavorare in altri.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Può modificare il layout della bacheca e assegnare membri alle schede.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Può gestire le impostazioni di sistema e agire come proprietario del progetto.',\n      canOnlyViewBoard: 'Può solo visualizzare la bacheca.',\n      cardActions_title: 'Azioni scheda',\n      cardNotFound_title: 'Scheda non trovata',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Le schede in questa lista sono disponibili per tutti i membri della bacheca.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Le schede in questa lista sono complete e pronte per essere archiviate.',\n      cardsOnThisListAreReadyToBeWorkedOn:\n        'Le schede in questa lista sono pronte per essere lavorate.',\n      clickHereOrRefreshPageToUpdate: '<0>Clicca qui</0> o ricarica la pagina per aggiornare.',\n      clientHostnameInEhlo: 'Hostname del client in EHLO',\n      closed: 'Chiuso',\n      color: 'Colore',\n      comments: 'Commenti',\n      contentExceedsLimit: 'Il contenuto supera {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Il contenuto di questo allegato è troppo grande per essere visualizzato.',\n      copy_inline: 'copia',\n      createBoard_title: 'Crea bacheca',\n      createCustomFieldGroup_title: 'Crea campi personalizzati',\n      createLabel_title: 'Crea etichetta',\n      createNewOneOrSelectExistingOne: 'Crea nuovo o seleziona uno<br />esistente.',\n      createProject_title: 'Crea progetto',\n      createTextFile_title: 'Crea file di testo',\n      creator: 'Creatore',\n      currentPassword: 'Password attuale',\n      currentUser: 'Utente corrente',\n      customFieldGroup_title: 'Campi personalizzati',\n      customFieldGroups_title: 'Campi personalizzati',\n      customField_title: 'Campo personalizzato',\n      customFields_title: 'Campi personalizzati',\n      customerPanel_title: 'Pannello cliente',\n      dangerZone_title: 'Zona pericolosa',\n      date: 'Data',\n      deactivateUser_title: 'Disattiva utente',\n      defaultCardType_title: 'Tipo di scheda predefinito',\n      defaultFrom: '\"Da\" predefinito',\n      defaultView_title: 'Vista predefinita',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Elimina tutte le bacheche per poter eliminare questo progetto.',\n      deleteApiKey_title: 'Elimina chiave API',\n      deleteAttachment_title: 'Elimina allegato',\n      deleteBackgroundImage_title: 'Elimina immagine di sfondo',\n      deleteBoard_title: 'Elimina bacheca',\n      deleteCardForever_title: 'Elimina scheda per sempre',\n      deleteCard_title: 'Elimina scheda',\n      deleteComment_title: 'Elimina commento',\n      deleteCustomFieldGroup_title: 'Elimina campi personalizzati',\n      deleteCustomField_title: 'Elimina campo personalizzato',\n      deleteLabel_title: 'Elimina etichetta',\n      deleteList_title: 'Elimina elenco',\n      deleteNotificationService_title: 'Elimina servizio di notifica',\n      deleteProject_title: 'Elimina progetto',\n      deleteTaskList_title: 'Elimina lista di task',\n      deleteTask_title: 'Elimina task',\n      deleteUser_title: 'Elimina utente',\n      deleteWebhook_title: 'Elimina webhook',\n      deletedUser_title: 'Utente eliminato',\n      description: 'Descrizione',\n      display: 'Mostra',\n      displayCardAges: 'Mostra età delle schede',\n      dropFileToUpload: 'Trascina il file da caricare',\n      dueDate_title: 'Data di scadenza',\n      dynamicAndUnevenlySpacedLayout: 'Layout dinamico e irregolarmente distribuito.',\n      editAttachment_title: 'Modifica allegato',\n      editAvatar_title: 'Modifica avatar',\n      editColor_title: 'Modifica colore',\n      editCustomFieldGroup_title: 'Modifica campi personalizzati',\n      editCustomField_title: 'Modifica campo personalizzato',\n      editDueDate_title: 'Modifica data di scadenza',\n      editEmail_title: 'Modifica indirizzo e-mail',\n      editInformation_title: 'Modifica informazioni',\n      editLabel_title: 'Modifica etichetta',\n      editPassword_title: 'Modifica password',\n      editPermissions_title: 'Modifica permessi',\n      editRole_title: 'Modifica ruolo',\n      editStopwatch_title: 'Modifica timer',\n      editType_title: 'Modifica tipo',\n      editUsername_title: 'Modifica username',\n      editor: 'Editor',\n      editors: 'Editors',\n      email: 'E-mail',\n      emptyTrash_title: 'Svuota cestino',\n      enterCardTitle: 'Inserire titolo della scheda...',\n      enterDescription: 'Inserire descrizione...',\n      enterFilename: 'Inserire nome del file',\n      enterListTitle: 'Inserire titolo della lista...',\n      enterTaskDescription: 'Inserire descrizione della task...',\n      events: 'Eventi',\n      excludedEvents: 'Eventi esclusi',\n      expandTaskListsByDefault: 'Espandi le liste delle attività per impostazione predefinita',\n      filterByLabels_title: 'Filtra per etichetta',\n      filterByMembers_title: 'Filtra per membro',\n      forPersonalProjects: 'Per progetti personali.',\n      forTeamBasedProjects: 'Per progetti di gruppo.',\n      fromComputer_title: 'Dal computer',\n      fromTrello: 'Da Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'La chiave completa è nascosta per motivi di sicurezza. Rigenerala per crearne una nuova.',\n      general: 'Generale',\n      gradients: 'Gradiente',\n      grid: 'Griglia',\n      hideCompletedTasks: 'Nascondi task completate',\n      hideFromProjectListAndFavorites: 'Nascondi dalla lista dei progetti e dai preferiti',\n      host: 'Host',\n      hours: 'Ore',\n      identity: 'Identità',\n      importBoard_title: 'Importa board',\n      information: 'Informazione',\n      invalidCurrentPassword: 'Password corrente non valida',\n      kanban: 'Kanban',\n      labels: 'Etichette',\n      language: 'Lingua',\n      leaveBoard_title: 'Abbandona bacheca',\n      leaveProject_title: 'Abbandona progetto',\n      limitCardTypesToDefaultOne: 'Limita i tipi di scheda a quello predefinito',\n      linkToCard: 'Collega alla scheda',\n      list: 'Lista',\n      listActions_title: 'Azioni lista',\n      lists: 'Liste',\n      makeProjectPrivate_title: 'Rendi progetto privato',\n      makeProjectShared_title: 'Rendi progetto condiviso',\n      managers: 'Managers',\n      memberActions_title: 'Azioni membro',\n      members: 'Membri',\n      minutes: 'Minuti',\n      moreActions: 'Altre azioni',\n      moreActions_title: 'Altre azioni',\n      moveCard_title: 'Sposta scheda',\n      moveList_title: 'Sposta lista',\n      myOwn_title: 'Personali',\n      name: 'Nome',\n      newEmail: 'Nuova e-mail',\n      newPassword: 'Nuova password',\n      newUsername: 'Nuovo username',\n      newVersionAvailable: 'Nuova versione disponibile',\n      newestFirst: 'Dal più recente',\n      noApiKeyCreated: 'Nessuna chiave API creata.',\n      noBoards: 'Nessuna bacheca',\n      noCardsFound: 'Nessuna scheda trovata.',\n      noConnectionToServer: 'Nessuna connessione al server',\n      noLists: 'Nessuna lista',\n      noProjects: 'Nessun progetto',\n      noUnreadNotifications: 'Nessuna notifica da leggere.',\n      notifications: 'Notifiche',\n      oldestFirst: 'Dal meno recente',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Deve rimanere solo un manager per rendere questo progetto privato',\n      openBoard_title: 'Bacheca aperta',\n      optional_inline: 'opzionale',\n      organization: 'Organizazzione',\n      others: 'Altri',\n      passwordIsSet: 'Password impostata',\n      phone: 'Telefono',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA utilizza <1><0>Apprise</0></1> per inviare notifiche a oltre 100 servizi popolari.',\n      port: 'Porta',\n      preferences: 'Preferenze',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Consiglio: premi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard.',\n      private: 'Privato',\n      project: 'Progetto',\n      projectNotFound_title: 'Progetto non trovato',\n      projectOwner: 'Proprietario del progetto',\n      referenceDataAndKnowledgeStorage: 'Dati di riferimento e di archiviazione.',\n      regenerateApiKey_title: 'Rigenera chiave API',\n      rejectUnauthorizedTlsCertificates: 'Rifiuta certificati TLS non autorizzati',\n      removeManager_title: 'Rimuovi manager',\n      removeMember_title: 'Rimuovi membro',\n      role: 'Ruolo',\n      saveThisKeyItWillNotBeShownAgain: 'Salva questa chiave — non verrà più mostrata!',\n      searchCards: 'Cerca schede...',\n      searchCustomFieldGroups: 'Cerca campi personalizzati...',\n      searchCustomFields: 'Cerca campi personalizzati...',\n      searchLabels: 'Cerca etichette...',\n      searchLists: 'Cerca liste...',\n      searchMembers: 'Cerca membri...',\n      searchProjects: 'Cerca progetti...',\n      searchUsers: 'Cerca utenti...',\n      seconds: 'Secondi',\n      selectAssignee_title: 'Seleziona assegnatario',\n      selectBoard: 'Seleziona bacheca',\n      selectList: 'Seleziona lista',\n      selectListToRestoreThisCard: 'Seleziona lista per ripristinare questa scheda',\n      selectOrder_title: 'Seleziona ordine',\n      selectPermissions_title: 'Seleziona permessi',\n      selectProject: 'Seleziona progetto',\n      selectRole_title: 'Seleziona ruolo',\n      selectType_title: 'Seleziona tipo',\n      sequentialDisplayOfCards: 'Visualizzazione sequenziale delle schede.',\n      settings: 'Impostazioni',\n      shared: 'Condiviso',\n      sharedWithMe_title: 'Condiviso con me',\n      showOnFrontOfCard: 'Mostra davanti alla scheda',\n      smtp: 'SMTP',\n      sortList_title: 'Ordina',\n      sourceCardIsNoLongerAvailableForCopying:\n        'La scheda sorgente non è più disponibile per la copia.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'La scheda sorgente non è più disponibile per lo spostamento.',\n      stopwatch: 'Timer',\n      story: 'Storia',\n      subscribeToCardWhenCommenting: 'Iscrivimi alla scheda quando commento',\n      subscribeToMyOwnCardsByDefault: 'Abbonati alle mie scheda per impostazione predefinita',\n      taskActions_title: 'Azioni task',\n      taskAssignmentAndProjectCompletion: 'Assegnazione di task e completamento del progetto.',\n      taskListActions_title: 'Azioni lista di task',\n      taskList_title: 'Lista di task',\n      team: 'Team',\n      termsOfService_title: 'Termini di servizio',\n      testLog_title: 'Log di test',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'Non è disponibile alcuna anteprima per questo allegato.',\n      time: 'Tempo',\n      title: 'Titolo',\n      trash: 'Cestino',\n      trashHasBeenSuccessfullyEmptied: 'Il cestino è stato svuotato con successo.',\n      turnOffRecentCardHighlighting: \"Disattiva l'evidenziazione delle scheda recenti\",\n      typeNameToConfirm: 'Digita il nome per confermare.',\n      typeTitleToConfirm: 'Digita il titolo per confermare.',\n      unlinkSso_title: 'Scollegamento SSO',\n      unsavedChanges: 'Modifiche non salvate',\n      uploadFailedFileIsTooBig: 'Caricamento fallito: il file è troppo grande.',\n      uploadFailedNotEnoughStorageSpace:\n        'Caricamento fallito: spazio di archiviazione insufficiente.',\n      uploadedImages: 'Immagini caricate',\n      url: 'URL',\n      useSecureConnection: 'Usa connessione sicura',\n      userActions_title: 'Azioni utente',\n      userAddedCardToList: '<0>{{user}}</0> ha aggiunto <2>{{card}}</2> a {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> ha aggiunto questa task a {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> ha aggiunto {{addedUser}} a <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> ha aggiunto {{addedUser}} a questa task',\n      userAddedYouToCard: '<0>{{user}}</0> ti ha aggiunto a <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> ha completato {{task}} in <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> ha completato {{task}} in questa task',\n      userJoinedCard: '<0>{{user}}</0> è entrato in <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> è entrato in questa task',\n      userLeftCard: '<0>{{user}}</0> ha lasciato <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> ha lasciato un commento «{{comment}}» a <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> ha lasciato questa task',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> ha contrassegnato {{task}} come incompleta in <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> ha contrassegnato {{task}} come incompleta in questa task',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> ti ha menzionato in un commento «{{comment}}» su <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> ha spostato <2>{{card}}</2> da {{fromList}} a {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> ha spostato questa scheda da {{fromList}} a {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> ha rimosso {{removedUser}} da <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> ha rimosso {{removedUser}} da questa task',\n      username: 'Username',\n      users: 'Utenti',\n      viewer: 'Visualizzatore',\n      viewers: 'Visualizzatori',\n      visualTaskManagementWithLists: 'Gestione visiva dei task con liste.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Novità',\n      withoutBaseGroup: 'Senza gruppo base',\n      writeComment: 'Scrivi un commento...',\n    },\n\n    action: {\n      activateUser: 'Attiva utente',\n      activateUser_title: 'Attiva utente',\n      addAnotherCard: \"Aggiungi un'altra scheda\",\n      addAnotherList: \"Aggiungi un'altra lista\",\n      addAnotherTask: 'Aggiungi un altro task',\n      addCard: 'Aggiungi scheda',\n      addCard_title: 'Aggiungi scheda',\n      addComment: 'Aggiungi commento',\n      addCustomField: 'Aggiungi campo personalizzato',\n      addCustomFieldGroup: 'Aggiungi campi personalizzati',\n      addList: 'Aggiungi lista',\n      addMember: 'Aggiungi membro',\n      addMoreDetailedDescription: 'Aggiungi una descrizione più dettagliata',\n      addTask: 'Aggiungi task',\n      addTaskList: 'Aggiungi lista di task',\n      addToCard: 'Aggiungi alla scheda',\n      addUser: 'Aggiungi utente',\n      addWebhook: 'Aggiungi webhook',\n      archive: 'Archivia',\n      archiveCard: 'Archivia scheda',\n      archiveCard_title: 'Archivia scheda',\n      archiveCards: 'Archivia schede',\n      archiveCards_title: 'Archivia schede',\n      assignAsOwner: 'Assegna come proprietario',\n      cancel: 'Annulla',\n      copy: 'Copia',\n      copyCard_title: 'Copia scheda',\n      createApiKey: 'Crea chiave API',\n      createBoard: 'Crea bacheca',\n      createCustomFieldGroup: 'Crea campi personalizzati',\n      createFile: 'Crea file',\n      createLabel: 'Crea etichetta',\n      createNewLabel: 'Crea nuova etichetta',\n      createProject: 'Crea progetto',\n      cut: 'Taglia',\n      cutCard_title: 'Taglia scheda',\n      deactivateUser: 'Disattiva utente',\n      deactivateUser_title: 'Disattiva utente',\n      delete: 'Elimina',\n      deleteApiKey: 'Elimina chiave API',\n      deleteAttachment: 'Elimina allegato',\n      deleteAvatar: 'Elimina avatar',\n      deleteBackgroundImage: 'Elimina immagine di sfondo',\n      deleteBoard: 'Elimina bacheca',\n      deleteBoard_title: 'Elimina bacheca',\n      deleteCard: 'Elimina scheda',\n      deleteCardForever: 'Elimina scheda per sempre',\n      deleteCard_title: 'Elimina scheda',\n      deleteComment: 'Elimina commento',\n      deleteCustomField: 'Elimina campo personalizzato',\n      deleteCustomFieldGroup: 'Elimina campi personalizzati',\n      deleteForever_title: 'Elimina per sempre',\n      deleteGroup: 'Elimina gruppo',\n      deleteLabel: 'Elimina etichetta',\n      deleteList: 'Elimina lista',\n      deleteList_title: 'Elimina lista',\n      deleteNotificationService: 'Elimina servizio di notifica',\n      deleteProject: 'Elimina progetto',\n      deleteProject_title: 'Elimina progetto',\n      deleteTask: 'Elimina task',\n      deleteTaskList: 'Elimina lista di list',\n      deleteTask_title: 'Elimina task',\n      deleteUser: 'Elimina utente',\n      deleteUser_title: 'Elimina utente',\n      deleteWebhook: 'Elimina webhook',\n      dismissAll: 'Ignora tutto',\n      download: 'Scarica',\n      duplicateCard_title: 'Duplica scheda',\n      edit: 'Modifica',\n      editColor_title: 'Modifica colore',\n      editDescription_title: 'Modifica descrizione',\n      editDueDate_title: 'Modifica data di scadenza',\n      editEmail_title: 'Modifica indirizzo e-mail',\n      editGroup: 'Modifica gruppo',\n      editInformation_title: 'Modifca informazioni',\n      editPassword_title: 'Modifica password',\n      editPermissions: 'Modifica permessi',\n      editRole_title: 'Modifica ruolo',\n      editStopwatch_title: 'Modifica timer',\n      editTitle_title: 'Modifica titolo',\n      editType_title: 'Modifica tipo',\n      editUsername_title: 'Modifica username',\n      emptyTrash: 'Svuota cestino',\n      emptyTrash_title: 'Svuota cestino',\n      import: 'Importa',\n      join: 'Entra',\n      leave: 'Abbandona',\n      leaveBoard: 'Abbandona bacheca',\n      leaveProject: 'Abbandona progetto',\n      logOut_title: 'Disconnettiti',\n      makeCover_title: 'Crea cover',\n      makeProjectPrivate: 'Rendi progetto privato',\n      makeProjectPrivate_title: 'Rendi progetto privato',\n      makeProjectShared: 'Rendi progetto condiviso',\n      makeProjectShared_title: 'Rendi progetto condiviso',\n      move: 'Muovi',\n      moveCard_title: 'Muovi scheda',\n      moveList_title: 'Muovi lista',\n      regenerateApiKey: 'Rigenera chiave API',\n      remove: 'Rimuovi',\n      removeAssignee: 'Rimuovi assegnatario',\n      removeColor: 'Rimuovi colore',\n      removeCover_title: 'Rimuovi copertina',\n      removeFromBoard: 'Rimuovi dalla bacheca',\n      removeFromProject: 'Rimuovi dal progetto',\n      removeManager: 'Rimuovi manager',\n      removeMember: 'Rimuovi membro',\n      restoreToList: 'Ripristina a {{list}}',\n      returnToBoard: 'Torna alla bacheca',\n      save: 'Salva',\n      sendTestEmail: 'Invia email di test',\n      showActive: 'Mostra attivi',\n      showAllAttachments: 'Mostra tutti gli allegati ({{hidden}} nascosti)',\n      showCardsWithThisUser: 'Mostra schede con questo utente',\n      showDeactivated: 'Mostra disattivati',\n      showFewerAttachments: 'Mostra meno allegati',\n      showLess: 'Mostra di meno',\n      showMore: 'Mostra di più',\n      sortList_title: 'Ordina',\n      start: 'Inizio',\n      stop: 'Interrompi',\n      subscribe: 'Iscriviti',\n      unlinkSso: 'Scollega SSO',\n      unlinkSso_title: 'Scollega SSO',\n      unsubscribe: 'Annulla iscrizione',\n      uploadNewAvatar: 'Carica nuovo avatar',\n      uploadNewImage: 'Carica nuova immagine',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/it-IT/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'it-IT',\n  country: 'it',\n  name: 'Italiano',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/it-IT/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Limite utenti attivi raggiunto',\n      adminLoginRequiredToInitializeInstance:\n        \"Login amministratore richiesto per inizializzare l'istanza\",\n      emailAlreadyInUse: 'E-mail già in uso',\n      emailOrUsername: 'E-mail o username',\n      invalidCredentials: 'Credenziali non valide',\n      invalidEmailOrUsername: 'E-mail o username non valido',\n      invalidPassword: 'Password non valida',\n      logIn_title: 'Accedi',\n      noInternetConnection: 'Nessuna connessione internet',\n      or: 'Oppure',\n      pageNotFound_title: 'Pagina non trovata',\n      password: 'Password',\n      poweredByPlanka: 'Powered by <1>PLANKA</1>',\n      serverConnectionFailed: 'Connesione al server fallita',\n      unknownError: 'Errore sconosciuto, prova ancora',\n      useSingleSignOn: 'Accedi con SSO',\n      usernameAlreadyInUse: 'Username già in uso',\n      whoops_title: 'Ops!',\n    },\n\n    action: {\n      cancelAndClose: 'Annulla e chiudi',\n      continue: 'Continua',\n      debugSso: 'Debug SSO',\n      goBack: 'Torna indietro',\n      goHome: 'Vai alla home',\n      logIn: 'Accedi',\n      logInWithSso: 'Accedi con SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/it-IT/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Questo è un testo senza titolo.\\nSia il titolo che il testo\\npossono essere evidenziati in grassetto, corsivo, colore,\\nbarrato e sottolineato.\",\n    \"text-with-head\": \"Questo è un testo con titolo.\\nSia il titolo che il testo\\npossono essere evidenziati in grassetto, corsivo, colore,\\nbarrato e sottolineato.\",\n    \"heading\": \"Titolo\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Errore nell'editor markdown\",\n    \"settings_wysiwyg\": \"Editor visuale (wysiwyg)\",\n    \"settings_markup\": \"Markup markdown\",\n    \"markup_placeholder\": \"Inserisci il markup markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Rimuovi\",\n    \"empty_option\": \"Nessuna corrispondenza trovata\",\n    \"show_line_numbers\": \"Numerazione righe\"\n  },\n  \"common\": {\n    \"delete\": \"Elimina\",\n    \"edit\": \"Modifica\",\n    \"toolbar_action_disabled\": \"Elemento di markup non compatibile\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Annulla\",\n    \"common_action_submit\": \"Invia\",\n    \"common_action_upload\": \"Seleziona\",\n    \"common_tab_attach\": \"Aggiungi dal dispositivo\",\n    \"common_tab_link\": \"Aggiungi tramite link\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Dimensione, px\",\n    \"image_name\": \"Titolo\",\n    \"image_link_href\": \"Link immagine\",\n    \"image_link_href_help\": \"Indirizzo a cui porta il link dell'immagine.\",\n    \"image_alt\": \"Testo alternativo\",\n    \"image_alt_help\": \"Il testo alternativo viene visualizzato se l'immagine non può essere caricata.\",\n    \"image_upload_help\": \"Immagine JPEG, GIF o PNG non superiore a 1 MB.\",\n    \"image_upload_failed\": \"Impossibile aggiungere l'immagine\",\n    \"image_size_width\": \"Larghezza\",\n    \"image_size_height\": \"Altezza\",\n    \"link_url_help\": \"Indirizzo a cui porta il link.\",\n    \"link_text\": \"Testo del link\",\n    \"link_text_help\": \"Testo visualizzato come link.\",\n    \"link_open_help\": \"Apri il link in una nuova scheda\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Intestazione\",\n    \"header_hint\": \"# Il tuo testo\",\n    \"italic_title\": \"Corsivo\",\n    \"italic_hint\": \"_Il tuo testo_\",\n    \"bold_title\": \"Grassetto\",\n    \"bold_hint\": \"**Il tuo testo**\",\n    \"strikethrough_title\": \"Barrato\",\n    \"strikethrough_hint\": \"~~Il tuo testo~~\",\n    \"blockquote_title\": \"Citazione\",\n    \"blockquote_hint\": \"> Il tuo testo\",\n    \"code_title\": \"Codice\",\n    \"code_hint\": \"```Il tuo testo```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[Il tuo testo](url)\",\n    \"image_title\": \"Immagine\",\n    \"image_hint\": \"![Il tuo testo](url)\",\n    \"list_title\": \"Elemento elenco\",\n    \"list_hint\": \"- Il tuo testo\",\n    \"numbered-list_title\": \"Elenco numerato\",\n    \"numbered-list_hint\": \"1. Il tuo testo\",\n    \"documentation\": \"Documentazione\",\n    \"documentation_link\": \"https://diplodoc.com/docs/it/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Grassetto\",\n    \"code\": \"Codice\",\n    \"code_inline\": \"Codice in linea\",\n    \"codeblock\": \"Blocco di codice\",\n    \"colorify\": \"Colore del testo\",\n    \"colorify__color_blue\": \"Blu\",\n    \"colorify__color_default\": \"Predefinito\",\n    \"colorify__color_gray\": \"Grigio\",\n    \"colorify__color_green\": \"Verde\",\n    \"colorify__color_orange\": \"Arancione\",\n    \"colorify__color_red\": \"Rosso\",\n    \"colorify__color_violet\": \"Viola\",\n    \"colorify__color_yellow\": \"Giallo\",\n    \"colorify__group_text\": \"Testo\",\n    \"cut\": \"Taglia\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Gli emoji possono essere aggiunti in WYSIWYG o manualmente con il markup\",\n    \"heading\": \"Intestazione\",\n    \"heading1\": \"Intestazione 1\",\n    \"heading2\": \"Intestazione 2\",\n    \"heading3\": \"Intestazione 3\",\n    \"heading4\": \"Intestazione 4\",\n    \"heading5\": \"Intestazione 5\",\n    \"heading6\": \"Intestazione 6\",\n    \"hrule\": \"Separatore\",\n    \"image\": \"Immagine\",\n    \"italic\": \"Corsivo\",\n    \"link\": \"Link\",\n    \"list\": \"Elenco\",\n    \"list__action_lift\": \"Sposta elemento su\",\n    \"list__action_sink\": \"Sposta elemento giù\",\n    \"list_action_disabled\": \"Contraddice la logica dell'elenco\",\n    \"mark\": \"Evidenziato\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Altre azioni\",\n    \"note\": \"Nota\",\n    \"olist\": \"Elenco ordinato\",\n    \"quote\": \"Citazione\",\n    \"redo\": \"Ripeti\",\n    \"strike\": \"Barrato\",\n    \"table\": \"Tabella\",\n    \"text\": \"Testo\",\n    \"ulist\": \"Elenco puntato\",\n    \"underline\": \"Sottolineato\",\n    \"undo\": \"Annulla\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Digita / per usare i comandi...\",\n    \"checkbox\": \"Inserisci la descrizione dell'attività...\",\n    \"deflist_term\": \"Termine\",\n    \"deflist_desc\": \"Descrizione della definizione\",\n    \"heading\": \"Intestazione\",\n    \"cut_title\": \"Titolo\",\n    \"cut_content\": \"Contenuto da visualizzare al clic\",\n    \"note_title\": \"Titolo\",\n    \"note_content\": \"Contenuto della nota\",\n    \"table_cell\": \"Contenuto della cella\",\n    \"select_filter\": \"Cerca lingue...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Distingui maiuscole/minuscole\",\n    \"label_whole-word\": \"Parola intera\",\n    \"title\": \"Cerca nel codice\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Nessun risultato trovato\"\n  },\n  \"widgets\": {\n    \"image\": \"Aggiungi immagine\",\n    \"link\": \"Aggiungi link\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Nota\",\n    \"tip\": \"Suggerimento\",\n    \"warning\": \"Avviso\",\n    \"alert\": \"Allerta\",\n    \"remove\": \"Rimuovi\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Aggiungi colonna prima\",\n    \"column.add.after\": \"Aggiungi colonna dopo\",\n    \"column.remove\": \"Rimuovi colonna\",\n    \"column.remove.multiple\": \"Rimuovi colonne\",\n    \"row.add.before\": \"Aggiungi riga prima\",\n    \"row.add.after\": \"Aggiungi riga dopo\",\n    \"row.remove\": \"Rimuovi riga\",\n    \"row.remove.multiple\": \"Rimuovi righe\",\n    \"cells.clear\": \"Svuota celle\",\n    \"table.remove\": \"Rimuovi tabella\",\n    \"table.menu.cell.align.left\": \"Allinea il contenuto della cella a sinistra\",\n    \"table.menu.cell.align.right\": \"Allinea il contenuto della cella a destra\",\n    \"table.menu.cell.align.center\": \"Allinea il contenuto della cella al centro\",\n    \"table.menu.row.add\": \"Aggiungi riga dopo\",\n    \"table.menu.row.remove\": \"Rimuovi riga\",\n    \"table.menu.column.add\": \"Aggiungi colonna dopo\",\n    \"table.menu.column.remove\": \"Rimuovi colonna\",\n    \"table.menu.convert.yfm\": \"Converti in tabella YFM\",\n    \"table.menu.table.remove\": \"Rimuovi tabella\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/ja-JP/core.js",
    "content": "import dateFns from 'date-fns/locale/ja';\nimport timeAgo from 'javascript-time-ago/locale/ja';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'yyyy/M/d',\n    time: 'HH:mm',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'MMMMd日',\n    longDateTime: \"MMMMd'日 ' HH:mm\",\n    fullDate: 'yyyy年M月d日',\n    fullDateTime: 'yyyy年M月d日 HH:mm',\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'アプリについて',\n      aboutPlanka_title: 'PLANKAについて',\n      accessToken: 'アクセストークン',\n      account: 'アカウント',\n      actions: 'アクション',\n      activateUser_title: 'ユーザーをアクティブにする',\n      active: 'アクティブ',\n      addAttachment_title: '添付ファイルを追加',\n      addCustomFieldGroup_title: 'カスタムフィールドグループを追加',\n      addCustomField_title: 'カスタムフィールドを追加',\n      addManager_title: 'マネージャーを追加',\n      addMember_title: 'メンバーを追加',\n      addTaskList_title: 'タスクリストを追加',\n      addUser_title: 'ユーザーを追加',\n      admin: '管理者',\n      administration: '管理',\n      all: 'すべて',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        '全ての変更は接続回復後<br />自動的に保存されます。',\n      alphabetically: 'アルファベット順',\n      alwaysDisplayCardCreator: 'カード作成者を常に表示',\n      apiKeyCreated_title: 'APIキーが作成されました',\n      apiKey_title: 'APIキー',\n      archive: 'アーカイブ',\n      archiveCard_title: 'カードをアーカイブ',\n      archiveCards_title: 'カードをアーカイブ',\n      areYouSureYouWantToActivateThisUser: 'このユーザーをアクティブにしてもよろしいですか？',\n      areYouSureYouWantToArchiveCards: 'これらのカードをアーカイブしてもよろしいですか？',\n      areYouSureYouWantToArchiveThisCard: 'このカードをアーカイブしてもよろしいですか？',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'このプロジェクトマネージャーをオーナーに割り当ててもよろしいですか？',\n      areYouSureYouWantToDeactivateThisUser: 'このユーザーを非アクティブにしてもよろしいですか？',\n      areYouSureYouWantToDeleteThisApiKey: 'このAPIキーを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisAttachment: 'この添付ファイルを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisBackgroundImage: 'この背景画像を削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisBoard: 'このボードを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisCard: 'このカードを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisCardForever: 'このカードを完全に削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisComment: 'このコメントを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisCustomField:\n        'このカスタムフィールドを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'このカスタムフィールドグループを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisLabel: 'このラベルを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisList:\n        'このリストを削除してもよろしいですか？すべてのカードがゴミ箱に移動されます。',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'この通知サービスを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisProject: 'このプロジェクトを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisTask: 'このタスクを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisTaskList: 'このタスクリストを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisUser: 'このユーザーを削除してもよろしいですか？',\n      areYouSureYouWantToDeleteThisWebhook: 'このWebhookを削除してもよろしいですか？',\n      areYouSureYouWantToEmptyTrash: 'ゴミ箱を空にしてもよろしいですか？',\n      areYouSureYouWantToLeaveBoard: 'ボードから退出してもよろしいですか？',\n      areYouSureYouWantToLeaveProject: 'プロジェクトから退出してもよろしいですか？',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'このプロジェクトをプライベートにしてもよろしいですか？',\n      areYouSureYouWantToMakeThisProjectShared: 'このプロジェクトを共有にしてもよろしいですか？',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'このAPIキーを再生成してもよろしいですか？以前のキーは使用できなくなります。',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'このマネージャーをプロジェクトから外してもよろしいですか？',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'このメンバーをボードから外してもよろしいですか？',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'このユーザーからSSOのリンクを解除してもよろしいですか？これにより、ユーザーはパスワードでログインできるようになります。',\n      assignAsOwner_title: 'オーナーとして割り当て',\n      atLeastOneListMustBePresent: '少なくとも1つのリストが必要です',\n      attachment: '添付ファイル',\n      attachments: '添付ファイル',\n      authentication: '認証',\n      background: '背景',\n      baseCustomFields_title: 'ベースカスタムフィールド',\n      baseGroup: 'ベースグループ',\n      board: 'ボード',\n      boardActions_title: 'ボードアクション',\n      boardNotFound_title: 'ボードが見つかりません',\n      boardSubscribed: 'ボード購読済み',\n      boardUser: 'ボードユーザー',\n      byCreationTime: '作成時間順',\n      byDefault: 'デフォルト',\n      byDueDate: '期限日順',\n      canBeInvitedToWorkInBoards: 'ボードでの作業に招待されることができます。',\n      canComment: 'コメントを作成することができます',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        '自分のプロジェクトを作成し、他のプロジェクトに招待されることができます。',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'ボードレイアウトを編集し、カードにメンバーを割り当てることができます。',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'システム全体の設定を管理し、プロジェクトオーナーとして行動できます。',\n      canOnlyViewBoard: 'ボードの読み取りのみ可能です。',\n      cardActions_title: 'カードのアクション',\n      cardNotFound_title: 'カードが見つかりません',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'このリストのカードはすべてのボードメンバーが利用できます。',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'このリストのカードは完了しており、アーカイブの準備ができています。',\n      cardsOnThisListAreReadyToBeWorkedOn: 'このリストのカードは作業準備ができています。',\n      clickHereOrRefreshPageToUpdate: '<0>ここをクリック</0>するかページを更新してください',\n      clientHostnameInEhlo: 'EHLOでのクライアントホスト名',\n      closed: '閉じる',\n      color: '色',\n      comments: 'コメント',\n      contentExceedsLimit: 'コンテンツが{{limit}}を超えています',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'この添付ファイルのコンテンツは表示するには大きすぎます。',\n      copy_inline: 'コピー',\n      createBoard_title: 'ボードを作成',\n      createCustomFieldGroup_title: 'カスタムフィールドグループを作成',\n      createLabel_title: 'ラベルを作成',\n      createNewOneOrSelectExistingOne: '新規作成もしくは<br />既存のものから選択。',\n      createProject_title: 'プロジェクトを作成',\n      createTextFile_title: 'テキストファイルを作成',\n      creator: '作成者',\n      currentPassword: '現在のパスワード',\n      currentUser: '現在のユーザー',\n      customFieldGroup_title: 'カスタムフィールドグループ',\n      customFieldGroups_title: 'カスタムフィールドグループ',\n      customField_title: 'カスタムフィールド',\n      customFields_title: 'カスタムフィールド',\n      customerPanel_title: '顧客パネル',\n      dangerZone_title: '危険ゾーン',\n      date: '日付',\n      deactivateUser_title: 'ユーザーを非アクティブにする',\n      defaultCardType_title: 'デフォルトカードタイプ',\n      defaultFrom: 'デフォルト送信者',\n      defaultView_title: 'デフォルトビュー',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'このプロジェクトを削除するには、すべてのボードを削除してください',\n      deleteApiKey_title: 'APIキーを削除',\n      deleteAttachment_title: '添付ファイルを削除',\n      deleteBackgroundImage_title: '背景画像を削除',\n      deleteBoard_title: 'ボードを削除',\n      deleteCardForever_title: 'カードを完全に削除',\n      deleteCard_title: 'カードを削除',\n      deleteComment_title: 'コメントを削除',\n      deleteCustomFieldGroup_title: 'カスタムフィールドグループを削除',\n      deleteCustomField_title: 'カスタムフィールドを削除',\n      deleteLabel_title: 'ラベルを削除',\n      deleteList_title: 'リストを削除',\n      deleteNotificationService_title: '通知サービスを削除',\n      deleteProject_title: 'プロジェクトを削除',\n      deleteTaskList_title: 'タスクリストを削除',\n      deleteTask_title: 'タスクを削除',\n      deleteUser_title: 'ユーザーを削除',\n      deleteWebhook_title: 'Webhookを削除',\n      deletedUser_title: '削除されたユーザー',\n      description: '説明',\n      display: '表示',\n      displayCardAges: 'カードの経過時間を表示',\n      dropFileToUpload: 'ファイルをドロップしてアップロード',\n      dueDate_title: '期限',\n      dynamicAndUnevenlySpacedLayout: '動的で不均等な間隔のレイアウト。',\n      editAttachment_title: '添付ファイルを編集',\n      editAvatar_title: 'アバターを編集',\n      editColor_title: '色を編集',\n      editCustomFieldGroup_title: 'カスタムフィールドグループを編集',\n      editCustomField_title: 'カスタムフィールドを編集',\n      editDueDate_title: '期限を編集',\n      editEmail_title: 'Eメールを編集',\n      editInformation_title: '情報を編集',\n      editLabel_title: 'ラベルを編集',\n      editPassword_title: 'パスワードを編集',\n      editPermissions_title: '権限を編集',\n      editRole_title: '役割を編集',\n      editStopwatch_title: 'タイマーを編集',\n      editType_title: 'タイプを編集',\n      editUsername_title: 'ユーザー名を編集',\n      editor: 'エディター',\n      editors: 'エディター',\n      email: 'Eメール',\n      emptyTrash_title: 'ゴミ箱を空にする',\n      enterCardTitle: 'カードのタイトルを入力…',\n      enterDescription: '説明を入力…',\n      enterFilename: 'ファイル名を入力',\n      enterListTitle: 'リストのタイトルを入力…',\n      enterTaskDescription: 'タスクの説明を入力…',\n      events: 'イベント',\n      excludedEvents: '除外されたイベント',\n      expandTaskListsByDefault: 'デフォルトでタスクリストを展開',\n      filterByLabels_title: 'ラベルで絞り込む',\n      filterByMembers_title: 'メンバーで絞り込む',\n      forPersonalProjects: '個人プロジェクト用。',\n      forTeamBasedProjects: 'チームベースプロジェクト用。',\n      fromComputer_title: 'コンピューターから',\n      fromTrello: 'Trelloから',\n      fullKeyIsHiddenForSecurityReasons:\n        'セキュリティ上の理由により、完全なキーは非表示になっています。新しいキーを作成するには再生成してください。',\n      general: '一般',\n      gradients: 'グラデーション',\n      grid: 'グリッド',\n      hideCompletedTasks: '完了したタスクを非表示',\n      hideFromProjectListAndFavorites: 'プロジェクトリストとお気に入りから非表示',\n      host: 'ホスト',\n      hours: '時間',\n      identity: '身元',\n      importBoard_title: 'インポートボード',\n      information: '情報',\n      invalidCurrentPassword: '現在のパスワードが無効',\n      kanban: 'カンバン',\n      labels: 'ラベル',\n      language: '使用言語',\n      leaveBoard_title: 'ボードから退出',\n      leaveProject_title: 'プロジェクトから退出',\n      limitCardTypesToDefaultOne: 'カードタイプをデフォルトに制限',\n      linkToCard: 'カードへのリンク',\n      list: 'リスト',\n      listActions_title: 'アクションのリスト',\n      lists: 'リスト',\n      makeProjectPrivate_title: 'プロジェクトをプライベートにする',\n      makeProjectShared_title: 'プロジェクトを共有にする',\n      managers: 'マネージャー',\n      memberActions_title: 'メンバーアクション',\n      members: 'メンバー',\n      minutes: '分',\n      moreActions: 'その他のアクション',\n      moreActions_title: 'その他のアクション',\n      moveCard_title: 'カードを移動',\n      moveList_title: 'リストを移動',\n      myOwn_title: '自分の',\n      name: '名前',\n      newEmail: '新しいEメール',\n      newPassword: '新しいパスワード',\n      newUsername: '新しいユーザー名',\n      newVersionAvailable: '新しいバージョンが利用可能',\n      newestFirst: '新しい順',\n      noApiKeyCreated: 'APIキーが作成されていません。',\n      noBoards: 'ボードがありません',\n      noCardsFound: 'カードが見つかりません。',\n      noConnectionToServer: 'サーバーへ接続されていません',\n      noLists: 'リストがありません',\n      noProjects: 'プロジェクトがありません',\n      noUnreadNotifications: '未読の通知はありません。',\n      notifications: '通知',\n      oldestFirst: '古い順',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'このプロジェクトをプライベートにするには、マネージャーを1人だけ残す必要があります',\n      openBoard_title: 'ボードを開く',\n      optional_inline: '任意',\n      organization: '組織',\n      others: 'その他',\n      passwordIsSet: 'パスワードが設定されています',\n      phone: '電話番号',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKAは<1><0>Apprise</0></1>を使用して100以上の人気サービスに通知を送信します。',\n      port: 'ポート',\n      preferences: '環境設定',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'ヒント: Ctrl-V(MacではCmd-V)を押して、クリップボードから添付ファイルを追加します。',\n      private: 'プライベート',\n      project: 'プロジェクト',\n      projectNotFound_title: 'プロジェクトがありません',\n      projectOwner: 'プロジェクトオーナー',\n      referenceDataAndKnowledgeStorage: '参照データと知識の保存。',\n      regenerateApiKey_title: 'APIキーを再生成',\n      rejectUnauthorizedTlsCertificates: '未承認のTLS証明書を拒否',\n      removeManager_title: 'マネージャーを削除',\n      removeMember_title: 'メンバーを削除',\n      role: '役割',\n      saveThisKeyItWillNotBeShownAgain: 'このキーを保存してください。再表示されません！',\n      searchCards: 'カードを検索...',\n      searchCustomFieldGroups: 'カスタムフィールドグループを検索...',\n      searchCustomFields: 'カスタムフィールドを検索...',\n      searchLabels: 'ラベルから探す…',\n      searchLists: 'リストを検索...',\n      searchMembers: 'メンバーから探す…',\n      searchProjects: 'プロジェクトを検索...',\n      searchUsers: 'ユーザーから探す…',\n      seconds: '秒',\n      selectAssignee_title: '担当者を選択',\n      selectBoard: 'ボードを選択',\n      selectList: 'リストを選択',\n      selectListToRestoreThisCard: 'このカードを復元するリストを選択',\n      selectOrder_title: '順序を選択',\n      selectPermissions_title: '権限を選択',\n      selectProject: 'プロジェクトを選択',\n      selectRole_title: '役割を選択',\n      selectType_title: 'タイプを選択',\n      sequentialDisplayOfCards: 'カードの順次表示。',\n      settings: '設定',\n      shared: '共有',\n      sharedWithMe_title: '共有されたもの',\n      showOnFrontOfCard: 'カードの前面に表示',\n      smtp: 'SMTP',\n      sortList_title: 'リストを並び替え',\n      sourceCardIsNoLongerAvailableForCopying: 'ソースカードはコピーできなくなりました。',\n      sourceCardIsNoLongerAvailableForMoving: 'ソースカードは移動できなくなりました。',\n      stopwatch: 'タイマー',\n      story: 'ストーリー',\n      subscribeToCardWhenCommenting: 'コメント時にカードを購読',\n      subscribeToMyOwnCardsByDefault: '自分のカードをデフォルトで購読する',\n      taskActions_title: 'タスクのアクション',\n      taskAssignmentAndProjectCompletion: 'タスクの割り当てとプロジェクトの完了。',\n      taskListActions_title: 'タスクリストアクション',\n      taskList_title: 'タスクリスト',\n      team: 'チーム',\n      termsOfService_title: 'サービス利用規約',\n      testLog_title: 'テストログ',\n      thereIsNoPreviewAvailableForThisAttachment: 'この添付ファイルにはプレビューがありません。',\n      time: '時間',\n      title: 'タイトル',\n      trash: 'ゴミ箱',\n      trashHasBeenSuccessfullyEmptied: 'ゴミ箱が正常に空になりました。',\n      turnOffRecentCardHighlighting: '最近のカードのハイライトをオフにする',\n      typeNameToConfirm: '確認のため名前を入力してください。',\n      typeTitleToConfirm: '確認のためタイトルを入力してください。',\n      unlinkSso_title: 'SSOのリンク解除',\n      unsavedChanges: '未保存の変更',\n      uploadFailedFileIsTooBig: 'アップロード失敗 - ファイルが大きすぎます。',\n      uploadFailedNotEnoughStorageSpace: 'アップロード失敗 - ストレージ容量が不足しています。',\n      uploadedImages: 'アップロードされた画像',\n      url: 'URL',\n      useSecureConnection: 'セキュア接続を使用',\n      userActions_title: 'ユーザーのアクション',\n      userAddedCardToList: '<0>{{user}}</0>様が<2>{{card}}</2>を{{list}}に追加しました',\n      userAddedThisCardToList: '<0>{{user}}</0> 様が {{list}} をこのカードに追加しました',\n      userAddedUserToCard: '<0>{{actorUser}}</0>様が{{addedUser}}を<4>{{card}}</4>に追加しました',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0>様が{{addedUser}}をこのカードに追加しました',\n      userAddedYouToCard: '<0>{{user}}</0>様があなたを<2>{{card}}</2>に追加しました',\n      userCompletedTaskOnCard: '<0>{{user}}</0>様が<4>{{card}}</4>の{{task}}を完了しました',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0>様がこのカードの{{task}}を完了しました',\n      userJoinedCard: '<0>{{user}}</0>様が<2>{{card}}</2>に参加しました',\n      userJoinedThisCard: '<0>{{user}}</0>様がこのカードに参加しました',\n      userLeftCard: '<0>{{user}}</0>様が<2>{{card}}</2>から退出しました',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> 様が <2>{{card}}</2> に新しいコメント «{{comment}}» を残しました',\n      userLeftThisCard: '<0>{{user}}</0>様がこのカードから退出しました',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0>様が<4>{{card}}</4>の{{task}}を未完了にマークしました',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0>様がこのカードの{{task}}を未完了にマークしました',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0>様が<2>{{card}}</2>のコメント«{{comment}}»であなたをメンションしました',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> 様が <2>{{card}}</2> を {{fromList}} から {{toList}} に移動しました',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> 様がこのカードを {{fromList}} から {{toList}} に移動しました',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0>様が{{removedUser}}を<4>{{card}}</4>から削除しました',\n      userRemovedUserFromThisCard:\n        '<0>{{actorUser}}</0>様が{{removedUser}}をこのカードから削除しました',\n      username: 'ユーザー名',\n      users: 'ユーザー',\n      viewer: 'ビューア',\n      viewers: 'ビューア',\n      visualTaskManagementWithLists: 'リストによる視覚的タスク管理。',\n      webhooks: 'Webhook',\n      whatsNew_title: '新機能',\n      withoutBaseGroup: 'ベースグループなし',\n      writeComment: 'コメントを書く…',\n    },\n\n    action: {\n      activateUser: 'ユーザーをアクティブにする',\n      activateUser_title: 'ユーザーをアクティブにする',\n      addAnotherCard: '別のカードを追加',\n      addAnotherList: '別のリストを追加',\n      addAnotherTask: '別のタスクを追加',\n      addCard: 'カードを追加',\n      addCard_title: 'カードを追加',\n      addComment: 'コメントを追加',\n      addCustomField: 'カスタムフィールドを追加',\n      addCustomFieldGroup: 'カスタムフィールドグループを追加',\n      addList: 'リストを追加',\n      addMember: 'メンバーを追加',\n      addMoreDetailedDescription: 'より詳細な説明を追加',\n      addTask: 'タスクを追加',\n      addTaskList: 'タスクリストを追加',\n      addToCard: 'カードに追加',\n      addUser: 'ユーザーを追加',\n      addWebhook: 'Webhookを追加',\n      archive: 'アーカイブ',\n      archiveCard: 'カードをアーカイブ',\n      archiveCard_title: 'カードをアーカイブ',\n      archiveCards: 'カードをアーカイブ',\n      archiveCards_title: 'カードをアーカイブ',\n      assignAsOwner: 'オーナーとして割り当て',\n      cancel: 'キャンセル',\n      copy: 'コピー',\n      copyCard_title: 'カードをコピー',\n      createApiKey: 'APIキーを作成',\n      createBoard: 'ボードを作成',\n      createCustomFieldGroup: 'カスタムフィールドグループを作成',\n      createFile: 'ファイルを作成',\n      createLabel: 'ラベルを作成',\n      createNewLabel: '新しいラベルを作成',\n      createProject: 'プロジェクトを作成',\n      cut: '切り取り',\n      cutCard_title: 'カードを切り取り',\n      deactivateUser: 'ユーザーを非アクティブにする',\n      deactivateUser_title: 'ユーザーを非アクティブにする',\n      delete: '削除',\n      deleteApiKey: 'APIキーを削除',\n      deleteAttachment: '添付ファイルを削除',\n      deleteAvatar: 'アバターを削除',\n      deleteBackgroundImage: '背景画像を削除',\n      deleteBoard: 'ボードを削除',\n      deleteBoard_title: 'ボードを削除',\n      deleteCard: 'カードを削除',\n      deleteCardForever: 'カードを完全に削除',\n      deleteCard_title: 'カードを削除',\n      deleteComment: 'コメントを削除',\n      deleteCustomField: 'カスタムフィールドを削除',\n      deleteCustomFieldGroup: 'カスタムフィールドグループを削除',\n      deleteForever_title: '完全に削除',\n      deleteGroup: 'グループを削除',\n      deleteLabel: 'ラベルを削除',\n      deleteList: 'リストを削除',\n      deleteList_title: 'リストを削除',\n      deleteNotificationService: '通知サービスを削除',\n      deleteProject: 'プロジェクトを削除',\n      deleteProject_title: 'プロジェクトを削除',\n      deleteTask: 'タスクを削除',\n      deleteTaskList: 'タスクリストを削除',\n      deleteTask_title: 'タスクを削除',\n      deleteUser: 'ユーザーを削除',\n      deleteUser_title: 'ユーザーを削除',\n      deleteWebhook: 'Webhookを削除',\n      dismissAll: 'すべて閉じる',\n      download: 'ダウンロード',\n      duplicateCard_title: 'カードを複製',\n      edit: '編集',\n      editColor_title: '色を編集',\n      editDescription_title: '説明を編集',\n      editDueDate_title: '期限を編集',\n      editEmail_title: 'Eメールを編集',\n      editGroup: 'グループを編集',\n      editInformation_title: '情報を編集',\n      editPassword_title: 'パスワードの編集',\n      editPermissions: '権限を編集',\n      editRole_title: '役割を編集',\n      editStopwatch_title: 'タイマーの編集',\n      editTitle_title: 'タイトルの編集',\n      editType_title: 'タイプを編集',\n      editUsername_title: 'ユーザー名の編集',\n      emptyTrash: 'ゴミ箱を空にする',\n      emptyTrash_title: 'ゴミ箱を空にする',\n      import: 'インポート',\n      join: '参加',\n      leave: '退出',\n      leaveBoard: 'ボードから退出',\n      leaveProject: 'プロジェクトから退出',\n      logOut_title: 'ログアウト',\n      makeCover_title: 'カバーを作る',\n      makeProjectPrivate: 'プロジェクトをプライベートにする',\n      makeProjectPrivate_title: 'プロジェクトをプライベートにする',\n      makeProjectShared: 'プロジェクトを共有にする',\n      makeProjectShared_title: 'プロジェクトを共有にする',\n      move: '移動',\n      moveCard_title: 'カードを移動',\n      moveList_title: 'リストを移動',\n      regenerateApiKey: 'APIキーを再生成',\n      remove: '削除',\n      removeAssignee: '担当者を削除',\n      removeColor: '色を削除',\n      removeCover_title: 'カバーを削除',\n      removeFromBoard: 'ボードから削除',\n      removeFromProject: 'プロジェクトから削除',\n      removeManager: 'マネージャーを削除',\n      removeMember: 'メンバーを削除',\n      restoreToList: '{{list}}に復元',\n      returnToBoard: 'ボードに戻る',\n      save: '保存',\n      sendTestEmail: 'テストメールを送信',\n      showActive: 'アクティブを表示',\n      showAllAttachments: '全ての添付ファイルを表示する({{hidden}} 非表示)',\n      showCardsWithThisUser: 'このユーザーのカードを表示',\n      showDeactivated: '非アクティブを表示',\n      showFewerAttachments: 'テンプファイルの表示数を減らす',\n      showLess: '表示を減らす',\n      showMore: 'もっと表示',\n      sortList_title: 'リストを並び替え',\n      start: 'スタート',\n      stop: 'ストップ',\n      subscribe: '購読',\n      unlinkSso: 'SSOのリンクを解除',\n      unlinkSso_title: 'SSOのリンクを解除',\n      unsubscribe: '購読解除',\n      uploadNewAvatar: '新しいアバターをアップロード',\n      uploadNewImage: '新しい画像をアップロード',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ja-JP/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'ja-JP',\n  country: 'jp',\n  name: '日本語',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/ja-JP/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'アクティブユーザーの上限に達しました',\n      adminLoginRequiredToInitializeInstance:\n        'インスタンスを初期化するには管理者ログインが必要です',\n      emailAlreadyInUse: 'Eメールは既に使われています',\n      emailOrUsername: 'Eメールまたはユーザー名',\n      invalidCredentials: '認証情報が無効です',\n      invalidEmailOrUsername: 'Eメールまたはユーザー名が無効',\n      invalidPassword: 'パスワードが無効',\n      logIn_title: 'ログイン',\n      noInternetConnection: 'インターネットに接続されていません',\n      or: 'または',\n      pageNotFound_title: 'ページが見つかりません',\n      password: 'パスワード',\n      poweredByPlanka: '<1>PLANKA</1>で動作',\n      serverConnectionFailed: 'サーバーの接続に失敗',\n      unknownError: '不明なエラーです。後でもう一度試してください。',\n      useSingleSignOn: 'SSOを使用',\n      usernameAlreadyInUse: 'ユーザー名は既に使われています',\n      whoops_title: 'おっと！',\n    },\n\n    action: {\n      cancelAndClose: 'キャンセルして閉じる',\n      continue: '続行',\n      debugSso: 'SSOをデバッグ',\n      goBack: '戻る',\n      goHome: 'ホームへ',\n      logIn: 'ログイン',\n      logInWithSso: 'SSOでログイン',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ja-JP/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"これはタイトルのないテキストです。\\nタイトルとテキストの両方は、太字、斜体、色、\\n取り消し線、下線で強調できます。\",\n    \"text-with-head\": \"これはタイトル付きのテキストです。\\nタイトルとテキストの両方は、太字、斜体、色、\\n取り消し線、下線で強調できます。\",\n    \"heading\": \"タイトル\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Markdownエディターのエラー\",\n    \"settings_wysiwyg\": \"ビジュアルエディター (wysiwyg)\",\n    \"settings_markup\": \"Markdownマークアップ\",\n    \"markup_placeholder\": \"Markdownマークアップを入力してください...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"削除\",\n    \"empty_option\": \"一致するものがありません\",\n    \"show_line_numbers\": \"行番号\"\n  },\n  \"common\": {\n    \"delete\": \"削除\",\n    \"edit\": \"編集\",\n    \"toolbar_action_disabled\": \"互換性のないマークアップ要素\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"キャンセル\",\n    \"common_action_submit\": \"送信\",\n    \"common_action_upload\": \"選択\",\n    \"common_tab_attach\": \"デバイスから追加\",\n    \"common_tab_link\": \"リンクで追加\",\n    \"common_link\": \"リンク\",\n    \"common_sizes\": \"サイズ, px\",\n    \"image_name\": \"タイトル\",\n    \"image_link_href\": \"画像リンク\",\n    \"image_link_href_help\": \"画像リンクが導くアドレス。\",\n    \"image_alt\": \"代替テキスト\",\n    \"image_alt_help\": \"画像が読み込めない場合に表示されるテキストです。\",\n    \"image_upload_help\": \"JPEG、GIF、PNG画像（1MB以下）。\",\n    \"image_upload_failed\": \"画像の追加に失敗しました\",\n    \"image_size_width\": \"幅\",\n    \"image_size_height\": \"高さ\",\n    \"link_url_help\": \"リンクが導くアドレス。\",\n    \"link_text\": \"リンクテキスト\",\n    \"link_text_help\": \"リンクとして表示されるテキスト。\",\n    \"link_open_help\": \"リンクを新しいタブで開く\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"見出し\",\n    \"header_hint\": \"# あなたのテキスト\",\n    \"italic_title\": \"斜体\",\n    \"italic_hint\": \"_あなたのテキスト_\",\n    \"bold_title\": \"太字\",\n    \"bold_hint\": \"**あなたのテキスト**\",\n    \"strikethrough_title\": \"取り消し線\",\n    \"strikethrough_hint\": \"~~あなたのテキスト~~\",\n    \"blockquote_title\": \"引用\",\n    \"blockquote_hint\": \"> あなたのテキスト\",\n    \"code_title\": \"コード\",\n    \"code_hint\": \"```あなたのテキスト```\",\n    \"link_title\": \"リンク\",\n    \"link_hint\": \"[あなたのテキスト](url)\",\n    \"image_title\": \"画像\",\n    \"image_hint\": \"![あなたのテキスト](url)\",\n    \"list_title\": \"リスト項目\",\n    \"list_hint\": \"- あなたのテキスト\",\n    \"numbered-list_title\": \"番号付きリスト\",\n    \"numbered-list_hint\": \"1. あなたのテキスト\",\n    \"documentation\": \"ドキュメント\",\n    \"documentation_link\": \"https://diplodoc.com/docs/ja/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"太字\",\n    \"code\": \"コード\",\n    \"code_inline\": \"インラインコード\",\n    \"codeblock\": \"コードブロック\",\n    \"colorify\": \"テキストの色\",\n    \"colorify__color_blue\": \"青\",\n    \"colorify__color_default\": \"デフォルト\",\n    \"colorify__color_gray\": \"グレー\",\n    \"colorify__color_green\": \"緑\",\n    \"colorify__color_orange\": \"オレンジ\",\n    \"colorify__color_red\": \"赤\",\n    \"colorify__color_violet\": \"紫\",\n    \"colorify__color_yellow\": \"黄色\",\n    \"colorify__group_text\": \"テキスト\",\n    \"cut\": \"切り取り\",\n    \"emoji\": \"絵文字\",\n    \"emoji__hint\": \"絵文字はWYSIWYGまたはマークアップで追加できます\",\n    \"heading\": \"見出し\",\n    \"heading1\": \"見出し1\",\n    \"heading2\": \"見出し2\",\n    \"heading3\": \"見出し3\",\n    \"heading4\": \"見出し4\",\n    \"heading5\": \"見出し5\",\n    \"heading6\": \"見出し6\",\n    \"hrule\": \"区切り線\",\n    \"image\": \"画像\",\n    \"italic\": \"斜体\",\n    \"link\": \"リンク\",\n    \"list\": \"リスト\",\n    \"list__action_lift\": \"項目を上に移動\",\n    \"list__action_sink\": \"項目を下に移動\",\n    \"list_action_disabled\": \"リストのロジックに反します\",\n    \"mark\": \"マーク\",\n    \"mono\": \"等幅\",\n    \"more_action\": \"その他の操作\",\n    \"note\": \"ノート\",\n    \"olist\": \"番号付きリスト\",\n    \"quote\": \"引用\",\n    \"redo\": \"やり直し\",\n    \"strike\": \"取り消し線\",\n    \"table\": \"テーブル\",\n    \"text\": \"テキスト\",\n    \"ulist\": \"箇条書きリスト\",\n    \"underline\": \"下線\",\n    \"undo\": \"元に戻す\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"/ を入力してコマンドを使用...\",\n    \"checkbox\": \"タスクの説明を入力...\",\n    \"deflist_term\": \"用語\",\n    \"deflist_desc\": \"定義の説明\",\n    \"heading\": \"見出し\",\n    \"cut_title\": \"タイトル\",\n    \"cut_content\": \"クリックで表示される内容\",\n    \"note_title\": \"タイトル\",\n    \"note_content\": \"ノートの内容\",\n    \"table_cell\": \"セルの内容\",\n    \"select_filter\": \"言語を検索...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"大文字・小文字を区別\",\n    \"label_whole-word\": \"完全一致\",\n    \"title\": \"コード内検索\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"見つかりません\"\n  },\n  \"widgets\": {\n    \"image\": \"画像を追加\",\n    \"link\": \"リンクを追加\"\n  },\n  \"yfm-note\": {\n    \"info\": \"ノート\",\n    \"tip\": \"ヒント\",\n    \"warning\": \"警告\",\n    \"alert\": \"アラート\",\n    \"remove\": \"削除\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"前に列を追加\",\n    \"column.add.after\": \"後に列を追加\",\n    \"column.remove\": \"列を削除\",\n    \"column.remove.multiple\": \"列を削除\",\n    \"row.add.before\": \"前に行を追加\",\n    \"row.add.after\": \"後に行を追加\",\n    \"row.remove\": \"行を削除\",\n    \"row.remove.multiple\": \"行を削除\",\n    \"cells.clear\": \"セルをクリア\",\n    \"table.remove\": \"テーブルを削除\",\n    \"table.menu.cell.align.left\": \"セル内容を左揃え\",\n    \"table.menu.cell.align.right\": \"セル内容を右揃え\",\n    \"table.menu.cell.align.center\": \"セル内容を中央揃え\",\n    \"table.menu.row.add\": \"後に行を追加\",\n    \"table.menu.row.remove\": \"行を削除\",\n    \"table.menu.column.add\": \"後に列を追加\",\n    \"table.menu.column.remove\": \"列を削除\",\n    \"table.menu.convert.yfm\": \"YFMテーブルに変換\",\n    \"table.menu.table.remove\": \"テーブルを削除\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/ko-KR/core.js",
    "content": "import dateFns from 'date-fns/locale/ko';\nimport timeAgo from 'javascript-time-ago/locale/ko';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'yyyy년 M월 d일',\n    time: 'a h시 m분',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'M월 d일',\n    longDateTime: 'M월 d일 a h시 m분',\n    fullDate: 'yyyy년 M월 d일',\n    fullDateTime: 'yyyy년 M월 d일 a h시 m분',\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: '앱 소개',\n      aboutPlanka_title: 'PLANKA 소개',\n      accessToken: '액세스 토큰',\n      account: '계정',\n      actions: '작업',\n      activateUser_title: '사용자 활성화',\n      active: '활성',\n      addAttachment_title: '첨부 파일 추가',\n      addCustomFieldGroup_title: '사용자 정의 필드 그룹 추가',\n      addCustomField_title: '사용자 정의 필드 추가',\n      addManager_title: '관리자 추가',\n      addMember_title: '멤버 추가',\n      addTaskList_title: '작업 목록 추가',\n      addUser_title: '사용자 추가',\n      admin: '관리자',\n      administration: '관리',\n      all: '전체',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        '연결이 복구되면 모든 변경 사항이<br />자동으로 저장됩니다.',\n      alphabetically: '알파벳순',\n      alwaysDisplayCardCreator: '항상 카드 생성자 표시',\n      apiKeyCreated_title: 'API 키 생성됨',\n      apiKey_title: 'API 키',\n      archive: '보관',\n      archiveCard_title: '카드 보관',\n      archiveCards_title: '카드들 보관',\n      areYouSureYouWantToActivateThisUser: '이 사용자를 활성화하시겠습니까?',\n      areYouSureYouWantToArchiveCards: '카드들을 보관하시겠습니까?',\n      areYouSureYouWantToArchiveThisCard: '이 카드를 보관하시겠습니까?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        '이 프로젝트 관리자를 소유자로 지정하시겠습니까?',\n      areYouSureYouWantToDeactivateThisUser: '이 사용자를 비활성화하시겠습니까?',\n      areYouSureYouWantToDeleteThisApiKey: '이 API 키를 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisAttachment: '이 첨부 파일을 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisBackgroundImage: '이 배경 이미지를 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisBoard: '이 보드를 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisCard: '이 카드를 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisCardForever: '이 카드를 영구적으로 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisComment: '이 댓글을 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisCustomField: '이 사용자 정의 필드를 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup: '이 사용자 정의 필드 그룹을 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisLabel: '이 라벨을 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisList:\n        '이 목록을 삭제하시겠습니까? 모든 카드가 휴지통으로 이동됩니다.',\n      areYouSureYouWantToDeleteThisNotificationService: '이 알림 서비스를 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisProject: '이 프로젝트를 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisTask: '이 작업을 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisTaskList: '이 작업 목록을 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisUser: '이 사용자를 삭제하시겠습니까?',\n      areYouSureYouWantToDeleteThisWebhook: '이 웹훅을 삭제하시겠습니까?',\n      areYouSureYouWantToEmptyTrash: '휴지통을 비우시겠습니까?',\n      areYouSureYouWantToLeaveBoard: '이 보드를 떠나시겠습니까?',\n      areYouSureYouWantToLeaveProject: '이 프로젝트를 떠나시겠습니까?',\n      areYouSureYouWantToMakeThisProjectPrivate: '이 프로젝트를 비공개로 만드시겠습니까?',\n      areYouSureYouWantToMakeThisProjectShared: '이 프로젝트를 공유하시겠습니까?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        '이 API 키를 재생성하시겠습니까? 이전 키는 더 이상 작동하지 않습니다.',\n      areYouSureYouWantToRemoveThisManagerFromProject: '이 관리자를 프로젝트에서 제거하시겠습니까?',\n      areYouSureYouWantToRemoveThisMemberFromBoard: '이 멤버를 보드에서 제거하시겠습니까?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        '이 사용자로부터 SSO 연결을 해제하시겠습니까? 이렇게 하면 사용자가 비밀번호로 로그인할 수 있습니다.',\n      assignAsOwner_title: '소유자로 지정',\n      atLeastOneListMustBePresent: '최소 하나의 목록이 있어야 합니다',\n      attachment: '첨부 파일',\n      attachments: '첨부 파일들',\n      authentication: '인증',\n      background: '배경',\n      baseCustomFields_title: '기본 사용자 정의 필드',\n      baseGroup: '기본 그룹',\n      board: '보드',\n      boardActions_title: '보드 작업',\n      boardNotFound_title: '보드를 찾을 수 없음',\n      boardSubscribed: '보드 구독됨',\n      boardUser: '보드 사용자',\n      byCreationTime: '생성 시간순',\n      byDefault: '기본값',\n      byDueDate: '마감일순',\n      canBeInvitedToWorkInBoards: '보드에서 작업하도록 초대받을 수 있음.',\n      canComment: '댓글 작성 가능',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        '자신의 프로젝트를 만들고 다른 프로젝트에 초대받을 수 있음.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        '보드 레이아웃을 편집하고 카드에 멤버를 할당할 수 있음.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        '시스템 전체 설정을 관리하고 프로젝트 소유자 역할을 할 수 있음.',\n      canOnlyViewBoard: '보드를 보기만 할 수 있습니다.',\n      cardActions_title: '카드 작업',\n      cardNotFound_title: '카드를 찾을 수 없음',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        '이 목록의 카드들은 모든 보드 멤버가 사용할 수 있습니다.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        '이 목록의 카드들은 완료되어 보관할 준비가 되었습니다.',\n      cardsOnThisListAreReadyToBeWorkedOn: '이 목록의 카드들은 작업할 준비가 되었습니다.',\n      clickHereOrRefreshPageToUpdate:\n        '<0>여기를 클릭</0>하거나 페이지를 새로고침하여 업데이트하세요',\n      clientHostnameInEhlo: 'EHLO의 클라이언트 호스트명',\n      closed: '닫힘',\n      color: '색상',\n      comments: '댓글',\n      contentExceedsLimit: '내용이 {{limit}}을 초과했습니다',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        '이 첨부 파일의 내용이 너무 커서 표시할 수 없습니다.',\n      copy_inline: '복사',\n      createBoard_title: '보드 생성',\n      createCustomFieldGroup_title: '사용자 정의 필드 그룹 생성',\n      createLabel_title: '라벨 생성',\n      createNewOneOrSelectExistingOne: '새로 만들거나<br />기존 것을 선택하세요.',\n      createProject_title: '프로젝트 생성',\n      createTextFile_title: '텍스트 파일 생성',\n      creator: '생성자',\n      currentPassword: '현재 비밀번호',\n      currentUser: '현재 사용자',\n      customFieldGroup_title: '사용자 정의 필드 그룹',\n      customFieldGroups_title: '사용자 정의 필드 그룹들',\n      customField_title: '사용자 정의 필드',\n      customFields_title: '사용자 정의 필드들',\n      customerPanel_title: '고객 패널',\n      dangerZone_title: '위험 구역',\n      date: '날짜',\n      deactivateUser_title: '사용자 비활성화',\n      defaultCardType_title: '기본 카드 유형',\n      defaultFrom: '기본 발신자',\n      defaultView_title: '기본 보기',\n      deleteAllBoardsToBeAbleToDeleteThisProject: '이 프로젝트를 삭제하려면 모든 보드를 삭제하세요',\n      deleteApiKey_title: 'API 키 삭제',\n      deleteAttachment_title: '첨부 파일 삭제',\n      deleteBackgroundImage_title: '배경 이미지 삭제',\n      deleteBoard_title: '보드 삭제',\n      deleteCardForever_title: '카드 영구 삭제',\n      deleteCard_title: '카드 삭제',\n      deleteComment_title: '댓글 삭제',\n      deleteCustomFieldGroup_title: '사용자 정의 필드 그룹 삭제',\n      deleteCustomField_title: '사용자 정의 필드 삭제',\n      deleteLabel_title: '라벨 삭제',\n      deleteList_title: '목록 삭제',\n      deleteNotificationService_title: '알림 서비스 삭제',\n      deleteProject_title: '프로젝트 삭제',\n      deleteTaskList_title: '작업 목록 삭제',\n      deleteTask_title: '작업 삭제',\n      deleteUser_title: '사용자 삭제',\n      deleteWebhook_title: '웹훅 삭제',\n      deletedUser_title: '삭제된 사용자',\n      description: '설명',\n      display: '표시',\n      displayCardAges: '카드 경과 시간 표시',\n      dropFileToUpload: '업로드할 파일을 드롭하세요',\n      dueDate_title: '마감일',\n      dynamicAndUnevenlySpacedLayout: '동적이고 불균등한 간격의 레이아웃.',\n      editAttachment_title: '첨부 파일 편집',\n      editAvatar_title: '아바타 편집',\n      editColor_title: '색상 편집',\n      editCustomFieldGroup_title: '사용자 정의 필드 그룹 편집',\n      editCustomField_title: '사용자 정의 필드 편집',\n      editDueDate_title: '마감일 편집',\n      editEmail_title: '이메일 편집',\n      editInformation_title: '정보 편집',\n      editLabel_title: '라벨 편집',\n      editPassword_title: '비밀번호 편집',\n      editPermissions_title: '권한 편집',\n      editRole_title: '역할 편집',\n      editStopwatch_title: '스톱워치 편집',\n      editType_title: '유형 편집',\n      editUsername_title: '사용자 이름 편집',\n      editor: '편집기',\n      editors: '편집자들',\n      email: '이메일',\n      emptyTrash_title: '휴지통 비우기',\n      enterCardTitle: '카드 제목 입력...',\n      enterDescription: '설명 입력...',\n      enterFilename: '파일 이름 입력',\n      enterListTitle: '목록 제목 입력...',\n      enterTaskDescription: '작업 설명 입력...',\n      events: '이벤트',\n      excludedEvents: '제외된 이벤트',\n      expandTaskListsByDefault: '기본적으로 작업 목록 확장',\n      filterByLabels_title: '라벨별 필터링',\n      filterByMembers_title: '멤버별 필터링',\n      forPersonalProjects: '개인 프로젝트용.',\n      forTeamBasedProjects: '팀 기반 프로젝트용.',\n      fromComputer_title: '컴퓨터에서',\n      fromTrello: 'Trello에서',\n      fullKeyIsHiddenForSecurityReasons:\n        '보안상의 이유로 전체 키가 숨겨져 있습니다. 새 키를 만들려면 재생성하십시오.',\n      general: '일반',\n      gradients: '그라데이션',\n      grid: '격자',\n      hideCompletedTasks: '완료된 작업 숨기기',\n      hideFromProjectListAndFavorites: '프로젝트 목록과 즐겨찾기에서 숨기기',\n      host: '호스트',\n      hours: '시간',\n      identity: '신원',\n      importBoard_title: '보드 가져오기',\n      information: '정보',\n      invalidCurrentPassword: '잘못된 현재 비밀번호',\n      kanban: '칸반',\n      labels: '라벨',\n      language: '언어',\n      leaveBoard_title: '보드 떠나기',\n      leaveProject_title: '프로젝트 떠나기',\n      limitCardTypesToDefaultOne: '카드 유형을 기본값으로 제한',\n      linkToCard: '카드 링크',\n      list: '목록',\n      listActions_title: '목록 작업',\n      lists: '목록들',\n      makeProjectPrivate_title: '프로젝트 비공개로 만들기',\n      makeProjectShared_title: '프로젝트 공유하기',\n      managers: '관리자',\n      memberActions_title: '멤버 작업',\n      members: '멤버',\n      minutes: '분',\n      moreActions: '더 많은 작업',\n      moreActions_title: '더 많은 작업',\n      moveCard_title: '카드 이동',\n      moveList_title: '목록 이동',\n      myOwn_title: '내 소유',\n      name: '이름',\n      newEmail: '새 이메일',\n      newPassword: '새 비밀번호',\n      newUsername: '새 사용자 이름',\n      newVersionAvailable: '새 버전 사용 가능',\n      newestFirst: '최신순',\n      noApiKeyCreated: '생성된 API 키가 없습니다.',\n      noBoards: '보드 없음',\n      noCardsFound: '카드를 찾을 수 없음.',\n      noConnectionToServer: '서버에 연결되지 않음',\n      noLists: '목록 없음',\n      noProjects: '프로젝트 없음',\n      noUnreadNotifications: '읽지 않은 알림 없음.',\n      notifications: '알림',\n      oldestFirst: '오래된 순',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        '이 프로젝트를 비공개로 만들려면 관리자가 한 명만 남아있어야 합니다',\n      openBoard_title: '보드 열기',\n      optional_inline: '선택 사항',\n      organization: '조직',\n      others: '기타',\n      passwordIsSet: '비밀번호가 설정됨',\n      phone: '전화',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA는 <1><0>Apprise</0></1>를 사용하여 100개 이상의 인기 서비스에 알림을 보냅니다.',\n      port: '포트',\n      preferences: '환경 설정',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        '팁: Ctrl-V (Mac에서는 Cmd-V)를 눌러 클립보드에서 첨부 파일을 추가하세요.',\n      private: '비공개',\n      project: '프로젝트',\n      projectNotFound_title: '프로젝트를 찾을 수 없음',\n      projectOwner: '프로젝트 소유자',\n      referenceDataAndKnowledgeStorage: '참조 데이터 및 지식 저장소.',\n      regenerateApiKey_title: 'API 키 재생성',\n      rejectUnauthorizedTlsCertificates: '승인되지 않은 TLS 인증서 거부',\n      removeManager_title: '관리자 제거',\n      removeMember_title: '멤버 제거',\n      role: '역할',\n      saveThisKeyItWillNotBeShownAgain: '이 키를 저장하세요. 다시 표시되지 않습니다!',\n      searchCards: '카드 검색...',\n      searchCustomFieldGroups: '사용자 정의 필드 그룹 검색...',\n      searchCustomFields: '사용자 정의 필드 검색...',\n      searchLabels: '라벨 검색...',\n      searchLists: '목록 검색...',\n      searchMembers: '멤버 검색...',\n      searchProjects: '프로젝트 검색...',\n      searchUsers: '사용자 검색...',\n      seconds: '초',\n      selectAssignee_title: '담당자 선택',\n      selectBoard: '보드 선택',\n      selectList: '목록 선택',\n      selectListToRestoreThisCard: '이 카드를 복원할 목록을 선택하세요',\n      selectOrder_title: '순서 선택',\n      selectPermissions_title: '권한 선택',\n      selectProject: '프로젝트 선택',\n      selectRole_title: '역할 선택',\n      selectType_title: '유형 선택',\n      sequentialDisplayOfCards: '카드의 순차적 표시.',\n      settings: '설정',\n      shared: '공유됨',\n      sharedWithMe_title: '나와 공유됨',\n      showOnFrontOfCard: '카드 앞면에 표시',\n      smtp: 'SMTP',\n      sortList_title: '목록 정렬',\n      sourceCardIsNoLongerAvailableForCopying: '원본 카드를 더 이상 복사할 수 없습니다.',\n      sourceCardIsNoLongerAvailableForMoving: '원본 카드를 더 이상 이동할 수 없습니다.',\n      stopwatch: '스톱워치',\n      story: '스토리',\n      subscribeToCardWhenCommenting: '댓글 작성 시 카드 구독',\n      subscribeToMyOwnCardsByDefault: '기본적으로 내 카드 구독',\n      taskActions_title: '작업 작업',\n      taskAssignmentAndProjectCompletion: '작업 할당 및 프로젝트 완료.',\n      taskListActions_title: '작업 목록 작업',\n      taskList_title: '작업 목록',\n      team: '팀',\n      termsOfService_title: '서비스 약관',\n      testLog_title: '테스트 로그',\n      thereIsNoPreviewAvailableForThisAttachment:\n        '이 첨부 파일에 대한 미리보기를 사용할 수 없습니다.',\n      time: '시간',\n      title: '제목',\n      trash: '휴지통',\n      trashHasBeenSuccessfullyEmptied: '휴지통이 성공적으로 비워졌습니다.',\n      turnOffRecentCardHighlighting: '최근 카드 강조 표시 끄기',\n      typeNameToConfirm: '확인하려면 이름을 입력하세요.',\n      typeTitleToConfirm: '확인하려면 제목을 입력하세요.',\n      unlinkSso_title: 'SSO 연결 해제',\n      unsavedChanges: '저장되지 않은 변경사항',\n      uploadFailedFileIsTooBig: '업로드 실패: 파일이 너무 큽니다.',\n      uploadFailedNotEnoughStorageSpace: '업로드 실패: 저장 공간이 부족합니다.',\n      uploadedImages: '업로드된 이미지',\n      url: 'URL',\n      useSecureConnection: '보안 연결 사용',\n      userActions_title: '사용자 작업',\n      userAddedCardToList: '<0>{{user}}</0>님이 <2>{{card}}</2>를 {{list}}에 추가했습니다',\n      userAddedThisCardToList: '<0>{{user}}</0>님이 이 카드를 {{list}}에 추가했습니다',\n      userAddedUserToCard:\n        '<0>{{actorUser}}</0>님이 {{addedUser}}님을 <4>{{card}}</4>에 추가했습니다',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0>님이 {{addedUser}}님을 이 카드에 추가했습니다',\n      userAddedYouToCard: '<0>{{user}}</0>님이 당신을 <2>{{card}}</2>에 추가했습니다',\n      userCompletedTaskOnCard:\n        '<0>{{user}}</0>님이 <4>{{card}}</4>에서 {{task}} 작업을 완료했습니다',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0>님이 이 카드에서 {{task}} 작업을 완료했습니다',\n      userJoinedCard: '<0>{{user}}</0>님이 <2>{{card}}</2>에 참여했습니다',\n      userJoinedThisCard: '<0>{{user}}</0>님이 이 카드에 참여했습니다',\n      userLeftCard: '<0>{{user}}</0>님이 <2>{{card}}</2>를 떠났습니다',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0>님이 <2>{{card}}</2>에 새 댓글 «{{comment}}»을 남겼습니다',\n      userLeftThisCard: '<0>{{user}}</0>님이 이 카드를 떠났습니다',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0>님이 <4>{{card}}</4>에서 {{task}} 작업을 미완료로 표시했습니다',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0>님이 이 카드에서 {{task}} 작업을 미완료로 표시했습니다',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0>님이 <2>{{card}}</2>의 댓글 «{{comment}}»에서 당신을 언급했습니다',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0>님이 <2>{{card}}</2>를 {{fromList}}에서 {{toList}}로 이동했습니다',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0>님이 {{fromList}}에서 {{toList}}로 이 카드를 옮겼습니다',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0>님이 {{removedUser}}님을 <4>{{card}}</4>에서 제거했습니다',\n      userRemovedUserFromThisCard:\n        '<0>{{actorUser}}</0>님이 {{removedUser}}님을 이 카드에서 제거했습니다',\n      username: '사용자 이름',\n      users: '사용자들',\n      viewer: '뷰어',\n      viewers: '뷰어들',\n      visualTaskManagementWithLists: '목록을 통한 시각적 작업 관리.',\n      webhooks: '웹훅',\n      whatsNew_title: '새로운 소식',\n      withoutBaseGroup: '기본 그룹 없음',\n      writeComment: '댓글 작성...',\n    },\n\n    action: {\n      activateUser: '사용자 활성화',\n      activateUser_title: '사용자 활성화',\n      addAnotherCard: '카드 추가',\n      addAnotherList: '목록 추가',\n      addAnotherTask: '작업 추가',\n      addCard: '카드 추가',\n      addCard_title: '카드 추가',\n      addComment: '댓글 추가',\n      addCustomField: '사용자 정의 필드 추가',\n      addCustomFieldGroup: '사용자 정의 필드 그룹 추가',\n      addList: '목록 추가',\n      addMember: '멤버 추가',\n      addMoreDetailedDescription: '더 자세한 설명 추가',\n      addTask: '작업 추가',\n      addTaskList: '작업 목록 추가',\n      addToCard: '카드에 추가',\n      addUser: '사용자 추가',\n      addWebhook: '웹훅 추가',\n      archive: '보관',\n      archiveCard: '카드 보관',\n      archiveCard_title: '카드 보관',\n      archiveCards: '카드들 보관',\n      archiveCards_title: '카드들 보관',\n      assignAsOwner: '소유자로 지정',\n      cancel: '취소',\n      copy: '복사',\n      copyCard_title: '카드 복사',\n      createApiKey: 'API 키 생성',\n      createBoard: '보드 생성',\n      createCustomFieldGroup: '사용자 정의 필드 그룹 생성',\n      createFile: '파일 생성',\n      createLabel: '라벨 생성',\n      createNewLabel: '새 라벨 생성',\n      createProject: '프로젝트 생성',\n      cut: '잘라내기',\n      cutCard_title: '카드 잘라내기',\n      deactivateUser: '사용자 비활성화',\n      deactivateUser_title: '사용자 비활성화',\n      delete: '삭제',\n      deleteApiKey: 'API 키 삭제',\n      deleteAttachment: '첨부 파일 삭제',\n      deleteAvatar: '아바타 삭제',\n      deleteBackgroundImage: '배경 이미지 삭제',\n      deleteBoard: '보드 삭제',\n      deleteBoard_title: '보드 삭제',\n      deleteCard: '카드 삭제',\n      deleteCardForever: '카드 영구 삭제',\n      deleteCard_title: '카드 삭제',\n      deleteComment: '댓글 삭제',\n      deleteCustomField: '사용자 정의 필드 삭제',\n      deleteCustomFieldGroup: '사용자 정의 필드 그룹 삭제',\n      deleteForever_title: '영구 삭제',\n      deleteGroup: '그룹 삭제',\n      deleteLabel: '라벨 삭제',\n      deleteList: '목록 삭제',\n      deleteList_title: '목록 삭제',\n      deleteNotificationService: '알림 서비스 삭제',\n      deleteProject: '프로젝트 삭제',\n      deleteProject_title: '프로젝트 삭제',\n      deleteTask: '작업 삭제',\n      deleteTaskList: '작업 목록 삭제',\n      deleteTask_title: '작업 삭제',\n      deleteUser: '사용자 삭제',\n      deleteUser_title: '사용자 삭제',\n      deleteWebhook: '웹훅 삭제',\n      dismissAll: '모두 해제',\n      download: '다운로드',\n      duplicateCard_title: '카드 복제',\n      edit: '편집',\n      editColor_title: '색상 편집',\n      editDescription_title: '설명 편집',\n      editDueDate_title: '마감일 편집',\n      editEmail_title: '이메일 편집',\n      editGroup: '그룹 편집',\n      editInformation_title: '정보 편집',\n      editPassword_title: '비밀번호 편집',\n      editPermissions: '권한 편집',\n      editRole_title: '역할 편집',\n      editStopwatch_title: '스톱워치 편집',\n      editTitle_title: '제목 편집',\n      editType_title: '유형 편집',\n      editUsername_title: '사용자 이름 편집',\n      emptyTrash: '휴지통 비우기',\n      emptyTrash_title: '휴지통 비우기',\n      import: '가져오기',\n      join: '참여',\n      leave: '떠나기',\n      leaveBoard: '보드 떠나기',\n      leaveProject: '프로젝트 떠나기',\n      logOut_title: '로그아웃',\n      makeCover_title: '커버 만들기',\n      makeProjectPrivate: '프로젝트 비공개로 만들기',\n      makeProjectPrivate_title: '프로젝트 비공개로 만들기',\n      makeProjectShared: '프로젝트 공유하기',\n      makeProjectShared_title: '프로젝트 공유하기',\n      move: '이동',\n      moveCard_title: '카드 이동',\n      moveList_title: '목록 이동',\n      regenerateApiKey: 'API 키 재생성',\n      remove: '제거',\n      removeAssignee: '담당자 제거',\n      removeColor: '색상 제거',\n      removeCover_title: '커버 제거',\n      removeFromBoard: '보드에서 제거',\n      removeFromProject: '프로젝트에서 제거',\n      removeManager: '관리자 제거',\n      removeMember: '멤버 제거',\n      restoreToList: '{{list}}로 복원',\n      returnToBoard: '보드로 돌아가기',\n      save: '저장',\n      sendTestEmail: '테스트 이메일 보내기',\n      showActive: '활성 표시',\n      showAllAttachments: '모든 첨부 파일 보기 ({{hidden}} 숨김)',\n      showCardsWithThisUser: '이 사용자의 카드 표시',\n      showDeactivated: '비활성화된 항목 표시',\n      showFewerAttachments: '첨부 파일 적게 보기',\n      showLess: '적게 표시',\n      showMore: '더 표시',\n      sortList_title: '목록 정렬',\n      start: '시작',\n      stop: '중지',\n      subscribe: '구독',\n      unlinkSso: 'SSO 연결 해제',\n      unlinkSso_title: 'SSO 연결 해제',\n      unsubscribe: '구독 취소',\n      uploadNewAvatar: '새 아바타 업로드',\n      uploadNewImage: '새 이미지 업로드',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ko-KR/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'ko-KR',\n  country: 'kr',\n  name: '한국어',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/ko-KR/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: '활성 사용자 한도에 도달했습니다',\n      adminLoginRequiredToInitializeInstance: '인스턴스 초기화를 위해 관리자 로그인이 필요합니다',\n      emailAlreadyInUse: '이미 사용 중인 이메일',\n      emailOrUsername: '이메일 또는 사용자 이름',\n      invalidCredentials: '잘못된 자격 증명',\n      invalidEmailOrUsername: '잘못된 이메일 또는 사용자 이름',\n      invalidPassword: '잘못된 비밀번호',\n      logIn_title: '로그인',\n      noInternetConnection: '인터넷 연결 없음',\n      or: '또는',\n      pageNotFound_title: '페이지를 찾을 수 없음',\n      password: '비밀번호',\n      poweredByPlanka: '<1>PLANKA</1>로 구동됨',\n      serverConnectionFailed: '서버 연결 실패',\n      unknownError: '알 수 없는 오류, 나중에 다시 시도하세요',\n      useSingleSignOn: 'Single Sign-On(SSO) 사용',\n      usernameAlreadyInUse: '이미 사용 중인 사용자 이름',\n      whoops_title: '앗!',\n    },\n\n    action: {\n      cancelAndClose: '취소 후 닫기',\n      continue: '계속',\n      debugSso: 'SSO 디버그',\n      goBack: '뒤로 가기',\n      goHome: '홈으로 가기',\n      logIn: '로그인',\n      logInWithSso: 'SSO로 로그인',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ko-KR/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"제목이 없는 텍스트입니다.\\n제목과 텍스트 모두\\n굵게, 기울임, 색상,\\n취소선, 밑줄로 강조할 수 있습니다.\",\n    \"text-with-head\": \"제목이 있는 텍스트입니다.\\n제목과 텍스트 모두\\n굵게, 기울임, 색상,\\n취소선, 밑줄로 강조할 수 있습니다.\",\n    \"heading\": \"제목\"\n  },\n  \"bundle\": {\n    \"error-title\": \"마크다운 에디터 오류\",\n    \"settings_wysiwyg\": \"비주얼 에디터 (wysiwyg)\",\n    \"settings_markup\": \"마크다운 마크업\",\n    \"markup_placeholder\": \"마크다운 마크업을 입력하세요...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"제거\",\n    \"empty_option\": \"일치하는 항목이 없습니다\",\n    \"show_line_numbers\": \"줄 번호\"\n  },\n  \"common\": {\n    \"delete\": \"삭제\",\n    \"edit\": \"편집\",\n    \"toolbar_action_disabled\": \"호환되지 않는 마크업 요소\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"취소\",\n    \"common_action_submit\": \"제출\",\n    \"common_action_upload\": \"선택\",\n    \"common_tab_attach\": \"기기에서 추가\",\n    \"common_tab_link\": \"링크로 추가\",\n    \"common_link\": \"링크\",\n    \"common_sizes\": \"크기, px\",\n    \"image_name\": \"제목\",\n    \"image_link_href\": \"이미지 링크\",\n    \"image_link_href_help\": \"이미지 링크가 연결되는 주소입니다.\",\n    \"image_alt\": \"대체 텍스트\",\n    \"image_alt_help\": \"이미지를 불러올 수 없을 때 표시되는 텍스트입니다.\",\n    \"image_upload_help\": \"JPEG, GIF 또는 PNG 이미지(1MB 이하).\",\n    \"image_upload_failed\": \"이미지 추가 실패\",\n    \"image_size_width\": \"너비\",\n    \"image_size_height\": \"높이\",\n    \"link_url_help\": \"링크가 연결되는 주소입니다.\",\n    \"link_text\": \"링크 텍스트\",\n    \"link_text_help\": \"링크로 표시되는 텍스트입니다.\",\n    \"link_open_help\": \"새 탭에서 링크 열기\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"헤더\",\n    \"header_hint\": \"# 텍스트 입력\",\n    \"italic_title\": \"기울임\",\n    \"italic_hint\": \"_텍스트 입력_\",\n    \"bold_title\": \"굵게\",\n    \"bold_hint\": \"**텍스트 입력**\",\n    \"strikethrough_title\": \"취소선\",\n    \"strikethrough_hint\": \"~~텍스트 입력~~\",\n    \"blockquote_title\": \"인용구\",\n    \"blockquote_hint\": \"> 텍스트 입력\",\n    \"code_title\": \"코드\",\n    \"code_hint\": \"```텍스트 입력```\",\n    \"link_title\": \"링크\",\n    \"link_hint\": \"[텍스트 입력](url)\",\n    \"image_title\": \"이미지\",\n    \"image_hint\": \"![텍스트 입력](url)\",\n    \"list_title\": \"리스트 항목\",\n    \"list_hint\": \"- 텍스트 입력\",\n    \"numbered-list_title\": \"번호 매기기 리스트\",\n    \"numbered-list_hint\": \"1. 텍스트 입력\",\n    \"documentation\": \"문서\",\n    \"documentation_link\": \"https://diplodoc.com/docs/ko/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"굵게\",\n    \"code\": \"코드\",\n    \"code_inline\": \"인라인 코드\",\n    \"codeblock\": \"코드 블록\",\n    \"colorify\": \"텍스트 색상\",\n    \"colorify__color_blue\": \"파랑\",\n    \"colorify__color_default\": \"기본값\",\n    \"colorify__color_gray\": \"회색\",\n    \"colorify__color_green\": \"초록\",\n    \"colorify__color_orange\": \"주황\",\n    \"colorify__color_red\": \"빨강\",\n    \"colorify__color_violet\": \"보라\",\n    \"colorify__color_yellow\": \"노랑\",\n    \"colorify__group_text\": \"텍스트\",\n    \"cut\": \"잘라내기\",\n    \"emoji\": \"이모지\",\n    \"emoji__hint\": \"이모지는 WYSIWYG 또는 마크업으로 추가할 수 있습니다\",\n    \"heading\": \"헤더\",\n    \"heading1\": \"헤더 1\",\n    \"heading2\": \"헤더 2\",\n    \"heading3\": \"헤더 3\",\n    \"heading4\": \"헤더 4\",\n    \"heading5\": \"헤더 5\",\n    \"heading6\": \"헤더 6\",\n    \"hrule\": \"구분선\",\n    \"image\": \"이미지\",\n    \"italic\": \"기울임\",\n    \"link\": \"링크\",\n    \"list\": \"리스트\",\n    \"list__action_lift\": \"항목 위로 이동\",\n    \"list__action_sink\": \"항목 아래로 이동\",\n    \"list_action_disabled\": \"리스트 논리에 맞지 않습니다\",\n    \"mark\": \"마크\",\n    \"mono\": \"모노스페이스\",\n    \"more_action\": \"더 많은 작업\",\n    \"note\": \"노트\",\n    \"olist\": \"번호 매기기 리스트\",\n    \"quote\": \"인용구\",\n    \"redo\": \"다시 실행\",\n    \"strike\": \"취소선\",\n    \"table\": \"테이블\",\n    \"text\": \"텍스트\",\n    \"ulist\": \"글머리표 리스트\",\n    \"underline\": \"밑줄\",\n    \"undo\": \"실행 취소\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"/를 입력하여 명령어 사용...\",\n    \"checkbox\": \"작업 설명 입력...\",\n    \"deflist_term\": \"용어\",\n    \"deflist_desc\": \"정의 설명\",\n    \"heading\": \"헤더\",\n    \"cut_title\": \"제목\",\n    \"cut_content\": \"클릭 시 표시될 내용\",\n    \"note_title\": \"제목\",\n    \"note_content\": \"노트 내용\",\n    \"table_cell\": \"셀 내용\",\n    \"select_filter\": \"언어 검색...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"대소문자 구분\",\n    \"label_whole-word\": \"전체 단어\",\n    \"title\": \"코드에서 검색\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"찾을 수 없음\"\n  },\n  \"widgets\": {\n    \"image\": \"이미지 추가\",\n    \"link\": \"링크 추가\"\n  },\n  \"yfm-note\": {\n    \"info\": \"노트\",\n    \"tip\": \"팁\",\n    \"warning\": \"경고\",\n    \"alert\": \"알림\",\n    \"remove\": \"제거\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"앞에 열 추가\",\n    \"column.add.after\": \"뒤에 열 추가\",\n    \"column.remove\": \"열 제거\",\n    \"column.remove.multiple\": \"열 제거\",\n    \"row.add.before\": \"앞에 행 추가\",\n    \"row.add.after\": \"뒤에 행 추가\",\n    \"row.remove\": \"행 제거\",\n    \"row.remove.multiple\": \"행 제거\",\n    \"cells.clear\": \"셀 지우기\",\n    \"table.remove\": \"테이블 제거\",\n    \"table.menu.cell.align.left\": \"셀 내용을 왼쪽 정렬\",\n    \"table.menu.cell.align.right\": \"셀 내용을 오른쪽 정렬\",\n    \"table.menu.cell.align.center\": \"셀 내용을 가운데 정렬\",\n    \"table.menu.row.add\": \"뒤에 행 추가\",\n    \"table.menu.row.remove\": \"행 제거\",\n    \"table.menu.column.add\": \"뒤에 열 추가\",\n    \"table.menu.column.remove\": \"열 제거\",\n    \"table.menu.convert.yfm\": \"YFM 테이블로 변환\",\n    \"table.menu.table.remove\": \"테이블 제거\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/nl-NL/core.js",
    "content": "import dateFns from 'date-fns/locale/nl';\nimport timeAgo from 'javascript-time-ago/locale/nl';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd-M-yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'om' p\",\n    fullDate: 'd MMM y',\n    fullDateTime: \"d MMMM y 'om' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Over de app',\n      aboutPlanka_title: 'Over PLANKA',\n      accessToken: 'Toegangstoken',\n      account: 'Account',\n      actions: 'Acties',\n      activateUser_title: 'Gebruiker activeren',\n      active: 'Actief',\n      addAttachment_title: 'Bijlage toevoegen',\n      addCustomFieldGroup_title: 'Aangepaste veldgroep toevoegen',\n      addCustomField_title: 'Aangepast veld toevoegen',\n      addManager_title: 'Manager toevoegen',\n      addMember_title: 'Lid toevoegen',\n      addTaskList_title: 'Takenlijst toevoegen',\n      addUser_title: 'Gebruiker toevoegen',\n      admin: 'Beheerder',\n      administration: 'Beheer',\n      all: 'Alle',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Alle wijzigingen worden automatisch opgeslagen<br />nadat de verbinding is hersteld.',\n      alphabetically: 'Alfabetisch',\n      alwaysDisplayCardCreator: 'Kaartmaker altijd weergeven',\n      apiKeyCreated_title: 'API-sleutel aangemaakt',\n      apiKey_title: 'API-sleutel',\n      archive: 'Archief',\n      archiveCard_title: 'Kaart archiveren',\n      archiveCards_title: 'Kaarten archiveren',\n      areYouSureYouWantToActivateThisUser: 'Weet u zeker dat u deze gebruiker wilt activeren?',\n      areYouSureYouWantToArchiveCards: 'Weet u zeker dat u deze kaarten wilt archiveren?',\n      areYouSureYouWantToArchiveThisCard: 'Weet u zeker dat u deze kaart wilt archiveren?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Weet u zeker dat u deze projectmanager als eigenaar wilt toewijzen?',\n      areYouSureYouWantToDeactivateThisUser: 'Weet u zeker dat u deze gebruiker wilt deactiveren?',\n      areYouSureYouWantToDeleteThisApiKey:\n        'Weet je zeker dat je deze API-sleutel wilt verwijderen?',\n      areYouSureYouWantToDeleteThisAttachment: 'Weet u zeker dat u deze bijlage wilt verwijderen?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Weet u zeker dat u deze achtergrondafbeelding wilt verwijderen?',\n      areYouSureYouWantToDeleteThisBoard: 'Weet u zeker dat u dit bord wilt verwijderen?',\n      areYouSureYouWantToDeleteThisCard: 'Weet u zeker dat u deze kaart wilt verwijderen?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Weet u zeker dat u deze kaart permanent wilt verwijderen?',\n      areYouSureYouWantToDeleteThisComment: 'Weet u zeker dat u deze opmerking wilt verwijderen?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Weet u zeker dat u dit aangepaste veld wilt verwijderen?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Weet u zeker dat u deze aangepaste veldgroep wilt verwijderen?',\n      areYouSureYouWantToDeleteThisLabel: 'Weet u zeker dat u dit label wilt verwijderen?',\n      areYouSureYouWantToDeleteThisList:\n        'Weet u zeker dat u deze lijst wilt verwijderen? Alle kaarten worden naar de prullenbak verplaatst.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Weet u zeker dat u deze meldingsservice wilt verwijderen?',\n      areYouSureYouWantToDeleteThisProject: 'Weet u zeker dat u dit project wilt verwijderen?',\n      areYouSureYouWantToDeleteThisTask: 'Weet u zeker dat u deze taak wilt verwijderen?',\n      areYouSureYouWantToDeleteThisTaskList: 'Weet u zeker dat u deze takenlijst wilt verwijderen?',\n      areYouSureYouWantToDeleteThisUser: 'Weet u zeker dat u deze gebruiker wilt verwijderen?',\n      areYouSureYouWantToDeleteThisWebhook: 'Weet u zeker dat u deze webhook wilt verwijderen?',\n      areYouSureYouWantToEmptyTrash: 'Weet u zeker dat u de prullenbak wilt legen?',\n      areYouSureYouWantToLeaveBoard: 'Weet u zeker dat u het bord wilt verlaten?',\n      areYouSureYouWantToLeaveProject: 'Weet u zeker dat u het project wilt verlaten?',\n      areYouSureYouWantToMakeThisProjectPrivate: 'Weet u zeker dat u dit project privé wilt maken?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Weet u zeker dat u dit project gedeeld wilt maken?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Weet je zeker dat je deze API-sleutel opnieuw wilt genereren? De vorige sleutel werkt niet meer.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Weet u zeker dat u deze manager uit het project wilt verwijderen?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Weet u zeker dat u dit lid uit het bord wilt verwijderen?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Weet je zeker dat je SSO wilt ontkoppelen van deze gebruiker? Dit stelt de gebruiker in staat om in te loggen met een wachtwoord.',\n      assignAsOwner_title: 'Toewijzen als eigenaar',\n      atLeastOneListMustBePresent: 'Er moet ten minste één lijst aanwezig zijn',\n      attachment: 'Bijlage',\n      attachments: 'Bijlagen',\n      authentication: 'Authenticatie',\n      background: 'Achtergrond',\n      baseCustomFields_title: 'Basis aangepaste velden',\n      baseGroup: 'Basisgroep',\n      board: 'Bord',\n      boardActions_title: 'Bordacties',\n      boardNotFound_title: 'Bord niet gevonden',\n      boardSubscribed: 'Geabonneerd op bord',\n      boardUser: 'Bordgebruiker',\n      byCreationTime: 'Op aanmaaktijd',\n      byDefault: 'Standaard',\n      byDueDate: 'Op vervaldatum',\n      canBeInvitedToWorkInBoards: 'Kan worden uitgenodigd om aan borden te werken.',\n      canComment: 'Kan opmerking plaatsen',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Kan eigen projecten maken en worden uitgenodigd om aan anderen te werken.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Kan bordindeling bewerken en leden toewijzen aan kaarten.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Kan systeembrede instellingen beheren en optreden als projecteigenaar.',\n      canOnlyViewBoard: 'Kan alleen het bord bekijken.',\n      cardActions_title: 'Kaartacties',\n      cardNotFound_title: 'Kaart niet gevonden',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Kaarten op deze lijst zijn beschikbaar voor alle bordleden.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Kaarten op deze lijst zijn voltooid en klaar om te worden gearchiveerd.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Kaarten op deze lijst zijn klaar om aan te werken.',\n      clickHereOrRefreshPageToUpdate: '<0>Klik hier</0> of ververs de pagina om bij te werken.',\n      clientHostnameInEhlo: 'Client hostnaam in EHLO',\n      closed: 'Gesloten',\n      color: 'Kleur',\n      comments: 'Opmerkingen',\n      contentExceedsLimit: 'Inhoud overschrijdt {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'De inhoud van deze bijlage is te groot om weer te geven.',\n      copy_inline: 'kopie',\n      createBoard_title: 'Bord aanmaken',\n      createCustomFieldGroup_title: 'Aangepaste veldgroep maken',\n      createLabel_title: 'Label aanmaken',\n      createNewOneOrSelectExistingOne: 'Maak een nieuwe of selecteer een bestaande.',\n      createProject_title: 'Project aanmaken',\n      createTextFile_title: 'Tekstbestand aanmaken',\n      creator: 'Maker',\n      currentPassword: 'Huidig wachtwoord',\n      currentUser: 'Huidige gebruiker',\n      customFieldGroup_title: 'Aangepaste veldgroep',\n      customFieldGroups_title: 'Aangepaste veldgroepen',\n      customField_title: 'Aangepast veld',\n      customFields_title: 'Aangepaste velden',\n      customerPanel_title: 'Klantenpaneel',\n      dangerZone_title: 'Gevaarlijke zone',\n      date: 'Datum',\n      deactivateUser_title: 'Gebruiker deactiveren',\n      defaultCardType_title: 'Standaard kaarttype',\n      defaultFrom: 'Standaard \"van\"',\n      defaultView_title: 'Standaardweergave',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Verwijder alle borden om dit project te kunnen verwijderen',\n      deleteApiKey_title: 'API-sleutel verwijderen',\n      deleteAttachment_title: 'Bijlage verwijderen',\n      deleteBackgroundImage_title: 'Achtergrondafbeelding verwijderen',\n      deleteBoard_title: 'Bord verwijderen',\n      deleteCardForever_title: 'Kaart permanent verwijderen',\n      deleteCard_title: 'Kaart verwijderen',\n      deleteComment_title: 'Opmerking verwijderen',\n      deleteCustomFieldGroup_title: 'Aangepaste veldgroep verwijderen',\n      deleteCustomField_title: 'Aangepast veld verwijderen',\n      deleteLabel_title: 'Label verwijderen',\n      deleteList_title: 'Lijst verwijderen',\n      deleteNotificationService_title: 'Meldingsservice verwijderen',\n      deleteProject_title: 'Project verwijderen',\n      deleteTaskList_title: 'Takenlijst verwijderen',\n      deleteTask_title: 'Taak verwijderen',\n      deleteUser_title: 'Gebruiker verwijderen',\n      deleteWebhook_title: 'Webhook verwijderen',\n      deletedUser_title: 'Verwijderde gebruiker',\n      description: 'Beschrijving',\n      display: 'Weergave',\n      displayCardAges: 'Kaartleeftijd weergeven',\n      dropFileToUpload: 'Sleep bestand om te uploaden',\n      dueDate_title: 'Vervaldatum',\n      dynamicAndUnevenlySpacedLayout: 'Dynamische en ongelijk verdeelde indeling.',\n      editAttachment_title: 'Bijlage bewerken',\n      editAvatar_title: 'Avatar bewerken',\n      editColor_title: 'Kleur bewerken',\n      editCustomFieldGroup_title: 'Aangepaste veldgroep bewerken',\n      editCustomField_title: 'Aangepast veld bewerken',\n      editDueDate_title: 'Vervaldatum bewerken',\n      editEmail_title: 'E-mail bewerken',\n      editInformation_title: 'Informatie bewerken',\n      editLabel_title: 'Label bewerken',\n      editPassword_title: 'Wachtwoord bewerken',\n      editPermissions_title: 'Machtigingen bewerken',\n      editRole_title: 'Rol bewerken',\n      editStopwatch_title: 'Stopwatch bewerken',\n      editType_title: 'Type bewerken',\n      editUsername_title: 'Gebruikersnaam bewerken',\n      editor: 'Editor',\n      editors: 'Editors',\n      email: 'E-mail',\n      emptyTrash_title: 'Prullenbak legen',\n      enterCardTitle: 'Voer kaarttitel in...',\n      enterDescription: 'Beschrijving invoeren...',\n      enterFilename: 'Bestandsnaam invoeren',\n      enterListTitle: 'Voer lijsttitel in...',\n      enterTaskDescription: 'Taakbeschrijving invoeren...',\n      events: 'Gebeurtenissen',\n      excludedEvents: 'Uitgesloten gebeurtenissen',\n      expandTaskListsByDefault: 'Takenlijsten standaard uitklappen',\n      filterByLabels_title: 'Filteren op labels',\n      filterByMembers_title: 'Filteren op leden',\n      forPersonalProjects: 'Voor persoonlijke projecten.',\n      forTeamBasedProjects: 'Voor teamgebaseerde projecten.',\n      fromComputer_title: 'Van computer',\n      fromTrello: 'Van Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'De volledige sleutel is om veiligheidsredenen verborgen. Genereer deze opnieuw om een nieuwe te maken.',\n      general: 'Algemeen',\n      gradients: 'Verlopen',\n      grid: 'Raster',\n      hideCompletedTasks: 'Voltooide taken verbergen',\n      hideFromProjectListAndFavorites: 'Verbergen uit projectlijst en favorieten',\n      host: 'Host',\n      hours: 'Uren',\n      identity: 'Identiteit',\n      importBoard_title: 'Bord importeren',\n      information: 'Informatie',\n      invalidCurrentPassword: 'Ongeldig huidig wachtwoord',\n      kanban: 'Kanban',\n      labels: 'Labels',\n      language: 'Taal',\n      leaveBoard_title: 'Bord verlaten',\n      leaveProject_title: 'Project verlaten',\n      limitCardTypesToDefaultOne: 'Kaarttypes beperken tot standaardtype',\n      linkToCard: 'Link naar kaart',\n      list: 'Lijst',\n      listActions_title: 'Lijstacties',\n      lists: 'Lijsten',\n      makeProjectPrivate_title: 'Project privé maken',\n      makeProjectShared_title: 'Project gedeeld maken',\n      managers: 'Managers',\n      memberActions_title: 'Lidacties',\n      members: 'Leden',\n      minutes: 'Minuten',\n      moreActions: 'Meer acties',\n      moreActions_title: 'Meer acties',\n      moveCard_title: 'Kaart verplaatsen',\n      moveList_title: 'Lijst verplaatsen',\n      myOwn_title: 'Mijn eigen',\n      name: 'Naam',\n      newEmail: 'Nieuwe e-mail',\n      newPassword: 'Nieuw wachtwoord',\n      newUsername: 'Nieuwe gebruikersnaam',\n      newVersionAvailable: 'Nieuwe versie beschikbaar',\n      newestFirst: 'Nieuwste eerst',\n      noApiKeyCreated: 'Geen API-sleutel aangemaakt.',\n      noBoards: 'Geen borden',\n      noCardsFound: 'Geen kaarten gevonden.',\n      noConnectionToServer: 'Geen verbinding met server',\n      noLists: 'Geen lijsten',\n      noProjects: 'Geen projecten',\n      noUnreadNotifications: 'Geen ongelezen meldingen.',\n      notifications: 'Meldingen',\n      oldestFirst: 'Oudste eerst',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Slechts één manager moet overblijven om dit project privé te maken',\n      openBoard_title: 'Bord openen',\n      optional_inline: 'optioneel',\n      organization: 'Organisatie',\n      others: 'Anderen',\n      passwordIsSet: 'Wachtwoord is ingesteld',\n      phone: 'Telefoon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA gebruikt <1><0>Apprise</0></1> om meldingen naar meer dan 100 populaire services te sturen.',\n      port: 'Poort',\n      preferences: 'Voorkeuren',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Tip: druk op Ctrl-V (Cmd-V op Mac) om een bijlage van het klembord toe te voegen.',\n      private: 'Privé',\n      project: 'Project',\n      projectNotFound_title: 'Project niet gevonden',\n      projectOwner: 'Projecteigenaar',\n      referenceDataAndKnowledgeStorage: 'Referentiegegevens en kennisopslag.',\n      regenerateApiKey_title: 'API-sleutel opnieuw genereren',\n      rejectUnauthorizedTlsCertificates: 'Niet-geautoriseerde TLS-certificaten weigeren',\n      removeManager_title: 'Manager verwijderen',\n      removeMember_title: 'Lid verwijderen',\n      role: 'Rol',\n      saveThisKeyItWillNotBeShownAgain: 'Bewaar deze sleutel — deze wordt niet opnieuw getoond!',\n      searchCards: 'Kaarten zoeken...',\n      searchCustomFieldGroups: 'Aangepaste veldgroepen zoeken...',\n      searchCustomFields: 'Aangepaste velden zoeken...',\n      searchLabels: 'Labels zoeken...',\n      searchLists: 'Lijsten zoeken...',\n      searchMembers: 'Leden zoeken...',\n      searchProjects: 'Projecten zoeken...',\n      searchUsers: 'Gebruikers zoeken...',\n      seconds: 'Seconden',\n      selectAssignee_title: 'Toegewezene selecteren',\n      selectBoard: 'Bord selecteren',\n      selectList: 'Lijst selecteren',\n      selectListToRestoreThisCard: 'Selecteer een lijst om deze kaart te herstellen',\n      selectOrder_title: 'Volgorde selecteren',\n      selectPermissions_title: 'Machtigingen selecteren',\n      selectProject: 'Project selecteren',\n      selectRole_title: 'Rol selecteren',\n      selectType_title: 'Type selecteren',\n      sequentialDisplayOfCards: 'Opeenvolgende weergave van kaarten.',\n      settings: 'Instellingen',\n      shared: 'Gedeeld',\n      sharedWithMe_title: 'Met mij gedeeld',\n      showOnFrontOfCard: 'Tonen op voorkant van kaart',\n      smtp: 'SMTP',\n      sortList_title: 'Lijst sorteren',\n      sourceCardIsNoLongerAvailableForCopying: 'Bronkaart is niet meer beschikbaar voor kopiëren.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'Bronkaart is niet meer beschikbaar voor verplaatsen.',\n      stopwatch: 'Stopwatch',\n      story: 'Verhaal',\n      subscribeToCardWhenCommenting: 'Abonneren op kaart bij het plaatsen van commentaar',\n      subscribeToMyOwnCardsByDefault: 'Standaard abonneren op mijn eigen kaarten',\n      taskActions_title: 'Takenacties',\n      taskAssignmentAndProjectCompletion: 'Taaktoewijzing en projectvoltooiing.',\n      taskListActions_title: 'Takenlijstacties',\n      taskList_title: 'Takenlijst',\n      team: 'Team',\n      termsOfService_title: 'Servicevoorwaarden',\n      testLog_title: 'Testlog',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'Er is geen voorbeeld beschikbaar voor deze bijlage.',\n      time: 'Tijd',\n      title: 'Titel',\n      trash: 'Prullenbak',\n      trashHasBeenSuccessfullyEmptied: 'Prullenbak is succesvol geleegd.',\n      turnOffRecentCardHighlighting: 'Recente kaartmarkering uitschakelen',\n      typeNameToConfirm: 'Typ naam om te bevestigen.',\n      typeTitleToConfirm: 'Typ titel om te bevestigen.',\n      unlinkSso_title: 'SSO ontkoppelen',\n      unsavedChanges: 'Niet-opgeslagen wijzigingen',\n      uploadFailedFileIsTooBig: 'Upload mislukt: bestand is te groot.',\n      uploadFailedNotEnoughStorageSpace: 'Upload mislukt: niet genoeg opslagruimte.',\n      uploadedImages: 'Geüploade afbeeldingen',\n      url: 'URL',\n      useSecureConnection: 'Beveiligde verbinding gebruiken',\n      userActions_title: 'Gebruikersacties',\n      userAddedCardToList: '<0>{{user}}</0> heeft <2>{{card}}</2> toegevoegd aan {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> heeft deze kaart toegevoegd aan {{list}}',\n      userAddedUserToCard:\n        '<0>{{actorUser}}</0> heeft {{addedUser}} toegevoegd aan <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> heeft {{addedUser}} toegevoegd aan deze kaart',\n      userAddedYouToCard: '<0>{{user}}</0> heeft jou toegevoegd aan <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> heeft {{task}} voltooid op <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> heeft {{task}} voltooid op deze kaart',\n      userJoinedCard: '<0>{{user}}</0> is toegetreden tot <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> is toegetreden tot deze kaart',\n      userLeftCard: '<0>{{user}}</0> heeft <2>{{card}}</2> verlaten',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> heeft een nieuwe opmerking achtergelaten «{{comment}}» bij <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> heeft deze kaart verlaten',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> heeft {{task}} als onvolledig gemarkeerd op <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> heeft {{task}} als onvolledig gemarkeerd op deze kaart',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> heeft jou genoemd in een opmerking «{{comment}}» op <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> heeft <2>{{card}}</2> verplaatst van {{fromList}} naar {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> heeft deze kaart verplaatst van {{fromList}} naar {{toList}}',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0> heeft {{removedUser}} verwijderd van <4>{{card}}</4>',\n      userRemovedUserFromThisCard:\n        '<0>{{actorUser}}</0> heeft {{removedUser}} verwijderd van deze kaart',\n      username: 'Gebruikersnaam',\n      users: 'Gebruikers',\n      viewer: 'Kijker',\n      viewers: 'Kijkers',\n      visualTaskManagementWithLists: 'Visueel takenbeheer met lijsten.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Wat is er nieuw',\n      withoutBaseGroup: 'Zonder basisgroep',\n      writeComment: 'Schrijf een opmerking...',\n    },\n\n    action: {\n      activateUser: 'Gebruiker activeren',\n      activateUser_title: 'Gebruiker activeren',\n      addAnotherCard: 'Voeg nog een kaart toe',\n      addAnotherList: 'Voeg nog een lijst toe',\n      addAnotherTask: 'Voeg nog een taak toe',\n      addCard: 'Kaart toevoegen',\n      addCard_title: 'Kaart toevoegen',\n      addComment: 'Opmerking toevoegen',\n      addCustomField: 'Aangepast veld toevoegen',\n      addCustomFieldGroup: 'Aangepaste veldgroep toevoegen',\n      addList: 'Lijst toevoegen',\n      addMember: 'Lid toevoegen',\n      addMoreDetailedDescription: 'Meer gedetailleerde beschrijving toevoegen',\n      addTask: 'Taak toevoegen',\n      addTaskList: 'Takenlijst toevoegen',\n      addToCard: 'Toevoegen aan kaart',\n      addUser: 'Gebruiker toevoegen',\n      addWebhook: 'Webhook toevoegen',\n      archive: 'Archiveren',\n      archiveCard: 'Kaart archiveren',\n      archiveCard_title: 'Kaart archiveren',\n      archiveCards: 'Kaarten archiveren',\n      archiveCards_title: 'Kaarten archiveren',\n      assignAsOwner: 'Toewijzen als eigenaar',\n      cancel: 'Annuleren',\n      copy: 'Kopiëren',\n      copyCard_title: 'Kaart kopiëren',\n      createApiKey: 'API-sleutel aanmaken',\n      createBoard: 'Bord aanmaken',\n      createCustomFieldGroup: 'Aangepaste veldgroep aanmaken',\n      createFile: 'Bestand aanmaken',\n      createLabel: 'Label aanmaken',\n      createNewLabel: 'Nieuw label aanmaken',\n      createProject: 'Project aanmaken',\n      cut: 'Knippen',\n      cutCard_title: 'Kaart knippen',\n      deactivateUser: 'Gebruiker deactiveren',\n      deactivateUser_title: 'Gebruiker deactiveren',\n      delete: 'Verwijderen',\n      deleteApiKey: 'API-sleutel verwijderen',\n      deleteAttachment: 'Bijlage verwijderen',\n      deleteAvatar: 'Avatar verwijderen',\n      deleteBackgroundImage: 'Achtergrondafbeelding verwijderen',\n      deleteBoard: 'Bord verwijderen',\n      deleteBoard_title: 'Bord verwijderen',\n      deleteCard: 'Kaart verwijderen',\n      deleteCardForever: 'Kaart definitief verwijderen',\n      deleteCard_title: 'Kaart verwijderen',\n      deleteComment: 'Opmerking verwijderen',\n      deleteCustomField: 'Aangepast veld verwijderen',\n      deleteCustomFieldGroup: 'Aangepaste veldgroep verwijderen',\n      deleteForever_title: 'Definitief verwijderen',\n      deleteGroup: 'Groep verwijderen',\n      deleteLabel: 'Label verwijderen',\n      deleteList: 'Lijst verwijderen',\n      deleteList_title: 'Lijst verwijderen',\n      deleteNotificationService: 'Meldingsservice verwijderen',\n      deleteProject: 'Project verwijderen',\n      deleteProject_title: 'Project verwijderen',\n      deleteTask: 'Taak verwijderen',\n      deleteTaskList: 'Takenlijst verwijderen',\n      deleteTask_title: 'Taak verwijderen',\n      deleteUser: 'Gebruiker verwijderen',\n      deleteUser_title: 'Gebruiker verwijderen',\n      deleteWebhook: 'Webhook verwijderen',\n      dismissAll: 'Alles afwijzen',\n      download: 'Downloaden',\n      duplicateCard_title: 'Kaart dupliceren',\n      edit: 'Bewerken',\n      editColor_title: 'Kleur bewerken',\n      editDescription_title: 'Beschrijving bewerken',\n      editDueDate_title: 'Vervaldatum bewerken',\n      editEmail_title: 'E-mail bewerken',\n      editGroup: 'Groep bewerken',\n      editInformation_title: 'Informatie bewerken',\n      editPassword_title: 'Wachtwoord bewerken',\n      editPermissions: 'Machtigingen bewerken',\n      editRole_title: 'Rol bewerken',\n      editStopwatch_title: 'Stopwatch bewerken',\n      editTitle_title: 'Titel bewerken',\n      editType_title: 'Type bewerken',\n      editUsername_title: 'Gebruikersnaam bewerken',\n      emptyTrash: 'Prullenbak legen',\n      emptyTrash_title: 'Prullenbak legen',\n      import: 'Importeren',\n      join: 'Deelnemen',\n      leave: 'Verlaten',\n      leaveBoard: 'Bord verlaten',\n      leaveProject: 'Project verlaten',\n      logOut_title: 'Uitloggen',\n      makeCover_title: 'Omslag maken',\n      makeProjectPrivate: 'Project privé maken',\n      makeProjectPrivate_title: 'Project privé maken',\n      makeProjectShared: 'Project delen',\n      makeProjectShared_title: 'Project delen',\n      move: 'Verplaatsen',\n      moveCard_title: 'Kaart verplaatsen',\n      moveList_title: 'Lijst verplaatsen',\n      regenerateApiKey: 'API-sleutel opnieuw genereren',\n      remove: 'Verwijderen',\n      removeAssignee: 'Toegewezene verwijderen',\n      removeColor: 'Kleur verwijderen',\n      removeCover_title: 'Omslag verwijderen',\n      removeFromBoard: 'Verwijderen van bord',\n      removeFromProject: 'Verwijderen van project',\n      removeManager: 'Manager verwijderen',\n      removeMember: 'Lid verwijderen',\n      restoreToList: 'Herstellen naar {{list}}',\n      returnToBoard: 'Terug naar bord',\n      save: 'Opslaan',\n      sendTestEmail: 'Test e-mail verzenden',\n      showActive: 'Actieve tonen',\n      showAllAttachments: 'Alle bijlagen weergeven ({{hidden}} verbergen)',\n      showCardsWithThisUser: 'Kaarten met deze gebruiker tonen',\n      showDeactivated: 'Gedeactiveerde tonen',\n      showFewerAttachments: 'Minder bijlagen weergeven',\n      showLess: 'Minder tonen',\n      showMore: 'Meer tonen',\n      sortList_title: 'Lijst sorteren',\n      start: 'Start',\n      stop: 'Stop',\n      subscribe: 'Abonneren',\n      unlinkSso: 'SSO ontkoppelen',\n      unlinkSso_title: 'SSO ontkoppelen',\n      unsubscribe: 'Afmelden',\n      uploadNewAvatar: 'Nieuwe avatar uploaden',\n      uploadNewImage: 'Nieuwe afbeelding uploaden',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/nl-NL/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'nl-NL',\n  country: 'nl',\n  name: 'Nederlands',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/nl-NL/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Limiet voor actieve gebruikers bereikt',\n      adminLoginRequiredToInitializeInstance:\n        'Beheerder login vereist om instantie te initialiseren',\n      emailAlreadyInUse: 'E-mail is al in gebruik',\n      emailOrUsername: 'E-mail of gebruikersnaam',\n      invalidCredentials: 'Ongeldige inloggegevens',\n      invalidEmailOrUsername: 'Ongeldig e-mailadres of gebruikersnaam',\n      invalidPassword: 'Ongeldig wachtwoord',\n      logIn_title: 'Inloggen',\n      noInternetConnection: 'Geen internetverbinding',\n      or: 'Of',\n      pageNotFound_title: 'Pagina niet gevonden',\n      password: 'Wachtwoord',\n      poweredByPlanka: 'Mogelijk gemaakt door <1>PLANKA</1>',\n      serverConnectionFailed: 'Verbinding met de server mislukt',\n      unknownError: 'Onbekende fout, probeer het later opnieuw',\n      useSingleSignOn: 'Gebruik single sign-on',\n      usernameAlreadyInUse: 'Gebruikersnaam is al in gebruik',\n      whoops_title: 'Oeps!',\n    },\n\n    action: {\n      cancelAndClose: 'Annuleren en sluiten',\n      continue: 'Doorgaan',\n      debugSso: 'SSO debuggen',\n      goBack: 'Terug gaan',\n      goHome: 'Naar startpagina',\n      logIn: 'Inloggen',\n      logInWithSso: 'Inloggen met SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/nl-NL/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Dit is een tekst zonder titel.\\nZowel de titel als de tekst\\nkunnen worden gemarkeerd als vet, cursief, gekleurd,\\ndoorgehaald en onderstreept.\",\n    \"text-with-head\": \"Dit is een tekst met een titel.\\nZowel de titel als de tekst\\nkunnen worden gemarkeerd als vet, cursief, gekleurd,\\ndoorgehaald en onderstreept.\",\n    \"heading\": \"Titel\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Fout in markdown-editor\",\n    \"settings_wysiwyg\": \"Visuele editor (wysiwyg)\",\n    \"settings_markup\": \"Markdown-opmaak\",\n    \"markup_placeholder\": \"Voer markdown-opmaak in...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Verwijderen\",\n    \"empty_option\": \"Geen overeenkomsten gevonden\",\n    \"show_line_numbers\": \"Regelnummering\"\n  },\n  \"common\": {\n    \"delete\": \"Verwijderen\",\n    \"edit\": \"Bewerken\",\n    \"toolbar_action_disabled\": \"Onverenigbaar opmaakelement\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Annuleren\",\n    \"common_action_submit\": \"Indienen\",\n    \"common_action_upload\": \"Selecteren\",\n    \"common_tab_attach\": \"Toevoegen vanaf apparaat\",\n    \"common_tab_link\": \"Toevoegen via link\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Grootte, px\",\n    \"image_name\": \"Titel\",\n    \"image_link_href\": \"Afbeeldingslink\",\n    \"image_link_href_help\": \"Adres waar de afbeeldingslink naartoe leidt.\",\n    \"image_alt\": \"Alt-tekst\",\n    \"image_alt_help\": \"Alt-tekst wordt weergegeven als de afbeelding niet kan worden geladen.\",\n    \"image_upload_help\": \"JPEG-, GIF- of PNG-afbeelding van maximaal 1 MB.\",\n    \"image_upload_failed\": \"Afbeelding toevoegen mislukt\",\n    \"image_size_width\": \"Breedte\",\n    \"image_size_height\": \"Hoogte\",\n    \"link_url_help\": \"Adres waar de link naartoe leidt.\",\n    \"link_text\": \"Linktekst\",\n    \"link_text_help\": \"Tekst die als link wordt weergegeven.\",\n    \"link_open_help\": \"Open de link in een nieuw tabblad\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Koptekst\",\n    \"header_hint\": \"# Jouw tekst\",\n    \"italic_title\": \"Cursief\",\n    \"italic_hint\": \"_Jouw tekst_\",\n    \"bold_title\": \"Vet\",\n    \"bold_hint\": \"**Jouw tekst**\",\n    \"strikethrough_title\": \"Doorhalen\",\n    \"strikethrough_hint\": \"~~Jouw tekst~~\",\n    \"blockquote_title\": \"Citaat\",\n    \"blockquote_hint\": \"> Jouw tekst\",\n    \"code_title\": \"Code\",\n    \"code_hint\": \"```Jouw tekst```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[Jouw tekst](url)\",\n    \"image_title\": \"Afbeelding\",\n    \"image_hint\": \"![Jouw tekst](url)\",\n    \"list_title\": \"Lijstitem\",\n    \"list_hint\": \"- Jouw tekst\",\n    \"numbered-list_title\": \"Genummerde lijst\",\n    \"numbered-list_hint\": \"1. Jouw tekst\",\n    \"documentation\": \"Documentatie\",\n    \"documentation_link\": \"https://diplodoc.com/docs/nl/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Vet\",\n    \"code\": \"Code\",\n    \"code_inline\": \"Inline code\",\n    \"codeblock\": \"Codeblok\",\n    \"colorify\": \"Tekstkleur\",\n    \"colorify__color_blue\": \"Blauw\",\n    \"colorify__color_default\": \"Standaard\",\n    \"colorify__color_gray\": \"Grijs\",\n    \"colorify__color_green\": \"Groen\",\n    \"colorify__color_orange\": \"Oranje\",\n    \"colorify__color_red\": \"Rood\",\n    \"colorify__color_violet\": \"Violet\",\n    \"colorify__color_yellow\": \"Geel\",\n    \"colorify__group_text\": \"Tekst\",\n    \"cut\": \"Knippen\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emoji's kunnen worden toegevoegd in WYSIWYG of handmatig met opmaak\",\n    \"heading\": \"Koptekst\",\n    \"heading1\": \"Kop 1\",\n    \"heading2\": \"Kop 2\",\n    \"heading3\": \"Kop 3\",\n    \"heading4\": \"Kop 4\",\n    \"heading5\": \"Kop 5\",\n    \"heading6\": \"Kop 6\",\n    \"hrule\": \"Scheidingsteken\",\n    \"image\": \"Afbeelding\",\n    \"italic\": \"Cursief\",\n    \"link\": \"Link\",\n    \"list\": \"Lijst\",\n    \"list__action_lift\": \"Item omhoog\",\n    \"list__action_sink\": \"Item omlaag\",\n    \"list_action_disabled\": \"In strijd met de logica van de lijst\",\n    \"mark\": \"Gemarkeerd\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Meer acties\",\n    \"note\": \"Notitie\",\n    \"olist\": \"Geordende lijst\",\n    \"quote\": \"Citaat\",\n    \"redo\": \"Opnieuw\",\n    \"strike\": \"Doorhalen\",\n    \"table\": \"Tabel\",\n    \"text\": \"Tekst\",\n    \"ulist\": \"Ongenummerde lijst\",\n    \"underline\": \"Onderstrepen\",\n    \"undo\": \"Ongedaan maken\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"/ typen om slash-commando's te gebruiken...\",\n    \"checkbox\": \"Voer taakbeschrijving in...\",\n    \"deflist_term\": \"Term\",\n    \"deflist_desc\": \"Definitiebeschrijving\",\n    \"heading\": \"Koptekst\",\n    \"cut_title\": \"Titel\",\n    \"cut_content\": \"Inhoud die wordt weergegeven bij klikken\",\n    \"note_title\": \"Titel\",\n    \"note_content\": \"Notitie-inhoud\",\n    \"table_cell\": \"Celinhoud\",\n    \"select_filter\": \"Zoek talen...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Hoofdlettergevoelig\",\n    \"label_whole-word\": \"Hele woord\",\n    \"title\": \"Zoeken in code\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Niet gevonden\"\n  },\n  \"widgets\": {\n    \"image\": \"Afbeelding toevoegen\",\n    \"link\": \"Link toevoegen\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Notitie\",\n    \"tip\": \"Tip\",\n    \"warning\": \"Waarschuwing\",\n    \"alert\": \"Alarm\",\n    \"remove\": \"Verwijderen\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Kolom ervoor toevoegen\",\n    \"column.add.after\": \"Kolom erna toevoegen\",\n    \"column.remove\": \"Kolom verwijderen\",\n    \"column.remove.multiple\": \"Kolommen verwijderen\",\n    \"row.add.before\": \"Rij ervoor toevoegen\",\n    \"row.add.after\": \"Rij erna toevoegen\",\n    \"row.remove\": \"Rij verwijderen\",\n    \"row.remove.multiple\": \"Rijen verwijderen\",\n    \"cells.clear\": \"Cellen wissen\",\n    \"table.remove\": \"Tabel verwijderen\",\n    \"table.menu.cell.align.left\": \"Celinhoud links uitlijnen\",\n    \"table.menu.cell.align.right\": \"Celinhoud rechts uitlijnen\",\n    \"table.menu.cell.align.center\": \"Celinhoud centreren\",\n    \"table.menu.row.add\": \"Rij erna toevoegen\",\n    \"table.menu.row.remove\": \"Rij verwijderen\",\n    \"table.menu.column.add\": \"Kolom erna toevoegen\",\n    \"table.menu.column.remove\": \"Kolom verwijderen\",\n    \"table.menu.convert.yfm\": \"Converteren naar YFM-tabel\",\n    \"table.menu.table.remove\": \"Tabel verwijderen\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/pl-PL/core.js",
    "content": "import dateFns from 'date-fns/locale/pl';\nimport timeAgo from 'javascript-time-ago/locale/pl';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd/M/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'o' p\",\n    fullDate: 'd MMM y',\n    fullDateTime: \"d MMMM y 'o' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'O aplikacji',\n      aboutPlanka_title: 'O PLANKA',\n      accessToken: 'Token dostępu',\n      account: 'Konto',\n      actions: 'Akcje',\n      activateUser_title: 'Aktywuj użytkownika',\n      active: 'Aktywny',\n      addAttachment_title: 'Dodaj załącznik',\n      addCustomFieldGroup_title: 'Dodaj grupę własnych pól',\n      addCustomField_title: 'Dodaj własne pole',\n      addManager_title: 'Dodaj zarządcę',\n      addMember_title: 'Dodaj członka',\n      addTaskList_title: 'Dodaj listę zadań',\n      addUser_title: 'Dodaj użytkownika',\n      admin: 'Administrator',\n      administration: 'Administracja',\n      all: 'Wszystko',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Wszystkie zmiany zostaną automatycznie zapisane<br />po przywróceniu połączenia.',\n      alphabetically: 'Alfabetycznie',\n      alwaysDisplayCardCreator: 'Zawsze pokazuj twórcę karty',\n      apiKeyCreated_title: 'Klucz API utworzony',\n      apiKey_title: 'Klucz API',\n      archive: 'Archiwum',\n      archiveCard_title: 'Archiwizuj kartę',\n      archiveCards_title: 'Archiwizuj karty',\n      areYouSureYouWantToActivateThisUser: 'Jesteś pewien że chcesz aktywować tego użytkownika?',\n      areYouSureYouWantToArchiveCards: 'Jesteś pewien że chcesz archiwizować karty?',\n      areYouSureYouWantToArchiveThisCard: 'Jesteś pewien że chcesz archiwizować tę kartę?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Jesteś pewien że chcesz przypisać tego zarządcę jako właściciela projektu?',\n      areYouSureYouWantToDeactivateThisUser:\n        'Jesteś pewien że chcesz dezaktywować tego użytkownika?',\n      areYouSureYouWantToDeleteThisApiKey: 'Czy na pewno chcesz usunąć ten klucz API?',\n      areYouSureYouWantToDeleteThisAttachment: 'Jesteś pewien że chcesz usunąć ten załącznik?',\n      areYouSureYouWantToDeleteThisBackgroundImage: 'Jesteś pewien że chcesz usunąć to tło?',\n      areYouSureYouWantToDeleteThisBoard: 'Jesteś pewien że chcesz usunąć tę tablicę?',\n      areYouSureYouWantToDeleteThisCard: 'Jesteś pewien że chcesz usunąć tę kartę?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Jesteś pewien że chcesz usunąć tę kartę na zawsze?',\n      areYouSureYouWantToDeleteThisComment: 'Jesteś pewien że chcesz usunąć ten komentarz?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Jesteś pewien że chcesz usunąć to pole niestandardowe?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Jesteś pewien że chcesz usunąć tę grupę pól niestandardowych?',\n      areYouSureYouWantToDeleteThisLabel: 'Jesteś pewien że chcesz usunąć to oznaczenie?',\n      areYouSureYouWantToDeleteThisList:\n        'Jesteś pewien że chcesz usunąć tę listę? Wszystkie karty zostaną przeniesione do kosza.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Jesteś pewien że chcesz usunąć tę usługę powiadomień?',\n      areYouSureYouWantToDeleteThisProject: 'Jesteś pewien że chcesz usunąć ten projekt?',\n      areYouSureYouWantToDeleteThisTask: 'Jesteś pewien że chcesz usunąć to zadanie?',\n      areYouSureYouWantToDeleteThisTaskList: 'Jesteś pewien że chcesz usunąć tę listę zadań?',\n      areYouSureYouWantToDeleteThisUser: 'Jesteś pewien że chcesz usunąć tego użytkownika?',\n      areYouSureYouWantToDeleteThisWebhook: 'Jesteś pewien że chcesz usunąć ten webhook?',\n      areYouSureYouWantToEmptyTrash: 'Jesteś pewien że chcesz opróżnić kosz?',\n      areYouSureYouWantToLeaveBoard: 'Jesteś pewien że chcesz opuścić tablicę?',\n      areYouSureYouWantToLeaveProject: 'Jesteś pewien że chcesz opuścić projekt?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Jesteś pewien że chcesz uczynić ten projekt prywatnym?',\n      areYouSureYouWantToMakeThisProjectShared: 'Jesteś pewien że chcesz udostępnić ten projekt?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Czy na pewno chcesz wygenerować ponownie ten klucz API? Poprzedni klucz przestanie działać.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Jesteś pewien że chcesz usunąć tego zarządcę z projektu?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Jesteś pewien że chcesz usunąć tego członka z tablicy?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Czy na pewno chcesz odłączyć SSO od tego użytkownika? Pozwoli to użytkownikowi zalogować się przy użyciu hasła.',\n      assignAsOwner_title: 'Przypisz jako właściciela',\n      atLeastOneListMustBePresent: 'Przynajmniej jedna lista musi istnieć',\n      attachment: 'Załącznik',\n      attachments: 'Załączniki',\n      authentication: 'Uwierzytelnianie',\n      background: 'Tło',\n      baseCustomFields_title: 'Bazowe pola własne',\n      baseGroup: 'Grupa bazowa',\n      board: 'Tablica',\n      boardActions_title: 'Akcje tablicy',\n      boardNotFound_title: 'Tablica nie znaleziona',\n      boardSubscribed: 'Zasubskrybowano tablicę',\n      boardUser: 'Użytkownik tablicy',\n      byCreationTime: 'Po dacie utworzenia',\n      byDefault: 'Domyślnie',\n      byDueDate: 'Po dacie wykonania',\n      canBeInvitedToWorkInBoards: 'Może być zaproszony do pracy przy tablicach.',\n      canComment: 'Może komentować',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Może tworzyć własne projekty i być zaproszony do pracy w innych.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Może edytować układ tablicy i przypisywać użytkowników do kart.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Może zarządzać ustawieniami systemowymi i działać jako właściciel projektu.',\n      canOnlyViewBoard: 'Może tylko wyświetlać tablicę.',\n      cardActions_title: 'Akcje karty',\n      cardNotFound_title: 'Karta nie znaleziona',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Karty na tej liście są dostępne dla wszystkich członków tablicy.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Karty na tej liście są ukończone i gotowe do zarchiwizowania.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Karty na tej liście są gotowe do pracy nad nimi.',\n      clickHereOrRefreshPageToUpdate: '<0>Naciśnij tutaj</0> lub odśwież stronę, by zaktualizować.',\n      clientHostnameInEhlo: 'Nazwa hosta klienta w EHLO',\n      closed: 'Zamknięte',\n      color: 'Kolor',\n      comments: 'Komentarze',\n      contentExceedsLimit: 'Zawartość przekracza {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Zawartość tego załącznika jest zbyt duża do wyświetlenia.',\n      copy_inline: 'kopia',\n      createBoard_title: 'Utwórz tablicę',\n      createCustomFieldGroup_title: 'Utwórz grupę pól własnych',\n      createLabel_title: 'Utwórz oznaczenie',\n      createNewOneOrSelectExistingOne: 'Stwórz nowe lub wybierz<br />istniejące.',\n      createProject_title: 'Utwórz projekt',\n      createTextFile_title: 'Utwórz plik tekstowy',\n      creator: 'Twórca',\n      currentPassword: 'Obecne hasło',\n      currentUser: 'Bieżący użytkownik',\n      customFieldGroup_title: 'Grupa pól własnych',\n      customFieldGroups_title: 'Grupy pól własnych',\n      customField_title: 'Własne pole',\n      customFields_title: 'Własne pola',\n      customerPanel_title: 'Panel klienta',\n      dangerZone_title: 'Strefa niebezpieczeństwa',\n      date: 'Data',\n      deactivateUser_title: 'Dezaktywuj użytkownika',\n      defaultCardType_title: 'Domyślny typ karty',\n      defaultFrom: 'Domyślne \"od\"',\n      defaultView_title: 'Domyślny widok',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Usuń wszystkie tablice, aby móc usunąć ten projekt',\n      deleteApiKey_title: 'Usuń klucz API',\n      deleteAttachment_title: 'Usuń załącznik',\n      deleteBackgroundImage_title: 'Usuń tło',\n      deleteBoard_title: 'Usuń tablicę',\n      deleteCardForever_title: 'Usuń kartę permanentnie',\n      deleteCard_title: 'Usuń kartę',\n      deleteComment_title: 'Usuń komentarz',\n      deleteCustomFieldGroup_title: 'Usuń grupę pól własnych',\n      deleteCustomField_title: 'Usuń pole własne',\n      deleteLabel_title: 'Usuń oznaczenie',\n      deleteList_title: 'Usuń listę',\n      deleteNotificationService_title: 'Usuń serwis powiadomień',\n      deleteProject_title: 'Usuń projekt',\n      deleteTaskList_title: 'Usuń listę zadań',\n      deleteTask_title: 'Usuń zadanie',\n      deleteUser_title: 'Usuń użytkownika',\n      deleteWebhook_title: 'Usuń webhook',\n      deletedUser_title: 'Usunięty użytkownik',\n      description: 'Opis',\n      display: 'Wyświetlanie',\n      displayCardAges: 'Pokazuj wiek kart',\n      dropFileToUpload: 'Upuść plik aby wgrać',\n      dueDate_title: 'Termin',\n      dynamicAndUnevenlySpacedLayout: 'Dynamiczny i nierówny układ.',\n      editAttachment_title: 'Edytuj załącznik',\n      editAvatar_title: 'Edytuj awatar',\n      editColor_title: 'Edytuj kolor',\n      editCustomFieldGroup_title: 'Edytuj grupę pól własnych',\n      editCustomField_title: 'Edytuj pole własne',\n      editDueDate_title: 'Edytuj termin',\n      editEmail_title: 'Edytuj e-mail',\n      editInformation_title: 'Edytuj informacje',\n      editLabel_title: 'Edytuj oznaczenie',\n      editPassword_title: 'Edytuj hasło',\n      editPermissions_title: 'Edytuj uprawnienia',\n      editRole_title: 'Edytuj rolę',\n      editStopwatch_title: 'Edytuj stoper',\n      editType_title: 'Edytuj typ',\n      editUsername_title: 'Edytuj nazwę użytkownika',\n      editor: 'Edytor',\n      editors: 'Edytorzy',\n      email: 'E-mail',\n      emptyTrash_title: 'Opróżnij kosz',\n      enterCardTitle: 'Podaj tytuł karty...',\n      enterDescription: 'Podaj opis...',\n      enterFilename: 'Podaj nazwę pliku',\n      enterListTitle: 'Podaj tytuł listy...',\n      enterTaskDescription: 'Podaj opis zadania...',\n      events: 'Wydarzenia',\n      excludedEvents: 'Wykluczone wydarzenia',\n      expandTaskListsByDefault: 'Rozwiń listy zadań domyślnie',\n      filterByLabels_title: 'Filtruj po oznaczeniach',\n      filterByMembers_title: 'Filtruj po członkach',\n      forPersonalProjects: 'Dla projektów osobistych.',\n      forTeamBasedProjects: 'Dla projektów zespołowych.',\n      fromComputer_title: 'Z komputera',\n      fromTrello: 'Z Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Pełny klucz jest ukryty ze względów bezpieczeństwa. Wygeneruj go ponownie, aby utworzyć nowy.',\n      general: 'Ogólne',\n      gradients: 'Gradienty',\n      grid: 'Siatka',\n      hideCompletedTasks: 'Ukryj ukończone zadania',\n      hideFromProjectListAndFavorites: 'Ukryj z listy projektów i ulubionych',\n      host: 'Host',\n      hours: 'Godzin',\n      identity: 'Tożsamość',\n      importBoard_title: 'Importuj tablicę',\n      information: 'Informacja',\n      invalidCurrentPassword: 'Błędne obecne hasło',\n      kanban: 'Kanban',\n      labels: 'Oznaczenia',\n      language: 'Język',\n      leaveBoard_title: 'Opuść tablicę',\n      leaveProject_title: 'Opuść projekt',\n      limitCardTypesToDefaultOne: 'Ogranicz typy kart do domyślnego',\n      linkToCard: 'Link do karty',\n      list: 'Lista',\n      listActions_title: 'Akcje listy',\n      lists: 'Listy',\n      makeProjectPrivate_title: 'Uczyń projekt prywatnym',\n      makeProjectShared_title: 'Udostępnij projekt',\n      managers: 'Zarządcy',\n      memberActions_title: 'Akcje członków',\n      members: 'Członkowie',\n      minutes: 'Minut',\n      moreActions: 'Więcej akcji',\n      moreActions_title: 'Więcej akcji',\n      moveCard_title: 'Przenoszenie karty',\n      moveList_title: 'Przenieś listę',\n      myOwn_title: 'Moje',\n      name: 'Nazwa',\n      newEmail: 'Nowy e-mail',\n      newPassword: 'Nowe hasło',\n      newUsername: 'Nowa nazwa użytkownika',\n      newVersionAvailable: 'Nowa wersja dostępna',\n      newestFirst: 'Najpierw najnowsze',\n      noApiKeyCreated: 'Nie utworzono klucza API.',\n      noBoards: 'Brak tablic',\n      noCardsFound: 'Nie znaleziono kart.',\n      noConnectionToServer: 'Brak połączenia z serwerem',\n      noLists: 'Brak list',\n      noProjects: 'Brak projektów',\n      noUnreadNotifications: 'Brak nieprzeczytanych powiadomień.',\n      notifications: 'Powiadomienia',\n      oldestFirst: 'Najpierw najstarsze',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Tylko jeden zarządca powinien pozostać, aby uczynić ten projekt prywatnym',\n      openBoard_title: 'Otwórz tablicę',\n      optional_inline: 'opcjonalny',\n      organization: 'Organizacja',\n      others: 'Inne',\n      passwordIsSet: 'Hasło jest ustawione',\n      phone: 'Telefon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA używa <1><0>Apprise</0></1> do wysyłania powiadomień do ponad 100 popularnych serwisów.',\n      port: 'Port',\n      preferences: 'Preferencje',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Podpowiedź: naciśnij Ctrl-V (Cmd-V na Macu) aby dodać załącznik ze schowka.',\n      private: 'Prywatne',\n      project: 'Projekt',\n      projectNotFound_title: 'Projektu nie znaleziono',\n      projectOwner: 'Właściciel projektu',\n      referenceDataAndKnowledgeStorage: 'Odnoś się do danych i przechowuj wiedzę.',\n      regenerateApiKey_title: 'Wygeneruj ponownie klucz API',\n      rejectUnauthorizedTlsCertificates: 'Odrzuć nieautoryzowane certyfikaty TLS',\n      removeManager_title: 'Usuń zarządcę',\n      removeMember_title: 'Usuń członka',\n      role: 'Rola',\n      saveThisKeyItWillNotBeShownAgain: 'Zapisz ten klucz — nie zostanie ponownie wyświetlony!',\n      searchCards: 'Szukaj kart...',\n      searchCustomFieldGroups: 'Szukaj grup pól własnych...',\n      searchCustomFields: 'Szukaj pól własnych...',\n      searchLabels: 'Szukaj oznaczeń...',\n      searchLists: 'Szukaj list...',\n      searchMembers: 'Szukaj członków...',\n      searchProjects: 'Szukaj projektów...',\n      searchUsers: 'Szukaj użytkowników...',\n      seconds: 'Sekund',\n      selectAssignee_title: 'Wybierz osobę przypisaną',\n      selectBoard: 'Wybierz tablicę',\n      selectList: 'Wybierz listę',\n      selectListToRestoreThisCard: 'Wybierz listę, na którą ma być przywrócona ta karta',\n      selectOrder_title: 'Wybierz kolejność',\n      selectPermissions_title: 'Wybierz uprawnienia',\n      selectProject: 'Wybierz projekt',\n      selectRole_title: 'Wybierz rolę',\n      selectType_title: 'Wybierz typ',\n      sequentialDisplayOfCards: 'Sekwencyjne wyświetlania kart.',\n      settings: 'Ustawienia',\n      shared: 'Udostępniane',\n      sharedWithMe_title: 'Udostępniane dla mnie',\n      showOnFrontOfCard: 'Pokazuj na przodzie karty',\n      smtp: 'SMTP',\n      sortList_title: 'Sortowanie listy',\n      sourceCardIsNoLongerAvailableForCopying:\n        'Źródłowa karta nie jest już dostępna do skopiowania.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'Źródłowa karta nie jest już dostępna do przeniesienia.',\n      stopwatch: 'Stoper',\n      story: 'Scenorys',\n      subscribeToCardWhenCommenting: 'Subskrybuj kartę przy komentowaniu',\n      subscribeToMyOwnCardsByDefault: 'Subskrybuj moje karty automatycznie',\n      taskActions_title: 'Akcje zadań',\n      taskAssignmentAndProjectCompletion: 'Przypisanie zadań i stopień ukończenia projektów.',\n      taskListActions_title: 'Akcje listy zadań',\n      taskList_title: 'Lista zadań',\n      team: 'Zespół',\n      termsOfService_title: 'Warunki korzystania z usługi',\n      testLog_title: 'Dziennik testów',\n      thereIsNoPreviewAvailableForThisAttachment: 'Brak podglądu dostępnego dla tego załącznika.',\n      time: 'Czas',\n      title: 'Tytuł',\n      trash: 'Kosz',\n      trashHasBeenSuccessfullyEmptied: 'Kosz opróżniony pomyślnie.',\n      turnOffRecentCardHighlighting: 'Wyłącz podświetlanie nowych kart',\n      typeNameToConfirm: 'Wpisz nazwę aby potwierdzić.',\n      typeTitleToConfirm: 'Wpisz tytuł aby potwierdzić.',\n      unlinkSso_title: 'Odłączanie SSO',\n      unsavedChanges: 'Niezapisane zmiany',\n      uploadFailedFileIsTooBig: 'Przesyłanie nie powiodło się: plik jest za duży.',\n      uploadFailedNotEnoughStorageSpace: 'Przesyłanie nie powiodło się: za mało miejsca na dysku.',\n      uploadedImages: 'Wgrane obrazy',\n      url: 'URL',\n      useSecureConnection: 'Użyj bezpiecznego połączenia',\n      userActions_title: 'Akcje użytkownika',\n      userAddedCardToList: '<0>{{user}}</0> dodał <2>{{card}}</2> do {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> dodał tę kartę do {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> dodał {{addedUser}} do <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> dodał {{addedUser}} do tej karty',\n      userAddedYouToCard: '<0>{{user}}</0> dodał cię do <2>{{card}}</2>',\n      userCompletedTaskOnCard:\n        '<0>{{user}}</0> oznaczył {{task}} jako ukończone na <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> oznaczył {{task}} jako ukończone na tej karcie',\n      userJoinedCard: '<0>{{user}}</0> dołączył do <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> dołączył do tej karty',\n      userLeftCard: '<0>{{user}}</0> opuścił <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> zamieścił komentarz «{{comment}}» na <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> opuścił tę kartę',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> oznaczył {{task}} jako nieukończone na <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> oznaczył {{task}} jako nieukończone na tej karcie',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> wspomniał o tobie w komentarzu «{{comment}}» do <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> przeniósł <2>{{card}}</2> z {{fromList}} do {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> przeniósł tę kartę z {{fromList}} do {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> usunął {{removedUser}} z <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> usunął {{removedUser}} z tej karty',\n      username: 'Nazwa użytkownika',\n      users: 'Użytkownicy',\n      viewer: 'Wyświetlający',\n      viewers: 'Wyświetlający',\n      visualTaskManagementWithLists: 'Wizualne zarządzanie zadaniami z listami.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Co nowego',\n      withoutBaseGroup: 'Bez grupy bazowej',\n      writeComment: 'Napisz komentarz...',\n    },\n\n    action: {\n      activateUser: 'Aktywuj użytkownika',\n      activateUser_title: 'Aktywuj użytkownika',\n      addAnotherCard: 'Dodaj kolejną kartę',\n      addAnotherList: 'Dodaj kolejną listę',\n      addAnotherTask: 'Dodaj kolejne zadanie',\n      addCard: 'Dodaj kartę',\n      addCard_title: 'Dodaj kartę',\n      addComment: 'Dodaj komentarz',\n      addCustomField: 'Dodaj własne pole',\n      addCustomFieldGroup: 'Dodaj grupę własnych pól',\n      addList: 'Dodaj listę',\n      addMember: 'Dodaj członka',\n      addMoreDetailedDescription: 'Dodaj szczegółowy opis',\n      addTask: 'Dodaj zadanie',\n      addTaskList: 'Dodaj listę zadań',\n      addToCard: 'Dodaj do karty',\n      addUser: 'Dodaj użytkownika',\n      addWebhook: 'Dodaj webhook',\n      archive: 'Archiwizuj',\n      archiveCard: 'Archiwizuj kartę',\n      archiveCard_title: 'Archiwizuj kartę',\n      archiveCards: 'Archiwizuj karty',\n      archiveCards_title: 'Archiwizuj karty',\n      assignAsOwner: 'Przypisz jako właściciela',\n      cancel: 'Anuluj',\n      copy: 'Kopiuj',\n      copyCard_title: 'Kopiuj kartę',\n      createApiKey: 'Utwórz klucz API',\n      createBoard: 'Utwórz tablicę',\n      createCustomFieldGroup: 'Utwórz grupę pól własnych',\n      createFile: 'Utwórz plik',\n      createLabel: 'Utwórz oznaczenie',\n      createNewLabel: 'Utwórz nowe oznaczenie',\n      createProject: 'Utwórz projekt',\n      cut: 'Wytnij',\n      cutCard_title: 'Wytnij kartę',\n      deactivateUser: 'Dezaktywuj użytkownika',\n      deactivateUser_title: 'Dezaktywuj użytkownika',\n      delete: 'Usuń',\n      deleteApiKey: 'Usuń klucz API',\n      deleteAttachment: 'Usuń attachment',\n      deleteAvatar: 'Usuń awatar',\n      deleteBackgroundImage: 'Usuń tło',\n      deleteBoard: 'Usuń tablicę',\n      deleteBoard_title: 'Usuń tablicę',\n      deleteCard: 'Usuń kartę',\n      deleteCardForever: 'Usuń kartę permanetnie',\n      deleteCard_title: 'Usuń kartę',\n      deleteComment: 'Usuń komentarz',\n      deleteCustomField: 'Usuń pole własne',\n      deleteCustomFieldGroup: 'Usuń grupę pól własnych',\n      deleteForever_title: 'Usuń permanentnie',\n      deleteGroup: 'Usuń groupę',\n      deleteLabel: 'Usuń oznaczenie',\n      deleteList: 'Usuń listę',\n      deleteList_title: 'Usuń listę',\n      deleteNotificationService: 'Usuń serwis powiadomień',\n      deleteProject: 'Usuń projekt',\n      deleteProject_title: 'Usuń projekt',\n      deleteTask: 'Usuń zadanie',\n      deleteTaskList: 'Usuń listę zadań',\n      deleteTask_title: 'Usuń zadanie',\n      deleteUser: 'Usuń użytkownika',\n      deleteUser_title: 'Usuń użytkownika',\n      deleteWebhook: 'Usuń webhook',\n      dismissAll: 'Odrzuć wszystkie',\n      download: 'Pobierz',\n      duplicateCard_title: 'Duplikuj kartę',\n      edit: 'Edytuj',\n      editColor_title: 'Edytuj kolor',\n      editDescription_title: 'Edytuj opis',\n      editDueDate_title: 'Edytuj termin',\n      editEmail_title: 'Edytuj e-mail',\n      editGroup: 'Edytuj groupę',\n      editInformation_title: 'Edytuj informacje',\n      editPassword_title: 'Edytuj hasło',\n      editPermissions: 'Edytuj uprawnienia',\n      editRole_title: 'Edytuj rolę',\n      editStopwatch_title: 'Edytuj stoper',\n      editTitle_title: 'Edytuj tytuł',\n      editType_title: 'Edytuj typ',\n      editUsername_title: 'Edytuj nazwę użytkownika',\n      emptyTrash: 'Opróżnij kosz',\n      emptyTrash_title: 'Opróżnij kosz',\n      import: 'Importuj',\n      join: 'Dołącz',\n      leave: 'Opuść',\n      leaveBoard: 'Opuść tablicę',\n      leaveProject: 'Opuść projekt',\n      logOut_title: 'Wyloguj',\n      makeCover_title: 'Stwórz okładkę',\n      makeProjectPrivate: 'Uczyń projekt prywatnym',\n      makeProjectPrivate_title: 'Uczyń projekt prywatnym',\n      makeProjectShared: 'Udostępnij projekt',\n      makeProjectShared_title: 'Udostępnij projekt',\n      move: 'Przenieś',\n      moveCard_title: 'Przenieś kartę',\n      moveList_title: 'Przenieś listę',\n      regenerateApiKey: 'Wygeneruj ponownie klucz API',\n      remove: 'Usuń',\n      removeAssignee: 'Usuń osobę przypisaną',\n      removeColor: 'Usuń kolor',\n      removeCover_title: 'Usuń okładkę',\n      removeFromBoard: 'Usuń z tablicy',\n      removeFromProject: 'Usuń z projektu',\n      removeManager: 'Usuń zarządcę',\n      removeMember: 'Usuń członka',\n      restoreToList: 'Przywróć na {{list}}',\n      returnToBoard: 'Przywróć do tablicy',\n      save: 'Zapisz',\n      sendTestEmail: 'Wyślij testowy e-mail',\n      showActive: 'Pokaż aktywne',\n      showAllAttachments: 'Pokaż wszystkie załączniki ({{hidden}} są ukryte)',\n      showCardsWithThisUser: 'Pokaż karty z tym użytkownikiem',\n      showDeactivated: 'Pokaż dezaktywowane',\n      showFewerAttachments: 'Pokaż mniej załączników',\n      showLess: 'Pokaż mniej',\n      showMore: 'Pokaż więcej',\n      sortList_title: 'Sortuj listę',\n      start: 'Start',\n      stop: 'Stop',\n      subscribe: 'Subskrybuj',\n      unlinkSso: 'Odłącz SSO',\n      unlinkSso_title: 'Odłącz SSO',\n      unsubscribe: 'Odsubskrybuj',\n      uploadNewAvatar: 'Wgraj nowy awatar',\n      uploadNewImage: 'Wgraj nowy obraz',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/pl-PL/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'pl-PL',\n  country: 'pl',\n  name: 'Polski',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/pl-PL/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Osiągnięto limit aktywnych użytkowników',\n      adminLoginRequiredToInitializeInstance:\n        'Wymagane logowanie administratora do inicjalizacji instancji',\n      emailAlreadyInUse: 'E-mail jest już używany',\n      emailOrUsername: 'E-mail lub nazwa użytkownika',\n      invalidCredentials: 'Błędne dane logowania',\n      invalidEmailOrUsername: 'Błędny e-mail lub nazwa użytkownika',\n      invalidPassword: 'Błędne hasło',\n      logIn_title: 'Logowanie',\n      noInternetConnection: 'Brak połączenia z internetem',\n      or: 'Lub',\n      pageNotFound_title: 'Strona nie znaleziona',\n      password: 'Hasło',\n      poweredByPlanka: 'Powered by <1>PLANKA</1>',\n      serverConnectionFailed: 'Błąd połączenia z serwerem',\n      unknownError: 'Nieznany błąd, spróbuj ponownie później',\n      useSingleSignOn: 'Użyj logowania SSO',\n      usernameAlreadyInUse: 'Nazwa użytkownika nie jest dostępna',\n      whoops_title: 'Ups!',\n    },\n\n    action: {\n      cancelAndClose: 'Anuluj i zamknij',\n      continue: 'Kontynuuj',\n      debugSso: 'Debuguj SSO',\n      goBack: 'Wróć',\n      goHome: 'Idź do domu',\n      logIn: 'Zaloguj',\n      logInWithSso: 'Zaloguj z SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/pl-PL/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"To jest tekst bez tytułu.\\nZarówno tytuł jak i tekst\\nmogą być wyróżnione za\\npomocą znaczników.\",\n    \"text-with-head\": \"To jest tekst z tytułem.\\nZarówno tytuł jak i tekst\\nmogą być wyróżnione za\\npomocą znaczników.\",\n    \"heading\": \"Tytuł\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Błąd w edytorze markdown\",\n    \"settings_wysiwyg\": \"Edytor graficzny (WYSIWYG)\",\n    \"settings_markup\": \"Znaczniki markdown\",\n    \"markup_placeholder\": \"Wpisz znacznik markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Usuń\",\n    \"empty_option\": \"Nie znaleziono dopasowań\",\n    \"show_line_numbers\": \"Numerowanie wierszy\"\n  },\n  \"common\": {\n    \"delete\": \"Usuń\",\n    \"edit\": \"Edytuj\",\n    \"toolbar_action_disabled\": \"Niekompatybilny znacznik markup\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Anuluj\",\n    \"common_action_submit\": \"Submit\",\n    \"common_action_upload\": \"Wybierz\",\n    \"common_tab_attach\": \"Dodaj z urządzenia\",\n    \"common_tab_link\": \"Dodaj z linku\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Rozmiar, px\",\n    \"image_name\": \"Tytuł\",\n    \"image_link_href\": \"Link do obrazu\",\n    \"image_link_href_help\": \"Adres, do którego prowadzi link obrazu.\",\n    \"image_alt\": \"Tekst alternarywny\",\n    \"image_alt_help\": \"Tekst alternatywny jest wyświetlany, kiedy obraz nie może być załadowany.\",\n    \"image_upload_help\": \"Obraz JPEG, GIF lub PNG nie może być większy niż 1 MB.\",\n    \"image_upload_failed\": \"Nie udało się dodać obrazu.\",\n    \"image_size_width\": \"Szerokość\",\n    \"image_size_height\": \"Wysokość\",\n    \"link_url_help\": \"Adres, do którego prowadzi link.\",\n    \"link_text\": \"Tekst linku\",\n    \"link_text_help\": \"Tekst wyświetlany jako link.\",\n    \"link_open_help\": \"Otwórz link w nowej karcie.\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Nagłówek\",\n    \"header_hint\": \"# Twój tekst\",\n    \"italic_title\": \"Kursywa\",\n    \"italic_hint\": \"_Twój tekst_\",\n    \"bold_title\": \"Pogrubienie\",\n    \"bold_hint\": \"**Twój tekst**\",\n    \"strikethrough_title\": \"Przekreślenie\",\n    \"strikethrough_hint\": \"~~Twój tekst~~\",\n    \"blockquote_title\": \"Cytat blokowy\",\n    \"blockquote_hint\": \"> Twój tekst\",\n    \"code_title\": \"Kod\",\n    \"code_hint\": \"```Twój tekst```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[Twój tekst](url)\",\n    \"image_title\": \"Obraz\",\n    \"image_hint\": \"![Twój tekst](url)\",\n    \"list_title\": \"Element listy\",\n    \"list_hint\": \"- Twój tekst\",\n    \"numbered-list_title\": \"Lista numerowana\",\n    \"numbered-list_hint\": \"1. Twój tekst\",\n    \"documentation\": \"Dokumentacja\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Pogrubienie\",\n    \"code\": \"Kod\",\n    \"code_inline\": \"Kod w linii\",\n    \"codeblock\": \"Blok kodu\",\n    \"colorify\": \"Kolor tekstu\",\n    \"colorify__color_blue\": \"Niebieski\",\n    \"colorify__color_default\": \"Domyślny\",\n    \"colorify__color_gray\": \"Szary\",\n    \"colorify__color_green\": \"Zielony\",\n    \"colorify__color_orange\": \"Pomarańczowy\",\n    \"colorify__color_red\": \"Czerwony\",\n    \"colorify__color_violet\": \"Fioletowy\",\n    \"colorify__color_yellow\": \"Żółty\",\n    \"colorify__group_text\": \"Tekst\",\n    \"cut\": \"Wytnij\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emotikony mogą być dodane w edytorze WYSIWYG lub ręcznie za pomocą znaczników\",\n    \"heading\": \"Nagłówek\",\n    \"heading1\": \"Nagłówek 1\",\n    \"heading2\": \"Nagłówek 2\",\n    \"heading3\": \"Nagłówek 3\",\n    \"heading4\": \"Nagłówek 4\",\n    \"heading5\": \"Nagłówek 5\",\n    \"heading6\": \"Nagłówek 6\",\n    \"hrule\": \"Separator\",\n    \"image\": \"Obraz\",\n    \"italic\": \"Kursywa\",\n    \"link\": \"Link\",\n    \"list\": \"Lista\",\n    \"list__action_lift\": \"Podnieś element\",\n    \"list__action_sink\": \"Opuść element\",\n    \"list_action_disabled\": \"Przeczy logice listy\",\n    \"mark\": \"Zaznaczenie\",\n    \"mono\": \"Stała szerokość\",\n    \"more_action\": \"Więcej opcji\",\n    \"note\": \"Notka\",\n    \"olist\": \"Lista numerowana\",\n    \"quote\": \"Cytat\",\n    \"redo\": \"Ponów\",\n    \"strike\": \"Przekreślenie\",\n    \"table\": \"Tabela\",\n    \"text\": \"Tekst\",\n    \"ulist\": \"Lista wypunktowana\",\n    \"underline\": \"Podkreślenie\",\n    \"undo\": \"Cofnij\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Wpisz / by użyć komend...\",\n    \"checkbox\": \"Wpisz opis zadania...\",\n    \"deflist_term\": \"Termin\",\n    \"deflist_desc\": \"Opis definicji\",\n    \"heading\": \"Nagłówek\",\n    \"cut_title\": \"Tytuł\",\n    \"cut_content\": \"Zawartość wyświetlona po naciśnięciu\",\n    \"note_title\": \"Tytuł\",\n    \"note_content\": \"Zawartość notki\",\n    \"table_cell\": \"Zawartość komórki\",\n    \"select_filter\": \"Szukaj języków...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Zwracając uwagę na wielkość liter\",\n    \"label_whole-word\": \"Całe słowa\",\n    \"title\": \"Szukaj w kodzie\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Nie znaleziono\"\n  },\n  \"widgets\": {\n    \"image\": \"Dodaj obraz\",\n    \"link\": \"Dodaj link\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Notka\",\n    \"tip\": \"Porada\",\n    \"warning\": \"Ostrzeżenie\",\n    \"alert\": \"Alert\",\n    \"remove\": \"Usuń\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Dodaj kolumnę przed\",\n    \"column.add.after\": \"Dodaj kolumnę po\",\n    \"column.remove\": \"Usuń kolumnę\",\n    \"column.remove.multiple\": \"Usuń kolumny\",\n    \"row.add.before\": \"Dodaj wiersz przed\",\n    \"row.add.after\": \"Dodaj wiersz po\",\n    \"row.remove\": \"Usuń wiersz\",\n    \"row.remove.multiple\": \"Usuń wiersze\",\n    \"cells.clear\": \"Wyczyść komórki\",\n    \"table.remove\": \"Usuń tabelę\",\n    \"table.menu.cell.align.left\": \"Wyrównaj zawartość komórki do lewej\",\n    \"table.menu.cell.align.right\": \"Wyrównaj zawartość komórki do prawej\",\n    \"table.menu.cell.align.center\": \"Wyrównaj zawartość komórki do środka\",\n    \"table.menu.row.add\": \"Dodaj wiersz po\",\n    \"table.menu.row.remove\": \"Usuń wiersz\",\n    \"table.menu.column.add\": \"Dodaj kolumnę po\",\n    \"table.menu.column.remove\": \"Usuń kolumnę\",\n    \"table.menu.convert.yfm\": \"Konwertuj na tabelę YFM\",\n    \"table.menu.table.remove\": \"Usuń tabelę\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/pt-BR/core.js",
    "content": "import dateFns from 'date-fns/locale/pt-BR';\nimport timeAgo from 'javascript-time-ago/locale/pt';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'dd/MM/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d 'de' MMMM 'às' p\",\n    fullDate: 'd MMM, y',\n    fullDateTime: \"d 'de' MMMM, y 'às' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Sobre o aplicativo',\n      aboutPlanka_title: 'Sobre o PLANKA',\n      accessToken: 'Token de acesso',\n      account: 'Conta',\n      actions: 'Ações',\n      activateUser_title: 'Ativar usuário',\n      active: 'Ativo',\n      addAttachment_title: 'Adicionar anexo',\n      addCustomFieldGroup_title: 'Adicionar grupo de campo personalizado',\n      addCustomField_title: 'Adicionar campo personalizado',\n      addManager_title: 'Adicionar gerente',\n      addMember_title: 'Adicionar membro',\n      addTaskList_title: 'Adicionar lista de tarefas',\n      addUser_title: 'Adicionar usuário',\n      admin: 'Admin',\n      administration: 'Administração',\n      all: 'Todos',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Todas as alterações serão salvas automaticamente<br />após a conexão ser restaurada.',\n      alphabetically: 'Em ordem alfabética',\n      alwaysDisplayCardCreator: 'Sempre exibir criador do cartão',\n      apiKeyCreated_title: 'Chave API criada',\n      apiKey_title: 'Chave API',\n      archive: 'Arquivar',\n      archiveCard_title: 'Arquivar cartão',\n      archiveCards_title: 'Arquivar cartões',\n      areYouSureYouWantToActivateThisUser: 'Tem certeza de que deseja ativar este usuário?',\n      areYouSureYouWantToArchiveCards: 'Tem certeza de que deseja arquivar estes cartões?',\n      areYouSureYouWantToArchiveThisCard: 'Tem certeza de que deseja arquivar este cartão?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Tem certeza de que deseja atribuir este gerente de projeto como proprietário?',\n      areYouSureYouWantToDeactivateThisUser: 'Tem certeza de que deseja desativar este usuário?',\n      areYouSureYouWantToDeleteThisApiKey: 'Tem certeza de que deseja excluir esta chave API?',\n      areYouSureYouWantToDeleteThisAttachment: 'Tem certeza de que deseja excluir este anexo?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Tem certeza que deseja excluir esta imagem de fundo?',\n      areYouSureYouWantToDeleteThisBoard: 'Tem certeza de que deseja excluir este quadro?',\n      areYouSureYouWantToDeleteThisCard: 'Tem certeza de que deseja excluir este cartão?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Tem certeza de que deseja excluir este cartão para sempre?',\n      areYouSureYouWantToDeleteThisComment: 'Tem certeza de que deseja excluir este comentário?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Tem certeza de que deseja excluir este campo personalizado?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Tem certeza de que deseja excluir este grupo de campo personalizado?',\n      areYouSureYouWantToDeleteThisLabel: 'Tem certeza de que deseja excluir este rótulo?',\n      areYouSureYouWantToDeleteThisList:\n        'Tem certeza de que deseja excluir esta lista? Todos os cartões serão movidos para a lixeira.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Tem certeza de que deseja excluir este serviço de notificação?',\n      areYouSureYouWantToDeleteThisProject: 'Tem certeza de que deseja excluir este projeto?',\n      areYouSureYouWantToDeleteThisTask: 'Tem certeza de que deseja excluir esta tarefa?',\n      areYouSureYouWantToDeleteThisTaskList:\n        'Tem certeza de que deseja excluir esta lista de tarefas?',\n      areYouSureYouWantToDeleteThisUser: 'Tem certeza de que deseja excluir este usuário?',\n      areYouSureYouWantToDeleteThisWebhook: 'Tem certeza de que deseja excluir este webhook?',\n      areYouSureYouWantToEmptyTrash: 'Tem certeza de que deseja esvaziar a lixeira?',\n      areYouSureYouWantToLeaveBoard: 'Tem certeza de que deseja sair do quadro?',\n      areYouSureYouWantToLeaveProject: 'Tem certeza de que deseja sair do projeto?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Tem certeza de que deseja tornar este projeto privado?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Tem certeza de que deseja tornar este projeto compartilhado?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Tem certeza de que deseja regenerar esta chave API? A chave anterior não funcionará mais.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Tem certeza de que deseja remover este gerente do projeto?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Tem certeza de que deseja remover este membro do quadro?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Tem certeza de que deseja desvincular o SSO deste usuário? Isso permitirá que o usuário faça login com uma senha.',\n      assignAsOwner_title: 'Atribuir como proprietário',\n      atLeastOneListMustBePresent: 'Pelo menos uma lista deve estar presente',\n      attachment: 'Anexo',\n      attachments: 'Anexos',\n      authentication: 'Autenticação',\n      background: 'Fundo',\n      baseCustomFields_title: 'Campos personalizados básicos',\n      baseGroup: 'Grupo básico',\n      board: 'Quadro',\n      boardActions_title: 'Ações do quadro',\n      boardNotFound_title: 'Quadro não encontrado',\n      boardSubscribed: 'Inscrito no quadro',\n      boardUser: 'Usuário do quadro',\n      byCreationTime: 'Por ordem de criação',\n      byDefault: 'Por padrão',\n      byDueDate: 'Por data de vencimento',\n      canBeInvitedToWorkInBoards: 'Pode ser convidado para trabalhar em quadros.',\n      canComment: 'Pode comentar',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Pode criar seus próprios projetos e ser convidado para trabalhar em outros.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Pode editar o layout do quadro e atribuir membros aos cartões.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Pode gerenciar configurações de sistema e atuar como proprietário do projeto.',\n      canOnlyViewBoard: 'Só pode visualizar o quadro.',\n      cardActions_title: 'Ações do cartão',\n      cardNotFound_title: 'Cartão não encontrado',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Os cartões nesta lista estão disponíveis para todos os membros do quadro.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Os cartões nesta lista estão completos e prontos para serem arquivados.',\n      cardsOnThisListAreReadyToBeWorkedOn:\n        'Os cartões nesta lista estão prontos para serem trabalhados.',\n      clickHereOrRefreshPageToUpdate: '<0>Clique aqui</0> ou atualize a página para atualizar.',\n      clientHostnameInEhlo: 'Nome do host do cliente no EHLO',\n      closed: 'Fechado',\n      color: 'Cor',\n      comments: 'Comentários',\n      contentExceedsLimit: 'O conteúdo excede o {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'O conteúdo deste anexo é muito grande para ser exibido.',\n      copy_inline: 'cópia',\n      createBoard_title: 'Criar quadro',\n      createCustomFieldGroup_title: 'Criar grupo de campo personalizado',\n      createLabel_title: 'Criar rótulo',\n      createNewOneOrSelectExistingOne: 'Criar um novo ou selecionar<br />um existente.',\n      createProject_title: 'Criar projeto',\n      createTextFile_title: 'Criar arquivo de texto',\n      creator: 'Criador',\n      currentPassword: 'Senha atual',\n      currentUser: 'Usuário atual',\n      customFieldGroup_title: 'Grupo de campo personalizado',\n      customFieldGroups_title: 'Grupos de campo personalizado',\n      customField_title: 'Campo personalizado',\n      customFields_title: 'Campos personalizados',\n      customerPanel_title: 'Painel do cliente',\n      dangerZone_title: 'Zona de perigo',\n      date: 'Data',\n      deactivateUser_title: 'Desativar usuário',\n      defaultCardType_title: 'Tipo de cartão padrão',\n      defaultFrom: '\"De\" padrão',\n      defaultView_title: 'Visualização padrão',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Excluir todos os quadros para poder excluir este projeto',\n      deleteApiKey_title: 'Excluir chave API',\n      deleteAttachment_title: 'Excluir anexo',\n      deleteBackgroundImage_title: 'Excluir imagem de fundo',\n      deleteBoard_title: 'Excluir quadro',\n      deleteCardForever_title: 'Excluir cartão para sempre',\n      deleteCard_title: 'Excluir cartão',\n      deleteComment_title: 'Excluir comentário',\n      deleteCustomFieldGroup_title: 'Excluir grupo de campo personalizado',\n      deleteCustomField_title: 'Excluir campo personalizado',\n      deleteLabel_title: 'Excluir rótulo',\n      deleteList_title: 'Excluir lista',\n      deleteNotificationService_title: 'Excluir serviço de notificação',\n      deleteProject_title: 'Excluir projeto',\n      deleteTaskList_title: 'Excluir lista de tarefas',\n      deleteTask_title: 'Excluir tarefa',\n      deleteUser_title: 'Excluir usuário',\n      deleteWebhook_title: 'Excluir webhook',\n      deletedUser_title: 'Usuário excluído',\n      description: 'Descrição',\n      display: 'Exibir',\n      displayCardAges: 'Exibir idade dos cartões',\n      dropFileToUpload: 'Solte o arquivo para enviar',\n      dueDate_title: 'Data de vencimento',\n      dynamicAndUnevenlySpacedLayout: 'Layout dinâmico e desigualmente espaçado.',\n      editAttachment_title: 'Editar anexo',\n      editAvatar_title: 'Editar avatar',\n      editColor_title: 'Editar cor',\n      editCustomFieldGroup_title: 'Editar grupo de campo personalizado',\n      editCustomField_title: 'Editar campo personalizado',\n      editDueDate_title: 'Editar data de vencimento',\n      editEmail_title: 'Editar e-mail',\n      editInformation_title: 'Editar informações',\n      editLabel_title: 'Editar rótulo',\n      editPassword_title: 'Editar senha',\n      editPermissions_title: 'Editar permissões',\n      editRole_title: 'Alterar função',\n      editStopwatch_title: 'Editar cronômetro',\n      editType_title: 'Alterar tipo',\n      editUsername_title: 'Editar nome de usuário',\n      editor: 'Editor',\n      editors: 'Editores',\n      email: 'E-mail',\n      emptyTrash_title: 'Esvaziar lixeira',\n      enterCardTitle: 'Digite o título do cartão...',\n      enterDescription: 'Digite a descrição...',\n      enterFilename: 'Digite o nome do arquivo',\n      enterListTitle: 'Digite o título da lista...',\n      enterTaskDescription: 'Digite a descrição da tarefa...',\n      events: 'Eventos',\n      excludedEvents: 'Eventos excluídos',\n      expandTaskListsByDefault: 'Expandir listas de tarefas por padrão',\n      filterByLabels_title: 'Filtrar por rótulos',\n      filterByMembers_title: 'Filtrar por membros',\n      forPersonalProjects: 'Para projetos pessoais.',\n      forTeamBasedProjects: 'Para projetos em equipe.',\n      fromComputer_title: 'Do computador',\n      fromTrello: 'Do Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'A chave completa está oculta por motivos de segurança. Regenere-a para criar uma nova.',\n      general: 'Geral',\n      gradients: 'Gradientes',\n      grid: 'Grade',\n      hideCompletedTasks: 'Ocultar tarefas concluídas',\n      hideFromProjectListAndFavorites: 'Ocultar da lista de projetos e favoritos',\n      host: 'Host',\n      hours: 'Horas',\n      identity: 'Identidade',\n      importBoard_title: 'Importar quadro',\n      information: 'Informação',\n      invalidCurrentPassword: 'Senha atual inválida',\n      kanban: 'Kanban',\n      labels: 'Rótulos',\n      language: 'Idioma',\n      leaveBoard_title: 'Sair do quadro',\n      leaveProject_title: 'Sair do projeto',\n      limitCardTypesToDefaultOne: 'Limitar tipos de cartão ao padrão',\n      linkToCard: 'Link para o cartão',\n      list: 'Lista',\n      listActions_title: 'Ações da lista',\n      lists: 'Listas',\n      makeProjectPrivate_title: 'Tornar projeto privado',\n      makeProjectShared_title: 'Tornar projeto compartilhado',\n      managers: 'Gerentes',\n      memberActions_title: 'Ações do membro',\n      members: 'Membros',\n      minutes: 'Minutos',\n      moreActions: 'Mais ações',\n      moreActions_title: 'Mais ações',\n      moveCard_title: 'Mover cartão',\n      moveList_title: 'Mover lista',\n      myOwn_title: 'Meus',\n      name: 'Nome',\n      newEmail: 'Novo e-mail',\n      newPassword: 'Nova senha',\n      newUsername: 'Novo nome de usuário',\n      newVersionAvailable: 'Nova versão disponível',\n      newestFirst: 'Mais recentes primeiro',\n      noApiKeyCreated: 'Nenhuma chave API criada.',\n      noBoards: 'Sem quadros',\n      noCardsFound: 'Nenhum cartão encontrado.',\n      noConnectionToServer: 'Sem conexão com o servidor',\n      noLists: 'Sem listas',\n      noProjects: 'Sem projetos',\n      noUnreadNotifications: 'Nenhuma notificação não lida.',\n      notifications: 'Notificações',\n      oldestFirst: 'Mais antigos primeiro',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Apenas um gerente deve permanecer para tornar este projeto privado',\n      openBoard_title: 'Abrir quadro',\n      optional_inline: 'opcional',\n      organization: 'Organização',\n      others: 'Outros',\n      passwordIsSet: 'Senha definida',\n      phone: 'Telefone',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA usa <1><0>Apprise</0></1> para enviar notificações para mais de 100 serviços populares.',\n      port: 'Porta',\n      preferences: 'Preferências',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Dica: pressione Ctrl-V (Cmd-V no Mac) para adicionar um anexo da área de transferência.',\n      private: 'Privado',\n      project: 'Projeto',\n      projectNotFound_title: 'Projeto não encontrado',\n      projectOwner: 'Proprietário do projeto',\n      referenceDataAndKnowledgeStorage: 'Armazenamento de dados de referência e conhecimento.',\n      regenerateApiKey_title: 'Regenerar chave API',\n      rejectUnauthorizedTlsCertificates: 'Rejeitar certificados TLS não autorizados',\n      removeManager_title: 'Remover gerente',\n      removeMember_title: 'Remover membro',\n      role: 'Função',\n      saveThisKeyItWillNotBeShownAgain: 'Salve esta chave — ela não será exibida novamente!',\n      searchCards: 'Pesquisar cartões...',\n      searchCustomFieldGroups: 'Pesquisar grupos de campos personalizados...',\n      searchCustomFields: 'Pesquisar campos personalizados...',\n      searchLabels: 'Pesquisar rótulos...',\n      searchLists: 'Pesquisar listas...',\n      searchMembers: 'Pesquisar membros...',\n      searchProjects: 'Pesquisar projetos...',\n      searchUsers: 'Pesquisar usuários...',\n      seconds: 'Segundos',\n      selectAssignee_title: 'Selecionar responsável',\n      selectBoard: 'Selecionar quadro',\n      selectList: 'Selecionar lista',\n      selectListToRestoreThisCard: 'Selecionar lista para restaurar este cartão',\n      selectOrder_title: 'Selecionar ordem',\n      selectPermissions_title: 'Selecionar permissões',\n      selectProject: 'Selecionar projeto',\n      selectRole_title: 'Selecionar função',\n      selectType_title: 'Selecionar tipo',\n      sequentialDisplayOfCards: 'Exibição sequencial de cartões.',\n      settings: 'Configurações',\n      shared: 'Compartilhado',\n      sharedWithMe_title: 'Compartilhado comigo',\n      showOnFrontOfCard: 'Mostrar na frente do cartão',\n      smtp: 'SMTP',\n      sortList_title: 'Ordenar lista',\n      sourceCardIsNoLongerAvailableForCopying:\n        'O cartão de origem não está mais disponível para cópia.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'O cartão de origem não está mais disponível para mover.',\n      stopwatch: 'Cronômetro',\n      story: 'História',\n      subscribeToCardWhenCommenting: 'Inscrever-se no cartão ao comentar',\n      subscribeToMyOwnCardsByDefault: 'Inscrever-se automaticamente em meus próprios cartões',\n      taskActions_title: 'Ações da tarefa',\n      taskAssignmentAndProjectCompletion: 'Atribuição de tarefas e conclusão do projeto.',\n      taskListActions_title: 'Ações da lista de tarefas',\n      taskList_title: 'Lista de tarefas',\n      team: 'Equipe',\n      termsOfService_title: 'Termos de serviço',\n      testLog_title: 'Log de teste',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'Não há pré-visualização disponível para este anexo.',\n      time: 'Tempo',\n      title: 'Título',\n      trash: 'Lixeira',\n      trashHasBeenSuccessfullyEmptied: 'A lixeira foi esvaziada com sucesso.',\n      turnOffRecentCardHighlighting: 'Desativar destaque de cartões recentes',\n      typeNameToConfirm: 'Digite o nome para confirmar.',\n      typeTitleToConfirm: 'Digite o título para confirmar.',\n      unlinkSso_title: 'Desvinculação de SSO',\n      unsavedChanges: 'Alterações não salvas',\n      uploadFailedFileIsTooBig: 'Falha no upload: arquivo muito grande.',\n      uploadFailedNotEnoughStorageSpace: 'Falha no upload: espaço de armazenamento insuficiente.',\n      uploadedImages: 'Imagens enviadas',\n      url: 'URL',\n      useSecureConnection: 'Usar conexão segura',\n      userActions_title: 'Ações do usuário',\n      userAddedCardToList: '<0>{{user}}</0> adicionou <2>{{card}}</2> à {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> adicionou este cartão a {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> adicionou {{addedUser}} a <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> adicionou {{addedUser}} a este cartão',\n      userAddedYouToCard: '<0>{{user}}</0> adicionou você a <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> concluiu {{task}} em <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> concluiu {{task}} neste cartão',\n      userJoinedCard: '<0>{{user}}</0> entrou em <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> entrou neste cartão',\n      userLeftCard: '<0>{{user}}</0> saiu de <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> deixou um novo comentário «{{comment}}» em <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> saiu deste cartão',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> marcou {{task}} como incompleta em <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> marcou {{task}} como incompleta neste cartão',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> mencionou você em um comentário «{{comment}}» em <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> moveu <2>{{card}}</2> de {{fromList}} para {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> moveu este cartão de {{fromList}} para {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> removeu {{removedUser}} de <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> removeu {{removedUser}} deste cartão',\n      username: 'Nome de usuário',\n      users: 'Usuários',\n      viewer: 'Visualizador',\n      viewers: 'Visualizadores',\n      visualTaskManagementWithLists: 'Gerenciamento visual de tarefas com listas.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Novidades',\n      withoutBaseGroup: 'Sem grupo básico',\n      writeComment: 'Escreva um comentário...',\n    },\n\n    action: {\n      activateUser: 'Ativar usuário',\n      activateUser_title: 'Ativar usuário',\n      addAnotherCard: 'Adicionar outro cartão',\n      addAnotherList: 'Adicionar outra lista',\n      addAnotherTask: 'Adicionar outra tarefa',\n      addCard: 'Adicionar cartão',\n      addCard_title: 'Adicionar cartão',\n      addComment: 'Adicionar comentário',\n      addCustomField: 'Adicionar campo personalizado',\n      addCustomFieldGroup: 'Adicionar grupo de campos personalizados',\n      addList: 'Adicionar lista',\n      addMember: 'Adicionar membro',\n      addMoreDetailedDescription: 'Adicionar descrição mais detalhada',\n      addTask: 'Adicionar tarefa',\n      addTaskList: 'Adicionar lista de tarefas',\n      addToCard: 'Adicionar ao cartão',\n      addUser: 'Adicionar usuário',\n      addWebhook: 'Adicionar webhook',\n      archive: 'Arquivar',\n      archiveCard: 'Arquivar cartão',\n      archiveCard_title: 'Arquivar cartão',\n      archiveCards: 'Arquivar cartões',\n      archiveCards_title: 'Arquivar cartões',\n      assignAsOwner: 'Atribuir como proprietário',\n      cancel: 'Cancelar',\n      copy: 'Copiar',\n      copyCard_title: 'Copiar cartão',\n      createApiKey: 'Criar chave API',\n      createBoard: 'Criar quadro',\n      createCustomFieldGroup: 'Criar grupo de campos personalizados',\n      createFile: 'Criar arquivo',\n      createLabel: 'Criar rótulo',\n      createNewLabel: 'Criar novo rótulo',\n      createProject: 'Criar projeto',\n      cut: 'Cortar',\n      cutCard_title: 'Cortar cartão',\n      deactivateUser: 'Desativar usuário',\n      deactivateUser_title: 'Desativar usuário',\n      delete: 'Excluir',\n      deleteApiKey: 'Excluir chave API',\n      deleteAttachment: 'Excluir anexo',\n      deleteAvatar: 'Excluir avatar',\n      deleteBackgroundImage: 'Excluir imagem de fundo',\n      deleteBoard: 'Excluir quadro',\n      deleteBoard_title: 'Excluir quadro',\n      deleteCard: 'Excluir cartão',\n      deleteCardForever: 'Excluir cartão para sempre',\n      deleteCard_title: 'Excluir cartão',\n      deleteComment: 'Excluir comentário',\n      deleteCustomField: 'Excluir campo personalizado',\n      deleteCustomFieldGroup: 'Excluir grupo de campos personalizados',\n      deleteForever_title: 'Excluir para sempre',\n      deleteGroup: 'Excluir grupo',\n      deleteLabel: 'Excluir rótulo',\n      deleteList: 'Excluir lista',\n      deleteList_title: 'Excluir lista',\n      deleteNotificationService: 'Excluir serviço de notificação',\n      deleteProject: 'Excluir projeto',\n      deleteProject_title: 'Excluir projeto',\n      deleteTask: 'Excluir tarefa',\n      deleteTaskList: 'Excluir lista de tarefas',\n      deleteTask_title: 'Excluir tarefa',\n      deleteUser: 'Excluir usuário',\n      deleteUser_title: 'Excluir usuário',\n      deleteWebhook: 'Excluir webhook',\n      dismissAll: 'Dispensar todos',\n      download: 'Baixar',\n      duplicateCard_title: 'Duplicar cartão',\n      edit: 'Editar',\n      editColor_title: 'Editar cor',\n      editDescription_title: 'Editar descrição',\n      editDueDate_title: 'Editar data de vencimento',\n      editEmail_title: 'Editar e-mail',\n      editGroup: 'Editar grupo',\n      editInformation_title: 'Editar informações',\n      editPassword_title: 'Editar senha',\n      editPermissions: 'Editar permissões',\n      editRole_title: 'Editar função',\n      editStopwatch_title: 'Editar cronômetro',\n      editTitle_title: 'Editar título',\n      editType_title: 'Editar tipo',\n      editUsername_title: 'Editar nome de usuário',\n      emptyTrash: 'Esvaziar lixeira',\n      emptyTrash_title: 'Esvaziar lixeira',\n      import: 'Importar',\n      join: 'Participar',\n      leave: 'Sair',\n      leaveBoard: 'Sair do quadro',\n      leaveProject: 'Sair do projeto',\n      logOut_title: 'Sair',\n      makeCover_title: 'Tornar capa',\n      makeProjectPrivate: 'Tornar projeto privado',\n      makeProjectPrivate_title: 'Tornar projeto privado',\n      makeProjectShared: 'Tornar projeto compartilhado',\n      makeProjectShared_title: 'Tornar projeto compartilhado',\n      move: 'Mover',\n      moveCard_title: 'Mover cartão',\n      moveList_title: 'Mover lista',\n      regenerateApiKey: 'Regenerar chave API',\n      remove: 'Remover',\n      removeAssignee: 'Remover responsável',\n      removeColor: 'Remover cor',\n      removeCover_title: 'Remover capa',\n      removeFromBoard: 'Remover do quadro',\n      removeFromProject: 'Remover do projeto',\n      removeManager: 'Remover gerente',\n      removeMember: 'Remover membro',\n      restoreToList: 'Restaurar para {{list}}',\n      returnToBoard: 'Voltar ao quadro',\n      save: 'Salvar',\n      sendTestEmail: 'Enviar email de teste',\n      showActive: 'Mostrar ativos',\n      showAllAttachments: 'Mostrar todos os anexos ({{hidden}} ocultos)',\n      showCardsWithThisUser: 'Mostrar cartões com este usuário',\n      showDeactivated: 'Mostrar desativados',\n      showFewerAttachments: 'Mostrar menos anexos',\n      showLess: 'Mostrar menos',\n      showMore: 'Mostrar mais',\n      sortList_title: 'Ordenar lista',\n      start: 'Iniciar',\n      stop: 'Parar',\n      subscribe: 'Inscrever-se',\n      unlinkSso: 'Desvincular SSO',\n      unlinkSso_title: 'Desvincular SSO',\n      unsubscribe: 'Cancelar inscrição',\n      uploadNewAvatar: 'Enviar novo avatar',\n      uploadNewImage: 'Enviar nova imagem',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/pt-BR/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'pt-BR',\n  country: 'br',\n  name: 'Português',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/pt-BR/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Limite de usuários ativos atingido',\n      adminLoginRequiredToInitializeInstance:\n        'Login de administrador necessário para inicializar a instância',\n      emailAlreadyInUse: 'E-mail já está em uso',\n      emailOrUsername: 'E-mail ou nome de usuário',\n      invalidCredentials: 'Credenciais inválidas',\n      invalidEmailOrUsername: 'E-mail ou nome de usuário inválido',\n      invalidPassword: 'Senha inválida',\n      logIn_title: 'Entrar',\n      noInternetConnection: 'Sem conexão com a internet',\n      or: 'Ou',\n      pageNotFound_title: 'Página não encontrada',\n      password: 'Senha',\n      poweredByPlanka: 'Desenvolvido por <1>PLANKA</1>',\n      serverConnectionFailed: 'Falha na conexão com o servidor',\n      unknownError: 'Erro desconhecido, tente novamente mais tarde',\n      useSingleSignOn: 'Usar login único',\n      usernameAlreadyInUse: 'Nome de usuário já está em uso',\n      whoops_title: 'Ops!',\n    },\n\n    action: {\n      cancelAndClose: 'Cancelar e fechar',\n      continue: 'Continuar',\n      debugSso: 'Depurar SSO',\n      goBack: 'Voltar',\n      goHome: 'Ir para início',\n      logIn: 'Entrar',\n      logInWithSso: 'Entrar com SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/pt-BR/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Este é um texto sem título.\\nTanto o título quanto o texto\\npodem ser destacados em negrito, itálico, cor,\\nriscado e sublinhado.\",\n    \"text-with-head\": \"Este é um texto com título.\\nTanto o título quanto o texto\\npodem ser destacados em negrito, itálico, cor,\\nriscado e sublinhado.\",\n    \"heading\": \"Título\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Erro no editor markdown\",\n    \"settings_wysiwyg\": \"Editor visual (wysiwyg)\",\n    \"settings_markup\": \"Marcação markdown\",\n    \"markup_placeholder\": \"Digite a marcação markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Remover\",\n    \"empty_option\": \"Nenhuma correspondência encontrada\",\n    \"show_line_numbers\": \"Numeração de linhas\"\n  },\n  \"common\": {\n    \"delete\": \"Excluir\",\n    \"edit\": \"Editar\",\n    \"toolbar_action_disabled\": \"Elemento de marcação incompatível\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Cancelar\",\n    \"common_action_submit\": \"Enviar\",\n    \"common_action_upload\": \"Selecionar\",\n    \"common_tab_attach\": \"Adicionar do dispositivo\",\n    \"common_tab_link\": \"Adicionar por link\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Tamanho, px\",\n    \"image_name\": \"Título\",\n    \"image_link_href\": \"Link da imagem\",\n    \"image_link_href_help\": \"Endereço para o qual o link da imagem leva.\",\n    \"image_alt\": \"Texto alternativo\",\n    \"image_alt_help\": \"O texto alternativo é exibido se a imagem não puder ser carregada.\",\n    \"image_upload_help\": \"Imagem JPEG, GIF ou PNG de até 1 MB.\",\n    \"image_upload_failed\": \"Falha ao adicionar imagem\",\n    \"image_size_width\": \"Largura\",\n    \"image_size_height\": \"Altura\",\n    \"link_url_help\": \"Endereço para o qual o link leva.\",\n    \"link_text\": \"Texto do link\",\n    \"link_text_help\": \"Texto exibido como link.\",\n    \"link_open_help\": \"Abrir o link em uma nova aba\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Cabeçalho\",\n    \"header_hint\": \"# Seu texto\",\n    \"italic_title\": \"Itálico\",\n    \"italic_hint\": \"_Seu texto_\",\n    \"bold_title\": \"Negrito\",\n    \"bold_hint\": \"**Seu texto**\",\n    \"strikethrough_title\": \"Riscado\",\n    \"strikethrough_hint\": \"~~Seu texto~~\",\n    \"blockquote_title\": \"Citação\",\n    \"blockquote_hint\": \"> Seu texto\",\n    \"code_title\": \"Código\",\n    \"code_hint\": \"```Seu texto```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[Seu texto](url)\",\n    \"image_title\": \"Imagem\",\n    \"image_hint\": \"![Seu texto](url)\",\n    \"list_title\": \"Item da lista\",\n    \"list_hint\": \"- Seu texto\",\n    \"numbered-list_title\": \"Lista numerada\",\n    \"numbered-list_hint\": \"1. Seu texto\",\n    \"documentation\": \"Documentação\",\n    \"documentation_link\": \"https://diplodoc.com/docs/pt/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Negrito\",\n    \"code\": \"Código\",\n    \"code_inline\": \"Código inline\",\n    \"codeblock\": \"Bloco de código\",\n    \"colorify\": \"Cor do texto\",\n    \"colorify__color_blue\": \"Azul\",\n    \"colorify__color_default\": \"Padrão\",\n    \"colorify__color_gray\": \"Cinza\",\n    \"colorify__color_green\": \"Verde\",\n    \"colorify__color_orange\": \"Laranja\",\n    \"colorify__color_red\": \"Vermelho\",\n    \"colorify__color_violet\": \"Violeta\",\n    \"colorify__color_yellow\": \"Amarelo\",\n    \"colorify__group_text\": \"Texto\",\n    \"cut\": \"Cortar\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emojis podem ser adicionados no WYSIWYG ou manualmente com marcação\",\n    \"heading\": \"Cabeçalho\",\n    \"heading1\": \"Cabeçalho 1\",\n    \"heading2\": \"Cabeçalho 2\",\n    \"heading3\": \"Cabeçalho 3\",\n    \"heading4\": \"Cabeçalho 4\",\n    \"heading5\": \"Cabeçalho 5\",\n    \"heading6\": \"Cabeçalho 6\",\n    \"hrule\": \"Separador\",\n    \"image\": \"Imagem\",\n    \"italic\": \"Itálico\",\n    \"link\": \"Link\",\n    \"list\": \"Lista\",\n    \"list__action_lift\": \"Levantar item\",\n    \"list__action_sink\": \"Rebaixar item\",\n    \"list_action_disabled\": \"Contradiz a lógica da lista\",\n    \"mark\": \"Marcado\",\n    \"mono\": \"Monoespaçado\",\n    \"more_action\": \"Mais ações\",\n    \"note\": \"Nota\",\n    \"olist\": \"Lista ordenada\",\n    \"quote\": \"Citação\",\n    \"redo\": \"Refazer\",\n    \"strike\": \"Riscado\",\n    \"table\": \"Tabela\",\n    \"text\": \"Texto\",\n    \"ulist\": \"Lista com marcadores\",\n    \"underline\": \"Sublinhado\",\n    \"undo\": \"Desfazer\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Digite / para usar comandos de barra...\",\n    \"checkbox\": \"Digite a descrição da tarefa...\",\n    \"deflist_term\": \"Termo\",\n    \"deflist_desc\": \"Descrição da definição\",\n    \"heading\": \"Cabeçalho\",\n    \"cut_title\": \"Título\",\n    \"cut_content\": \"Conteúdo a ser exibido ao clicar\",\n    \"note_title\": \"Título\",\n    \"note_content\": \"Conteúdo da nota\",\n    \"table_cell\": \"Conteúdo da célula\",\n    \"select_filter\": \"Pesquisar idiomas...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Diferenciar maiúsculas de minúsculas\",\n    \"label_whole-word\": \"Palavra inteira\",\n    \"title\": \"Pesquisar no código\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Não encontrado\"\n  },\n  \"widgets\": {\n    \"image\": \"Adicionar imagem\",\n    \"link\": \"Adicionar link\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Nota\",\n    \"tip\": \"Dica\",\n    \"warning\": \"Aviso\",\n    \"alert\": \"Alerta\",\n    \"remove\": \"Remover\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Adicionar coluna antes\",\n    \"column.add.after\": \"Adicionar coluna depois\",\n    \"column.remove\": \"Remover coluna\",\n    \"column.remove.multiple\": \"Remover colunas\",\n    \"row.add.before\": \"Adicionar linha antes\",\n    \"row.add.after\": \"Adicionar linha depois\",\n    \"row.remove\": \"Remover linha\",\n    \"row.remove.multiple\": \"Remover linhas\",\n    \"cells.clear\": \"Limpar células\",\n    \"table.remove\": \"Remover tabela\",\n    \"table.menu.cell.align.left\": \"Alinhar conteúdo da célula à esquerda\",\n    \"table.menu.cell.align.right\": \"Alinhar conteúdo da célula à direita\",\n    \"table.menu.cell.align.center\": \"Centralizar conteúdo da célula\",\n    \"table.menu.row.add\": \"Adicionar linha depois\",\n    \"table.menu.row.remove\": \"Remover linha\",\n    \"table.menu.column.add\": \"Adicionar coluna depois\",\n    \"table.menu.column.remove\": \"Remover coluna\",\n    \"table.menu.convert.yfm\": \"Converter para tabela YFM\",\n    \"table.menu.table.remove\": \"Remover tabela\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/pt-PT/core.js",
    "content": "import dateFns from 'date-fns/locale/pt';\nimport timeAgo from 'javascript-time-ago/locale/pt';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'dd/MM/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d 'de' MMMM 'às' p\",\n    fullDate: 'd MMM, y',\n    fullDateTime: \"d 'de' MMMM, y 'às' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Sobre a aplicação',\n      aboutPlanka_title: 'Sobre o PLANKA',\n      accessToken: 'Token de acesso',\n      account: 'Conta',\n      actions: 'Ações',\n      activateUser_title: 'Ativar utilizador',\n      active: 'Ativo',\n      addAttachment_title: 'Anexar ficheiro',\n      addCustomFieldGroup_title: 'Adicionar grupo de campos personalizados',\n      addCustomField_title: 'Adicionar campo personalizado',\n      addManager_title: 'Adicionar gestor',\n      addMember_title: 'Adicionar membro',\n      addTaskList_title: 'Adicionar lista de tarefas',\n      addUser_title: 'Adicionar utilizador',\n      admin: 'Administrador',\n      administration: 'Administração',\n      all: 'Todos',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Todas as alterações serão automaticamente guardadas<br />após a ligação ser restabelecida.',\n      alphabetically: 'Alfabeticamente',\n      alwaysDisplayCardCreator: 'Mostrar sempre o criador do cartão',\n      apiKeyCreated_title: 'Chave API criada',\n      apiKey_title: 'Chave API',\n      archive: 'Arquivo',\n      archiveCard_title: 'Arquivar cartão',\n      archiveCards_title: 'Arquivar cartões',\n      areYouSureYouWantToActivateThisUser: 'Tem a certeza de que pretende ativar este utilizador?',\n      areYouSureYouWantToArchiveCards: 'Tem a certeza de que pretende arquivar estes cartões?',\n      areYouSureYouWantToArchiveThisCard: 'Tem a certeza de que pretende arquivar este cartão?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Tem a certeza de que pretende atribuir este gestor de projeto como proprietário?',\n      areYouSureYouWantToDeactivateThisUser:\n        'Tem a certeza de que pretende desativar este utilizador?',\n      areYouSureYouWantToDeleteThisApiKey: 'Tem a certeza de que pretende eliminar esta chave API?',\n      areYouSureYouWantToDeleteThisAttachment: 'Tem a certeza de que pretende eliminar este anexo?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Tem a certeza de que pretende eliminar esta imagem de fundo?',\n      areYouSureYouWantToDeleteThisBoard: 'Tem a certeza de que pretende eliminar este quadro?',\n      areYouSureYouWantToDeleteThisCard: 'Tem a certeza de que pretende eliminar este cartão?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Tem a certeza de que pretende eliminar este cartão para sempre?',\n      areYouSureYouWantToDeleteThisComment:\n        'Tem a certeza de que pretende eliminar este comentário?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Tem a certeza de que pretende eliminar este campo personalizado?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Tem a certeza de que pretende eliminar este grupo de campos personalizados?',\n      areYouSureYouWantToDeleteThisLabel: 'Tem a certeza de que pretende eliminar esta etiqueta?',\n      areYouSureYouWantToDeleteThisList:\n        'Tem a certeza de que pretende eliminar esta lista? Todos os cartões serão movidos para o lixo.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Tem a certeza de que pretende eliminar este serviço de notificação?',\n      areYouSureYouWantToDeleteThisProject: 'Tem a certeza de que pretende eliminar este projeto?',\n      areYouSureYouWantToDeleteThisTask: 'Tem a certeza de que pretende eliminar esta tarefa?',\n      areYouSureYouWantToDeleteThisTaskList:\n        'Tem a certeza de que pretende eliminar esta lista de tarefas?',\n      areYouSureYouWantToDeleteThisUser: 'Tem a certeza de que pretende eliminar este utilizador?',\n      areYouSureYouWantToDeleteThisWebhook: 'Tem a certeza de que pretende eliminar este webhook?',\n      areYouSureYouWantToEmptyTrash: 'Tem a certeza de que pretende esvaziar o lixo?',\n      areYouSureYouWantToLeaveBoard: 'Tem a certeza de que pretende sair do quadro?',\n      areYouSureYouWantToLeaveProject: 'Tem a certeza de que pretende sair do projeto?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Tem a certeza de que pretende tornar este projeto privado?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Tem a certeza de que pretende tornar este projeto partilhado?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Tem a certeza de que pretende regenerar esta chave API? A chave anterior deixará de funcionar.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Tem a certeza de que pretende remover este gestor do projeto?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Tem a certeza de que pretende remover este membro do quadro?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Tem a certeza de que pretende desvincular o SSO deste utilizador? Isto permitirá que o utilizador inicie sessão com uma palavra-passe.',\n      assignAsOwner_title: 'Atribuir como proprietário',\n      atLeastOneListMustBePresent: 'Pelo menos uma lista deve estar presente',\n      attachment: 'Anexo',\n      attachments: 'Anexos',\n      authentication: 'Autenticação',\n      background: 'Fundo',\n      baseCustomFields_title: 'Campos personalizados base',\n      baseGroup: 'Grupo base',\n      board: 'Quadro',\n      boardActions_title: 'Ações do quadro',\n      boardNotFound_title: 'Quadro não encontrado',\n      boardSubscribed: 'Quadro subscrito',\n      boardUser: 'Utilizador do quadro',\n      byCreationTime: 'Por hora de criação',\n      byDefault: 'Por defeito',\n      byDueDate: 'Por data de vencimento',\n      canBeInvitedToWorkInBoards: 'Pode ser convidado para trabalhar em quadros.',\n      canComment: 'Pode comentar',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Pode criar os seus próprios projetos e ser convidado para trabalhar noutros.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Pode editar o layout do quadro e atribuir membros aos cartões.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Pode gerir configurações do sistema e atuar como proprietário do projeto.',\n      canOnlyViewBoard: 'Só pode visualizar o quadro.',\n      cardActions_title: 'Ações do cartão',\n      cardNotFound_title: 'Cartão não encontrado',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Os cartões desta lista estão disponíveis para todos os membros do quadro.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Os cartões desta lista estão completos e prontos para serem arquivados.',\n      cardsOnThisListAreReadyToBeWorkedOn:\n        'Os cartões desta lista estão prontos para serem trabalhados.',\n      clickHereOrRefreshPageToUpdate: '<0>Clique aqui</0> ou atualize a página para atualizar.',\n      clientHostnameInEhlo: 'Nome do host do cliente no EHLO',\n      closed: 'Fechado',\n      color: 'Cor',\n      comments: 'Comentários',\n      contentExceedsLimit: 'O conteúdo excede {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'O conteúdo deste anexo é demasiado grande para ser exibido.',\n      copy_inline: 'cópia',\n      createBoard_title: 'Criar quadro',\n      createCustomFieldGroup_title: 'Criar grupo de campos personalizados',\n      createLabel_title: 'Criar etiqueta',\n      createNewOneOrSelectExistingOne: 'Crie um novo ou selecione<br />um existente.',\n      createProject_title: 'Criar projeto',\n      createTextFile_title: 'Criar ficheiro de texto',\n      creator: 'Criador',\n      currentPassword: 'Palavra-passe atual',\n      currentUser: 'Utilizador atual',\n      customFieldGroup_title: 'Grupo de campos personalizados',\n      customFieldGroups_title: 'Grupos de campos personalizados',\n      customField_title: 'Campo personalizado',\n      customFields_title: 'Campos personalizados',\n      customerPanel_title: 'Painel do cliente',\n      dangerZone_title: 'Zona perigosa',\n      date: 'Data',\n      deactivateUser_title: 'Desativar utilizador',\n      defaultCardType_title: 'Tipo de cartão padrão',\n      defaultFrom: 'Padrão de',\n      defaultView_title: 'Vista padrão',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Elimine todos os quadros para poder eliminar este projeto',\n      deleteApiKey_title: 'Eliminar chave API',\n      deleteAttachment_title: 'Eliminar anexo',\n      deleteBackgroundImage_title: 'Eliminar imagem de fundo',\n      deleteBoard_title: 'Eliminar quadro',\n      deleteCardForever_title: 'Eliminar cartão para sempre',\n      deleteCard_title: 'Eliminar cartão',\n      deleteComment_title: 'Eliminar comentário',\n      deleteCustomFieldGroup_title: 'Eliminar grupo de campos personalizados',\n      deleteCustomField_title: 'Eliminar campo personalizado',\n      deleteLabel_title: 'Eliminar etiqueta',\n      deleteList_title: 'Eliminar lista',\n      deleteNotificationService_title: 'Eliminar serviço de notificação',\n      deleteProject_title: 'Eliminar projeto',\n      deleteTaskList_title: 'Eliminar lista de tarefas',\n      deleteTask_title: 'Eliminar tarefa',\n      deleteUser_title: 'Eliminar utilizador',\n      deleteWebhook_title: 'Eliminar webhook',\n      deletedUser_title: 'Utilizador eliminado',\n      description: 'Descrição',\n      display: 'Exibir',\n      displayCardAges: 'Mostrar idade dos cartões',\n      dropFileToUpload: 'Largue o ficheiro para carregar',\n      dueDate_title: 'Data de vencimento',\n      dynamicAndUnevenlySpacedLayout: 'Layout dinâmico e espaçamento irregular.',\n      editAttachment_title: 'Editar anexo',\n      editAvatar_title: 'Editar avatar',\n      editColor_title: 'Editar cor',\n      editCustomFieldGroup_title: 'Editar grupo de campos personalizados',\n      editCustomField_title: 'Editar campo personalizado',\n      editDueDate_title: 'Editar data de vencimento',\n      editEmail_title: 'Editar e-mail',\n      editInformation_title: 'Editar informações',\n      editLabel_title: 'Editar etiqueta',\n      editPassword_title: 'Editar palavra-passe',\n      editPermissions_title: 'Editar permissões',\n      editRole_title: 'Editar função',\n      editStopwatch_title: 'Editar cronómetro',\n      editType_title: 'Editar tipo',\n      editUsername_title: 'Editar nome de utilizador',\n      editor: 'Editor',\n      editors: 'Editores',\n      email: 'E-mail',\n      emptyTrash_title: 'Esvaziar lixo',\n      enterCardTitle: 'Introduza o título do cartão...',\n      enterDescription: 'Introduza a descrição...',\n      enterFilename: 'Introduza o nome do ficheiro',\n      enterListTitle: 'Introduza o título da lista...',\n      enterTaskDescription: 'Introduza a descrição da tarefa...',\n      events: 'Eventos',\n      excludedEvents: 'Eventos excluídos',\n      expandTaskListsByDefault: 'Expandir listas de tarefas por defeito',\n      filterByLabels_title: 'Filtrar por etiquetas',\n      filterByMembers_title: 'Filtrar por membros',\n      forPersonalProjects: 'Para projetos pessoais.',\n      forTeamBasedProjects: 'Para projetos baseados em equipa.',\n      fromComputer_title: 'Do computador',\n      fromTrello: 'Do Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'A chave completa está oculta por razões de segurança. Regenere-a para criar uma nova.',\n      general: 'Geral',\n      gradients: 'Gradientes',\n      grid: 'Grelha',\n      hideCompletedTasks: 'Ocultar tarefas concluídas',\n      hideFromProjectListAndFavorites: 'Ocultar da lista de projetos e favoritos',\n      host: 'Anfitrião',\n      hours: 'Horas',\n      identity: 'Identidade',\n      importBoard_title: 'Importar quadro',\n      information: 'Informação',\n      invalidCurrentPassword: 'Palavra-passe atual inválida',\n      kanban: 'Kanban',\n      labels: 'Etiquetas',\n      language: 'Idioma',\n      leaveBoard_title: 'Sair do quadro',\n      leaveProject_title: 'Sair do projeto',\n      limitCardTypesToDefaultOne: 'Limitar tipos de cartão ao padrão',\n      linkToCard: 'Ligação para cartão',\n      list: 'Lista',\n      listActions_title: 'Ações da lista',\n      lists: 'Listas',\n      makeProjectPrivate_title: 'Tornar projeto privado',\n      makeProjectShared_title: 'Tornar projeto partilhado',\n      managers: 'Gestores',\n      memberActions_title: 'Ações do membro',\n      members: 'Membros',\n      minutes: 'Minutos',\n      moreActions: 'Mais ações',\n      moreActions_title: 'Mais ações',\n      moveCard_title: 'Mover cartão',\n      moveList_title: 'Mover lista',\n      myOwn_title: 'Os meus',\n      name: 'Nome',\n      newEmail: 'Novo e-mail',\n      newPassword: 'Nova palavra-passe',\n      newUsername: 'Novo nome de utilizador',\n      newVersionAvailable: 'Nova versão disponível',\n      newestFirst: 'Mais recentes primeiro',\n      noApiKeyCreated: 'Nenhuma chave API criada.',\n      noBoards: 'Sem quadros',\n      noCardsFound: 'Nenhum cartão encontrado.',\n      noConnectionToServer: 'Sem ligação ao servidor',\n      noLists: 'Sem listas',\n      noProjects: 'Sem projetos',\n      noUnreadNotifications: 'Nenhuma notificação por ler.',\n      notifications: 'Notificações',\n      oldestFirst: 'Mais antigos primeiro',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Apenas um gestor deve permanecer para tornar este projeto privado',\n      openBoard_title: 'Abrir quadro',\n      optional_inline: 'opcional',\n      organization: 'Organização',\n      others: 'Outros',\n      passwordIsSet: 'Palavra-passe definida',\n      phone: 'Telefone',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'O PLANKA usa o <1><0>Apprise</0></1> para enviar notificações para mais de 100 serviços populares.',\n      port: 'Porta',\n      preferences: 'Preferências',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Dica: prima Ctrl-V (Cmd-V no Mac) para adicionar um anexo da área de transferência.',\n      private: 'Privado',\n      project: 'Projeto',\n      projectNotFound_title: 'Projeto não encontrado',\n      projectOwner: 'Proprietário do projeto',\n      referenceDataAndKnowledgeStorage: 'Armazenamento de dados de referência e conhecimento.',\n      regenerateApiKey_title: 'Regenerar chave API',\n      rejectUnauthorizedTlsCertificates: 'Rejeitar certificados TLS não autorizados',\n      removeManager_title: 'Remover gestor',\n      removeMember_title: 'Remover membro',\n      role: 'Função',\n      saveThisKeyItWillNotBeShownAgain: 'Guarde esta chave — não será mostrada novamente!',\n      searchCards: 'Pesquisar cartões...',\n      searchCustomFieldGroups: 'Pesquisar grupos de campos personalizados...',\n      searchCustomFields: 'Pesquisar campos personalizados...',\n      searchLabels: 'Pesquisar etiquetas...',\n      searchLists: 'Pesquisar listas...',\n      searchMembers: 'Pesquisar membros...',\n      searchProjects: 'Pesquisar projetos...',\n      searchUsers: 'Pesquisar utilizadores...',\n      seconds: 'Segundos',\n      selectAssignee_title: 'Selecionar responsável',\n      selectBoard: 'Selecionar quadro',\n      selectList: 'Selecionar lista',\n      selectListToRestoreThisCard: 'Selecionar lista para restaurar este cartão',\n      selectOrder_title: 'Selecionar ordem',\n      selectPermissions_title: 'Selecionar permissões',\n      selectProject: 'Selecionar projeto',\n      selectRole_title: 'Selecionar função',\n      selectType_title: 'Selecionar tipo',\n      sequentialDisplayOfCards: 'Exibição sequencial de cartões.',\n      settings: 'Configurações',\n      shared: 'Partilhado',\n      sharedWithMe_title: 'Partilhados comigo',\n      showOnFrontOfCard: 'Mostrar na frente do cartão',\n      smtp: 'SMTP',\n      sortList_title: 'Ordenar lista',\n      sourceCardIsNoLongerAvailableForCopying:\n        'O cartão de origem já não está disponível para cópia.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'O cartão de origem já não está disponível para mover.',\n      stopwatch: 'Cronómetro',\n      story: 'História',\n      subscribeToCardWhenCommenting: 'Subscrever cartão ao comentar',\n      subscribeToMyOwnCardsByDefault: 'Subscrever automaticamente os meus cartões',\n      taskActions_title: 'Ações da tarefa',\n      taskAssignmentAndProjectCompletion: 'Atribuição de tarefas e conclusão de projeto.',\n      taskListActions_title: 'Ações da lista de tarefas',\n      taskList_title: 'Lista de tarefas',\n      team: 'Equipa',\n      termsOfService_title: 'Termos de serviço',\n      testLog_title: 'Registo de teste',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'Não há pré-visualização disponível para este anexo.',\n      time: 'Tempo',\n      title: 'Título',\n      trash: 'Lixo',\n      trashHasBeenSuccessfullyEmptied: 'O lixo foi esvaziado com sucesso.',\n      turnOffRecentCardHighlighting: 'Desativar destaque de cartões recentes',\n      typeNameToConfirm: 'Digite o nome para confirmar.',\n      typeTitleToConfirm: 'Digite o título para confirmar.',\n      unlinkSso_title: 'Desvinculação de SSO',\n      unsavedChanges: 'Alterações não guardadas',\n      uploadFailedFileIsTooBig: 'Carregamento falhado - ficheiro demasiado grande.',\n      uploadFailedNotEnoughStorageSpace:\n        'Carregamento falhado - espaço de armazenamento insuficiente.',\n      uploadedImages: 'Imagens carregadas',\n      url: 'URL',\n      useSecureConnection: 'Usar ligação segura',\n      userActions_title: 'Ações do utilizador',\n      userAddedCardToList: '<0>{{user}}</0> adicionou <2>{{card}}</2> à {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> adicionou este cartão à {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> adicionou {{addedUser}} a <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> adicionou {{addedUser}} a este cartão',\n      userAddedYouToCard: '<0>{{user}}</0> adicionou-o a <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> completou {{task}} em <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> completou {{task}} neste cartão',\n      userJoinedCard: '<0>{{user}}</0> juntou-se a <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> juntou-se a este cartão',\n      userLeftCard: '<0>{{user}}</0> saiu de <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> deixou um novo comentário «{{comment}}» em <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> saiu deste cartão',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> marcou {{task}} como incompleta em <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> marcou {{task}} como incompleta neste cartão',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> mencionou-o num comentário «{{comment}}» em <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> moveu <2>{{card}}</2> de {{fromList}} para {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> moveu este cartão de {{fromList}} para {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> removeu {{removedUser}} de <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> removeu {{removedUser}} deste cartão',\n      username: 'Nome de utilizador',\n      users: 'Utilizadores',\n      viewer: 'Visualizador',\n      viewers: 'Visualizadores',\n      visualTaskManagementWithLists: 'Gestão visual de tarefas com listas.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Novidades',\n      withoutBaseGroup: 'Sem grupo base',\n      writeComment: 'Escreva um comentário...',\n    },\n\n    action: {\n      activateUser: 'Ativar utilizador',\n      activateUser_title: 'Ativar utilizador',\n      addAnotherCard: 'Adicionar outro cartão',\n      addAnotherList: 'Adicionar outra lista',\n      addAnotherTask: 'Adicionar outra tarefa',\n      addCard: 'Adicionar cartão',\n      addCard_title: 'Adicionar cartão',\n      addComment: 'Adicionar comentário',\n      addCustomField: 'Adicionar campo personalizado',\n      addCustomFieldGroup: 'Adicionar grupo de campos personalizados',\n      addList: 'Adicionar lista',\n      addMember: 'Adicionar membro',\n      addMoreDetailedDescription: 'Adicionar descrição mais detalhada',\n      addTask: 'Adicionar tarefa',\n      addTaskList: 'Adicionar lista de tarefas',\n      addToCard: 'Adicionar ao cartão',\n      addUser: 'Adicionar utilizador',\n      addWebhook: 'Adicionar webhook',\n      archive: 'Arquivo',\n      archiveCard: 'Arquivar cartão',\n      archiveCard_title: 'Arquivar cartão',\n      archiveCards: 'Arquivar cartões',\n      archiveCards_title: 'Arquivar cartões',\n      assignAsOwner: 'Atribuir como proprietário',\n      cancel: 'Cancelar',\n      copy: 'Copiar',\n      copyCard_title: 'Copiar cartão',\n      createApiKey: 'Criar chave API',\n      createBoard: 'Criar quadro',\n      createCustomFieldGroup: 'Criar grupo de campos personalizados',\n      createFile: 'Criar ficheiro',\n      createLabel: 'Criar etiqueta',\n      createNewLabel: 'Criar nova etiqueta',\n      createProject: 'Criar projeto',\n      cut: 'Cortar',\n      cutCard_title: 'Cortar cartão',\n      deactivateUser: 'Desativar utilizador',\n      deactivateUser_title: 'Desativar utilizador',\n      delete: 'Eliminar',\n      deleteApiKey: 'Eliminar chave API',\n      deleteAttachment: 'Eliminar anexo',\n      deleteAvatar: 'Eliminar avatar',\n      deleteBackgroundImage: 'Eliminar imagem de fundo',\n      deleteBoard: 'Eliminar quadro',\n      deleteBoard_title: 'Eliminar quadro',\n      deleteCard: 'Eliminar cartão',\n      deleteCardForever: 'Eliminar cartão para sempre',\n      deleteCard_title: 'Eliminar cartão',\n      deleteComment: 'Eliminar comentário',\n      deleteCustomField: 'Eliminar campo personalizado',\n      deleteCustomFieldGroup: 'Eliminar grupo de campos personalizados',\n      deleteForever_title: 'Eliminar para sempre',\n      deleteGroup: 'Eliminar grupo',\n      deleteLabel: 'Eliminar etiqueta',\n      deleteList: 'Eliminar lista',\n      deleteList_title: 'Eliminar lista',\n      deleteNotificationService: 'Eliminar serviço de notificação',\n      deleteProject: 'Eliminar projeto',\n      deleteProject_title: 'Eliminar projeto',\n      deleteTask: 'Eliminar tarefa',\n      deleteTaskList: 'Eliminar lista de tarefas',\n      deleteTask_title: 'Eliminar tarefa',\n      deleteUser: 'Eliminar utilizador',\n      deleteUser_title: 'Eliminar utilizador',\n      deleteWebhook: 'Eliminar webhook',\n      dismissAll: 'Dispensar tudo',\n      download: 'Transferir',\n      duplicateCard_title: 'Duplicar cartão',\n      edit: 'Editar',\n      editColor_title: 'Editar cor',\n      editDescription_title: 'Editar descrição',\n      editDueDate_title: 'Editar data de vencimento',\n      editEmail_title: 'Editar e-mail',\n      editGroup: 'Editar grupo',\n      editInformation_title: 'Editar informações',\n      editPassword_title: 'Editar palavra-passe',\n      editPermissions: 'Editar permissões',\n      editRole_title: 'Editar função',\n      editStopwatch_title: 'Editar cronómetro',\n      editTitle_title: 'Editar título',\n      editType_title: 'Editar tipo',\n      editUsername_title: 'Editar nome de utilizador',\n      emptyTrash: 'Esvaziar lixo',\n      emptyTrash_title: 'Esvaziar lixo',\n      import: 'Importar',\n      join: 'Aderir',\n      leave: 'Sair',\n      leaveBoard: 'Sair do quadro',\n      leaveProject: 'Sair do projeto',\n      logOut_title: 'Terminar sessão',\n      makeCover_title: 'Definir como capa',\n      makeProjectPrivate: 'Tornar projeto privado',\n      makeProjectPrivate_title: 'Tornar projeto privado',\n      makeProjectShared: 'Tornar projeto partilhado',\n      makeProjectShared_title: 'Tornar projeto partilhado',\n      move: 'Mover',\n      moveCard_title: 'Mover cartão',\n      moveList_title: 'Mover lista',\n      regenerateApiKey: 'Regenerar chave API',\n      remove: 'Remover',\n      removeAssignee: 'Remover responsável',\n      removeColor: 'Remover cor',\n      removeCover_title: 'Remover capa',\n      removeFromBoard: 'Remover do quadro',\n      removeFromProject: 'Remover do projeto',\n      removeManager: 'Remover gestor',\n      removeMember: 'Remover membro',\n      restoreToList: 'Restaurar para {{list}}',\n      returnToBoard: 'Voltar ao quadro',\n      save: 'Guardar',\n      sendTestEmail: 'Enviar e-mail de teste',\n      showActive: 'Mostrar ativo',\n      showAllAttachments: 'Mostrar todos os anexos ({{hidden}} ocultos)',\n      showCardsWithThisUser: 'Mostrar cartões com este utilizador',\n      showDeactivated: 'Mostrar desativados',\n      showFewerAttachments: 'Mostrar menos anexos',\n      showLess: 'Mostrar menos',\n      showMore: 'Mostrar mais',\n      sortList_title: 'Ordenar lista',\n      start: 'Iniciar',\n      stop: 'Parar',\n      subscribe: 'Subscrever',\n      unlinkSso: 'Desvincular SSO',\n      unlinkSso_title: 'Desvincular SSO',\n      unsubscribe: 'Cancelar subscrição',\n      uploadNewAvatar: 'Carregar novo avatar',\n      uploadNewImage: 'Carregar nova imagem',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/pt-PT/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'pt-PT',\n  country: 'pt',\n  name: 'Português',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/pt-PT/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Limite de utilizadores activos atingido',\n      adminLoginRequiredToInitializeInstance:\n        'Início de sessão de administrador necessário para inicializar a instância',\n      emailAlreadyInUse: 'E-mail já está em uso',\n      emailOrUsername: 'E-mail ou nome de utilizador',\n      invalidCredentials: 'Credenciais inválidas',\n      invalidEmailOrUsername: 'E-mail ou nome de utilizador inválido',\n      invalidPassword: 'Palavra-passe inválida',\n      logIn_title: 'Iniciar sessão',\n      noInternetConnection: 'Sem ligação à internet',\n      or: 'Ou',\n      pageNotFound_title: 'Página não encontrada',\n      password: 'Palavra-passe',\n      poweredByPlanka: 'Desenvolvido por <1>PLANKA</1>',\n      serverConnectionFailed: 'Falha na ligação ao servidor',\n      unknownError: 'Erro desconhecido, tente novamente mais tarde',\n      useSingleSignOn: 'Utilizar início de sessão único',\n      usernameAlreadyInUse: 'Nome de utilizador já está em uso',\n      whoops_title: 'Ups!',\n    },\n\n    action: {\n      cancelAndClose: 'Cancelar e fechar',\n      continue: 'Continuar',\n      debugSso: 'Depurar SSO',\n      goBack: 'Voltar',\n      goHome: 'Ir para início',\n      logIn: 'Iniciar sessão',\n      logInWithSso: 'Iniciar sessão com SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/pt-PT/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Este é um texto sem título.\\nTanto o título como o texto\\npodem ser destacados a negrito, itálico, cor,\\nriscado e sublinhado.\",\n    \"text-with-head\": \"Este é um texto com título.\\nTanto o título como o texto\\npodem ser destacados a negrito, itálico, cor,\\nriscado e sublinhado.\",\n    \"heading\": \"Título\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Erro no editor markdown\",\n    \"settings_wysiwyg\": \"Editor visual (wysiwyg)\",\n    \"settings_markup\": \"Marcação markdown\",\n    \"markup_placeholder\": \"Introduza a marcação markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Remover\",\n    \"empty_option\": \"Nenhuma correspondência encontrada\",\n    \"show_line_numbers\": \"Numeração de linhas\"\n  },\n  \"common\": {\n    \"delete\": \"Eliminar\",\n    \"edit\": \"Editar\",\n    \"toolbar_action_disabled\": \"Elemento de marcação incompatível\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Cancelar\",\n    \"common_action_submit\": \"Submeter\",\n    \"common_action_upload\": \"Selecionar\",\n    \"common_tab_attach\": \"Adicionar do dispositivo\",\n    \"common_tab_link\": \"Adicionar por link\",\n    \"common_link\": \"Ligação\",\n    \"common_sizes\": \"Tamanho, px\",\n    \"image_name\": \"Título\",\n    \"image_link_href\": \"Link da imagem\",\n    \"image_link_href_help\": \"Endereço para onde a ligação da imagem direciona.\",\n    \"image_alt\": \"Texto alternativo\",\n    \"image_alt_help\": \"O texto alternativo é apresentado se a imagem não puder ser carregada.\",\n    \"image_upload_help\": \"Imagem JPEG, GIF ou PNG até 1 MB.\",\n    \"image_upload_failed\": \"Falha ao adicionar imagem\",\n    \"image_size_width\": \"Largura\",\n    \"image_size_height\": \"Altura\",\n    \"link_url_help\": \"Endereço para onde a ligação direciona.\",\n    \"link_text\": \"Texto da ligação\",\n    \"link_text_help\": \"Texto apresentado como ligação.\",\n    \"link_open_help\": \"Abrir ligação numa nova aba\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Cabeçalho\",\n    \"header_hint\": \"# O seu texto\",\n    \"italic_title\": \"Itálico\",\n    \"italic_hint\": \"_O seu texto_\",\n    \"bold_title\": \"Negrito\",\n    \"bold_hint\": \"**O seu texto**\",\n    \"strikethrough_title\": \"Riscado\",\n    \"strikethrough_hint\": \"~~O seu texto~~\",\n    \"blockquote_title\": \"Citação\",\n    \"blockquote_hint\": \"> O seu texto\",\n    \"code_title\": \"Código\",\n    \"code_hint\": \"``````\",\n    \"link_title\": \"Ligação\",\n    \"link_hint\": \"[O seu texto](url)\",\n    \"image_title\": \"Imagem\",\n    \"image_hint\": \"![O seu texto](url)\",\n    \"list_title\": \"Item da lista\",\n    \"list_hint\": \"- O seu texto\",\n    \"numbered-list_title\": \"Lista numerada\",\n    \"numbered-list_hint\": \"1. O seu texto\",\n    \"documentation\": \"Documentação\",\n    \"documentation_link\": \"https://diplodoc.com/docs/pt/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Negrito\",\n    \"code\": \"Código\",\n    \"code_inline\": \"Código em linha\",\n    \"codeblock\": \"Bloco de código\",\n    \"colorify\": \"Cor do texto\",\n    \"colorify__color_blue\": \"Azul\",\n    \"colorify__color_default\": \"Padrão\",\n    \"colorify__color_gray\": \"Cinzento\",\n    \"colorify__color_green\": \"Verde\",\n    \"colorify__color_orange\": \"Laranja\",\n    \"colorify__color_red\": \"Vermelho\",\n    \"colorify__color_violet\": \"Violeta\",\n    \"colorify__color_yellow\": \"Amarelo\",\n    \"colorify__group_text\": \"Texto\",\n    \"cut\": \"Cortar\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Pode adicionar emojis no WYSIWYG ou manualmente com marcação\",\n    \"heading\": \"Cabeçalho\",\n    \"heading1\": \"Cabeçalho 1\",\n    \"heading2\": \"Cabeçalho 2\",\n    \"heading3\": \"Cabeçalho 3\",\n    \"heading4\": \"Cabeçalho 4\",\n    \"heading5\": \"Cabeçalho 5\",\n    \"heading6\": \"Cabeçalho 6\",\n    \"hrule\": \"Separador\",\n    \"image\": \"Imagem\",\n    \"italic\": \"Itálico\",\n    \"link\": \"Ligação\",\n    \"list\": \"Lista\",\n    \"list__action_lift\": \"Subir item\",\n    \"list__action_sink\": \"Descer item\",\n    \"list_action_disabled\": \"Contradiz a lógica da lista\",\n    \"mark\": \"Marcado\",\n    \"mono\": \"Monoespaçado\",\n    \"more_action\": \"Mais ações\",\n    \"note\": \"Nota\",\n    \"olist\": \"Lista ordenada\",\n    \"quote\": \"Citação\",\n    \"redo\": \"Refazer\",\n    \"strike\": \"Riscado\",\n    \"table\": \"Tabela\",\n    \"text\": \"Texto\",\n    \"ulist\": \"Lista com marcadores\",\n    \"underline\": \"Sublinhado\",\n    \"undo\": \"Desfazer\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Digite / para ver comandos...\",\n    \"checkbox\": \"Introduza a descrição da tarefa...\",\n    \"deflist_term\": \"Termo\",\n    \"deflist_desc\": \"Descrição da definição\",\n    \"heading\": \"Cabeçalho\",\n    \"cut_title\": \"Título\",\n    \"cut_content\": \"Conteúdo apresentado ao clicar\",\n    \"note_title\": \"Título\",\n    \"note_content\": \"Conteúdo da nota\",\n    \"table_cell\": \"Conteúdo da célula\",\n    \"select_filter\": \"Procurar idiomas...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Diferenciar maiúsculas e minúsculas\",\n    \"label_whole-word\": \"Palavra inteira\",\n    \"title\": \"Procurar no código\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Não encontrado\"\n  },\n  \"widgets\": {\n    \"image\": \"Adicionar imagem\",\n    \"link\": \"Adicionar ligação\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Nota\",\n    \"tip\": \"Dica\",\n    \"warning\": \"Aviso\",\n    \"alert\": \"Alerta\",\n    \"remove\": \"Remover\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Adicionar coluna antes\",\n    \"column.add.after\": \"Adicionar coluna depois\",\n    \"column.remove\": \"Remover coluna\",\n    \"column.remove.multiple\": \"Remover colunas\",\n    \"row.add.before\": \"Adicionar linha antes\",\n    \"row.add.after\": \"Adicionar linha depois\",\n    \"row.remove\": \"Remover linha\",\n    \"row.remove.multiple\": \"Remover linhas\",\n    \"cells.clear\": \"Limpar células\",\n    \"table.remove\": \"Remover tabela\",\n    \"table.menu.cell.align.left\": \"Alinhar o conteúdo da célula à esquerda\",\n    \"table.menu.cell.align.right\": \"Alinhar o conteúdo da célula à direita\",\n    \"table.menu.cell.align.center\": \"Centrar o conteúdo da célula\",\n    \"table.menu.row.add\": \"Adicionar linha depois\",\n    \"table.menu.row.remove\": \"Remover linha\",\n    \"table.menu.column.add\": \"Adicionar coluna depois\",\n    \"table.menu.column.remove\": \"Remover coluna\",\n    \"table.menu.convert.yfm\": \"Converter para tabela YFM\",\n    \"table.menu.table.remove\": \"Remover tabela\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/ro-RO/core.js",
    "content": "import dateFns from 'date-fns/locale/ro';\nimport timeAgo from 'javascript-time-ago/locale/ro';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'P',\n    time: 'HH:mm',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'в' p\",\n    fullDate: 'd MMM y',\n    fullDateTime: \"d MMMM y 'в' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Despre aplicație',\n      aboutPlanka_title: 'Despre PLANKA',\n      accessToken: 'Token de acces',\n      account: 'Cont',\n      actions: 'Acțiuni',\n      activateUser_title: 'Activează utilizatorul',\n      active: 'Activ',\n      addAttachment_title: 'Adaugă atașament',\n      addCustomFieldGroup_title: 'Adaugă grup de câmpuri personalizate',\n      addCustomField_title: 'Adaugă câmp personalizat',\n      addManager_title: 'Adaugă manager',\n      addMember_title: 'Adaugă membru',\n      addTaskList_title: 'Adaugă listă de sarcini',\n      addUser_title: 'Adaugă utilizator',\n      admin: 'Administrator',\n      administration: 'Administrare',\n      all: 'Toate',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Toate modificarile vor fi salvate automat<br />dupa restabilirea conexiunii.',\n      alphabetically: 'Alfabetic',\n      alwaysDisplayCardCreator: 'Afișează întotdeauna creatorul cardului',\n      apiKeyCreated_title: 'Cheie API creată',\n      apiKey_title: 'Cheie API',\n      archive: 'Arhivă',\n      archiveCard_title: 'Arhivează cardul',\n      archiveCards_title: 'Arhivează cardurile',\n      areYouSureYouWantToActivateThisUser: 'Sigur doriți să activați acest utilizator?',\n      areYouSureYouWantToArchiveCards: 'Sigur doriți să arhivați cardurile?',\n      areYouSureYouWantToArchiveThisCard: 'Sigur doriți să arhivați acest card?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Sigur doriți să atribuiți acest manager de proiect ca proprietar?',\n      areYouSureYouWantToDeactivateThisUser: 'Sigur doriți să dezactivați acest utilizator?',\n      areYouSureYouWantToDeleteThisApiKey: 'Sigur doriți să ștergeți această cheie API?',\n      areYouSureYouWantToDeleteThisAttachment: 'Sigur doriți să ștergeți acest atașament?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Sigur doriți să ștergeți această imagine de fundal?',\n      areYouSureYouWantToDeleteThisBoard: 'Sigur doriți să ștergeți acest panou?',\n      areYouSureYouWantToDeleteThisCard: 'Sigur doriți să ștergeți acest card?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Sigur doriți să ștergeți acest card pentru totdeauna?',\n      areYouSureYouWantToDeleteThisComment: 'Sigur doriți să ștergeți acest comentariu?',\n      areYouSureYouWantToDeleteThisCustomField: 'Sigur doriți să ștergeți acest câmp personalizat?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Sigur doriți să ștergeți acest grup de câmpuri personalizate?',\n      areYouSureYouWantToDeleteThisLabel: 'Sigur doriți să ștergeți această etichetă?',\n      areYouSureYouWantToDeleteThisList:\n        'Sigur doriți să ștergeți această listă? Toate cardurile vor fi mutate în coșul de gunoi.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Sigur doriți să ștergeți acest serviciu de notificare?',\n      areYouSureYouWantToDeleteThisProject: 'Sigur doriți să ștergeți acest proiect?',\n      areYouSureYouWantToDeleteThisTask: 'Sigur doriți să ștergeți această sarcină?',\n      areYouSureYouWantToDeleteThisTaskList: 'Sigur doriți să ștergeți această listă de sarcini?',\n      areYouSureYouWantToDeleteThisUser: 'Sigur doriți să ștergeți acest utilizator?',\n      areYouSureYouWantToDeleteThisWebhook: 'Sigur doriți să ștergeți acest webhook?',\n      areYouSureYouWantToEmptyTrash: 'Sigur doriți să goliți coșul de gunoi?',\n      areYouSureYouWantToLeaveBoard: 'Ești sigur că vrei să părăsești tabla?',\n      areYouSureYouWantToLeaveProject: 'Ești sigur că vrei să părăsești proiectul?',\n      areYouSureYouWantToMakeThisProjectPrivate: 'Sigur doriți să faceți acest proiect privat?',\n      areYouSureYouWantToMakeThisProjectShared: 'Sigur doriți să faceți acest proiect partajat?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Sigur doriți să regenerați această cheie API? Cheia anterioară nu va mai funcționa.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Sigur doriți să eliminați acest manager din proiect?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Sigur doriți să eliminați acest membru din consiliu?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Sigur doriți să deconectați SSO de la acest utilizator? Acest lucru va permite utilizatorului să se conecteze cu o parolă.',\n      assignAsOwner_title: 'Atribuie ca proprietar',\n      atLeastOneListMustBePresent: 'Cel puțin o listă trebuie să fie prezentă',\n      attachment: 'Atașament',\n      attachments: 'Atasamente',\n      authentication: 'Autentificare',\n      background: 'Fundal',\n      baseCustomFields_title: 'Câmpuri personalizate de bază',\n      baseGroup: 'Grup de bază',\n      board: 'Tabla',\n      boardActions_title: 'Acțiuni tablă',\n      boardNotFound_title: 'Tabla nu a fost găsită',\n      boardSubscribed: 'Tablă abonată',\n      boardUser: 'Utilizator tablă',\n      byCreationTime: 'După timpul creării',\n      byDefault: 'Implicit',\n      byDueDate: 'După data scadentă',\n      canBeInvitedToWorkInBoards: 'Poate fi invitat să lucreze în table.',\n      canComment: 'Poate comenta',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Poate crea propriile proiecte și poate fi invitat să lucreze în altele.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Poate edita aspectul tablei și poate atribui membri la carduri.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Poate gestiona setările la nivel de sistem și poate acționa ca proprietar de proiect.',\n      canOnlyViewBoard: 'Poate doar vizualiza tabla.',\n      cardActions_title: 'Acțiuni cu carduri',\n      cardNotFound_title: 'Cardul nu a fost găsit',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Cardurile din această listă sunt disponibile pentru toți membrii tablei.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Cardurile din această listă sunt complete și gata să fie arhivate.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Cardurile din această listă sunt gata să fie lucrate.',\n      clickHereOrRefreshPageToUpdate:\n        '<0>Faceți clic aici</0> sau reîmprospătați pagina pentru a actualiza.',\n      clientHostnameInEhlo: 'Numele de gazdă al clientului în EHLO',\n      closed: 'Închis',\n      color: 'Culoarea',\n      comments: 'Comentarii',\n      contentExceedsLimit: 'Conținutul depășește {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Conținutul acestui atașament este prea mare pentru a fi afișat.',\n      copy_inline: 'copie',\n      createBoard_title: 'Creare tablă',\n      createCustomFieldGroup_title: 'Creează grup de câmpuri personalizate',\n      createLabel_title: 'Creați etichetă',\n      createNewOneOrSelectExistingOne: 'Crează unu nou sau selectează<br />unu deja existent.',\n      createProject_title: 'Crează proiect',\n      createTextFile_title: 'Crează un fișier text',\n      creator: 'Creator',\n      currentPassword: 'Parola curentă',\n      currentUser: 'Utilizator curent',\n      customFieldGroup_title: 'Grup de câmpuri personalizate',\n      customFieldGroups_title: 'Grupuri de câmpuri personalizate',\n      customField_title: 'Câmp personalizat',\n      customFields_title: 'Câmpuri personalizate',\n      customerPanel_title: 'Panoul clientului',\n      dangerZone_title: 'Zona periculoasă',\n      date: 'Data',\n      deactivateUser_title: 'Dezactivează utilizatorul',\n      defaultCardType_title: 'Tipul implicit al cardului',\n      defaultFrom: 'Implicit de la',\n      defaultView_title: 'Vizualizarea implicită',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Ștergeți toate tablele pentru a putea șterge acest proiect',\n      deleteApiKey_title: 'Șterge cheia API',\n      deleteAttachment_title: 'Ștergeți atașamentul',\n      deleteBackgroundImage_title: 'Șterge imaginea de fundal',\n      deleteBoard_title: 'Ștergeți tabla',\n      deleteCardForever_title: 'Șterge cardul pentru totdeauna',\n      deleteCard_title: 'Ștergeți cardul',\n      deleteComment_title: 'Șterge comentariul',\n      deleteCustomFieldGroup_title: 'Șterge grupul de câmpuri personalizate',\n      deleteCustomField_title: 'Șterge câmpul personalizat',\n      deleteLabel_title: 'Ștergeți eticheta',\n      deleteList_title: 'Șterge lista',\n      deleteNotificationService_title: 'Șterge serviciul de notificare',\n      deleteProject_title: 'Șterge proiectul',\n      deleteTaskList_title: 'Șterge lista de sarcini',\n      deleteTask_title: 'Ștergeți sarcina',\n      deleteUser_title: 'Șterge utilizator',\n      deleteWebhook_title: 'Șterge webhook-ul',\n      deletedUser_title: 'Utilizator șters',\n      description: 'Descriere',\n      display: 'Afișare',\n      displayCardAges: 'Afișează vârsta cardurilor',\n      dropFileToUpload: 'Aruncă fișierul pentru a încărca',\n      dueDate_title: 'Data scadentă',\n      dynamicAndUnevenlySpacedLayout: 'Aspect dinamic și spațiat neuniform.',\n      editAttachment_title: 'Editați atașamentul',\n      editAvatar_title: 'Editați avatarul',\n      editColor_title: 'Editează culoarea',\n      editCustomFieldGroup_title: 'Editează grupul de câmpuri personalizate',\n      editCustomField_title: 'Editează câmpul personalizat',\n      editDueDate_title: 'Editați data scadentă',\n      editEmail_title: 'Editați e-mail',\n      editInformation_title: 'Editați informații',\n      editLabel_title: 'Editați eticheta',\n      editPassword_title: 'Editați parola',\n      editPermissions_title: 'Editați permisiunile',\n      editRole_title: 'Editează rolul',\n      editStopwatch_title: 'Editați cronometrul',\n      editType_title: 'Editează tipul',\n      editUsername_title: 'Editați numele de utilizator',\n      editor: 'Editor',\n      editors: 'Editori',\n      email: 'E-mail',\n      emptyTrash_title: 'Golește coșul de gunoi',\n      enterCardTitle: 'Introduceți titlul cardului...',\n      enterDescription: 'Introduceți descrierea...',\n      enterFilename: 'Introduceți numele fișierului',\n      enterListTitle: 'Introduceți titlul listei...',\n      enterTaskDescription: 'Introduceți descrierea sarcinii...',\n      events: 'Evenimente',\n      excludedEvents: 'Evenimente excluse',\n      expandTaskListsByDefault: 'Extinde listele de sarcini implicit',\n      filterByLabels_title: 'Filtrați după etichete',\n      filterByMembers_title: 'Filtrați după membri',\n      forPersonalProjects: 'Pentru proiecte personale.',\n      forTeamBasedProjects: 'Pentru proiecte bazate pe echipă.',\n      fromComputer_title: 'De pe computer',\n      fromTrello: 'De pe Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Cheia completă este ascunsă din motive de securitate. Regenerați-o pentru a crea una nouă.',\n      general: 'General',\n      gradients: 'Gradiente',\n      grid: 'Grilă',\n      hideCompletedTasks: 'Ascunde sarcinile completate',\n      hideFromProjectListAndFavorites: 'Ascunde din lista de proiecte și favorite',\n      host: 'Gazdă',\n      hours: 'Ore',\n      identity: 'Identitate',\n      importBoard_title: 'Import tablă',\n      information: 'Informații',\n      invalidCurrentPassword: 'Parolă actuală nevalidă',\n      kanban: 'Kanban',\n      labels: 'Etichete',\n      language: 'Limba',\n      leaveBoard_title: 'Părăsiți tabla',\n      leaveProject_title: 'Părăsiți proiectul',\n      limitCardTypesToDefaultOne: 'Limitează tipurile de carduri la cel implicit',\n      linkToCard: 'Link către card',\n      list: 'Lista',\n      listActions_title: 'Listează acțiuni',\n      lists: 'Liste',\n      makeProjectPrivate_title: 'Fă proiectul privat',\n      makeProjectShared_title: 'Fă proiectul partajat',\n      managers: 'Managerii',\n      memberActions_title: 'Acțiuni membri',\n      members: 'Membri',\n      minutes: 'Minute',\n      moreActions: 'Mai multe acțiuni',\n      moreActions_title: 'Mai multe acțiuni',\n      moveCard_title: 'Mutați cardul',\n      moveList_title: 'Mută lista',\n      myOwn_title: 'Ale mele',\n      name: 'Nume',\n      newEmail: 'Email nou',\n      newPassword: 'Parolă nouă',\n      newUsername: 'Nume de utilizator nou',\n      newVersionAvailable: 'Versiune nouă disponibilă',\n      newestFirst: 'Cele mai noi primul',\n      noApiKeyCreated: 'Nicio cheie API creată.',\n      noBoards: 'Fără table',\n      noCardsFound: 'Nu s-au găsit carduri.',\n      noConnectionToServer: 'Nicio conexiune la server',\n      noLists: 'Fără liste',\n      noProjects: 'Fără proiecte',\n      noUnreadNotifications: 'Fără notificări necitite.',\n      notifications: 'Notificări',\n      oldestFirst: 'Cele mai vechi primul',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Doar un manager ar trebui să rămână pentru a face acest proiect privat',\n      openBoard_title: 'Tablă deschisă',\n      optional_inline: 'optional',\n      organization: 'Organizatia',\n      others: 'Alții',\n      passwordIsSet: 'Parola este setată',\n      phone: 'Telefon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA folosește <1><0>Apprise</0></1> pentru a trimite notificări către peste 100 de servicii populare.',\n      port: 'Port',\n      preferences: 'Preferințe',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Sfat: apăsați Ctrl-V (Cmd-V pe Mac) pentru a adăuga un atașament din clipboard.',\n      private: 'Privat',\n      project: 'Proiect',\n      projectNotFound_title: 'Proiectul nu a fost găsit',\n      projectOwner: 'Proprietar proiect',\n      referenceDataAndKnowledgeStorage: 'Stocare de date de referință și cunoștințe.',\n      regenerateApiKey_title: 'Regenerează cheia API',\n      rejectUnauthorizedTlsCertificates: 'Respinge certificatele TLS neautorizate',\n      removeManager_title: 'Eliminați manager',\n      removeMember_title: 'Eliminați membru',\n      role: 'Rol',\n      saveThisKeyItWillNotBeShownAgain: 'Salvați această cheie — nu va fi afișată din nou!',\n      searchCards: 'Caută carduri...',\n      searchCustomFieldGroups: 'Caută grupuri de câmpuri personalizate...',\n      searchCustomFields: 'Caută câmpuri personalizate...',\n      searchLabels: 'Căutați etichete...',\n      searchLists: 'Caută liste...',\n      searchMembers: 'Căutați membri...',\n      searchProjects: 'Caută proiecte...',\n      searchUsers: 'Căutați utilizatori...',\n      seconds: 'Secunde',\n      selectAssignee_title: 'Selectează persoana atribuită',\n      selectBoard: 'Selectați tabla',\n      selectList: 'Selectați lista',\n      selectListToRestoreThisCard: 'Selectați lista pentru a restaura acest card',\n      selectOrder_title: 'Selectează ordinea',\n      selectPermissions_title: 'Selectați permisiuni',\n      selectProject: 'Selectați proiectul',\n      selectRole_title: 'Selectează rolul',\n      selectType_title: 'Selectează tipul',\n      sequentialDisplayOfCards: 'Afișare secvențială a cardurilor.',\n      settings: 'Setări',\n      shared: 'Partajat',\n      sharedWithMe_title: 'Partajat cu mine',\n      showOnFrontOfCard: 'Afișează pe fața cardului',\n      smtp: 'SMTP',\n      sortList_title: 'Sortează lista',\n      sourceCardIsNoLongerAvailableForCopying:\n        'Cardul sursă nu mai este disponibil pentru copiere.',\n      sourceCardIsNoLongerAvailableForMoving: 'Cardul sursă nu mai este disponibil pentru mutare.',\n      stopwatch: 'Cronometru',\n      story: 'Poveste',\n      subscribeToCardWhenCommenting: 'Abonează-te la card când comentezi',\n      subscribeToMyOwnCardsByDefault: 'Abonați-vă la propriile carduri în mod implicit',\n      taskActions_title: 'Acțiuni de sarcină',\n      taskAssignmentAndProjectCompletion: 'Atribuirea sarcinilor și finalizarea proiectului.',\n      taskListActions_title: 'Acțiuni listă de sarcini',\n      taskList_title: 'Listă de sarcini',\n      team: 'Echipă',\n      termsOfService_title: 'Termeni de serviciu',\n      testLog_title: 'Jurnal de test',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'Nu există nicio previzualizare disponibilă pentru acest atașament.',\n      time: 'Timp',\n      title: 'Titlu',\n      trash: 'Coș de gunoi',\n      trashHasBeenSuccessfullyEmptied: 'Coșul de gunoi a fost golit cu succes.',\n      turnOffRecentCardHighlighting: 'Oprește evidențierea cardurilor recente',\n      typeNameToConfirm: 'Tastează numele pentru confirmare.',\n      typeTitleToConfirm: 'Tastează titlul pentru confirmare.',\n      unlinkSso_title: 'Deconectarea SSO',\n      unsavedChanges: 'Modificări nesalvate',\n      uploadFailedFileIsTooBig: 'Încărcarea a eșuat: fișierul este prea mare.',\n      uploadFailedNotEnoughStorageSpace: 'Încărcarea a eșuat: spațiu de stocare insuficient.',\n      uploadedImages: 'Imagini încărcate',\n      url: 'URL',\n      useSecureConnection: 'Folosește conexiune securizată',\n      userActions_title: 'Acțiunile utilizatorului',\n      userAddedCardToList: '<0>{{user}}</0> a adăugat <2>{{card}}</2> în {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> a adăugat acest card în {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> a adăugat pe {{addedUser}} la <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> a adăugat pe {{addedUser}} la acest card',\n      userAddedYouToCard: '<0>{{user}}</0> te-a adăugat la <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> a completat {{task}} pe <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> a completat {{task}} pe acest card',\n      userJoinedCard: '<0>{{user}}</0> s-a alăturat la <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> s-a alăturat la acest card',\n      userLeftCard: '<0>{{user}}</0> a părăsit <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> a lăsat un nou comentariu «{{comment}}» în <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> a părăsit acest card',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> a marcat {{task}} ca incompletă pe <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> a marcat {{task}} ca incompletă pe acest card',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> te-a menționat într-un comentariu «{{comment}}» pe <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> a mutat <2>{{card}}</2> din {{fromList}} în {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> a mutat aceast card din {{fromList}} în {{toList}}',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0> a eliminat pe {{removedUser}} de la <4>{{card}}</4>',\n      userRemovedUserFromThisCard:\n        '<0>{{actorUser}}</0> a eliminat pe {{removedUser}} de la acest card',\n      username: 'Nume utilizator',\n      users: 'Utilizatori',\n      viewer: 'Vizualizator',\n      viewers: 'Vizualizatori',\n      visualTaskManagementWithLists: 'Gestionarea vizuală a sarcinilor cu liste.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Ce este nou',\n      withoutBaseGroup: 'Fără grup de bază',\n      writeComment: 'Scrie un comentariu...',\n    },\n\n    action: {\n      activateUser: 'Activează utilizatorul',\n      activateUser_title: 'Activează utilizatorul',\n      addAnotherCard: 'Adăugați un alt card',\n      addAnotherList: 'Adăugați o altă listă',\n      addAnotherTask: 'Adăugați o altă sarcină',\n      addCard: 'Adăugați card',\n      addCard_title: 'Adăugați card',\n      addComment: 'Adauga comentariu',\n      addCustomField: 'Adaugă câmp personalizat',\n      addCustomFieldGroup: 'Adaugă grup de câmpuri personalizate',\n      addList: 'Adăugați listă',\n      addMember: 'Adăugați membru',\n      addMoreDetailedDescription: 'Adăugați descrierea mai detaliata',\n      addTask: 'Adăugați sarcină',\n      addTaskList: 'Adaugă listă de sarcini',\n      addToCard: 'Adauga în card',\n      addUser: 'Adăugați utilizator',\n      addWebhook: 'Adaugă webhook',\n      archive: 'Arhivează',\n      archiveCard: 'Arhivează cardul',\n      archiveCard_title: 'Arhivează cardul',\n      archiveCards: 'Arhivează cardurile',\n      archiveCards_title: 'Arhivează cardurile',\n      assignAsOwner: 'Atribuie ca proprietar',\n      cancel: 'Anulează',\n      copy: 'Copiază',\n      copyCard_title: 'Copiază cardul',\n      createApiKey: 'Creează cheie API',\n      createBoard: 'Creați tablă',\n      createCustomFieldGroup: 'Creați grup de câmpuri personalizate',\n      createFile: 'Creați fișier',\n      createLabel: 'Creați eticheta',\n      createNewLabel: 'Creați o nouă etichetă',\n      createProject: 'Creați proiect',\n      cut: 'Taie',\n      cutCard_title: 'Taie cardul',\n      deactivateUser: 'Dezactivați utilizatorul',\n      deactivateUser_title: 'Dezactivați utilizatorul',\n      delete: 'Ștergeți',\n      deleteApiKey: 'Șterge cheia API',\n      deleteAttachment: 'Ștergeți atașamentul',\n      deleteAvatar: 'Ștergeți avatarul',\n      deleteBackgroundImage: 'Ștergeți imaginea de fundal',\n      deleteBoard: 'Ștergeți tabla',\n      deleteBoard_title: 'Ștergeți tabla',\n      deleteCard: 'Ștergeți cardul',\n      deleteCardForever: 'Ștergeți cardul pentru totdeauna',\n      deleteCard_title: 'Ștergeți cardul',\n      deleteComment: 'Șterge comentariu',\n      deleteCustomField: 'Ștergeți câmpul personalizat',\n      deleteCustomFieldGroup: 'Ștergeți grupul de câmpuri personalizate',\n      deleteForever_title: 'Ștergeți pentru totdeauna',\n      deleteGroup: 'Ștergeți grupul',\n      deleteLabel: 'Ștergeți eticheta',\n      deleteList: 'Ștergeți lista',\n      deleteList_title: 'Ștergeți lista',\n      deleteNotificationService: 'Ștergeți serviciul de notificare',\n      deleteProject: 'Ștergeți proiectul',\n      deleteProject_title: 'Ștergeți proiectul',\n      deleteTask: 'Ștergeți sarcina',\n      deleteTaskList: 'Ștergeți lista de sarcini',\n      deleteTask_title: 'Ștergeți sarcina',\n      deleteUser: 'Ștergeți utilizatorul',\n      deleteUser_title: 'Ștergeți utilizatorul',\n      deleteWebhook: 'Ștergeți webhook-ul',\n      dismissAll: 'Respingeți toate',\n      download: 'Descărcați',\n      duplicateCard_title: 'Duplicați cardul',\n      edit: 'Editați',\n      editColor_title: 'Editați culoarea',\n      editDescription_title: 'Editați descrierea',\n      editDueDate_title: 'Editați data scadentă',\n      editEmail_title: 'Editați e-mailul',\n      editGroup: 'Editați grupul',\n      editInformation_title: 'Editați informații',\n      editPassword_title: 'Editați parola',\n      editPermissions: 'Editați permisiunile',\n      editRole_title: 'Editați rolul',\n      editStopwatch_title: 'Editați cronometrul',\n      editTitle_title: 'Editați titlul',\n      editType_title: 'Editați tipul',\n      editUsername_title: 'Editați numele utilizator',\n      emptyTrash: 'Goliți coșul de gunoi',\n      emptyTrash_title: 'Goliți coșul de gunoi',\n      import: 'Import',\n      join: 'Alăturați-vă',\n      leave: 'Părăsiți',\n      leaveBoard: 'Părăsiți bordul',\n      leaveProject: 'Părăsiți proiect',\n      logOut_title: 'Deconectați-vă',\n      makeCover_title: 'Faceți coperta',\n      makeProjectPrivate: 'Faceți proiectul privat',\n      makeProjectPrivate_title: 'Faceți proiectul privat',\n      makeProjectShared: 'Faceți proiectul partajat',\n      makeProjectShared_title: 'Faceți proiectul partajat',\n      move: 'Mutați',\n      moveCard_title: 'Mutați cardul',\n      moveList_title: 'Mutați lista',\n      regenerateApiKey: 'Regenerează cheia API',\n      remove: 'Eliminați',\n      removeAssignee: 'Eliminați cesionarul',\n      removeColor: 'Eliminați culoarea',\n      removeCover_title: 'Eliminați coperta',\n      removeFromBoard: 'Eliminați din tabla',\n      removeFromProject: 'Eliminați din proiect',\n      removeManager: 'Eliminați managerul',\n      removeMember: 'Eliminați membrul',\n      restoreToList: 'Restaurați în {{list}}',\n      returnToBoard: 'Întoarceți-vă la tablă',\n      save: 'Salveaza',\n      sendTestEmail: 'Trimiteți e-mail de test',\n      showActive: 'Afișați activ',\n      showAllAttachments: 'Afișați toate atașamentele ({{hidden}} ascunse)',\n      showCardsWithThisUser: 'Afișați cardurile cu acest utilizator',\n      showDeactivated: 'Afișați dezactivat',\n      showFewerAttachments: 'Afișați mai puține atașamente',\n      showLess: 'Afișați mai puțin',\n      showMore: 'Afișați mai mult',\n      sortList_title: 'Sortați lista',\n      start: 'Start',\n      stop: 'Stop',\n      subscribe: 'Abonati-va',\n      unlinkSso: 'Deconectează SSO',\n      unlinkSso_title: 'Deconectează SSO',\n      unsubscribe: 'Dezabonați-vă',\n      uploadNewAvatar: 'Încărcați un avatar nou',\n      uploadNewImage: 'Încărcați o nouă imagine',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ro-RO/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'ro-RO',\n  country: 'ro',\n  name: 'Română',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/ro-RO/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Limita utilizatorilor activi a fost atinsă',\n      adminLoginRequiredToInitializeInstance:\n        'Este necesară autentificarea administratorului pentru a inițializa instanța',\n      emailAlreadyInUse: 'E-mail deja utilizat',\n      emailOrUsername: 'E-mail sau nume de utilizator',\n      invalidCredentials: 'Credențiale nevalide',\n      invalidEmailOrUsername: 'E-mail sau nume de utilizator introduse greșit',\n      invalidPassword: 'Parola greșita',\n      logIn_title: 'Autentificare',\n      noInternetConnection: 'Lipsește conexiunea cu internet',\n      or: 'Sau',\n      pageNotFound_title: 'Pagina n-a fost găsită',\n      password: 'Parola',\n      poweredByPlanka: 'Susținut de <1>PLANKA</1>',\n      serverConnectionFailed: 'Conexiunea la server a eșuat',\n      unknownError: 'Eroarea necunoscuta, mai incercați',\n      useSingleSignOn: 'Folosiți autentificarea unica',\n      usernameAlreadyInUse: 'Nume utilizator deja exista',\n      whoops_title: 'Ups!',\n    },\n\n    action: {\n      cancelAndClose: 'Anulează și închide',\n      continue: 'Continuă',\n      debugSso: 'Depanează SSO',\n      goBack: 'Înapoi',\n      goHome: 'Acasă',\n      logIn: 'Autentificarea',\n      logInWithSso: 'Autentificarea cu SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ro-RO/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Acesta este un text fără titlu.\\nAtât titlul cât și textul\\npot fi evidențiate cu aldine, italic, culoare,\\ntăiere și subliniere.\",\n    \"text-with-head\": \"Acesta este un text cu titlu.\\nAtât titlul cât și textul\\npot fi evidențiate cu aldine, italic, culoare,\\ntăiere și subliniere.\",\n    \"heading\": \"Titlu\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Eroare în editorul markdown\",\n    \"settings_wysiwyg\": \"Editor vizual (wysiwyg)\",\n    \"settings_markup\": \"Marcaj markdown\",\n    \"markup_placeholder\": \"Introduceți marcajul markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Șterge\",\n    \"empty_option\": \"Nicio potrivire găsită\",\n    \"show_line_numbers\": \"Numerotare rânduri\"\n  },\n  \"common\": {\n    \"delete\": \"Șterge\",\n    \"edit\": \"Editează\",\n    \"toolbar_action_disabled\": \"Element de marcaj incompatibil\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Anulează\",\n    \"common_action_submit\": \"Trimite\",\n    \"common_action_upload\": \"Selectează\",\n    \"common_tab_attach\": \"Adaugă de pe dispozitiv\",\n    \"common_tab_link\": \"Adaugă prin link\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Dimensiune, px\",\n    \"image_name\": \"Titlu\",\n    \"image_link_href\": \"Link imagine\",\n    \"image_link_href_help\": \"Adresa la care duce linkul imaginii.\",\n    \"image_alt\": \"Text alternativ\",\n    \"image_alt_help\": \"Textul alternativ este afișat dacă imaginea nu poate fi încărcată.\",\n    \"image_upload_help\": \"Imagine JPEG, GIF sau PNG de maximum 1 MB.\",\n    \"image_upload_failed\": \"Adăugarea imaginii a eșuat\",\n    \"image_size_width\": \"Lățime\",\n    \"image_size_height\": \"Înălțime\",\n    \"link_url_help\": \"Adresa la care duce linkul.\",\n    \"link_text\": \"Textul linkului\",\n    \"link_text_help\": \"Text afișat ca link.\",\n    \"link_open_help\": \"Deschide linkul într-o filă nouă\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Antet\",\n    \"header_hint\": \"# Textul tău\",\n    \"italic_title\": \"Italic\",\n    \"italic_hint\": \"_Textul tău_\",\n    \"bold_title\": \"Aldin\",\n    \"bold_hint\": \"**Textul tău**\",\n    \"strikethrough_title\": \"Tăiere\",\n    \"strikethrough_hint\": \"~~Textul tău~~\",\n    \"blockquote_title\": \"Citat\",\n    \"blockquote_hint\": \"> Textul tău\",\n    \"code_title\": \"Cod\",\n    \"code_hint\": \"```Textul tău```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[Textul tău](url)\",\n    \"image_title\": \"Imagine\",\n    \"image_hint\": \"![Textul tău](url)\",\n    \"list_title\": \"Element de listă\",\n    \"list_hint\": \"- Textul tău\",\n    \"numbered-list_title\": \"Listă numerotată\",\n    \"numbered-list_hint\": \"1. Textul tău\",\n    \"documentation\": \"Documentație\",\n    \"documentation_link\": \"https://diplodoc.com/docs/ro/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Aldin\",\n    \"code\": \"Cod\",\n    \"code_inline\": \"Cod inline\",\n    \"codeblock\": \"Bloc de cod\",\n    \"colorify\": \"Culoare text\",\n    \"colorify__color_blue\": \"Albastru\",\n    \"colorify__color_default\": \"Implicit\",\n    \"colorify__color_gray\": \"Gri\",\n    \"colorify__color_green\": \"Verde\",\n    \"colorify__color_orange\": \"Portocaliu\",\n    \"colorify__color_red\": \"Roșu\",\n    \"colorify__color_violet\": \"Violet\",\n    \"colorify__color_yellow\": \"Galben\",\n    \"colorify__group_text\": \"Text\",\n    \"cut\": \"Taie\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emoji-urile pot fi adăugate în WYSIWYG sau manual cu marcaj\",\n    \"heading\": \"Antet\",\n    \"heading1\": \"Antet 1\",\n    \"heading2\": \"Antet 2\",\n    \"heading3\": \"Antet 3\",\n    \"heading4\": \"Antet 4\",\n    \"heading5\": \"Antet 5\",\n    \"heading6\": \"Antet 6\",\n    \"hrule\": \"Separator\",\n    \"image\": \"Imagine\",\n    \"italic\": \"Italic\",\n    \"link\": \"Link\",\n    \"list\": \"Listă\",\n    \"list__action_lift\": \"Ridică elementul\",\n    \"list__action_sink\": \"Coboară elementul\",\n    \"list_action_disabled\": \"Contrazice logica listei\",\n    \"mark\": \"Marcat\",\n    \"mono\": \"Monospațiat\",\n    \"more_action\": \"Mai multe acțiuni\",\n    \"note\": \"Notă\",\n    \"olist\": \"Listă ordonată\",\n    \"quote\": \"Citat\",\n    \"redo\": \"Refă\",\n    \"strike\": \"Tăiere\",\n    \"table\": \"Tabel\",\n    \"text\": \"Text\",\n    \"ulist\": \"Listă cu puncte\",\n    \"underline\": \"Subliniază\",\n    \"undo\": \"Anulează\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Tastează / pentru a folosi comenzi slash...\",\n    \"checkbox\": \"Introduceți descrierea sarcinii...\",\n    \"deflist_term\": \"Termen\",\n    \"deflist_desc\": \"Descrierea definiției\",\n    \"heading\": \"Antet\",\n    \"cut_title\": \"Titlu\",\n    \"cut_content\": \"Conținut afișat la clic\",\n    \"note_title\": \"Titlu\",\n    \"note_content\": \"Conținutul notei\",\n    \"table_cell\": \"Conținutul celulei\",\n    \"select_filter\": \"Caută limbi...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Sensibil la majuscule\",\n    \"label_whole-word\": \"Cuvânt întreg\",\n    \"title\": \"Caută în cod\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Nu a fost găsit\"\n  },\n  \"widgets\": {\n    \"image\": \"Adaugă imagine\",\n    \"link\": \"Adaugă link\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Notă\",\n    \"tip\": \"Sfat\",\n    \"warning\": \"Avertisment\",\n    \"alert\": \"Alertă\",\n    \"remove\": \"Șterge\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Adaugă coloană înainte\",\n    \"column.add.after\": \"Adaugă coloană după\",\n    \"column.remove\": \"Șterge coloana\",\n    \"column.remove.multiple\": \"Șterge coloanele\",\n    \"row.add.before\": \"Adaugă rând înainte\",\n    \"row.add.after\": \"Adaugă rând după\",\n    \"row.remove\": \"Șterge rândul\",\n    \"row.remove.multiple\": \"Șterge rândurile\",\n    \"cells.clear\": \"Golește celulele\",\n    \"table.remove\": \"Șterge tabelul\",\n    \"table.menu.cell.align.left\": \"Aliniază conținutul la stânga\",\n    \"table.menu.cell.align.right\": \"Aliniază conținutul la dreapta\",\n    \"table.menu.cell.align.center\": \"Centrează conținutul\",\n    \"table.menu.row.add\": \"Adaugă rând după\",\n    \"table.menu.row.remove\": \"Șterge rândul\",\n    \"table.menu.column.add\": \"Adaugă coloană după\",\n    \"table.menu.column.remove\": \"Șterge coloana\",\n    \"table.menu.convert.yfm\": \"Convertește în tabel YFM\",\n    \"table.menu.table.remove\": \"Șterge tabelul\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/ru-RU/core.js",
    "content": "import dateFns from 'date-fns/locale/ru';\nimport timeAgo from 'javascript-time-ago/locale/ru';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'P',\n    time: 'HH:mm',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'в' p\",\n    fullDate: 'd MMM y',\n    fullDateTime: \"d MMMM y 'в' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'О приложении',\n      aboutPlanka_title: 'О PLANKA',\n      accessToken: 'Токен доступа',\n      account: 'Учетная запись',\n      actions: 'Действия',\n      activateUser_title: 'Активировать пользователя',\n      active: 'Активен',\n      addAttachment_title: 'Добавление вложения',\n      addCustomFieldGroup_title: 'Добавить группу настраиваемых полей',\n      addCustomField_title: 'Добавить настраиваемое поле',\n      addManager_title: 'Добавление менеджера',\n      addMember_title: 'Добавление участника',\n      addTaskList_title: 'Добавить список задач',\n      addUser_title: 'Добавление пользователя',\n      admin: 'Админ',\n      administration: 'Администрирование',\n      all: 'Все',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Все изменения сохранятся автоматически,<br />как только подключение восстановится.',\n      alphabetically: 'По алфавиту',\n      alwaysDisplayCardCreator: 'Всегда отображать создателя карточек',\n      apiKeyCreated_title: 'Ключ API создан',\n      apiKey_title: 'Ключ API',\n      archive: 'Архив',\n      archiveCard_title: 'Архивировать карточку',\n      archiveCards_title: 'Архивировать карточки',\n      areYouSureYouWantToActivateThisUser:\n        'Вы уверены, что хотите активировать этого пользователя?',\n      areYouSureYouWantToArchiveCards: 'Вы уверены, что хотите архивировать карточки?',\n      areYouSureYouWantToArchiveThisCard: 'Вы уверены, что хотите архивировать эту карточку?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Вы уверены, что хотите назначить этого менеджера проекта владельцем?',\n      areYouSureYouWantToDeactivateThisUser:\n        'Вы уверены, что хотите деактивировать этого пользователя?',\n      areYouSureYouWantToDeleteThisApiKey: 'Вы уверены, что хотите удалить этот ключ API?',\n      areYouSureYouWantToDeleteThisAttachment: 'Вы уверены, что хотите удалить это вложение?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Вы уверены, что хотите удалить этот фоновый рисунок?',\n      areYouSureYouWantToDeleteThisBoard: 'Вы уверены, что хотите удалить эту доску?',\n      areYouSureYouWantToDeleteThisCard: 'Вы уверены, что хотите удалить эту карточку?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Вы уверены, что хотите навсегда удалить эту карточку?',\n      areYouSureYouWantToDeleteThisComment: 'Вы уверены, что хотите удалить этот комментарий?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Вы уверены, что хотите удалить это настраиваемое поле?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Вы уверены, что хотите удалить эту группу настраиваемых полей?',\n      areYouSureYouWantToDeleteThisLabel: 'Вы уверены, что хотите удалить эту метку?',\n      areYouSureYouWantToDeleteThisList:\n        'Вы уверены, что хотите удалить этот список? Все карточки будут перемещены в корзину.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Вы уверены, что хотите удалить этот сервис уведомлений?',\n      areYouSureYouWantToDeleteThisProject: 'Вы уверены, что хотите удалить этот проект?',\n      areYouSureYouWantToDeleteThisTask: 'Вы уверены, что хотите удалить эту задачу?',\n      areYouSureYouWantToDeleteThisTaskList: 'Вы уверены, что хотите удалить этот список задач?',\n      areYouSureYouWantToDeleteThisUser: 'Вы уверены, что хотите удалить этого пользователя?',\n      areYouSureYouWantToDeleteThisWebhook: 'Вы уверены, что хотите удалить этот веб-хук?',\n      areYouSureYouWantToEmptyTrash: 'Вы уверены, что хотите очистить корзину?',\n      areYouSureYouWantToLeaveBoard: 'Вы уверены, что хотите покинуть эту доску?',\n      areYouSureYouWantToLeaveProject: 'Вы уверены, что хотите покинуть этот проект?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Вы уверены, что хотите сделать этот проект частным?',\n      areYouSureYouWantToMakeThisProjectShared: 'Вы уверены, что хотите сделать этот проект общим?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Вы уверены, что хотите перегенерировать этот ключ API? Предыдущий ключ больше не будет работать.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Вы уверены, что хотите удалить этого менеджера из проекта?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Вы уверены, что хотите удалить этого участника из доски?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Вы уверены, что хотите отвязать SSO от этого пользователя? Это позволит пользователю входить с помощью пароля.',\n      assignAsOwner_title: 'Назначить владельцем',\n      atLeastOneListMustBePresent: 'Должен быть хотя бы один список',\n      attachment: 'Вложение',\n      attachments: 'Вложения',\n      authentication: 'Аутентификация',\n      background: 'Фон',\n      baseCustomFields_title: 'Основные настраиваемые поля',\n      baseGroup: 'Основная группа',\n      board: 'Доска',\n      boardActions_title: 'Действия с доской',\n      boardNotFound_title: 'Доска не найдена',\n      boardSubscribed: 'Подписан на доску',\n      boardUser: 'Пользователь доски',\n      byCreationTime: 'По времени создания',\n      byDefault: 'По умолчанию',\n      byDueDate: 'По дате выполнения',\n      canBeInvitedToWorkInBoards: 'Может быть приглашен для работы на досках.',\n      canComment: 'Может комментировать',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Может создавать собственные проекты и быть приглашенным работать в других.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Может редактировать макет доски и назначать участников для карточек.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Может управлять глобальными настройками системы и выступать в роли владельца проекта.',\n      canOnlyViewBoard: 'Может только просматривать доску.',\n      cardActions_title: 'Действия с карточкой',\n      cardNotFound_title: 'Карточка не найдена',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Карточки в этом списке доступны всем членам доски.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Карточки в этом списке завершены и готовы к архивированию.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Карточки в этом списке готовы к работе.',\n      clickHereOrRefreshPageToUpdate: '<0>Нажмите здесь</0> или обновите страницу для обновления.',\n      clientHostnameInEhlo: 'Имя хоста клиента в EHLO',\n      closed: 'Закрыто',\n      color: 'Цвет',\n      comments: 'Комментарии',\n      contentExceedsLimit: 'Содержимое превышает {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Содержимое этого вложения слишком большое для отображения.',\n      copy_inline: 'копия',\n      createBoard_title: 'Создание доски',\n      createCustomFieldGroup_title: 'Создать группу настраиваемых полей',\n      createLabel_title: 'Создание метки',\n      createNewOneOrSelectExistingOne: 'Создайте новую или выберите<br />уже существующую.',\n      createProject_title: 'Создание проекта',\n      createTextFile_title: 'Создание текстового файла',\n      creator: 'Создатель',\n      currentPassword: 'Текущий пароль',\n      currentUser: 'Текущий пользователь',\n      customFieldGroup_title: 'Группа настраиваемых полей',\n      customFieldGroups_title: 'Группы настраиваемых полей',\n      customField_title: 'Настраиваемое поле',\n      customFields_title: 'Настраиваемые поля',\n      customerPanel_title: 'Панель клиента',\n      dangerZone_title: 'Опасная зона',\n      date: 'Дата',\n      deactivateUser_title: 'Деактивировать пользователя',\n      defaultCardType_title: 'Тип карточки по умолчанию',\n      defaultFrom: 'По умолчанию \"от\"',\n      defaultView_title: 'Вид по умолчанию',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Удалите все доски, чтобы иметь возможность удалить этот проект',\n      deleteApiKey_title: 'Удалить ключ API',\n      deleteAttachment_title: 'Удаление вложения',\n      deleteBackgroundImage_title: 'Удалить фоновое изображение',\n      deleteBoard_title: 'Удаление доски',\n      deleteCardForever_title: 'Удалить карточку навсегда',\n      deleteCard_title: 'Удаление карточки',\n      deleteComment_title: 'Удаление комментария',\n      deleteCustomFieldGroup_title: 'Удалить группу настраиваемых полей',\n      deleteCustomField_title: 'Удалить настраиваемое поле',\n      deleteLabel_title: 'Удаление метки',\n      deleteList_title: 'Удаление списка',\n      deleteNotificationService_title: 'Удалить сервис уведомлений',\n      deleteProject_title: 'Удаление проекта',\n      deleteTaskList_title: 'Удалить список задач',\n      deleteTask_title: 'Удаление задачи',\n      deleteUser_title: 'Удаление пользователя',\n      deleteWebhook_title: 'Удалить веб-хук',\n      deletedUser_title: 'Удалённый пользователь',\n      description: 'Описание',\n      display: 'Отображение',\n      displayCardAges: 'Отображать возраст карточек',\n      dropFileToUpload: 'Перетяните файл, чтобы загрузить',\n      dueDate_title: 'Срок исполнения',\n      dynamicAndUnevenlySpacedLayout: 'Динамичное и неравномерно распределённое расположение.',\n      editAttachment_title: 'Изменение вложения',\n      editAvatar_title: 'Изменение аватара',\n      editColor_title: 'Изменить цвет',\n      editCustomFieldGroup_title: 'Изменить группу настраиваемых полей',\n      editCustomField_title: 'Изменить настраиваемое поле',\n      editDueDate_title: 'Изменить срок выполнения',\n      editEmail_title: 'Изменение e-mail',\n      editInformation_title: 'Изменение информации',\n      editLabel_title: 'Изменение метки',\n      editPassword_title: 'Изменение пароля',\n      editPermissions_title: 'Редактирование разрешений',\n      editRole_title: 'Изменить роль',\n      editStopwatch_title: 'Изменение секундомера',\n      editType_title: 'Изменить тип',\n      editUsername_title: 'Изменение имени пользователя',\n      editor: 'Редактор',\n      editors: 'Редакторы',\n      email: 'E-mail',\n      emptyTrash_title: 'Очистить корзину',\n      enterCardTitle: 'Введите заголовок для этой карточки...',\n      enterDescription: 'Введите описание...',\n      enterFilename: 'Введите название файла',\n      enterListTitle: 'Введите заголовок списка...',\n      enterTaskDescription: 'Введите описание задачи...',\n      events: 'События',\n      excludedEvents: 'Исключенные события',\n      expandTaskListsByDefault: 'Разворачивать списки задач по умолчанию',\n      filterByLabels_title: 'Фильтр по меткам',\n      filterByMembers_title: 'Фильтр по участникам',\n      forPersonalProjects: 'Для личных проектов.',\n      forTeamBasedProjects: 'Для командных проектов.',\n      fromComputer_title: 'С компьютера',\n      fromTrello: 'Из Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Полный ключ скрыт по соображениям безопасности. Перегенерируйте его, чтобы создать новый.',\n      general: 'Основные',\n      gradients: 'Градиенты',\n      grid: 'Сетка',\n      hideCompletedTasks: 'Скрыть выполненные задачи',\n      hideFromProjectListAndFavorites: 'Скрыть из списка проектов и избранного',\n      host: 'Хост',\n      hours: 'Часы',\n      identity: 'Личность',\n      importBoard_title: 'Импорт доски',\n      information: 'Информация',\n      invalidCurrentPassword: 'Неверный текущий пароль',\n      kanban: 'Канбан',\n      labels: 'Метки',\n      language: 'Язык',\n      leaveBoard_title: 'Покинуть доску',\n      leaveProject_title: 'Покинуть проект',\n      limitCardTypesToDefaultOne: 'Разрешён только тип карточки по умолчанию',\n      linkToCard: 'Ссылка на карточку',\n      list: 'Список',\n      listActions_title: 'Действия со списком',\n      lists: 'Списки',\n      makeProjectPrivate_title: 'Сделать проект частным',\n      makeProjectShared_title: 'Сделать проект общим',\n      managers: 'Менеджеры',\n      memberActions_title: 'Действия участников',\n      members: 'Участники',\n      minutes: 'Минуты',\n      moreActions: 'Больше действий',\n      moreActions_title: 'Больше действий',\n      moveCard_title: 'Перемещение карточки',\n      moveList_title: 'Переместить список',\n      myOwn_title: 'Мои собственные',\n      name: 'Имя',\n      newEmail: 'Новый e-mail',\n      newPassword: 'Новый пароль',\n      newUsername: 'Новое имя пользователя',\n      newVersionAvailable: 'Доступна новая версия',\n      newestFirst: 'Новые первые',\n      noApiKeyCreated: 'Ключ API не создан.',\n      noBoards: 'Досок нет',\n      noCardsFound: 'Карточки не найдены.',\n      noConnectionToServer: 'Нет соединения с сервером',\n      noLists: 'Списков нет',\n      noProjects: 'Проектов нет',\n      noUnreadNotifications: 'Уведомлений нет.',\n      notifications: 'Уведомления',\n      oldestFirst: 'Старые первые',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Должен остаться только один менеджер, чтобы сделать этот проект частным',\n      openBoard_title: 'Откройте доску',\n      optional_inline: 'необязательно',\n      organization: 'Организация',\n      others: 'Другие',\n      passwordIsSet: 'Пароль установлен',\n      phone: 'Телефон',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA использует <1><0>Apprise</0></1> для отправки уведомлений в более чем 100 популярных сервисов.',\n      port: 'Порт',\n      preferences: 'Предпочтения',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Совет: нажмите Ctrl-V (Cmd-V на Mac), чтобы добавить вложение из буфера обмена.',\n      private: 'Частный',\n      project: 'Проект',\n      projectNotFound_title: 'Проект не найден',\n      projectOwner: 'Владелец проекта',\n      referenceDataAndKnowledgeStorage: 'Хранение справочных данных и знаний.',\n      regenerateApiKey_title: 'Перегенерировать ключ API',\n      rejectUnauthorizedTlsCertificates: 'Отклонять неавторизованные TLS-сертификаты',\n      removeManager_title: 'Удалить менеджера',\n      removeMember_title: 'Удаление участника',\n      role: 'Роль',\n      saveThisKeyItWillNotBeShownAgain: 'Сохраните этот ключ — он больше не будет показан!',\n      searchCards: 'Поиск карточек...',\n      searchCustomFieldGroups: 'Поиск групп настраиваемых полей...',\n      searchCustomFields: 'Поиск настраиваемых полей...',\n      searchLabels: 'Поиск меток...',\n      searchLists: 'Поиск списков...',\n      searchMembers: 'Поиск участников...',\n      searchProjects: 'Поиск проектов...',\n      searchUsers: 'Поиск пользователей...',\n      seconds: 'Секунды',\n      selectAssignee_title: 'Выбрать исполнителя',\n      selectBoard: 'Выберите доску',\n      selectList: 'Выберите список',\n      selectListToRestoreThisCard: 'Выберите список для восстановления этой карточки',\n      selectOrder_title: 'Выбрать порядок',\n      selectPermissions_title: 'Выбор разрешений',\n      selectProject: 'Выберите проект',\n      selectRole_title: 'Выбрать роль',\n      selectType_title: 'Выбрать тип',\n      sequentialDisplayOfCards: 'Последовательное отображение карточек.',\n      settings: 'Настройки',\n      shared: 'Общий',\n      sharedWithMe_title: 'Общие со мной',\n      showOnFrontOfCard: 'Показать на лицевой стороне карточки',\n      smtp: 'SMTP',\n      sortList_title: 'Сортировка списка',\n      sourceCardIsNoLongerAvailableForCopying:\n        'Исходная карточка больше не доступна для копирования.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'Исходная карточка больше не доступна для перемещения.',\n      stopwatch: 'Секундомер',\n      story: 'История',\n      subscribeToCardWhenCommenting: 'Подписаться на карточку при комментировании',\n      subscribeToMyOwnCardsByDefault: 'По умолчанию подписываться на мои собственные карточки',\n      taskActions_title: 'Действия по задаче',\n      taskAssignmentAndProjectCompletion: 'Назначение задач и завершение проекта.',\n      taskListActions_title: 'Действия с списком задач',\n      taskList_title: 'Список задач',\n      team: 'Командные',\n      termsOfService_title: 'Условия использования',\n      testLog_title: 'Журнал тестирования',\n      thereIsNoPreviewAvailableForThisAttachment: 'Предпросмотр для этого вложения недоступен.',\n      time: 'Время',\n      title: 'Название',\n      trash: 'Корзина',\n      trashHasBeenSuccessfullyEmptied: 'Корзина успешно очищена.',\n      turnOffRecentCardHighlighting: 'Выключить подсветку последних карточек',\n      typeNameToConfirm: 'Введите имя для подтверждения.',\n      typeTitleToConfirm: 'Введите название для подтверждения.',\n      unlinkSso_title: 'Отвязка SSO',\n      unsavedChanges: 'Несохранённые изменения',\n      uploadFailedFileIsTooBig: 'Загрузка не удалась: файл слишком большой.',\n      uploadFailedNotEnoughStorageSpace: 'Загрузка не удалась: недостаточно места для хранения.',\n      uploadedImages: 'Загруженные изображения',\n      url: 'URL',\n      useSecureConnection: 'Использовать безопасное соединение',\n      userActions_title: 'Действия с пользователем',\n      userAddedCardToList: '<0>{{user}}</0> добавил(а) <2>{{card}}</2> в {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> добавил(а) эту карточку в {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> добавил(а) {{addedUser}} к <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> добавил(а) {{addedUser}} к этой карточке',\n      userAddedYouToCard: '<0>{{user}}</0> добавил(а) вас к <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> выполнил(а) {{task}} в <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> выполнил(а) {{task}} в этой карточке',\n      userJoinedCard: '<0>{{user}}</0> присоединился(лась) к <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> присоединился(лась) к этой карточке',\n      userLeftCard: '<0>{{user}}</0> покинул(а) <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> оставил(а) комментарий «{{comment}}» к <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> покинул(а) эту карточку',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> отметил(а) {{task}} как невыполненную в <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> отметил(а) {{task}} как невыполненную в этой карточке',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> упомянул(а) вас в комментарии «{{comment}}» к <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> переместил(а) <2>{{card}}</2> из {{fromList}} в {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> переместил(а) эту карточку из {{fromList}} в {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> удалил(а) {{removedUser}} из <4>{{card}}</4>',\n      userRemovedUserFromThisCard:\n        '<0>{{actorUser}}</0> удалил(а) {{removedUser}} из этой карточки',\n      username: 'Имя пользователя',\n      users: 'Пользователи',\n      viewer: 'Читатель',\n      viewers: 'Читатели',\n      visualTaskManagementWithLists: 'Визуальное управление задачами с помощью списков.',\n      webhooks: 'Веб-хуки',\n      whatsNew_title: 'Что нового',\n      withoutBaseGroup: 'Без основной группы',\n      writeComment: 'Напишите комментарий...',\n    },\n\n    action: {\n      activateUser: 'Активировать пользователя',\n      activateUser_title: 'Активировать пользователя',\n      addAnotherCard: 'Добавить еще одну карточку',\n      addAnotherList: 'Добавить еще один список',\n      addAnotherTask: 'Добавить еще одну задачу',\n      addCard: 'Добавить карточку',\n      addCard_title: 'Добавить карточку',\n      addComment: 'Добавить комментарий',\n      addCustomField: 'Добавить настраиваемое поле',\n      addCustomFieldGroup: 'Добавить группу настраиваемых полей',\n      addList: 'Добавить список',\n      addMember: 'Добавить участника',\n      addMoreDetailedDescription: 'Добавить более подробное описание',\n      addTask: 'Добавить задачу',\n      addTaskList: 'Добавить список задач',\n      addToCard: 'Добавить на карточку',\n      addUser: 'Добавить пользователя',\n      addWebhook: 'Добавить веб-хук',\n      archive: 'Архивировать',\n      archiveCard: 'Архивировать карточку',\n      archiveCard_title: 'Архивировать карточку',\n      archiveCards: 'Архивировать карточки',\n      archiveCards_title: 'Архивировать карточки',\n      assignAsOwner: 'Назначить владельцем',\n      cancel: 'Отменить',\n      copy: 'Копировать',\n      copyCard_title: 'Копировать карточку',\n      createApiKey: 'Создать ключ API',\n      createBoard: 'Создать доску',\n      createCustomFieldGroup: 'Создать группу настраиваемых полей',\n      createFile: 'Создать файл',\n      createLabel: 'Создать метку',\n      createNewLabel: 'Создать новую метку',\n      createProject: 'Создать проект',\n      cut: 'Вырезать',\n      cutCard_title: 'Вырезать карточку',\n      deactivateUser: 'Деактивировать пользователя',\n      deactivateUser_title: 'Деактивировать пользователя',\n      delete: 'Удалить',\n      deleteApiKey: 'Удалить ключ API',\n      deleteAttachment: 'Удалить вложение',\n      deleteAvatar: 'Удалить аватар',\n      deleteBackgroundImage: 'Удалить фоновое изображение',\n      deleteBoard: 'Удалить доску',\n      deleteBoard_title: 'Удалить доску',\n      deleteCard: 'Удалить карточку',\n      deleteCardForever: 'Удалить карточку навсегда',\n      deleteCard_title: 'Удалить карточку',\n      deleteComment: 'Удалить комментарий',\n      deleteCustomField: 'Удалить настраиваемое поле',\n      deleteCustomFieldGroup: 'Удалить группу настраиваемых полей',\n      deleteForever_title: 'Удалить навсегда',\n      deleteGroup: 'Удалить группу',\n      deleteLabel: 'Удалить метку',\n      deleteList: 'Удалить список',\n      deleteList_title: 'Удалить список',\n      deleteNotificationService: 'Удалить сервис уведомлений',\n      deleteProject: 'Удалить проект',\n      deleteProject_title: 'Удалить проект',\n      deleteTask: 'Удалить задачу',\n      deleteTaskList: 'Удалить список задач',\n      deleteTask_title: 'Удалить задачу',\n      deleteUser: 'Удалить пользователя',\n      deleteUser_title: 'Удалить пользователя',\n      deleteWebhook: 'Удалить веб-хук',\n      dismissAll: 'Отклонить все',\n      download: 'Скачать',\n      duplicateCard_title: 'Дублировать карточку',\n      edit: 'Изменить',\n      editColor_title: 'Изменить цвет',\n      editDescription_title: 'Изменить описание',\n      editDueDate_title: 'Изменить срок',\n      editEmail_title: 'Изменить e-mail',\n      editGroup: 'Изменить группу',\n      editInformation_title: 'Изменить информацию',\n      editPassword_title: 'Изменить пароль',\n      editPermissions: 'Изменить разрешения',\n      editRole_title: 'Изменить роль',\n      editStopwatch_title: 'Изменить секундомер',\n      editTitle_title: 'Изменить название',\n      editType_title: 'Изменить тип',\n      editUsername_title: 'Изменить имя пользователя',\n      emptyTrash: 'Очистить корзину',\n      emptyTrash_title: 'Очистить корзину',\n      import: 'Импорт',\n      join: 'Присоединиться',\n      leave: 'Покинуть',\n      leaveBoard: 'Покинуть доску',\n      leaveProject: 'Покинуть проект',\n      logOut_title: 'Выйти',\n      makeCover_title: 'Сделать обложкой',\n      makeProjectPrivate: 'Сделать проект частным',\n      makeProjectPrivate_title: 'Сделать проект частным',\n      makeProjectShared: 'Сделать проект общим',\n      makeProjectShared_title: 'Сделать проект общим',\n      move: 'Переместить',\n      moveCard_title: 'Переместить карточку',\n      moveList_title: 'Переместить список',\n      regenerateApiKey: 'Перегенерировать ключ API',\n      remove: 'Убрать',\n      removeAssignee: 'Удалить исполнителя',\n      removeColor: 'Удалить цвет',\n      removeCover_title: 'Убрать обложку',\n      removeFromBoard: 'Удалить из доски',\n      removeFromProject: 'Удалить из проекта',\n      removeManager: 'Удалить менеджера',\n      removeMember: 'Удалить участника',\n      restoreToList: 'Восстановить в {{list}}',\n      returnToBoard: 'Вернуться на доску',\n      save: 'Сохранить',\n      sendTestEmail: 'Отправить тестовое письмо',\n      showActive: 'Показать активные',\n      showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)',\n      showCardsWithThisUser: 'Показать карточки с этим пользователем',\n      showDeactivated: 'Показать деактивированные',\n      showFewerAttachments: 'Показать меньше вложений',\n      showLess: 'Показать меньше',\n      showMore: 'Показать больше',\n      sortList_title: 'Сортировать список',\n      start: 'Начать',\n      stop: 'Остановить',\n      subscribe: 'Подписаться',\n      unlinkSso: 'Отвязать SSO',\n      unlinkSso_title: 'Отвязать SSO',\n      unsubscribe: 'Отписаться',\n      uploadNewAvatar: 'Загрузить новый аватар',\n      uploadNewImage: 'Загрузить новое изображение',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ru-RU/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'ru-RU',\n  country: 'ru',\n  name: 'Русский',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/ru-RU/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Достигнут лимит активных пользователей',\n      adminLoginRequiredToInitializeInstance:\n        'Требуется вход администратора для инициализации экземпляра',\n      emailAlreadyInUse: 'E-mail уже занят',\n      emailOrUsername: 'E-mail или имя пользователя',\n      invalidCredentials: 'Недействительные учетные данные',\n      invalidEmailOrUsername: 'Неверный e-mail или имя пользователя',\n      invalidPassword: 'Неверный пароль',\n      logIn_title: 'Войти',\n      noInternetConnection: 'Нет соединения',\n      or: 'Или',\n      pageNotFound_title: 'Страница не найдена',\n      password: 'Пароль',\n      poweredByPlanka: 'Powered by <1>PLANKA</1>',\n      serverConnectionFailed: 'Не могу подключиться к серверу',\n      unknownError: 'Что-то пошло не так, попробуйте позже',\n      useSingleSignOn: 'Используйте единый вход',\n      usernameAlreadyInUse: 'Имя пользователя уже занято',\n      whoops_title: 'Упс!',\n    },\n\n    action: {\n      cancelAndClose: 'Отменить и закрыть',\n      continue: 'Продолжить',\n      debugSso: 'Отладить SSO',\n      goBack: 'Назад',\n      goHome: 'На главную',\n      logIn: 'Войти',\n      logInWithSso: 'Войти с помощью единого входа',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/ru-RU/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Это текст без заголовка.\\nКак заголовок, так и текст\\nмогут быть выделены жирным, курсивом, цветом,\\nзачеркнутым и подчеркнутым.\",\n    \"text-with-head\": \"Это текст с заголовком.\\nКак заголовок, так и текст\\nмогут быть выделены жирным, курсивом, цветом,\\nзачеркнутым и подчеркнутым.\",\n    \"heading\": \"Заголовок\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Ошибка в редакторе markdown\",\n    \"settings_wysiwyg\": \"Визуальный редактор (wysiwyg)\",\n    \"settings_markup\": \"Разметка markdown\",\n    \"markup_placeholder\": \"Введите разметку markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Удалить\",\n    \"empty_option\": \"Совпадений не найдено\",\n    \"show_line_numbers\": \"Нумерация строк\"\n  },\n  \"common\": {\n    \"delete\": \"Удалить\",\n    \"edit\": \"Редактировать\",\n    \"toolbar_action_disabled\": \"Несовместимый элемент разметки\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Отмена\",\n    \"common_action_submit\": \"Отправить\",\n    \"common_action_upload\": \"Выбрать\",\n    \"common_tab_attach\": \"Добавить с устройства\",\n    \"common_tab_link\": \"Добавить по ссылке\",\n    \"common_link\": \"Ссылка\",\n    \"common_sizes\": \"Размер, px\",\n    \"image_name\": \"Заголовок\",\n    \"image_link_href\": \"Ссылка на изображение\",\n    \"image_link_href_help\": \"Адрес, на который ведет ссылка на изображение.\",\n    \"image_alt\": \"Alt текст\",\n    \"image_alt_help\": \"Alt текст отображается, если изображение не может быть загружено.\",\n    \"image_upload_help\": \"Изображение в формате JPEG, GIF или PNG размером не более 1 МБ.\",\n    \"image_upload_failed\": \"Не удалось добавить изображение\",\n    \"image_size_width\": \"Ширина\",\n    \"image_size_height\": \"Высота\",\n    \"link_url_help\": \"Адрес, на который ведет ссылка.\",\n    \"link_text\": \"Текст ссылки\",\n    \"link_text_help\": \"Текст, отображаемый как ссылка.\",\n    \"link_open_help\": \"Открыть ссылку в новой вкладке\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Заголовок\",\n    \"header_hint\": \"# Ваш текст\",\n    \"italic_title\": \"Курсив\",\n    \"italic_hint\": \"_Ваш текст_\",\n    \"bold_title\": \"Жирный\",\n    \"bold_hint\": \"**Ваш текст**\",\n    \"strikethrough_title\": \"Зачеркнутый\",\n    \"strikethrough_hint\": \"~~Ваш текст~~\",\n    \"blockquote_title\": \"Цитата\",\n    \"blockquote_hint\": \"> Ваш текст\",\n    \"code_title\": \"Код\",\n    \"code_hint\": \"```Ваш текст```\",\n    \"link_title\": \"Ссылка\",\n    \"link_hint\": \"[Ваш текст](url)\",\n    \"image_title\": \"Изображение\",\n    \"image_hint\": \"![Ваш текст](url)\",\n    \"list_title\": \"Элемент списка\",\n    \"list_hint\": \"- Ваш текст\",\n    \"numbered-list_title\": \"Нумерованный список\",\n    \"numbered-list_hint\": \"1. Ваш текст\",\n    \"documentation\": \"Документация\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Жирный\",\n    \"code\": \"Код\",\n    \"code_inline\": \"Встроенный код\",\n    \"codeblock\": \"Блок кода\",\n    \"colorify\": \"Цвет текста\",\n    \"colorify__color_blue\": \"Синий\",\n    \"colorify__color_default\": \"По умолчанию\",\n    \"colorify__color_gray\": \"Серый\",\n    \"colorify__color_green\": \"Зеленый\",\n    \"colorify__color_orange\": \"Оранжевый\",\n    \"colorify__color_red\": \"Красный\",\n    \"colorify__color_violet\": \"Фиолетовый\",\n    \"colorify__color_yellow\": \"Желтый\",\n    \"colorify__group_text\": \"Текст\",\n    \"cut\": \"Вырезать\",\n    \"emoji\": \"Эмодзи\",\n    \"emoji__hint\": \"Эмодзи можно добавить в WYSIWYG или вручную с разметкой\",\n    \"heading\": \"Заголовок\",\n    \"heading1\": \"Заголовок 1\",\n    \"heading2\": \"Заголовок 2\",\n    \"heading3\": \"Заголовок 3\",\n    \"heading4\": \"Заголовок 4\",\n    \"heading5\": \"Заголовок 5\",\n    \"heading6\": \"Заголовок 6\",\n    \"hrule\": \"Разделитель\",\n    \"image\": \"Изображение\",\n    \"italic\": \"Курсив\",\n    \"link\": \"Ссылка\",\n    \"list\": \"Список\",\n    \"list__action_lift\": \"Поднять элемент\",\n    \"list__action_sink\": \"Опустить элемент\",\n    \"list_action_disabled\": \"Противоречит логике списка\",\n    \"mark\": \"Выделить\",\n    \"mono\": \"Моноширинный\",\n    \"more_action\": \"Больше действий\",\n    \"note\": \"Заметка\",\n    \"olist\": \"Упорядоченный список\",\n    \"quote\": \"Цитата\",\n    \"redo\": \"Повторить\",\n    \"strike\": \"Зачеркнутый\",\n    \"table\": \"Таблица\",\n    \"text\": \"Текст\",\n    \"ulist\": \"Маркированный список\",\n    \"underline\": \"Подчеркнутый\",\n    \"undo\": \"Отменить\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Введите / для использования команд через слэш...\",\n    \"checkbox\": \"Введите описание задачи...\",\n    \"deflist_term\": \"Термин\",\n    \"deflist_desc\": \"Описание термина\",\n    \"heading\": \"Заголовок\",\n    \"cut_title\": \"Заголовок\",\n    \"cut_content\": \"Содержимое для отображения по клику\",\n    \"note_title\": \"Заголовок\",\n    \"note_content\": \"Содержание заметки\",\n    \"table_cell\": \"Содержимое ячейки\",\n    \"select_filter\": \"Поиск языков...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Чувствительность к регистру\",\n    \"label_whole-word\": \"Полное слово\",\n    \"title\": \"Поиск в коде\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Не найдено\"\n  },\n  \"widgets\": {\n    \"image\": \"Добавить изображение\",\n    \"link\": \"Добавить ссылку\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Информация\",\n    \"tip\": \"Совет\",\n    \"warning\": \"Предупреждение\",\n    \"alert\": \"Предупреждение\",\n    \"remove\": \"Удалить\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Добавить колонку перед\",\n    \"column.add.after\": \"Добавить колонку после\",\n    \"column.remove\": \"Удалить колонку\",\n    \"column.remove.multiple\": \"Удалить колонки\",\n    \"row.add.before\": \"Добавить строку перед\",\n    \"row.add.after\": \"Добавить строку после\",\n    \"row.remove\": \"Удалить строку\",\n    \"row.remove.multiple\": \"Удалить строки\",\n    \"cells.clear\": \"Очистить ячейки\",\n    \"table.remove\": \"Удалить таблицу\",\n    \"table.menu.cell.align.left\": \"Выравнять содержимое ячейки по левому краю\",\n    \"table.menu.cell.align.right\": \"Выравнять содержимое ячейки по правому краю\",\n    \"table.menu.cell.align.center\": \"Выравнять содержимое ячейки по центру\",\n    \"table.menu.row.add\": \"Добавить строку после\",\n    \"table.menu.row.remove\": \"Удалить строку\",\n    \"table.menu.column.add\": \"Добавить колонку после\",\n    \"table.menu.column.remove\": \"Удалить колонку\",\n    \"table.menu.convert.yfm\": \"Преобразовать в таблицу YFM\",\n    \"table.menu.table.remove\": \"Удалить таблицу\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/sk-SK/core.js",
    "content": "import dateFns from 'date-fns/locale/sk';\nimport timeAgo from 'javascript-time-ago/locale/sk';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd.M.yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'v' p\",\n    fullDate: 'd MMM y',\n    fullDateTime: \"d MMMM y 'v' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'O aplikácii',\n      aboutPlanka_title: 'O Planke',\n      accessToken: 'Prístupový token',\n      account: 'Účet',\n      actions: 'Akcia',\n      activateUser_title: 'Aktivovať používateľa',\n      active: 'Aktívny',\n      addAttachment_title: 'Pridať prílohu',\n      addCustomFieldGroup_title: 'Pridať skupinu vlastných polí',\n      addCustomField_title: 'Pridať vlastné pole',\n      addManager_title: 'Pridať správcu',\n      addMember_title: 'Pridať člena',\n      addTaskList_title: 'Pridať zoznam úloh',\n      addUser_title: 'Pridať používateľa',\n      admin: 'Správca',\n      administration: 'Správa',\n      all: 'Všetko',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Všetky zmeny budú automaticky uložené<br />po obnovení spojenia.',\n      alphabetically: 'Abecedne',\n      alwaysDisplayCardCreator: 'Vždy zobraziť tvorcu karty',\n      apiKeyCreated_title: 'API kľúč vytvorený',\n      apiKey_title: 'API kľúč',\n      archive: 'Archív',\n      archiveCard_title: 'Archivovať kartu',\n      archiveCards_title: 'Archivovať karty',\n      areYouSureYouWantToActivateThisUser: 'Naozaj chcete aktivovať tohoto používateľa?',\n      areYouSureYouWantToArchiveCards: 'Naozaj chcete archivovať karty?',\n      areYouSureYouWantToArchiveThisCard: 'Naozaj chcete archivovať túto kartu?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Naozaj chcete prideliť tohoto správcu projektu ako vlastníka?',\n      areYouSureYouWantToDeactivateThisUser: 'Naozaj chcete deaktivovať tohoto používateľa?',\n      areYouSureYouWantToDeleteThisApiKey: 'Naozaj chcete odstrániť tento API kľúč?',\n      areYouSureYouWantToDeleteThisAttachment: 'Naozaj chcete zmazať túto prílohu?',\n      areYouSureYouWantToDeleteThisBackgroundImage: 'Naozaj chcete zmazať tento obrázok pozadia?',\n      areYouSureYouWantToDeleteThisBoard: 'Naozaj chcete zmazať túto tabuľu?',\n      areYouSureYouWantToDeleteThisCard: 'Naozaj chcete zmazať túto kartu?',\n      areYouSureYouWantToDeleteThisCardForever: 'Naozaj chcete natrvalo zmazať túto kartu?',\n      areYouSureYouWantToDeleteThisComment: 'Naozaj chcete zmazať tento komentár?',\n      areYouSureYouWantToDeleteThisCustomField: 'Naozaj chcete zmazať toto vlastné pole?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Naozaj chcete zmazať túto skupinu vlastných polí?',\n      areYouSureYouWantToDeleteThisLabel: 'Naozaj chcete zmazať tento štítok?',\n      areYouSureYouWantToDeleteThisList:\n        'Naozaj chcete zmazať tento zoznam? Všetky karty budú presunuté do koša.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Naozaj chcete zmazať túto notifikačnú službu?',\n      areYouSureYouWantToDeleteThisProject: 'Naozaj chcete zmazať tento projekt?',\n      areYouSureYouWantToDeleteThisTask: 'Naozaj chcete zmazať túto úlohu?',\n      areYouSureYouWantToDeleteThisTaskList: 'Naozaj chcete zmazať tento zoznam úloh?',\n      areYouSureYouWantToDeleteThisUser: 'Naozaj chcete zmazať tohoto používateľa?',\n      areYouSureYouWantToDeleteThisWebhook: 'Naozaj chcete zmazať tento webhook?',\n      areYouSureYouWantToEmptyTrash: 'Naozaj chcete vyprázdniť kôš?',\n      areYouSureYouWantToLeaveBoard: 'Naozaj chcete opustiť túto tabuľu?',\n      areYouSureYouWantToLeaveProject: 'Naozaj chcete opustiť tento projekt?',\n      areYouSureYouWantToMakeThisProjectPrivate: 'Naozaj chcete urobiť tento projekt súkromným?',\n      areYouSureYouWantToMakeThisProjectShared: 'Naozaj chcete urobiť tento projekt zdieľaným?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Naozaj chcete znovu vygenerovať tento API kľúč? Predchádzajúci kľúč už nebude fungovať.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Naozaj chcete zmazať daného správcu tohto projektu?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Naozaj chcete odstrániť tohoto člena z tabule?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Naozaj chcete odpojiť SSO od tohto používateľa? Tým umožníte používateľovi prihlásiť sa pomocou hesla.',\n      assignAsOwner_title: 'Prideliť ako vlastníka',\n      atLeastOneListMustBePresent: 'Musí byť prítomný aspoň jeden zoznam',\n      attachment: 'Príloha',\n      attachments: 'Prílohy',\n      authentication: 'Overenie',\n      background: 'Pozadie',\n      baseCustomFields_title: 'Základné vlastné polia',\n      baseGroup: 'Základná skupina',\n      board: 'Tabuľa',\n      boardActions_title: 'Akcie tabule',\n      boardNotFound_title: 'Tabuľa neexistuje',\n      boardSubscribed: 'Tabuľa odberateľa',\n      boardUser: 'Používateľ tabule',\n      byCreationTime: 'Podľa času vytvorenia',\n      byDefault: 'Predvolene',\n      byDueDate: 'Podľa termínu',\n      canBeInvitedToWorkInBoards: 'Môže byť pozvaný pracovať v tabuliach.',\n      canComment: 'Môže komentovať',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Môže vytvárať vlastné projekty a byť pozvaný pracovať v iných.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Môže upravovať rozloženie tabule a prideľovať členov kartám.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Môže spravovať systémové nastavenia a konať ako vlastník projektu.',\n      canOnlyViewBoard: 'Môže iba zobraziť tabuľu.',\n      cardActions_title: 'Akcie na karte',\n      cardNotFound_title: 'Karta neexistuje',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Karty v tomto zozname sú dostupné všetkým členom tabule.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Karty v tomto zozname sú dokončené a pripravené na archiváciu.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Karty v tomto zozname sú pripravené na prácu.',\n      clickHereOrRefreshPageToUpdate: '<0>Kliknite sem</0> alebo obnovte stránku na aktualizáciu.',\n      clientHostnameInEhlo: 'Názov hostiteľa klienta v EHLO',\n      closed: 'Zatvorené',\n      color: 'Farba',\n      comments: 'Komentáre',\n      contentExceedsLimit: 'Obsah prekračuje {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Obsah tejto prílohy je príliš veľký na zobrazenie.',\n      copy_inline: 'kópia',\n      createBoard_title: 'Vytvoriť tabuľu',\n      createCustomFieldGroup_title: 'Vytvoriť skupinu vlastných polí',\n      createLabel_title: 'Vytvoriť štítok',\n      createNewOneOrSelectExistingOne: 'Vytvoriť nový alebo vyberte<br />už existujúci.',\n      createProject_title: 'Vytvoriť projekt',\n      createTextFile_title: 'Vytvoriť textový súbor',\n      creator: 'Tvorca',\n      currentPassword: 'Aktuálne heslo',\n      currentUser: 'Aktuálny používateľ',\n      customFieldGroup_title: 'Skupina vlastných polí',\n      customFieldGroups_title: 'Skupiny vlastných polí',\n      customField_title: 'Vlastné pole',\n      customFields_title: 'Vlastné polia',\n      customerPanel_title: 'Panel zákazníka',\n      dangerZone_title: 'Nebezpečná zóna',\n      date: 'Dátum',\n      deactivateUser_title: 'Deaktivovať používateľa',\n      defaultCardType_title: 'Predvolený typ karty',\n      defaultFrom: 'Predvolené od',\n      defaultView_title: 'Predvolené zobrazenie',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Zmaž všetky tabule, aby si mohol zmazať tento projekt',\n      deleteApiKey_title: 'Odstrániť API kľúč',\n      deleteAttachment_title: 'Zmazať prílohu',\n      deleteBackgroundImage_title: 'Zmazať obrázok pozadia',\n      deleteBoard_title: 'Zmazať tabuľu',\n      deleteCardForever_title: 'Zmazať kartu natrvalo',\n      deleteCard_title: 'Zmazať kartu',\n      deleteComment_title: 'Zmazať komentár',\n      deleteCustomFieldGroup_title: 'Zmazať skupinu vlastných polí',\n      deleteCustomField_title: 'Zmazať vlastné pole',\n      deleteLabel_title: 'Zmazať štítok',\n      deleteList_title: 'Zmazať zoznam',\n      deleteNotificationService_title: 'Zmazať notifikačnú službu',\n      deleteProject_title: 'Zmazať projekt',\n      deleteTaskList_title: 'Zmazať zoznam úloh',\n      deleteTask_title: 'Zmazať úlohu',\n      deleteUser_title: 'Zmazať používateľa',\n      deleteWebhook_title: 'Zmazať webhook',\n      deletedUser_title: 'Zmazaný používateľ',\n      description: 'Popis',\n      display: 'Zobraziť',\n      displayCardAges: 'Zobraziť vek kariet',\n      dropFileToUpload: 'Potiahnutím nahraj súbor',\n      dueDate_title: 'Termín do',\n      dynamicAndUnevenlySpacedLayout: 'Dynamické a nerovnomerne rozložené usporiadanie.',\n      editAttachment_title: 'Upraviť prílohu',\n      editAvatar_title: 'Upraviť avatar',\n      editColor_title: 'Upraviť farbu',\n      editCustomFieldGroup_title: 'Upraviť skupinu vlastných polí',\n      editCustomField_title: 'Upraviť vlastné pole',\n      editDueDate_title: 'Upraviť termín do',\n      editEmail_title: 'Upraviť e-mail',\n      editInformation_title: 'Upraviť informácie',\n      editLabel_title: 'Upraviť štítok',\n      editPassword_title: 'Upraviť heslo',\n      editPermissions_title: 'Upraviť oprávnenia',\n      editRole_title: 'Upraviť rolu',\n      editStopwatch_title: 'Upraviť časovač',\n      editType_title: 'Upraviť typ',\n      editUsername_title: 'Upraviť používateľské meno',\n      editor: 'Editor',\n      editors: 'Editori',\n      email: 'E-mail',\n      emptyTrash_title: 'Vyprázdniť kôš',\n      enterCardTitle: 'Vlož názov karty...',\n      enterDescription: 'Vlož popis...',\n      enterFilename: 'Vlož názov súboru',\n      enterListTitle: 'Vlož názov zoznamu...',\n      enterTaskDescription: 'Vlož popis úlohy...',\n      events: 'Udalosti',\n      excludedEvents: 'Vylúčené udalosti',\n      expandTaskListsByDefault: 'Predvolene rozšíriť zoznamy úloh',\n      filterByLabels_title: 'Filtruj podľa štítku',\n      filterByMembers_title: 'Filtruj podľa člena',\n      forPersonalProjects: 'Pre osobné projekty.',\n      forTeamBasedProjects: 'Pre tímové projekty.',\n      fromComputer_title: 'Z počítača',\n      fromTrello: 'Z Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Celý kľúč je z bezpečnostných dôvodov skrytý. Vygenerujte ho znovu na vytvorenie nového.',\n      general: 'Všeobecné',\n      gradients: 'Prechody',\n      grid: 'Mriežka',\n      hideCompletedTasks: 'Skryť dokončené úlohy',\n      hideFromProjectListAndFavorites: 'Skryť zo zoznamu projektov a obľúbených',\n      host: 'Hostiteľ',\n      hours: 'Hodiny',\n      identity: 'Identita',\n      importBoard_title: 'Importovať tabuľu',\n      information: 'Informácie',\n      invalidCurrentPassword: 'Neplatné aktuálne heslo',\n      kanban: 'Kanban',\n      labels: 'Štítky',\n      language: 'Jazyk',\n      leaveBoard_title: 'Opustiť tabuľu',\n      leaveProject_title: 'Opustiť projekt',\n      limitCardTypesToDefaultOne: 'Obmedziť typy kariet na predvolený',\n      linkToCard: 'Odkaz na kartu',\n      list: 'Zoznam',\n      listActions_title: 'Zoznam akcií',\n      lists: 'Zoznamy',\n      makeProjectPrivate_title: 'Urobiť projekt súkromným',\n      makeProjectShared_title: 'Urobiť projekt zdieľaným',\n      managers: 'Správcovia',\n      memberActions_title: 'Akcie členov',\n      members: 'Členovia',\n      minutes: 'Minúty',\n      moreActions: 'Viac akcií',\n      moreActions_title: 'Viac akcií',\n      moveCard_title: 'Presunúť kartu',\n      moveList_title: 'Presunúť zoznam',\n      myOwn_title: 'Moje vlastné',\n      name: 'Meno',\n      newEmail: 'Nový e-mail',\n      newPassword: 'Nové heslo',\n      newUsername: 'Nové používateľské meno',\n      newVersionAvailable: 'Nová verzia je dostupná',\n      newestFirst: 'Najnovšie prvé',\n      noApiKeyCreated: 'Nebol vytvorený žiadny API kľúč.',\n      noBoards: 'Žiadne tabule',\n      noCardsFound: 'Nenašli sa žiadne karty.',\n      noConnectionToServer: 'Nie je spojenie k serveru',\n      noLists: 'Žiadne zoznamy',\n      noProjects: 'Žiadne projekty',\n      noUnreadNotifications: 'Žiadne neprečítané oznámenia.',\n      notifications: 'Oznámenia',\n      oldestFirst: 'Najstaršie prvé',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Iba jeden správca by mal zostať, aby sa tento projekt stal súkromným',\n      openBoard_title: 'Otvoriť tabuľu',\n      optional_inline: 'voliteľné',\n      organization: 'Spoločnosť',\n      others: 'Ostatní',\n      passwordIsSet: 'Heslo je nastavené',\n      phone: 'Telefón',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA používa <1><0>Apprise</0></1> na posielanie oznámení do viac ako 100 populárnych služieb.',\n      port: 'Port',\n      preferences: 'Voľby',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Tip: stlačte Ctrl-V (Cmd-V na Mac) pre vloženie prílohy zo schránky.',\n      private: 'Súkromný',\n      project: 'Projekt',\n      projectNotFound_title: 'Projekt neexistuje',\n      projectOwner: 'Vlastník projektu',\n      referenceDataAndKnowledgeStorage: 'Referenčné údaje a úložisko znalostí.',\n      regenerateApiKey_title: 'Znovu vygenerovať API kľúč',\n      rejectUnauthorizedTlsCertificates: 'Odmietnuť neautorizované TLS certifikáty',\n      removeManager_title: 'Odstrániť správcu',\n      removeMember_title: 'Odstrániť člena',\n      role: 'Rola',\n      saveThisKeyItWillNotBeShownAgain: 'Uložte si tento kľúč — už sa nezobrazí!',\n      searchCards: 'Hľadať karty...',\n      searchCustomFieldGroups: 'Hľadať skupiny vlastných polí...',\n      searchCustomFields: 'Hľadať vlastné polia...',\n      searchLabels: 'Hľadať štítky...',\n      searchLists: 'Hľadať zoznamy...',\n      searchMembers: 'Hľadať členov...',\n      searchProjects: 'Hľadať projekty...',\n      searchUsers: 'Hľadať používateľov...',\n      seconds: 'Sekúnd',\n      selectAssignee_title: 'Vybrať pridelenú osobu',\n      selectBoard: 'Vybrať tabuľu',\n      selectList: 'Vybrať zoznam',\n      selectListToRestoreThisCard: 'Vyberte zoznam na obnovenie tejto karty',\n      selectOrder_title: 'Vybrať poradie',\n      selectPermissions_title: 'Vybrať oprávnenia',\n      selectProject: 'Vybrať projekt',\n      selectRole_title: 'Vybrať rolu',\n      selectType_title: 'Vybrať typ',\n      sequentialDisplayOfCards: 'Postupné zobrazenie kariet.',\n      settings: 'Nastavenia',\n      shared: 'Zdieľané',\n      sharedWithMe_title: 'Zdieľané so mnou',\n      showOnFrontOfCard: 'Zobraziť na prednej strane karty',\n      smtp: 'SMTP',\n      sortList_title: 'Zoradiť zoznam',\n      sourceCardIsNoLongerAvailableForCopying: 'Zdrojová karta už nie je dostupná na kopírovanie.',\n      sourceCardIsNoLongerAvailableForMoving: 'Zdrojová karta už nie je dostupná na presunutie.',\n      stopwatch: 'Časovač',\n      story: 'Príbeh',\n      subscribeToCardWhenCommenting: 'Odoberať kartu pri komentovaní',\n      subscribeToMyOwnCardsByDefault: 'Predvolene odoberať vlastné karty',\n      taskActions_title: 'Akcie na úlohe',\n      taskAssignmentAndProjectCompletion: 'Pridelenie úloh a dokončenie projektu.',\n      taskListActions_title: 'Akcie zoznamu úloh',\n      taskList_title: 'Zoznam úloh',\n      team: 'Tím',\n      termsOfService_title: 'Podmienky služby',\n      testLog_title: 'Testovací denník',\n      thereIsNoPreviewAvailableForThisAttachment: 'Pre túto prílohu nie je k dispozícii náhľad.',\n      time: 'Čas',\n      title: 'Názov',\n      trash: 'Kôš',\n      trashHasBeenSuccessfullyEmptied: 'Kôš bol úspešne vyprázdnený.',\n      turnOffRecentCardHighlighting: 'Vypnúť zvýrazňovanie nedávnych kariet',\n      typeNameToConfirm: 'Zadajte meno na potvrdenie.',\n      typeTitleToConfirm: 'Zadajte názov na potvrdenie.',\n      unlinkSso_title: 'Odpojenie SSO',\n      unsavedChanges: 'Neuložené zmeny',\n      uploadFailedFileIsTooBig: 'Nahrávanie zlyhalo: súbor je príliš veľký.',\n      uploadFailedNotEnoughStorageSpace: 'Nahrávanie zlyhalo: nedostatok úložného priestoru.',\n      uploadedImages: 'Nahrané obrázky',\n      url: 'URL',\n      useSecureConnection: 'Použiť zabezpečené pripojenie',\n      userActions_title: 'Akcie na používateľovi',\n      userAddedCardToList: '<0>{{user}}</0> pridal <2>{{card}}</2> do {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> pridal kartu do {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> pridal {{addedUser}} do <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> pridal {{addedUser}} do tejto karty',\n      userAddedYouToCard: '<0>{{user}}</0> vás pridal do <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> dokončil {{task}} na <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> dokončil {{task}} na tejto karte',\n      userJoinedCard: '<0>{{user}}</0> sa pripojil k <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> sa pripojil k tejto karte',\n      userLeftCard: '<0>{{user}}</0> opustil <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> zanechal nový komentár «{{comment}}» k <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> opustil túto kartu',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> označil {{task}} ako nedokončenú na <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> označil {{task}} ako nedokončenú na tejto karte',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> vás spomenul v komentári «{{comment}}» na <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> presunul <2>{{card}}</2> z {{fromList}} do {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> presunul túto kartu z {{fromList}} do {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> odstránil {{removedUser}} z <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> odstránil {{removedUser}} z tejto karty',\n      username: 'Používateľské meno',\n      users: 'Používatelia',\n      viewer: 'Prehliadač',\n      viewers: 'Prehliadače',\n      visualTaskManagementWithLists: 'Vizuálne riadenie úloh so zoznamami.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Čo je nové',\n      withoutBaseGroup: 'Bez základnej skupiny',\n      writeComment: 'Napísať komentár...',\n    },\n\n    action: {\n      activateUser: 'Aktivovať používateľa',\n      activateUser_title: 'Aktivovať používateľa',\n      addAnotherCard: 'Pridať ďalšiu kartu',\n      addAnotherList: 'Pridať ďalší zoznam',\n      addAnotherTask: 'Pridať ďalšiu úlohu',\n      addCard: 'Pridať kartu',\n      addCard_title: 'Pridať kartu',\n      addComment: 'Pridať komentár',\n      addCustomField: 'Pridať vlastné pole',\n      addCustomFieldGroup: 'Pridať skupinu vlastných polí',\n      addList: 'Pridať zoznam',\n      addMember: 'Pridať člena',\n      addMoreDetailedDescription: 'Pridať ďalší detailný popis',\n      addTask: 'Pridať úlohu',\n      addTaskList: 'Pridať zoznam úloh',\n      addToCard: 'Pridať na kartu',\n      addUser: 'Pridať používateľa',\n      addWebhook: 'Pridať webhook',\n      archive: 'Archivovať',\n      archiveCard: 'Archivovať kartu',\n      archiveCard_title: 'Archivovať kartu',\n      archiveCards: 'Archivovať karty',\n      archiveCards_title: 'Archivovať karty',\n      assignAsOwner: 'Prideliť ako vlastníka',\n      cancel: 'Zrušiť',\n      copy: 'Kopírovať',\n      copyCard_title: 'Kopírovať kartu',\n      createApiKey: 'Vytvoriť API kľúč',\n      createBoard: 'Vytvoriť tabuľu',\n      createCustomFieldGroup: 'Vytvoriť skupinu vlastných polí',\n      createFile: 'Vytvoriť súbor',\n      createLabel: 'Vytvoriť štítok',\n      createNewLabel: 'Vytvoriť nový štítok',\n      createProject: 'Vytvoriť projekt',\n      cut: 'Vystrihnúť',\n      cutCard_title: 'Vystrihnúť kartu',\n      deactivateUser: 'Deaktivovať používateľa',\n      deactivateUser_title: 'Deaktivovať používateľa',\n      delete: 'Zmazať',\n      deleteApiKey: 'Odstrániť API kľúč',\n      deleteAttachment: 'Zmazať prílohu',\n      deleteAvatar: 'Zmazať avatar',\n      deleteBackgroundImage: 'Zmazať obrázok pozadia',\n      deleteBoard: 'Zmazať tabuľu',\n      deleteBoard_title: 'Zmazať tabuľu',\n      deleteCard: 'Zmazať kartu',\n      deleteCardForever: 'Zmazať kartu natrvalo',\n      deleteCard_title: 'Zmazať kartu',\n      deleteComment: 'Zmazať komentár',\n      deleteCustomField: 'Zmazať vlastné pole',\n      deleteCustomFieldGroup: 'Zmazať skupinu vlastných polí',\n      deleteForever_title: 'Zmazať natrvalo',\n      deleteGroup: 'Zmazať skupinu',\n      deleteLabel: 'Zmazať štítok',\n      deleteList: 'Zmazať zoznam',\n      deleteList_title: 'Zmazať zoznam',\n      deleteNotificationService: 'Zmazať notifikačnú službu',\n      deleteProject: 'Zmazať projekt',\n      deleteProject_title: 'Zmazať projekt',\n      deleteTask: 'Zmazať úlohu',\n      deleteTaskList: 'Zmazať zoznam úloh',\n      deleteTask_title: 'Zmazať úlohu',\n      deleteUser: 'Zmazať používateľa',\n      deleteUser_title: 'Zmazať používateľa',\n      deleteWebhook: 'Zmazať webhook',\n      dismissAll: 'Zamietnuť všetko',\n      download: 'Stiahnuť',\n      duplicateCard_title: 'Duplikovať kartu',\n      edit: 'Upraviť',\n      editColor_title: 'Upraviť farbu',\n      editDescription_title: 'Upraviť popis',\n      editDueDate_title: 'Upraviť termín do',\n      editEmail_title: 'Upraviť e-mail',\n      editGroup: 'Upraviť skupinu',\n      editInformation_title: 'Upraviť informácie',\n      editPassword_title: 'Upraviť heslo',\n      editPermissions: 'Upraviť oprávnenia',\n      editRole_title: 'Upraviť rolu',\n      editStopwatch_title: 'Upraviť časovač',\n      editTitle_title: 'Upraviť názov',\n      editType_title: 'Upraviť typ',\n      editUsername_title: 'Upraviť používateľské meno',\n      emptyTrash: 'Vyprázdniť kôš',\n      emptyTrash_title: 'Vyprázdniť kôš',\n      import: 'Importovať',\n      join: 'Pripojiť sa',\n      leave: 'Opustiť',\n      leaveBoard: 'Opustiť tabuľu',\n      leaveProject: 'Opustiť projekt',\n      logOut_title: 'Odhlásiť sa',\n      makeCover_title: 'Vytvoriť obal',\n      makeProjectPrivate: 'Urobiť projekt súkromným',\n      makeProjectPrivate_title: 'Urobiť projekt súkromným',\n      makeProjectShared: 'Urobiť projekt zdieľaným',\n      makeProjectShared_title: 'Urobiť projekt zdieľaným',\n      move: 'Presunúť',\n      moveCard_title: 'Presunúť kartu',\n      moveList_title: 'Presunúť zoznam',\n      regenerateApiKey: 'Znovu vygenerovať API kľúč',\n      remove: 'Odstrániť',\n      removeAssignee: 'Odstrániť pridelenú osobu',\n      removeColor: 'Odstrániť farbu',\n      removeCover_title: 'Odstrániť obal',\n      removeFromBoard: 'Odstrániť z tabule',\n      removeFromProject: 'Odstrániť z projektu',\n      removeManager: 'Odstrániť správcu',\n      removeMember: 'Odstrániť člena',\n      restoreToList: 'Obnoviť do {{list}}',\n      returnToBoard: 'Vrátiť sa na tabuľu',\n      save: 'Uložiť',\n      sendTestEmail: 'Poslať testovací e-mail',\n      showActive: 'Zobraziť aktívne',\n      showAllAttachments: 'Zozbraziť všetky prílohy ({{hidden}} skryté)',\n      showCardsWithThisUser: 'Zobraziť karty s týmto používateľom',\n      showDeactivated: 'Zobraziť deaktivované',\n      showFewerAttachments: 'Zobraziť menej príloh',\n      showLess: 'Zobraziť menej',\n      showMore: 'Zobraziť viac',\n      sortList_title: 'Zoradiť zoznam',\n      start: 'Start',\n      stop: 'Stop',\n      subscribe: 'Odoberať',\n      unlinkSso: 'Odpojiť SSO',\n      unlinkSso_title: 'Odpojiť SSO',\n      unsubscribe: 'Neodoberať',\n      uploadNewAvatar: 'Nahrať nový avatar',\n      uploadNewImage: 'Nahrať nový obrázok',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/sk-SK/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'sk-SK',\n  country: 'sk',\n  name: 'Slovenčina',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/sk-SK/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Limit aktívnych používateľov bol dosiahnutý',\n      adminLoginRequiredToInitializeInstance:\n        'Na inicializáciu inštancie je potrebné prihlásenie správcu',\n      emailAlreadyInUse: 'E-mail je už použitý',\n      emailOrUsername: 'E-mail alebo používateľské meno',\n      invalidCredentials: 'Neplatné prihlasovacie údaje',\n      invalidEmailOrUsername: 'Nesprávny e-mail alebo používateľské meno',\n      invalidPassword: 'Nesprávne heslo',\n      logIn_title: 'Prihlásiť sa',\n      noInternetConnection: 'Bez pripojenia k internetu',\n      or: 'Alebo',\n      pageNotFound_title: 'Stránka neexistuje',\n      password: 'Heslo',\n      poweredByPlanka: 'Poháňané <1>PLANKA</1>',\n      serverConnectionFailed: 'Pripojenie k serveru zlyhalo',\n      unknownError: 'Neznáma chyba, skúste to neskôr',\n      useSingleSignOn: 'Použiť jednotné prihlásenie',\n      usernameAlreadyInUse: 'Používateľské meno je zabrané',\n      whoops_title: 'Ups!',\n    },\n\n    action: {\n      cancelAndClose: 'Zrušiť a zavrieť',\n      continue: 'Pokračovať',\n      debugSso: 'Ladiť SSO',\n      goBack: 'Ísť späť',\n      goHome: 'Ísť domov',\n      logIn: 'Prihlásiť sa',\n      logInWithSso: 'Prihlásiť sa cez SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/sk-SK/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Toto je text bez nadpisu.\\nNadpis aj text\\nmôžu byť zvýraznené tučným písmom, kurzívou, farbou,\\nprečiarknutím a podčiarknutím.\",\n    \"text-with-head\": \"Toto je text s nadpisom.\\nNadpis aj text\\nmôžu byť zvýraznené tučným písmom, kurzívou, farbou,\\nprečiarknutím a podčiarknutím.\",\n    \"heading\": \"Nadpis\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Chyba v markdown editore\",\n    \"settings_wysiwyg\": \"Vizuálny editor (wysiwyg)\",\n    \"settings_markup\": \"Markdown zápis\",\n    \"markup_placeholder\": \"Zadajte markdown zápis...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Odstrániť\",\n    \"empty_option\": \"Nenašli sa žiadne zhody\",\n    \"show_line_numbers\": \"Číslovanie riadkov\"\n  },\n  \"common\": {\n    \"delete\": \"Odstrániť\",\n    \"edit\": \"Upraviť\",\n    \"toolbar_action_disabled\": \"Nezlučiteľný prvok značkovania\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Zrušiť\",\n    \"common_action_submit\": \"Odoslať\",\n    \"common_action_upload\": \"Vybrať\",\n    \"common_tab_attach\": \"Pridať zo zariadenia\",\n    \"common_tab_link\": \"Pridať cez odkaz\",\n    \"common_link\": \"Odkaz\",\n    \"common_sizes\": \"Veľkosť, px\",\n    \"image_name\": \"Názov\",\n    \"image_link_href\": \"Odkaz na obrázok\",\n    \"image_link_href_help\": \"Adresa, na ktorú odkaz na obrázok vedie.\",\n    \"image_alt\": \"Alternatívny text\",\n    \"image_alt_help\": \"Alternatívny text sa zobrazí, ak sa obrázok nedá načítať.\",\n    \"image_upload_help\": \"Obrázok JPEG, GIF alebo PNG do 1 MB.\",\n    \"image_upload_failed\": \"Nepodarilo sa pridať obrázok\",\n    \"image_size_width\": \"Šírka\",\n    \"image_size_height\": \"Výška\",\n    \"link_url_help\": \"Adresa, na ktorú odkaz vedie.\",\n    \"link_text\": \"Text odkazu\",\n    \"link_text_help\": \"Text zobrazený ako odkaz.\",\n    \"link_open_help\": \"Otvoriť odkaz na novej karte\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Nadpis\",\n    \"header_hint\": \"# Váš text\",\n    \"italic_title\": \"Kurzíva\",\n    \"italic_hint\": \"_Váš text_\",\n    \"bold_title\": \"Tučné\",\n    \"bold_hint\": \"**Váš text**\",\n    \"strikethrough_title\": \"Prečiarknuté\",\n    \"strikethrough_hint\": \"~~Váš text~~\",\n    \"blockquote_title\": \"Citácia\",\n    \"blockquote_hint\": \"> Váš text\",\n    \"code_title\": \"Kód\",\n    \"code_hint\": \"```Váš text```\",\n    \"link_title\": \"Odkaz\",\n    \"link_hint\": \"[Váš text](url)\",\n    \"image_title\": \"Obrázok\",\n    \"image_hint\": \"![Váš text](url)\",\n    \"list_title\": \"Položka zoznamu\",\n    \"list_hint\": \"- Váš text\",\n    \"numbered-list_title\": \"Číslovaný zoznam\",\n    \"numbered-list_hint\": \"1. Váš text\",\n    \"documentation\": \"Dokumentácia\",\n    \"documentation_link\": \"https://diplodoc.com/docs/sk/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Tučné\",\n    \"code\": \"Kód\",\n    \"code_inline\": \"Vložený kód\",\n    \"codeblock\": \"Blok kódu\",\n    \"colorify\": \"Farba textu\",\n    \"colorify__color_blue\": \"Modrá\",\n    \"colorify__color_default\": \"Predvolená\",\n    \"colorify__color_gray\": \"Sivá\",\n    \"colorify__color_green\": \"Zelená\",\n    \"colorify__color_orange\": \"Oranžová\",\n    \"colorify__color_red\": \"Červená\",\n    \"colorify__color_violet\": \"Fialová\",\n    \"colorify__color_yellow\": \"Žltá\",\n    \"colorify__group_text\": \"Text\",\n    \"cut\": \"Vystrihnúť\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emoji je možné pridať vo WYSIWYG alebo manuálne pomocou značkovania\",\n    \"heading\": \"Nadpis\",\n    \"heading1\": \"Nadpis 1\",\n    \"heading2\": \"Nadpis 2\",\n    \"heading3\": \"Nadpis 3\",\n    \"heading4\": \"Nadpis 4\",\n    \"heading5\": \"Nadpis 5\",\n    \"heading6\": \"Nadpis 6\",\n    \"hrule\": \"Oddeľovač\",\n    \"image\": \"Obrázok\",\n    \"italic\": \"Kurzíva\",\n    \"link\": \"Odkaz\",\n    \"list\": \"Zoznam\",\n    \"list__action_lift\": \"Posunúť položku hore\",\n    \"list__action_sink\": \"Posunúť položku dole\",\n    \"list_action_disabled\": \"V rozpore s logikou zoznamu\",\n    \"mark\": \"Označené\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Viac akcií\",\n    \"note\": \"Poznámka\",\n    \"olist\": \"Číslovaný zoznam\",\n    \"quote\": \"Citácia\",\n    \"redo\": \"Znovu\",\n    \"strike\": \"Prečiarknuté\",\n    \"table\": \"Tabuľka\",\n    \"text\": \"Text\",\n    \"ulist\": \"Odrážkový zoznam\",\n    \"underline\": \"Podčiarknuté\",\n    \"undo\": \"Späť\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Napíšte / pre použitie príkazov...\",\n    \"checkbox\": \"Zadajte popis úlohy...\",\n    \"deflist_term\": \"Termín\",\n    \"deflist_desc\": \"Popis definície\",\n    \"heading\": \"Nadpis\",\n    \"cut_title\": \"Názov\",\n    \"cut_content\": \"Obsah zobrazený po kliknutí\",\n    \"note_title\": \"Názov\",\n    \"note_content\": \"Obsah poznámky\",\n    \"table_cell\": \"Obsah bunky\",\n    \"select_filter\": \"Vyhľadať jazyky...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Rozlišovať veľké/malé písmená\",\n    \"label_whole-word\": \"Celé slovo\",\n    \"title\": \"Hľadať v kóde\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Nič nenájdené\"\n  },\n  \"widgets\": {\n    \"image\": \"Pridať obrázok\",\n    \"link\": \"Pridať odkaz\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Poznámka\",\n    \"tip\": \"Tip\",\n    \"warning\": \"Upozornenie\",\n    \"alert\": \"Výstraha\",\n    \"remove\": \"Odstrániť\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Pridať stĺpec pred\",\n    \"column.add.after\": \"Pridať stĺpec za\",\n    \"column.remove\": \"Odstrániť stĺpec\",\n    \"column.remove.multiple\": \"Odstrániť stĺpce\",\n    \"row.add.before\": \"Pridať riadok pred\",\n    \"row.add.after\": \"Pridať riadok za\",\n    \"row.remove\": \"Odstrániť riadok\",\n    \"row.remove.multiple\": \"Odstrániť riadky\",\n    \"cells.clear\": \"Vymazať bunky\",\n    \"table.remove\": \"Odstrániť tabuľku\",\n    \"table.menu.cell.align.left\": \"Zarovnať obsah bunky vľavo\",\n    \"table.menu.cell.align.right\": \"Zarovnať obsah bunky vpravo\",\n    \"table.menu.cell.align.center\": \"Centrovať obsah bunky\",\n    \"table.menu.row.add\": \"Pridať riadok za\",\n    \"table.menu.row.remove\": \"Odstrániť riadok\",\n    \"table.menu.column.add\": \"Pridať stĺpec za\",\n    \"table.menu.column.remove\": \"Odstrániť stĺpec\",\n    \"table.menu.convert.yfm\": \"Konvertovať na YFM tabuľku\",\n    \"table.menu.table.remove\": \"Odstrániť tabuľku\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/sr-Cyrl-RS/core.js",
    "content": "import dateFns from 'date-fns/locale/sr';\nimport timeAgo from 'javascript-time-ago/locale/sr';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd.M.yyyy.',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd. MMM',\n    longDateTime: \"d. MMMM 'u' p\",\n    fullDate: 'd. MMM y',\n    fullDateTime: \"d. MMMM y 'u' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'О апликацији',\n      aboutPlanka_title: 'O PLANKA',\n      accessToken: 'Токен за приступ',\n      account: 'Налог',\n      actions: 'Радње',\n      activateUser_title: 'Активирај корисника',\n      active: 'Активан',\n      addAttachment_title: 'Додај прилог',\n      addCustomFieldGroup_title: 'Додај групу прилагођених поља',\n      addCustomField_title: 'Додај прилагођено поље',\n      addManager_title: 'Додај руководиоца',\n      addMember_title: 'Додај члана',\n      addTaskList_title: 'Додај листу задатака',\n      addUser_title: 'Додај корисника',\n      admin: 'Администратор',\n      administration: 'Администрација',\n      all: 'Све',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Све промене ће аутоматски бити сачуване<br />након успостављања конекције.',\n      alphabetically: 'Абецедно',\n      alwaysDisplayCardCreator: 'Увек прикажи творца картице',\n      apiKeyCreated_title: 'API кључ креиран',\n      apiKey_title: 'API кључ',\n      archive: 'Архива',\n      archiveCard_title: 'Архивирај картицу',\n      archiveCards_title: 'Архивирај картице',\n      areYouSureYouWantToActivateThisUser: 'Да ли заиста желите да активирате овог корисника?',\n      areYouSureYouWantToArchiveCards: 'Да ли заиста желите да архивирате картице?',\n      areYouSureYouWantToArchiveThisCard: 'Да ли заиста желите да архивирате ову картицу?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Да ли заиста желите да доделите овог руководиоца пројекта као власника?',\n      areYouSureYouWantToDeactivateThisUser: 'Да ли заиста желите да деактивирате овог корисника?',\n      areYouSureYouWantToDeleteThisApiKey: 'Да ли сте сигурни да желите да обришете овај API кључ?',\n      areYouSureYouWantToDeleteThisAttachment: 'Да ли заиста желите да обришете овај прилог?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Да ли заиста желите да обришете ову позадинску слику?',\n      areYouSureYouWantToDeleteThisBoard: 'Да ли заиста желите да обришете ову таблу?',\n      areYouSureYouWantToDeleteThisCard: 'Да ли заиста желите да обришете ову картицу?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Да ли заиста желите да обришете ову картицу заувек?',\n      areYouSureYouWantToDeleteThisComment: 'Да ли заиста желите да обришете овај коментар?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Да ли заиста желите да обришете ово прилагођено поље?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Да ли заиста желите да обришете ову групу прилагођених поља?',\n      areYouSureYouWantToDeleteThisLabel: 'Да ли заиста желите да обришете ову ознаку?',\n      areYouSureYouWantToDeleteThisList:\n        'Да ли заиста желите да обришете овај списак? Све картице ће бити премештене у корпу.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Да ли заиста желите да обришете ову услугу обавештења?',\n      areYouSureYouWantToDeleteThisProject: 'Да ли заиста желите да обришете овај пројекат?',\n      areYouSureYouWantToDeleteThisTask: 'Да ли заиста желите да обришете овај задатак?',\n      areYouSureYouWantToDeleteThisTaskList: 'Да ли заиста желите да обришете ову листу задатака?',\n      areYouSureYouWantToDeleteThisUser: 'Да ли заиста желите да обришете овог корисника?',\n      areYouSureYouWantToDeleteThisWebhook: 'Да ли заиста желите да обришете овај webhook?',\n      areYouSureYouWantToEmptyTrash: 'Да ли заиста желите да испразните корпу?',\n      areYouSureYouWantToLeaveBoard: 'Да ли заиста желите да напустите ову таблу?',\n      areYouSureYouWantToLeaveProject: 'Да ли заиста желите да напустите овај пројекат?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Да ли заиста желите да учините овај пројекат приватним?',\n      areYouSureYouWantToMakeThisProjectShared: 'Да ли заиста желите да поделите овај пројекат?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Да ли сте сигурни да желите да регенеришете овај API кључ? Претходни кључ више неће радити.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Да ли заиста желите да уклоните овог руководиоца из овог пројекта?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Да ли заиста желите да уклоните овог члана из ове табле?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Да ли сте сигурни да желите да одвојите SSO са овог корисника? Ово ће омогућити кориснику да се пријави помоћу лозинке.',\n      assignAsOwner_title: 'Додели као власника',\n      atLeastOneListMustBePresent: 'Мора постојати најмање једна листа',\n      attachment: 'Прилог',\n      attachments: 'Прилози',\n      authentication: 'Аутентификација',\n      background: 'Позадина',\n      baseCustomFields_title: 'Основна прилагођена поља',\n      baseGroup: 'Основна група',\n      board: 'Табла',\n      boardActions_title: 'Радње над таблом',\n      boardNotFound_title: 'Табла није пронађена',\n      boardSubscribed: 'Претплаћен на таблу',\n      boardUser: 'Корисник табле',\n      byCreationTime: 'По времену креирања',\n      byDefault: 'По подразумеваном',\n      byDueDate: 'По року доспећа',\n      canBeInvitedToWorkInBoards: 'Може бити позван да ради на таблама.',\n      canComment: 'Може да коментарише',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Може креирати сопствене пројекте и бити позван да ради на другима.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Може уређивати изглед табле и додељивати чланове картицама.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Може управљати системским подешавањима и деловати као власник пројекта.',\n      canOnlyViewBoard: 'Може само да прегледа таблу.',\n      cardActions_title: 'Радње над картицом',\n      cardNotFound_title: 'Картица није пронађена',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Картице на овој листи су доступне свим члановима табле.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Картице на овој листи су завршене и спремне за архивирање.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Картице на овој листи су спремне за рад.',\n      clickHereOrRefreshPageToUpdate: '<0>Кликните овде</0> или освежите страницу за ажурирање.',\n      clientHostnameInEhlo: 'Име хоста клијента у EHLO',\n      closed: 'Затворено',\n      color: 'Боја',\n      comments: 'Коментари',\n      contentExceedsLimit: 'Садржај превазилази {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay: 'Садржај овог прилога је превелик за приказ.',\n      copy_inline: 'копија',\n      createBoard_title: 'Направи таблу',\n      createCustomFieldGroup_title: 'Направи групу прилагођених поља',\n      createLabel_title: 'Направи ознаку',\n      createNewOneOrSelectExistingOne: 'Направи нову или изабери<br />постојећу.',\n      createProject_title: 'Направи пројекат',\n      createTextFile_title: 'Направи текстуалну датотеку',\n      creator: 'Творац',\n      currentPassword: 'Тренутна лозинка',\n      currentUser: 'Тренутни корисник',\n      customFieldGroup_title: 'Група прилагођених поља',\n      customFieldGroups_title: 'Групе прилагођених поља',\n      customField_title: 'Прилагођено поље',\n      customFields_title: 'Прилагођена поља',\n      customerPanel_title: 'Панел купца',\n      dangerZone_title: 'Опасна зона',\n      date: 'Датум',\n      deactivateUser_title: 'Деактивирај корисника',\n      defaultCardType_title: 'Подразумевани тип картице',\n      defaultFrom: 'Подразумевано од',\n      defaultView_title: 'Подразумевани приказ',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Обришите све табле да бисте могли да обришете овај пројекат',\n      deleteApiKey_title: 'Обриши API кључ',\n      deleteAttachment_title: 'Обриши прилог',\n      deleteBackgroundImage_title: 'Обриши позадинску слику',\n      deleteBoard_title: 'Обриши таблу',\n      deleteCardForever_title: 'Обриши картицу заувек',\n      deleteCard_title: 'Обриши картицу',\n      deleteComment_title: 'Обриши коментар',\n      deleteCustomFieldGroup_title: 'Обриши групу прилагођених поља',\n      deleteCustomField_title: 'Обриши прилагођено поље',\n      deleteLabel_title: 'Обриши ознаку',\n      deleteList_title: 'Обриши списак',\n      deleteNotificationService_title: 'Обриши услугу обавештења',\n      deleteProject_title: 'Обриши пројекат',\n      deleteTaskList_title: 'Обриши листу задатака',\n      deleteTask_title: 'Обриши задатак',\n      deleteUser_title: 'Обриши корисника',\n      deleteWebhook_title: 'Обриши webhook',\n      deletedUser_title: 'Обрисан корисник',\n      description: 'Опис',\n      display: 'Приказ',\n      displayCardAges: 'Прикажи старост картица',\n      dropFileToUpload: 'Превуци датотеку за слање',\n      dueDate_title: 'Рок',\n      dynamicAndUnevenlySpacedLayout: 'Динамички и неравномерно распоређен изглед.',\n      editAttachment_title: 'Уреди прилог',\n      editAvatar_title: 'Уреди аватара',\n      editColor_title: 'Уреди боју',\n      editCustomFieldGroup_title: 'Уреди групу прилагођених поља',\n      editCustomField_title: 'Уреди прилагођено поље',\n      editDueDate_title: 'Уреди рок',\n      editEmail_title: 'Уреди е-пошту',\n      editInformation_title: 'Уреди информације',\n      editLabel_title: 'Уреди ознаку',\n      editPassword_title: 'Измени лозинку',\n      editPermissions_title: 'Уреди овлашћења',\n      editRole_title: 'Уреди улогу',\n      editStopwatch_title: 'Уреди штоперицу',\n      editType_title: 'Уреди тип',\n      editUsername_title: 'Измени корисничко име',\n      editor: 'Уређивач',\n      editors: 'Уређивачи',\n      email: 'Е-пошта',\n      emptyTrash_title: 'Испразни корпу',\n      enterCardTitle: 'Унеси наслов картице...',\n      enterDescription: 'Унеси опис...',\n      enterFilename: 'Унеси назив датотеке',\n      enterListTitle: 'Унеси наслов списка...',\n      enterTaskDescription: 'Унеси опис задатка...',\n      events: 'Догађаји',\n      excludedEvents: 'Искључени догађаји',\n      expandTaskListsByDefault: 'Прошири листе задатака по подразумеваном',\n      filterByLabels_title: 'Филтрирај према ознакама',\n      filterByMembers_title: 'Филтрирај према члановима',\n      forPersonalProjects: 'За личне пројекте.',\n      forTeamBasedProjects: 'За тимске пројекте.',\n      fromComputer_title: 'Са рачунара',\n      fromTrello: 'Са Trello-а',\n      fullKeyIsHiddenForSecurityReasons:\n        'Комплетан кључ је сакривен из безбедносних разлога. Регенеришите га да бисте креирали нови.',\n      general: 'Опште',\n      gradients: 'Градијенти',\n      grid: 'Мрежа',\n      hideCompletedTasks: 'Сакриј завршене задатке',\n      hideFromProjectListAndFavorites: 'Сакриј из листе пројеката и омиљених',\n      host: 'Хост',\n      hours: 'Сати',\n      identity: 'Идентитет',\n      importBoard_title: 'Увези таблу',\n      information: 'Информације',\n      invalidCurrentPassword: 'Неисправна тренутна лозинка',\n      kanban: 'Канбан',\n      labels: 'Ознаке',\n      language: 'Језик',\n      leaveBoard_title: 'Напусти таблу',\n      leaveProject_title: 'Напусти пројекат',\n      limitCardTypesToDefaultOne: 'Ограничи типове картица на подразумевани',\n      linkToCard: 'Веза до картице',\n      list: 'Списак',\n      listActions_title: 'Радње над списком',\n      lists: 'Спискови',\n      makeProjectPrivate_title: 'Учини пројекат приватним',\n      makeProjectShared_title: 'Подели пројекат',\n      managers: 'Руководиоци',\n      memberActions_title: 'Радње над члановима',\n      members: 'Чланови',\n      minutes: 'Минути',\n      moreActions: 'Више радњи',\n      moreActions_title: 'Више радњи',\n      moveCard_title: 'Премести картицу',\n      moveList_title: 'Премести списак',\n      myOwn_title: 'Моји',\n      name: 'Име',\n      newEmail: 'Нова е-пошта',\n      newPassword: 'Нова лозинка',\n      newUsername: 'Ново корисничко име',\n      newVersionAvailable: 'Нова верзија је доступна',\n      newestFirst: 'Прво најновије',\n      noApiKeyCreated: 'API кључ није креиран.',\n      noBoards: 'Нема табли',\n      noCardsFound: 'Нема пронађених картица.',\n      noConnectionToServer: 'Нема конекције са сервером',\n      noLists: 'Нема спискова',\n      noProjects: 'Нема пројеката',\n      noUnreadNotifications: 'Нема непрочитаних обавештења.',\n      notifications: 'Обавештења',\n      oldestFirst: 'Прво најстарије',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Само један руководилац треба да остане да би овај пројекат постао приватан',\n      openBoard_title: 'Отвори таблу',\n      optional_inline: 'опционо',\n      organization: 'Организација',\n      others: 'Остали',\n      passwordIsSet: 'Лозинка је подешена',\n      phone: 'Телефон',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA користи <1><0>Apprise</0></1> за слање обавештења на преко 100 популарних сервиса.',\n      port: 'Порт',\n      preferences: 'Својства',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Савет: притисни Ctrl-V (Cmd-V на Меку) да би додао прилог са бележнице.',\n      private: 'Приватан',\n      project: 'Пројекат',\n      projectNotFound_title: 'Пројекат није пронађен',\n      projectOwner: 'Власник пројекта',\n      referenceDataAndKnowledgeStorage: 'Складиштење референтних података и знања.',\n      regenerateApiKey_title: 'Регенериши API кључ',\n      rejectUnauthorizedTlsCertificates: 'Одбаци неовлашћене TLS сертификате',\n      removeManager_title: 'Уклони руководиоца',\n      removeMember_title: 'Уклони члана',\n      role: 'Улога',\n      saveThisKeyItWillNotBeShownAgain: 'Сачувајте овај кључ — неће бити приказан поново!',\n      searchCards: 'Претражи картице...',\n      searchCustomFieldGroups: 'Претражи групе прилагођених поља...',\n      searchCustomFields: 'Претражи прилагођена поља...',\n      searchLabels: 'Претражи ознаке...',\n      searchLists: 'Претражи спискове...',\n      searchMembers: 'Претражи чланове...',\n      searchProjects: 'Претражи пројекте...',\n      searchUsers: 'Претражи кориснике...',\n      seconds: 'Секунде',\n      selectAssignee_title: 'Изабери извршиоца',\n      selectBoard: 'Изабери таблу',\n      selectList: 'Изабери списак',\n      selectListToRestoreThisCard: 'Изабери списак за враћање ове картице',\n      selectOrder_title: 'Изабери редослед',\n      selectPermissions_title: 'Изабери одобрења',\n      selectProject: 'Изабери пројекат',\n      selectRole_title: 'Изабери улогу',\n      selectType_title: 'Изабери тип',\n      sequentialDisplayOfCards: 'Секвенцијални приказ картица.',\n      settings: 'Подешавања',\n      shared: 'Подељено',\n      sharedWithMe_title: 'Подељено са мном',\n      showOnFrontOfCard: 'Прикажи на предњој страни картице',\n      smtp: 'SMTP',\n      sortList_title: 'Сложи списак',\n      sourceCardIsNoLongerAvailableForCopying: 'Изворна картица више није доступна за копирање.',\n      sourceCardIsNoLongerAvailableForMoving: 'Изворна картица више није доступна за премештање.',\n      stopwatch: 'Штоперица',\n      story: 'Прича',\n      subscribeToCardWhenCommenting: 'Претплати се на картицу при коментарисању',\n      subscribeToMyOwnCardsByDefault: 'Подразумевано се претплати на сопствене картице',\n      taskActions_title: 'Радње над задатком',\n      taskAssignmentAndProjectCompletion: 'Додељивање задатака и завршетак пројекта.',\n      taskListActions_title: 'Радње над листом задатака',\n      taskList_title: 'Листа задатака',\n      team: 'Тим',\n      termsOfService_title: 'Услови коришћења',\n      testLog_title: 'Тест дневник',\n      thereIsNoPreviewAvailableForThisAttachment: 'Нема прегледа доступног за овај прилог.',\n      time: 'Време',\n      title: 'Наслов',\n      trash: 'Корпа',\n      trashHasBeenSuccessfullyEmptied: 'Корпа је успешно испражњена.',\n      turnOffRecentCardHighlighting: 'Искључи истицање скорашњих картица',\n      typeNameToConfirm: 'Укуцај име за потврду.',\n      typeTitleToConfirm: 'Укуцај наслов за потврду.',\n      unlinkSso_title: 'Одвајање SSO',\n      unsavedChanges: 'Несачуване промене',\n      uploadFailedFileIsTooBig: 'Слање неуспешно: датотека је превелика.',\n      uploadFailedNotEnoughStorageSpace: 'Слање неуспешно: нема довољно простора за складиштење.',\n      uploadedImages: 'Послате слике',\n      url: 'URL',\n      useSecureConnection: 'Користи сигурну везу',\n      userActions_title: 'Корисничке радње',\n      userAddedCardToList: '<0>{{user}}</0> је додао <2>{{card}}</2> на {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> је додао ову картицу на {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> је додао {{addedUser}} на <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> је додао {{addedUser}} на ову картицу',\n      userAddedYouToCard: '<0>{{user}}</0> вас је додао на <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> је завршио {{task}} на <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> је завршио {{task}} на овој картици',\n      userJoinedCard: '<0>{{user}}</0> се придружио <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> се придружио овој картици',\n      userLeftCard: '<0>{{user}}</0> је напустио <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> је оставио нови коментар «{{comment}}» у <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> је напустио ову картицу',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> је означио {{task}} као незавршен на <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> је означио {{task}} као незавршен на овој картици',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> вас је поменуо у коментару «{{comment}}» на <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> је преместио <2>{{card}}</2> са {{fromList}} у {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> је преместио ову картицу са {{fromList}} на {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> је уклонио {{removedUser}} са <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> је уклонио {{removedUser}} са ове картице',\n      username: 'Корисничко име',\n      users: 'Корисници',\n      viewer: 'Прегледач',\n      viewers: 'Прегледачи',\n      visualTaskManagementWithLists: 'Визуелно управљање задацима са списковима.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Шта је ново',\n      withoutBaseGroup: 'Без основне групе',\n      writeComment: 'Напиши коментар...',\n    },\n\n    action: {\n      activateUser: 'Активирај корисника',\n      activateUser_title: 'Активирај корисника',\n      addAnotherCard: 'Додај још једну картицу',\n      addAnotherList: 'Додај још један списак',\n      addAnotherTask: 'Додај још један задатак',\n      addCard: 'Додај картицу',\n      addCard_title: 'Додај картицу',\n      addComment: 'Додај коментар',\n      addCustomField: 'Додај прилагођено поље',\n      addCustomFieldGroup: 'Додај групу прилагођених поља',\n      addList: 'Додај списак',\n      addMember: 'Додај члана',\n      addMoreDetailedDescription: 'Додај детаљнији опис',\n      addTask: 'Додај задатак',\n      addTaskList: 'Додај листу задатака',\n      addToCard: 'Додај на картицу',\n      addUser: 'Додај корисника',\n      addWebhook: 'Додај webhook',\n      archive: 'Архивирај',\n      archiveCard: 'Архивирај картицу',\n      archiveCard_title: 'Архивирај картицу',\n      archiveCards: 'Архивирај картице',\n      archiveCards_title: 'Архивирај картице',\n      assignAsOwner: 'Додели као власника',\n      cancel: 'Откажи',\n      copy: 'Копирај',\n      copyCard_title: 'Копирај картицу',\n      createApiKey: 'Креирај API кључ',\n      createBoard: 'Направи таблу',\n      createCustomFieldGroup: 'Направи групу прилагођених поља',\n      createFile: 'Направи датотеку',\n      createLabel: 'Направи ознаку',\n      createNewLabel: 'Направи нову ознаку',\n      createProject: 'Направи пројекат',\n      cut: 'Исеци',\n      cutCard_title: 'Исеци картицу',\n      deactivateUser: 'Деактивирај корисника',\n      deactivateUser_title: 'Деактивирај корисника',\n      delete: 'Обриши',\n      deleteApiKey: 'Обриши API кључ',\n      deleteAttachment: 'Обриши прилог',\n      deleteAvatar: 'Обриши аватара',\n      deleteBackgroundImage: 'Обриши позадинску слику',\n      deleteBoard: 'Обриши таблу',\n      deleteBoard_title: 'Обриши таблу',\n      deleteCard: 'Обриши картицу',\n      deleteCardForever: 'Обриши картицу заувек',\n      deleteCard_title: 'Обриши картицу',\n      deleteComment: 'Обриши коментар',\n      deleteCustomField: 'Обриши прилагођено поље',\n      deleteCustomFieldGroup: 'Обриши групу прилагођених поља',\n      deleteForever_title: 'Обриши заувек',\n      deleteGroup: 'Обриши групу',\n      deleteLabel: 'Обриши ознаку',\n      deleteList: 'Обриши списак',\n      deleteList_title: 'Обриши списак',\n      deleteNotificationService: 'Обриши услугу обавештења',\n      deleteProject: 'Обриши пројекат',\n      deleteProject_title: 'Обриши пројекат',\n      deleteTask: 'Обриши задатак',\n      deleteTaskList: 'Обриши листу задатака',\n      deleteTask_title: 'Обриши задатак',\n      deleteUser: 'Обриши корисника',\n      deleteUser_title: 'Обриши корисника',\n      deleteWebhook: 'Обриши webhook',\n      dismissAll: 'Одбаци све',\n      download: 'Преузми',\n      duplicateCard_title: 'Клонирај картицу',\n      edit: 'Измени',\n      editColor_title: 'Уреди боју',\n      editDescription_title: 'Измени опис',\n      editDueDate_title: 'Измени рок',\n      editEmail_title: 'Измени е-пошту',\n      editGroup: 'Уреди групу',\n      editInformation_title: 'Измени информације',\n      editPassword_title: 'Измени лозинку',\n      editPermissions: 'Измени одобрења',\n      editRole_title: 'Уреди улогу',\n      editStopwatch_title: 'Измени штоперицу',\n      editTitle_title: 'Измени наслов',\n      editType_title: 'Уреди тип',\n      editUsername_title: 'Измени корисничко име',\n      emptyTrash: 'Испразни корпу',\n      emptyTrash_title: 'Испразни корпу',\n      import: 'Увези',\n      join: 'Придружи се',\n      leave: 'Напусти',\n      leaveBoard: 'Напусти таблу',\n      leaveProject: 'Напусти пројекат',\n      logOut_title: 'Одјава',\n      makeCover_title: 'Направи омот',\n      makeProjectPrivate: 'Учини пројекат приватним',\n      makeProjectPrivate_title: 'Учини пројекат приватним',\n      makeProjectShared: 'Подели пројекат',\n      makeProjectShared_title: 'Подели пројекат',\n      move: 'Премести',\n      moveCard_title: 'Премести картицу',\n      moveList_title: 'Премести списак',\n      regenerateApiKey: 'Регенериши API кључ',\n      remove: 'Уклони',\n      removeAssignee: 'Уклони извршиоца',\n      removeColor: 'Уклони боју',\n      removeCover_title: 'Уклони омот',\n      removeFromBoard: 'Уклони са табле',\n      removeFromProject: 'Уклони из пројекта',\n      removeManager: 'Уклони руководиоца',\n      removeMember: 'Уклони члана',\n      restoreToList: 'Врати у {{list}}',\n      returnToBoard: 'Врати се на таблу',\n      save: 'Сачувај',\n      sendTestEmail: 'Пошаљи тест е-пошту',\n      showActive: 'Прикажи активне',\n      showAllAttachments: 'Прикажи све ({{hidden}} сакривене прилоге)',\n      showCardsWithThisUser: 'Прикажи картице са овим корисником',\n      showDeactivated: 'Прикажи деактивиране',\n      showFewerAttachments: 'Прикажи мање прилога',\n      showLess: 'Прикажи мање',\n      showMore: 'Прикажи више',\n      sortList_title: 'Сложи списак',\n      start: 'Почни',\n      stop: 'Заустави',\n      subscribe: 'Претплати се',\n      unlinkSso: 'Одвоји SSO',\n      unlinkSso_title: 'Одвоји SSO',\n      unsubscribe: 'Укини претплату',\n      uploadNewAvatar: 'Постави нови аватар',\n      uploadNewImage: 'Постави нову слику',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/sr-Cyrl-RS/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'sr-Cyrl-RS',\n  country: 'rs',\n  name: 'Српски (ћирилица)',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/sr-Cyrl-RS/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Достигнут је лимит активних корисника',\n      adminLoginRequiredToInitializeInstance:\n        'Потребна је администраторска пријава за иницијализацију инстанце',\n      emailAlreadyInUse: 'Е-пошта је већ у употреби',\n      emailOrUsername: 'Е-пошта или корисничко име',\n      invalidCredentials: 'Неисправни акредитиви',\n      invalidEmailOrUsername: 'Неисправна е-пошта или корисничко име',\n      invalidPassword: 'Неисправна лозинка',\n      logIn_title: 'Пријава',\n      noInternetConnection: 'Нема конекције са интернетом',\n      or: 'Или',\n      pageNotFound_title: 'Страница није пронађена',\n      password: 'Лозинка',\n      poweredByPlanka: 'Покреће <1>PLANKA</1>',\n      serverConnectionFailed: 'Неуспешна конекција са сервером',\n      unknownError: 'Непозната грешка, покушајте поново касније',\n      useSingleSignOn: 'Користи универзалну пријаву',\n      usernameAlreadyInUse: 'Корисничко име је већ у употреби',\n      whoops_title: 'Упс!',\n    },\n\n    action: {\n      cancelAndClose: 'Откажи и затвори',\n      continue: 'Настави',\n      debugSso: 'Дебагуј SSO',\n      goBack: 'Назад',\n      goHome: 'Иди кући',\n      logIn: 'Пријава',\n      logInWithSso: 'Пријава са УП',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/sr-Cyrl-RS/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Ово је текст без наслова.\\nИ наслов и текст\\nмогу бити истакнути подебљано, курзивом, бојом,\\nпрецртано и подвучено.\",\n    \"text-with-head\": \"Ово је текст са насловом.\\nИ наслов и текст\\nмогу бити истакнути подебљано, курзивом, бојом,\\nпрецртано и подвучено.\",\n    \"heading\": \"Наслов\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Грешка у markdown едитору\",\n    \"settings_wysiwyg\": \"Визуелни едитор (wysiwyg)\",\n    \"settings_markup\": \"Markdown ознаке\",\n    \"markup_placeholder\": \"Унесите markdown ознаке...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Уклони\",\n    \"empty_option\": \"Нема пронађених подударања\",\n    \"show_line_numbers\": \"Нумерација редова\"\n  },\n  \"common\": {\n    \"delete\": \"Уклони\",\n    \"edit\": \"Уреди\",\n    \"toolbar_action_disabled\": \"Неусаглашен елемент ознаке\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Откажи\",\n    \"common_action_submit\": \"Пошаљи\",\n    \"common_action_upload\": \"Изабери\",\n    \"common_tab_attach\": \"Додај са уређаја\",\n    \"common_tab_link\": \"Додај преко линка\",\n    \"common_link\": \"Линк\",\n    \"common_sizes\": \"Величина, px\",\n    \"image_name\": \"Наслов\",\n    \"image_link_href\": \"Линк слике\",\n    \"image_link_href_help\": \"Адреса на коју води линк слике.\",\n    \"image_alt\": \"Алтернативни текст\",\n    \"image_alt_help\": \"Алтернативни текст се приказује ако слика не може бити учитана.\",\n    \"image_upload_help\": \"JPEG, GIF или PNG слика до 1 MB.\",\n    \"image_upload_failed\": \"Неуспешно додавање слике\",\n    \"image_size_width\": \"Ширина\",\n    \"image_size_height\": \"Висина\",\n    \"link_url_help\": \"Адреса на коју води линк.\",\n    \"link_text\": \"Текст линка\",\n    \"link_text_help\": \"Текст који се приказује као линk.\",\n    \"link_open_help\": \"Отвори линк у новом табу\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Заглавље\",\n    \"header_hint\": \"# Ваш текст\",\n    \"italic_title\": \"Курзив\",\n    \"italic_hint\": \"_Ваш текст_\",\n    \"bold_title\": \"Подебљано\",\n    \"bold_hint\": \"**Ваш текст**\",\n    \"strikethrough_title\": \"Прецртано\",\n    \"strikethrough_hint\": \"~~Ваш текст~~\",\n    \"blockquote_title\": \"Цитат\",\n    \"blockquote_hint\": \"> Ваш текст\",\n    \"code_title\": \"Код\",\n    \"code_hint\": \"```Ваш текст```\",\n    \"link_title\": \"Линк\",\n    \"link_hint\": \"[Ваш текст](url)\",\n    \"image_title\": \"Слика\",\n    \"image_hint\": \"![Ваш текст](url)\",\n    \"list_title\": \"Ставка листе\",\n    \"list_hint\": \"- Ваш текст\",\n    \"numbered-list_title\": \"Нумерисана листа\",\n    \"numbered-list_hint\": \"1. Ваш текст\",\n    \"documentation\": \"Документација\",\n    \"documentation_link\": \"https://diplodoc.com/docs/sr/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Подебљано\",\n    \"code\": \"Код\",\n    \"code_inline\": \"Инлајн код\",\n    \"codeblock\": \"Блок кода\",\n    \"colorify\": \"Боја текста\",\n    \"colorify__color_blue\": \"Плава\",\n    \"colorify__color_default\": \"Подразумевано\",\n    \"colorify__color_gray\": \"Сива\",\n    \"colorify__color_green\": \"Зелена\",\n    \"colorify__color_orange\": \"Наранџаста\",\n    \"colorify__color_red\": \"Црвена\",\n    \"colorify__color_violet\": \"Љубичаста\",\n    \"colorify__color_yellow\": \"Жута\",\n    \"colorify__group_text\": \"Текст\",\n    \"cut\": \"Исеци\",\n    \"emoji\": \"Емоџи\",\n    \"emoji__hint\": \"Емоџи се могу додати у WYSIWYG или ручно преко ознака\",\n    \"heading\": \"Заглавље\",\n    \"heading1\": \"Заглавље 1\",\n    \"heading2\": \"Заглавље 2\",\n    \"heading3\": \"Заглавље 3\",\n    \"heading4\": \"Заглавље 4\",\n    \"heading5\": \"Заглавље 5\",\n    \"heading6\": \"Заглавље 6\",\n    \"hrule\": \"Раздвајач\",\n    \"image\": \"Слика\",\n    \"italic\": \"Курзив\",\n    \"link\": \"Линк\",\n    \"list\": \"Листа\",\n    \"list__action_lift\": \"Подигни ставку\",\n    \"list__action_sink\": \"Спусти ставку\",\n    \"list_action_disabled\": \"Супротно логици листе\",\n    \"mark\": \"Означено\",\n    \"mono\": \"Моноспацед\",\n    \"more_action\": \"Више радњи\",\n    \"note\": \"Белешка\",\n    \"olist\": \"Нумерисана листа\",\n    \"quote\": \"Цитат\",\n    \"redo\": \"Понови\",\n    \"strike\": \"Прецртано\",\n    \"table\": \"Табела\",\n    \"text\": \"Текст\",\n    \"ulist\": \"Маркирана листа\",\n    \"underline\": \"Подвучено\",\n    \"undo\": \"Опозови\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Укуцајте / за коришћење команди...\",\n    \"checkbox\": \"Унесите опис задатка...\",\n    \"deflist_term\": \"Термин\",\n    \"deflist_desc\": \"Опис дефиниције\",\n    \"heading\": \"Заглавље\",\n    \"cut_title\": \"Наслов\",\n    \"cut_content\": \"Садржај који се приказује на клик\",\n    \"note_title\": \"Наслов\",\n    \"note_content\": \"Садржај белешке\",\n    \"table_cell\": \"Садржај ћелије\",\n    \"select_filter\": \"Претражи језике...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Разликуј велика/мала слова\",\n    \"label_whole-word\": \"Цела реч\",\n    \"title\": \"Претражи у коду\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Није пронађено\"\n  },\n  \"widgets\": {\n    \"image\": \"Додај слику\",\n    \"link\": \"Додај линк\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Белешка\",\n    \"tip\": \"Савет\",\n    \"warning\": \"Упозорење\",\n    \"alert\": \"Аларм\",\n    \"remove\": \"Уклони\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Додај колону пре\",\n    \"column.add.after\": \"Додај колону после\",\n    \"column.remove\": \"Уклони колону\",\n    \"column.remove.multiple\": \"Уклони колоне\",\n    \"row.add.before\": \"Додај ред пре\",\n    \"row.add.after\": \"Додај ред после\",\n    \"row.remove\": \"Уклони ред\",\n    \"row.remove.multiple\": \"Уклони редове\",\n    \"cells.clear\": \"Очисти ћелије\",\n    \"table.remove\": \"Уклони табелу\",\n    \"table.menu.cell.align.left\": \"Поравнај садржај ћелије лево\",\n    \"table.menu.cell.align.right\": \"Поравнај садржај ћелије десно\",\n    \"table.menu.cell.align.center\": \"Центрирај садржај ћелије\",\n    \"table.menu.row.add\": \"Додај ред после\",\n    \"table.menu.row.remove\": \"Уклони ред\",\n    \"table.menu.column.add\": \"Додај колону после\",\n    \"table.menu.column.remove\": \"Уклони колону\",\n    \"table.menu.convert.yfm\": \"Конвертуј у YFM табелу\",\n    \"table.menu.table.remove\": \"Уклони табелу\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/sr-Latn-RS/core.js",
    "content": "import dateFns from 'date-fns/locale/sr-Latn';\nimport timeAgo from 'javascript-time-ago/locale/sr-Latn';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd.M.yyyy.',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd. MMM',\n    longDateTime: \"d. MMMM 'u' p\",\n    fullDate: 'd. MMM y',\n    fullDateTime: \"d. MMMM y 'u' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'O aplikaciji',\n      aboutPlanka_title: 'O PLANKA',\n      accessToken: 'Token za pristup',\n      account: 'Nalog',\n      actions: 'Radnje',\n      activateUser_title: 'Aktiviraj korisnika',\n      active: 'Aktivan',\n      addAttachment_title: 'Dodaj prilog',\n      addCustomFieldGroup_title: 'Dodaj grupu prilagođenih polja',\n      addCustomField_title: 'Dodaj prilagođeno polje',\n      addManager_title: 'Dodaj rukovodioca',\n      addMember_title: 'Dodaj člana',\n      addTaskList_title: 'Dodaj listu zadataka',\n      addUser_title: 'Dodaj korisnika',\n      admin: 'Administrator',\n      administration: 'Administracija',\n      all: 'Sve',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Sve promene će automatski biti sačuvane<br />nakon uspostavljanja konekcije.',\n      alphabetically: 'Abecedno',\n      alwaysDisplayCardCreator: 'Uvek prikaži tvorca kartice',\n      apiKeyCreated_title: 'API ključ kreiran',\n      apiKey_title: 'API ključ',\n      archive: 'Arhiva',\n      archiveCard_title: 'Arhiviraj karticu',\n      archiveCards_title: 'Arhiviraj kartice',\n      areYouSureYouWantToActivateThisUser: 'Da li zaista želite da aktivirate ovog korisnika?',\n      areYouSureYouWantToArchiveCards: 'Da li zaista želite da arhivirate kartice?',\n      areYouSureYouWantToArchiveThisCard: 'Da li zaista želite da arhivirate ovu karticu?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Da li zaista želite da dodelite ovog rukovodioca projekta kao vlasnika?',\n      areYouSureYouWantToDeactivateThisUser: 'Da li zaista želite da deaktivirate ovog korisnika?',\n      areYouSureYouWantToDeleteThisApiKey:\n        'Da li ste sigurni da želite da obrišete ovaj API ključ?',\n      areYouSureYouWantToDeleteThisAttachment: 'Da li zaista želite da obrišete ovaj prilog?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Da li zaista želite da obrišete ovu pozadinsku sliku?',\n      areYouSureYouWantToDeleteThisBoard: 'Da li zaista želite da obrišete ovu tablu?',\n      areYouSureYouWantToDeleteThisCard: 'Da li zaista želite da obrišete ovu karticu?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Da li zaista želite da obrišete ovu karticu zauvek?',\n      areYouSureYouWantToDeleteThisComment: 'Da li zaista želite da obrišete ovaj komentar?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Da li zaista želite da obrišete ovo prilagođeno polje?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Da li zaista želite da obrišete ovu grupu prilagođenih polja?',\n      areYouSureYouWantToDeleteThisLabel: 'Da li zaista želite da obrišete ovu oznaku?',\n      areYouSureYouWantToDeleteThisList:\n        'Da li zaista želite da obrišete ovaj spisak? Sve kartice će biti premeštene u korpu.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Da li zaista želite da obrišete ovu uslugu obaveštenja?',\n      areYouSureYouWantToDeleteThisProject: 'Da li zaista želite da obrišete ovaj projekat?',\n      areYouSureYouWantToDeleteThisTask: 'Da li zaista želite da obrišete ovaj zadatak?',\n      areYouSureYouWantToDeleteThisTaskList: 'Da li zaista želite da obrišete ovu listu zadataka?',\n      areYouSureYouWantToDeleteThisUser: 'Da li zaista želite da obrišete ovog korisnika?',\n      areYouSureYouWantToDeleteThisWebhook: 'Da li zaista želite da obrišete ovaj webhook?',\n      areYouSureYouWantToEmptyTrash: 'Da li zaista želite da ispraznite korpu?',\n      areYouSureYouWantToLeaveBoard: 'Da li zaista želite da napustite ovu tablu?',\n      areYouSureYouWantToLeaveProject: 'Da li zaista želite da napustite ovaj projekat?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Da li zaista želite da učinite ovaj projekat privatnim?',\n      areYouSureYouWantToMakeThisProjectShared: 'Da li zaista želite da podelite ovaj projekat?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Da li ste sigurni da želite da regenerišete ovaj API ključ? Prethodni ključ više neće raditi.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Da li zaista želite da uklonite ovog rukovodioca iz ovog projekta?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Da li zaista želite da uklonite ovog člana iz ove table?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Da li ste sigurni da želite da odvojite SSO sa ovog korisnika? Ovo će omogućiti korisniku da se prijavi pomoću lozinke.',\n      assignAsOwner_title: 'Dodeli kao vlasnika',\n      atLeastOneListMustBePresent: 'Mora postojati najmanje jedna lista',\n      attachment: 'Prilog',\n      attachments: 'Prilozi',\n      authentication: 'Autentifikacija',\n      background: 'Pozadina',\n      baseCustomFields_title: 'Osnovna prilagođena polja',\n      baseGroup: 'Osnovna grupa',\n      board: 'Tabla',\n      boardActions_title: 'Radnje nad tablom',\n      boardNotFound_title: 'Tabla nije pronađena',\n      boardSubscribed: 'Pretplaćen na tablu',\n      boardUser: 'Korisnik table',\n      byCreationTime: 'Po vremenu kreiranja',\n      byDefault: 'Po podrazumevanom',\n      byDueDate: 'Po roku dospeća',\n      canBeInvitedToWorkInBoards: 'Može biti pozvan da radi na tablama.',\n      canComment: 'Može da komentariše',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Može kreirati sopstvene projekte i biti pozvan da radi na drugima.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Može uređivati izgled table i dodeljivati članove karticama.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Može upravljati sistemskim podešavanjima i delovati kao vlasnik projekta.',\n      canOnlyViewBoard: 'Može samo da pregleda tablu.',\n      cardActions_title: 'Radnje nad karticom',\n      cardNotFound_title: 'Kartica nije pronađena',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Kartice na ovoj listi su dostupne svim članovima table.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Kartice na ovoj listi su završene i spremne za arhiviranje.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Kartice na ovoj listi su spremne za rad.',\n      clickHereOrRefreshPageToUpdate: '<0>Kliknite ovde</0> ili osvežite stranicu za ažuriranje.',\n      clientHostnameInEhlo: 'Ime hosta klijenta u EHLO',\n      closed: 'Zatvoreno',\n      color: 'Boja',\n      comments: 'Komentari',\n      contentExceedsLimit: 'Sadržaj prevazilazi {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay: 'Sadržaj ovog priloga je prevelik za prikaz.',\n      copy_inline: 'kopija',\n      createBoard_title: 'Napravi tablu',\n      createCustomFieldGroup_title: 'Napravi grupu prilagođenih polja',\n      createLabel_title: 'Napravi oznaku',\n      createNewOneOrSelectExistingOne: 'Napravi novu ili izaberi<br />postojeću.',\n      createProject_title: 'Napravi projekat',\n      createTextFile_title: 'Napravi tekstualnu datoteku',\n      creator: 'Tvorac',\n      currentPassword: 'Trenutna lozinka',\n      currentUser: 'Trenutni korisnik',\n      customFieldGroup_title: 'Grupa prilagođenih polja',\n      customFieldGroups_title: 'Grupe prilagođenih polja',\n      customField_title: 'Prilagođeno polje',\n      customFields_title: 'Prilagođena polja',\n      customerPanel_title: 'Panel kupca',\n      dangerZone_title: 'Opasna zona',\n      date: 'Datum',\n      deactivateUser_title: 'Deaktiviraj korisnika',\n      defaultCardType_title: 'Podrazumevani tip kartice',\n      defaultFrom: 'Podrazumevano od',\n      defaultView_title: 'Podrazumevani prikaz',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Obrišite sve table da biste mogli da obrišete ovaj projekat',\n      deleteApiKey_title: 'Obriši API ključ',\n      deleteAttachment_title: 'Obriši prilog',\n      deleteBackgroundImage_title: 'Obriši pozadinsku sliku',\n      deleteBoard_title: 'Obriši tablu',\n      deleteCardForever_title: 'Obriši karticu zauvek',\n      deleteCard_title: 'Obriši karticu',\n      deleteComment_title: 'Obriši komentar',\n      deleteCustomFieldGroup_title: 'Obriši grupu prilagođenih polja',\n      deleteCustomField_title: 'Obriši prilagođeno polje',\n      deleteLabel_title: 'Obriši oznaku',\n      deleteList_title: 'Obriši spisak',\n      deleteNotificationService_title: 'Obriši uslugu obaveštenja',\n      deleteProject_title: 'Obriši projekat',\n      deleteTaskList_title: 'Obriši listu zadataka',\n      deleteTask_title: 'Obriši zadatak',\n      deleteUser_title: 'Obriši korisnika',\n      deleteWebhook_title: 'Obriši webhook',\n      deletedUser_title: 'Obrisan korisnik',\n      description: 'Opis',\n      display: 'Prikaz',\n      displayCardAges: 'Prikaži starost kartica',\n      dropFileToUpload: 'Prevuci datoteku za slanje',\n      dueDate_title: 'Rok',\n      dynamicAndUnevenlySpacedLayout: 'Dinamički i neravnomerno raspoređen izgled.',\n      editAttachment_title: 'Uredi prilog',\n      editAvatar_title: 'Uredi avatara',\n      editColor_title: 'Uredi boju',\n      editCustomFieldGroup_title: 'Uredi grupu prilagođenih polja',\n      editCustomField_title: 'Uredi prilagođeno polje',\n      editDueDate_title: 'Uredi rok',\n      editEmail_title: 'Uredi e-poštu',\n      editInformation_title: 'Uredi informacije',\n      editLabel_title: 'Uredi oznaku',\n      editPassword_title: 'Izmeni lozinku',\n      editPermissions_title: 'Uredi ovlašćenja',\n      editRole_title: 'Uredi ulogu',\n      editStopwatch_title: 'Uredi štopericu',\n      editType_title: 'Uredi tip',\n      editUsername_title: 'Izmeni korisničko ime',\n      editor: 'Uređivač',\n      editors: 'Uređivači',\n      email: 'E-pošta',\n      emptyTrash_title: 'Isprazni korpu',\n      enterCardTitle: 'Unesi naslov kartice...',\n      enterDescription: 'Unesi opis...',\n      enterFilename: 'Unesi naziv datoteke',\n      enterListTitle: 'Unesi naslov spiska...',\n      enterTaskDescription: 'Unesi opis zadatka...',\n      events: 'Događaji',\n      excludedEvents: 'Isključeni događaji',\n      expandTaskListsByDefault: 'Proširi liste zadataka po podrazumevanom',\n      filterByLabels_title: 'Filtriraj prema oznakama',\n      filterByMembers_title: 'Filtriraj prema članovima',\n      forPersonalProjects: 'Za lične projekte.',\n      forTeamBasedProjects: 'Za timske projekte.',\n      fromComputer_title: 'Sa računara',\n      fromTrello: 'Sa Trello-a',\n      fullKeyIsHiddenForSecurityReasons:\n        'Kompletan ključ je sakriven iz bezbednosnih razloga. Regenerišite ga da biste kreirali novi.',\n      general: 'Opšte',\n      gradients: 'Gradijenti',\n      grid: 'Mreža',\n      hideCompletedTasks: 'Sakrij završene zadatke',\n      hideFromProjectListAndFavorites: 'Sakrij iz liste projekata i omiljenih',\n      host: 'Host',\n      hours: 'Sati',\n      identity: 'Identitet',\n      importBoard_title: 'Uvezi tablu',\n      information: 'Informacije',\n      invalidCurrentPassword: 'Neispravna trenutna lozinka',\n      kanban: 'Kanban',\n      labels: 'Oznake',\n      language: 'Jezik',\n      leaveBoard_title: 'Napusti tablu',\n      leaveProject_title: 'Napusti projekat',\n      limitCardTypesToDefaultOne: 'Ograniči tipove kartica na podrazumevani',\n      linkToCard: 'Veza do kartice',\n      list: 'Spisak',\n      listActions_title: 'Radnje nad spiskom',\n      lists: 'Spiskovi',\n      makeProjectPrivate_title: 'Učini projekat privatnim',\n      makeProjectShared_title: 'Podeli projekat',\n      managers: 'Rukovodioci',\n      memberActions_title: 'Radnje nad članovima',\n      members: 'Članovi',\n      minutes: 'Minuti',\n      moreActions: 'Više radnji',\n      moreActions_title: 'Više radnji',\n      moveCard_title: 'Premesti karticu',\n      moveList_title: 'Premesti spisak',\n      myOwn_title: 'Moji',\n      name: 'Ime',\n      newEmail: 'Nova e-pošta',\n      newPassword: 'Nova lozinka',\n      newUsername: 'Novo korisničko ime',\n      newVersionAvailable: 'Nova verzija je dostupna',\n      newestFirst: 'Prvo najnovije',\n      noApiKeyCreated: 'API ključ nije kreiran.',\n      noBoards: 'Nema tabli',\n      noCardsFound: 'Nema pronađenih kartica.',\n      noConnectionToServer: 'Nema konekcije sa serverom',\n      noLists: 'Nema spiskova',\n      noProjects: 'Nema projekata',\n      noUnreadNotifications: 'Nema nepročitanih obaveštenja.',\n      notifications: 'Obaveštenja',\n      oldestFirst: 'Prvo najstarije',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Samo jedan rukovodilac treba da ostane da bi ovaj projekat postao privatan',\n      openBoard_title: 'Otvori tablu',\n      optional_inline: 'opciono',\n      organization: 'Organizacija',\n      others: 'Ostali',\n      passwordIsSet: 'Lozinka je podešena',\n      phone: 'Telefon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA koristi <1><0>Apprise</0></1> za slanje obaveštenja na preko 100 popularnih servisa.',\n      port: 'Port',\n      preferences: 'Svojstva',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Savet: pritisni Ctrl-V (Cmd-V na Meku) da bi dodao prilog sa beležnice.',\n      private: 'Privatan',\n      project: 'Projekat',\n      projectNotFound_title: 'Projekat nije pronađen',\n      projectOwner: 'Vlasnik projekta',\n      referenceDataAndKnowledgeStorage: 'Skladištenje referentnih podataka i znanja.',\n      regenerateApiKey_title: 'Regeneriši API ključ',\n      rejectUnauthorizedTlsCertificates: 'Odbaci neovlašćene TLS sertifikate',\n      removeManager_title: 'Ukloni rukovodioca',\n      removeMember_title: 'Ukloni člana',\n      role: 'Uloga',\n      saveThisKeyItWillNotBeShownAgain: 'Sačuvajte ovaj ključ — neće biti prikazan ponovo!',\n      searchCards: 'Pretraži kartice...',\n      searchCustomFieldGroups: 'Pretraži grupe prilagođenih polja...',\n      searchCustomFields: 'Pretraži prilagođena polja...',\n      searchLabels: 'Pretraži oznake...',\n      searchLists: 'Pretraži spiskove...',\n      searchMembers: 'Pretraži članove...',\n      searchProjects: 'Pretraži projekte...',\n      searchUsers: 'Pretraži korisnike...',\n      seconds: 'Sekunde',\n      selectAssignee_title: 'Izaberi izvršioca',\n      selectBoard: 'Izaberi tablu',\n      selectList: 'Izaberi spisak',\n      selectListToRestoreThisCard: 'Izaberi spisak za vraćanje ove kartice',\n      selectOrder_title: 'Izaberi redosled',\n      selectPermissions_title: 'Izaberi odobrenja',\n      selectProject: 'Izaberi projekat',\n      selectRole_title: 'Izaberi ulogu',\n      selectType_title: 'Izaberi tip',\n      sequentialDisplayOfCards: 'Sekvencijalni prikaz kartica.',\n      settings: 'Podešavanja',\n      shared: 'Podeljeno',\n      sharedWithMe_title: 'Podeljeno sa mnom',\n      showOnFrontOfCard: 'Prikaži na prednjoj strani kartice',\n      smtp: 'SMTP',\n      sortList_title: 'Složi spisak',\n      sourceCardIsNoLongerAvailableForCopying: 'Izvorna kartica više nije dostupna za kopiranje.',\n      sourceCardIsNoLongerAvailableForMoving: 'Izvorna kartica više nije dostupna za premeštanje.',\n      stopwatch: 'Štoperica',\n      story: 'Priča',\n      subscribeToCardWhenCommenting: 'Pretplati se na karticu pri komentarisanju',\n      subscribeToMyOwnCardsByDefault: 'Podrazumevano se pretplati na sopstvene kartice',\n      taskActions_title: 'Radnje nad zadatkom',\n      taskAssignmentAndProjectCompletion: 'Dodeljivanje zadataka i završetak projekta.',\n      taskListActions_title: 'Radnje nad listom zadataka',\n      taskList_title: 'Lista zadataka',\n      team: 'Tim',\n      termsOfService_title: 'Uslovi korišćenja',\n      testLog_title: 'Test dnevnik',\n      thereIsNoPreviewAvailableForThisAttachment: 'Nema pregleda dostupnog za ovaj prilog.',\n      time: 'Vreme',\n      title: 'Naslov',\n      trash: 'Korpa',\n      trashHasBeenSuccessfullyEmptied: 'Korpa je uspešno ispražnjena.',\n      turnOffRecentCardHighlighting: 'Isključi isticanje skorašnjih kartica',\n      typeNameToConfirm: 'Ukucaj ime za potvrdu.',\n      typeTitleToConfirm: 'Ukucaj naslov za potvrdu.',\n      unlinkSso_title: 'Odvajanje SSO',\n      unsavedChanges: 'Nesačuvane promene',\n      uploadFailedFileIsTooBig: 'Slanje neuspešno: datoteka je prevelika.',\n      uploadFailedNotEnoughStorageSpace:\n        'Slanje neuspešno: nema dovoljno prostora za skladištenje.',\n      uploadedImages: 'Poslate slike',\n      url: 'URL',\n      useSecureConnection: 'Koristi sigurnu vezu',\n      userActions_title: 'Korisničke radnje',\n      userAddedCardToList: '<0>{{user}}</0> je dodao <2>{{card}}</2> na {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> je dodao ovu karticu na {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> je dodao {{addedUser}} na <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> je dodao {{addedUser}} na ovu karticu',\n      userAddedYouToCard: '<0>{{user}}</0> vas je dodao na <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> je završio {{task}} na <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> je završio {{task}} na ovoj kartici',\n      userJoinedCard: '<0>{{user}}</0> se pridružio <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> se pridružio ovoj kartici',\n      userLeftCard: '<0>{{user}}</0> je napustio <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> je ostavio novi komentar «{{comment}}» u <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> je napustio ovu karticu',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> je označio {{task}} kao nezavršen na <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> je označio {{task}} kao nezavršen na ovoj kartici',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> vas je pomenuo u komentaru «{{comment}}» na <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> je premestio <2>{{card}}</2> sa {{fromList}} u {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> je premestio ovu karticu sa {{fromList}} na {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> je uklonio {{removedUser}} sa <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> je uklonio {{removedUser}} sa ove kartice',\n      username: 'Korisničko ime',\n      users: 'Korisnici',\n      viewer: 'Pregledač',\n      viewers: 'Pregledači',\n      visualTaskManagementWithLists: 'Vizuelno upravljanje zadacima sa spiskovima.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Šta je novo',\n      withoutBaseGroup: 'Bez osnovne grupe',\n      writeComment: 'Napiši komentar...',\n    },\n\n    action: {\n      activateUser: 'Aktiviraj korisnika',\n      activateUser_title: 'Aktiviraj korisnika',\n      addAnotherCard: 'Dodaj još jednu karticu',\n      addAnotherList: 'Dodaj još jedan spisak',\n      addAnotherTask: 'Dodaj još jedan zadatak',\n      addCard: 'Dodaj karticu',\n      addCard_title: 'Dodaj karticu',\n      addComment: 'Dodaj komentar',\n      addCustomField: 'Dodaj prilagođeno polje',\n      addCustomFieldGroup: 'Dodaj grupu prilagođenih polja',\n      addList: 'Dodaj spisak',\n      addMember: 'Dodaj člana',\n      addMoreDetailedDescription: 'Dodaj detaljniji opis',\n      addTask: 'Dodaj zadatak',\n      addTaskList: 'Dodaj listu zadataka',\n      addToCard: 'Dodaj na karticu',\n      addUser: 'Dodaj korisnika',\n      addWebhook: 'Dodaj webhook',\n      archive: 'Arhiviraj',\n      archiveCard: 'Arhiviraj karticu',\n      archiveCard_title: 'Arhiviraj karticu',\n      archiveCards: 'Arhiviraj kartice',\n      archiveCards_title: 'Arhiviraj kartice',\n      assignAsOwner: 'Dodeli kao vlasnika',\n      cancel: 'Otkaži',\n      copy: 'Kopiraj',\n      copyCard_title: 'Kopiraj karticu',\n      createApiKey: 'Kreiraj API ključ',\n      createBoard: 'Napravi tablu',\n      createCustomFieldGroup: 'Napravi grupu prilagođenih polja',\n      createFile: 'Napravi datoteku',\n      createLabel: 'Napravi oznaku',\n      createNewLabel: 'Napravi novu oznaku',\n      createProject: 'Napravi projekat',\n      cut: 'Iseci',\n      cutCard_title: 'Iseci karticu',\n      deactivateUser: 'Deaktiviraj korisnika',\n      deactivateUser_title: 'Deaktiviraj korisnika',\n      delete: 'Obriši',\n      deleteApiKey: 'Obriši API ključ',\n      deleteAttachment: 'Obriši prilog',\n      deleteAvatar: 'Obriši avatara',\n      deleteBackgroundImage: 'Obriši pozadinsku sliku',\n      deleteBoard: 'Obriši tablu',\n      deleteBoard_title: 'Obriši tablu',\n      deleteCard: 'Obriši karticu',\n      deleteCardForever: 'Obriši karticu zauvek',\n      deleteCard_title: 'Obriši karticu',\n      deleteComment: 'Obriši komentar',\n      deleteCustomField: 'Obriši prilagođeno polje',\n      deleteCustomFieldGroup: 'Obriši grupu prilagođenih polja',\n      deleteForever_title: 'Obriši zauvek',\n      deleteGroup: 'Obriši grupu',\n      deleteLabel: 'Obriši oznaku',\n      deleteList: 'Obriši spisak',\n      deleteList_title: 'Obriši spisak',\n      deleteNotificationService: 'Obriši uslugu obaveštenja',\n      deleteProject: 'Obriši projekat',\n      deleteProject_title: 'Obriši projekat',\n      deleteTask: 'Obriši zadatak',\n      deleteTaskList: 'Obriši listu zadataka',\n      deleteTask_title: 'Obriši zadatak',\n      deleteUser: 'Obriši korisnika',\n      deleteUser_title: 'Obriši korisnika',\n      deleteWebhook: 'Obriši webhook',\n      dismissAll: 'Odbaci sve',\n      download: 'Preuzmi',\n      duplicateCard_title: 'Kloniraj karticu',\n      edit: 'Izmeni',\n      editColor_title: 'Uredi boju',\n      editDescription_title: 'Izmeni opis',\n      editDueDate_title: 'Izmeni rok',\n      editEmail_title: 'Izmeni e-poštu',\n      editGroup: 'Uredi grupu',\n      editInformation_title: 'Izmeni informacije',\n      editPassword_title: 'Izmeni lozinku',\n      editPermissions: 'Izmeni odobrenja',\n      editRole_title: 'Uredi ulogu',\n      editStopwatch_title: 'Izmeni štopericu',\n      editTitle_title: 'Izmeni naslov',\n      editType_title: 'Uredi tip',\n      editUsername_title: 'Izmeni korisničko ime',\n      emptyTrash: 'Isprazni korpu',\n      emptyTrash_title: 'Isprazni korpu',\n      import: 'Uvezi',\n      join: 'Pridruži se',\n      leave: 'Napusti',\n      leaveBoard: 'Napusti tablu',\n      leaveProject: 'Napusti projekat',\n      logOut_title: 'Odjava',\n      makeCover_title: 'Napravi omot',\n      makeProjectPrivate: 'Učini projekat privatnim',\n      makeProjectPrivate_title: 'Učini projekat privatnim',\n      makeProjectShared: 'Podeli projekat',\n      makeProjectShared_title: 'Podeli projekat',\n      move: 'Premesti',\n      moveCard_title: 'Premesti karticu',\n      moveList_title: 'Premesti spisak',\n      regenerateApiKey: 'Regeneriši API ključ',\n      remove: 'Ukloni',\n      removeAssignee: 'Ukloni izvršioca',\n      removeColor: 'Ukloni boju',\n      removeCover_title: 'Ukloni omot',\n      removeFromBoard: 'Ukloni sa table',\n      removeFromProject: 'Ukloni iz projekta',\n      removeManager: 'Ukloni rukovodioca',\n      removeMember: 'Ukloni člana',\n      restoreToList: 'Vrati u {{list}}',\n      returnToBoard: 'Vrati se na tablu',\n      save: 'Sačuvaj',\n      sendTestEmail: 'Pošalji test e-poštu',\n      showActive: 'Prikaži aktivne',\n      showAllAttachments: 'Prikaži sve ({{hidden}} sakrivene priloge)',\n      showCardsWithThisUser: 'Prikaži kartice sa ovim korisnikom',\n      showDeactivated: 'Prikaži deaktivirane',\n      showFewerAttachments: 'Prikaži manje priloga',\n      showLess: 'Prikaži manje',\n      showMore: 'Prikaži više',\n      sortList_title: 'Složi spisak',\n      start: 'Počni',\n      stop: 'Zaustavi',\n      subscribe: 'Pretplati se',\n      unlinkSso: 'Odvoji SSO',\n      unlinkSso_title: 'Odvoji SSO',\n      unsubscribe: 'Ukini pretplatu',\n      uploadNewAvatar: 'Postavi novi avatar',\n      uploadNewImage: 'Postavi novu sliku',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/sr-Latn-RS/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'sr-Latn-RS',\n  country: 'rs',\n  name: 'Srpski (latinica)',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/sr-Latn-RS/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Dostignut je limit aktivnih korisnika',\n      adminLoginRequiredToInitializeInstance:\n        'Potrebna je administratorska prijava za inicijalizaciju instance',\n      emailAlreadyInUse: 'E-pošta je već u upotrebi',\n      emailOrUsername: 'E-pošta ili korisničko ime',\n      invalidCredentials: 'Neispravni akreditivi',\n      invalidEmailOrUsername: 'Neispravna e-pošta ili korisničko ime',\n      invalidPassword: 'Neispravna lozinka',\n      logIn_title: 'Prijava',\n      noInternetConnection: 'Nema konekcije sa internetom',\n      or: 'Ili',\n      pageNotFound_title: 'Stranica nije pronađena',\n      password: 'Lozinka',\n      poweredByPlanka: 'Pokreće <1>PLANKA</1>',\n      serverConnectionFailed: 'Neuspešna konekcija sa serverom',\n      unknownError: 'Nepoznata greška, pokušajte ponovo kasnije',\n      useSingleSignOn: 'Koristi univerzalnu prijavu',\n      usernameAlreadyInUse: 'Korisničko ime je već u upotrebi',\n      whoops_title: 'Ups!',\n    },\n\n    action: {\n      cancelAndClose: 'Otkaži i zatvori',\n      continue: 'Nastavi',\n      debugSso: 'Debaguj SSO',\n      goBack: 'Nazad',\n      goHome: 'Idi kući',\n      logIn: 'Prijava',\n      logInWithSso: 'Prijava sa UP',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/sr-Latn-RS/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Ovo je tekst bez naslova.\\nI naslov i tekst\\nmogу biti istaknuti podebljanim, kurzivom, bojom,\\nprecrtan i podvučen.\",\n    \"text-with-head\": \"Ovo je tekst sa naslovom.\\nI naslov i tekst\\nmogу biti istaknuti podebljanim, kurzivom, bojom,\\nprecrtan i podvučen.\",\n    \"heading\": \"Naslov\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Greška u markdown editoru\",\n    \"settings_wysiwyg\": \"Vizuelni editor (wysiwyg)\",\n    \"settings_markup\": \"Markdown označavanje\",\n    \"markup_placeholder\": \"Unesite markdown označavanje...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Ukloni\",\n    \"empty_option\": \"Nema pronađenih poklapanja\",\n    \"show_line_numbers\": \"Numeracija redova\"\n  },\n  \"common\": {\n    \"delete\": \"Obriši\",\n    \"edit\": \"Uredi\",\n    \"toolbar_action_disabled\": \"Nekompatibilan element označavanja\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Otkaži\",\n    \"common_action_submit\": \"Pošalji\",\n    \"common_action_upload\": \"Izaberi\",\n    \"common_tab_attach\": \"Dodaj sa uređaja\",\n    \"common_tab_link\": \"Dodaj putem linka\",\n    \"common_link\": \"Link\",\n    \"common_sizes\": \"Veličina, px\",\n    \"image_name\": \"Naslov\",\n    \"image_link_href\": \"Link slike\",\n    \"image_link_href_help\": \"Adresa na koju vodi link slike.\",\n    \"image_alt\": \"Alt tekst\",\n    \"image_alt_help\": \"Alt tekst se prikazuje ako slika ne može biti učitana.\",\n    \"image_upload_help\": \"JPEG, GIF ili PNG slika ne veća od 1 MB.\",\n    \"image_upload_failed\": \"Neuspešno dodavanje slike\",\n    \"image_size_width\": \"Širina\",\n    \"image_size_height\": \"Visina\",\n    \"link_url_help\": \"Adresa na koju vodi link.\",\n    \"link_text\": \"Tekst linka\",\n    \"link_text_help\": \"Tekst prikazan kao link.\",\n    \"link_open_help\": \"Otvori link u novom tabu\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Zaglavlje\",\n    \"header_hint\": \"# Vaš tekst\",\n    \"italic_title\": \"Kurziv\",\n    \"italic_hint\": \"_Vaš tekst_\",\n    \"bold_title\": \"Podebljano\",\n    \"bold_hint\": \"**Vaš tekst**\",\n    \"strikethrough_title\": \"Precrtano\",\n    \"strikethrough_hint\": \"~~Vaš tekst~~\",\n    \"blockquote_title\": \"Citat\",\n    \"blockquote_hint\": \"> Vaš tekst\",\n    \"code_title\": \"Kod\",\n    \"code_hint\": \"```Vaš tekst```\",\n    \"link_title\": \"Link\",\n    \"link_hint\": \"[Vaš tekst](url)\",\n    \"image_title\": \"Slika\",\n    \"image_hint\": \"![Vaš tekst](url)\",\n    \"list_title\": \"Stavka liste\",\n    \"list_hint\": \"- Vaš tekst\",\n    \"numbered-list_title\": \"Numerisana lista\",\n    \"numbered-list_hint\": \"1. Vaš tekst\",\n    \"documentation\": \"Dokumentacija\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Podebljano\",\n    \"code\": \"Kod\",\n    \"code_inline\": \"Ugrađeni kod\",\n    \"codeblock\": \"Blok koda\",\n    \"colorify\": \"Boja teksta\",\n    \"colorify__color_blue\": \"Plava\",\n    \"colorify__color_default\": \"Podrazumevano\",\n    \"colorify__color_gray\": \"Siva\",\n    \"colorify__color_green\": \"Zelena\",\n    \"colorify__color_orange\": \"Narandžasta\",\n    \"colorify__color_red\": \"Crvena\",\n    \"colorify__color_violet\": \"Ljubičasta\",\n    \"colorify__color_yellow\": \"Žuta\",\n    \"colorify__group_text\": \"Tekst\",\n    \"cut\": \"Iseci\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emoji se mogu dodati u WYSIWYG ili ručno sa označavanjem\",\n    \"heading\": \"Naslov\",\n    \"heading1\": \"Naslov 1\",\n    \"heading2\": \"Naslov 2\",\n    \"heading3\": \"Naslov 3\",\n    \"heading4\": \"Naslov 4\",\n    \"heading5\": \"Naslov 5\",\n    \"heading6\": \"Naslov 6\",\n    \"hrule\": \"Separator\",\n    \"image\": \"Slika\",\n    \"italic\": \"Kurziv\",\n    \"link\": \"Link\",\n    \"list\": \"Lista\",\n    \"list__action_lift\": \"Podigni stavku\",\n    \"list__action_sink\": \"Spusti stavku\",\n    \"list_action_disabled\": \"Protivreči logici liste\",\n    \"mark\": \"Označeno\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Više akcija\",\n    \"note\": \"Beleška\",\n    \"olist\": \"Uređena lista\",\n    \"quote\": \"Citat\",\n    \"redo\": \"Ponovi\",\n    \"strike\": \"Precrtano\",\n    \"table\": \"Tabela\",\n    \"text\": \"Tekst\",\n    \"ulist\": \"Lista sa tačkama\",\n    \"underline\": \"Podvučeno\",\n    \"undo\": \"Opozovi\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Kucajte / da koristite slash komande...\",\n    \"checkbox\": \"Unesite opis zadatka...\",\n    \"deflist_term\": \"Termin\",\n    \"deflist_desc\": \"Opis definicije\",\n    \"heading\": \"Naslov\",\n    \"cut_title\": \"Naslov\",\n    \"cut_content\": \"Sadržaj za prikaz na klik\",\n    \"note_title\": \"Naslov\",\n    \"note_content\": \"Sadržaj beleške\",\n    \"table_cell\": \"Sadržaj ćelije\",\n    \"select_filter\": \"Pretraži jezike...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Osetljivo na velika/mala slova\",\n    \"label_whole-word\": \"Cela reč\",\n    \"title\": \"Pretraži u kodu\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Nije pronađeno\"\n  },\n  \"widgets\": {\n    \"image\": \"Dodaj sliku\",\n    \"link\": \"Dodaj link\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Informacija\",\n    \"tip\": \"Savet\",\n    \"warning\": \"Upozorenje\",\n    \"alert\": \"Uzbuna\",\n    \"remove\": \"Ukloni\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Dodaj kolonu pre\",\n    \"column.add.after\": \"Dodaj kolonu posle\",\n    \"column.remove\": \"Ukloni kolonu\",\n    \"column.remove.multiple\": \"Ukloni kolone\",\n    \"row.add.before\": \"Dodaj red pre\",\n    \"row.add.after\": \"Dodaj red posle\",\n    \"row.remove\": \"Ukloni red\",\n    \"row.remove.multiple\": \"Ukloni redove\",\n    \"cells.clear\": \"Očisti ćelije\",\n    \"table.remove\": \"Ukloni tabelu\",\n    \"table.menu.cell.align.left\": \"Poravnaj sadržaj ćelije levo\",\n    \"table.menu.cell.align.right\": \"Poravnaj sadržaj ćelije desno\",\n    \"table.menu.cell.align.center\": \"Poravnaj sadržaj ćelije u centar\",\n    \"table.menu.row.add\": \"Dodaj red posle\",\n    \"table.menu.row.remove\": \"Ukloni red\",\n    \"table.menu.column.add\": \"Dodaj kolonu posle\",\n    \"table.menu.column.remove\": \"Ukloni kolonu\",\n    \"table.menu.convert.yfm\": \"Konvertuj u YFM tabelu\",\n    \"table.menu.table.remove\": \"Ukloni tabelu\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/sv-SE/core.js",
    "content": "import dateFns from 'date-fns/locale/sv';\nimport timeAgo from 'javascript-time-ago/locale/sv';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd/M/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'MMM d',\n    longDateTime: \"MMMM d 'at' p\",\n    fullDate: 'MMM d, y',\n    fullDateTime: \"MMMM d, y 'at' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Om appen',\n      aboutPlanka_title: 'Om PLANKA',\n      accessToken: 'Åtkomsttoken',\n      account: 'Konto',\n      actions: 'Åtgärder',\n      activateUser_title: 'Aktivera användare',\n      active: 'Aktiv',\n      addAttachment_title: 'Bifoga',\n      addCustomFieldGroup_title: 'Lägg till anpassad fältgrupp',\n      addCustomField_title: 'Lägg till anpassat fält',\n      addManager_title: 'Lägg till projektledare',\n      addMember_title: 'Lägg till medlem',\n      addTaskList_title: 'Lägg till uppgiftslista',\n      addUser_title: 'Lägg till användare',\n      admin: 'Admin',\n      administration: 'Administration',\n      all: 'Alla',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Alla ändringar kommer att sparas automatiskt<br />så fort anslutningen är återställd.',\n      alphabetically: 'Alfabetiskt',\n      alwaysDisplayCardCreator: 'Visa alltid kortskapare',\n      apiKeyCreated_title: 'API-nyckel skapad',\n      apiKey_title: 'API-nyckel',\n      archive: 'Arkiv',\n      archiveCard_title: 'Arkivera kort',\n      archiveCards_title: 'Arkivera kort',\n      areYouSureYouWantToActivateThisUser:\n        'Är du säker på att du vill aktivera den här användaren?',\n      areYouSureYouWantToArchiveCards: 'Är du säker på att du vill arkivera kort?',\n      areYouSureYouWantToArchiveThisCard: 'Är du säker på att du vill arkivera det här kortet?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Är du säker på att du vill tilldela den här projektledaren som ägare?',\n      areYouSureYouWantToDeactivateThisUser:\n        'Är du säker på att du vill inaktivera den här användaren?',\n      areYouSureYouWantToDeleteThisApiKey: 'Är du säker på att du vill ta bort denna API-nyckel?',\n      areYouSureYouWantToDeleteThisAttachment:\n        'Är du säker på att du vill ta bort den här bilagan?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Är du säker på att du vill ta bort den här bakgrundsbilden?',\n      areYouSureYouWantToDeleteThisBoard: 'Är du säker på att du vill ta bort den här tavlan?',\n      areYouSureYouWantToDeleteThisCard: 'Är du säker på att du vill ta bort det här kortet?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Är du säker på att du vill ta bort det här kortet för alltid?',\n      areYouSureYouWantToDeleteThisComment:\n        'Är du säker på att du vill ta bort den här kommentaren?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Är du säker på att du vill ta bort det här anpassade fältet?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Är du säker på att du vill ta bort den här anpassade fältgruppen?',\n      areYouSureYouWantToDeleteThisLabel: 'Är du säker på att du vill ta bort den här etiketten?',\n      areYouSureYouWantToDeleteThisList:\n        'Är du säker på att du vill ta bort den här listan? Alla kort kommer att flyttas till papperskorgen.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Är du säker på att du vill ta bort den här aviseringstjänsten?',\n      areYouSureYouWantToDeleteThisProject: 'Är du säker på att du vill ta bort det här projektet?',\n      areYouSureYouWantToDeleteThisTask: 'Är du säker på att du vill ta bort den här uppgiften?',\n      areYouSureYouWantToDeleteThisTaskList:\n        'Är du säker på att du vill ta bort den här uppgiftslistan?',\n      areYouSureYouWantToDeleteThisUser: 'Är du säker på att du vill ta bort den här användaren?',\n      areYouSureYouWantToDeleteThisWebhook: 'Är du säker på att du vill ta bort den här webhooken?',\n      areYouSureYouWantToEmptyTrash: 'Är du säker på att du vill tömma papperskorgen?',\n      areYouSureYouWantToLeaveBoard: 'Är du säker på att du vill lämna tavlan?',\n      areYouSureYouWantToLeaveProject: 'Är du säker på att du vill lämna projektet?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Är du säker på att du vill göra det här projektet privat?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Är du säker på att du vill dela det här projektet?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Är du säker på att du vill regenerera denna API-nyckel? Den tidigare nyckeln kommer inte längre att fungera.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Är du säker på att du vill ta bort den här projektledaren från projektet?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Är du säker på att du vill ta bort den här medlemmen från tavlan?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Är du säker på att du vill avlänka SSO från den här användaren? Detta kommer att tillåta användaren att logga in med ett lösenord.',\n      assignAsOwner_title: 'Tilldela som ägare',\n      atLeastOneListMustBePresent: 'Minst en lista måste finnas',\n      attachment: 'Bilaga',\n      attachments: 'Bilagor',\n      authentication: 'Autentisering',\n      background: 'Bakgrund',\n      baseCustomFields_title: 'Grundläggande anpassade fält',\n      baseGroup: 'Basgrupp',\n      board: 'Tavla',\n      boardActions_title: 'Tavlåtgärder',\n      boardNotFound_title: 'Tavla hittades inte',\n      boardSubscribed: 'Tavla prenumererad',\n      boardUser: 'Tavlanvändare',\n      byCreationTime: 'Efter skapandetid',\n      byDefault: 'Som standard',\n      byDueDate: 'Efter förfallodatum',\n      canBeInvitedToWorkInBoards: 'Kan bjudas in att arbeta i tavlor.',\n      canComment: 'Kan kommentera',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Kan skapa egna projekt och bjudas in att arbeta i andras.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Kan redigera tavllayout och tilldela medlemmar till kort.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Kan hantera systemomfattande inställningar och agera som projektägare.',\n      canOnlyViewBoard: 'Kan endast visa tavla.',\n      cardActions_title: 'Kortåtgärder',\n      cardNotFound_title: 'Kort hittades inte',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Kort på den här listan är tillgängliga för alla tavlmedlemmar.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Kort på den här listan är färdiga och redo att arkiveras.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Kort på den här listan är redo att arbetas på.',\n      clickHereOrRefreshPageToUpdate: '<0>Klicka här</0> eller uppdatera sidan för att uppdatera.',\n      clientHostnameInEhlo: 'Klientvärdnamn i EHLO',\n      closed: 'Stängd',\n      color: 'Färg',\n      comments: 'Kommentarer',\n      contentExceedsLimit: 'Innehållet överskrider {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Innehållet i den här bilagan är för stort för att visas.',\n      copy_inline: 'kopia',\n      createBoard_title: 'Skapa tavla',\n      createCustomFieldGroup_title: 'Skapa anpassad fältgrupp',\n      createLabel_title: 'Skapa etikett',\n      createNewOneOrSelectExistingOne: 'Skapa en ny eller välj<br />en redan existerande.',\n      createProject_title: 'Skapa projekt',\n      createTextFile_title: 'Skapa textfil',\n      creator: 'Skapare',\n      currentPassword: 'Nuvarande lösenord',\n      currentUser: 'Nuvarande användare',\n      customFieldGroup_title: 'Anpassad fältgrupp',\n      customFieldGroups_title: 'Anpassade fältgrupper',\n      customField_title: 'Anpassat fält',\n      customFields_title: 'Anpassade fält',\n      customerPanel_title: 'Kundpanel',\n      dangerZone_title: 'Farozon',\n      date: 'Datum',\n      deactivateUser_title: 'Inaktivera användare',\n      defaultCardType_title: 'Standardkorttyp',\n      defaultFrom: 'Standard från',\n      defaultView_title: 'Standardvy',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Ta bort alla tavlor för att kunna ta bort det här projektet',\n      deleteApiKey_title: 'Ta bort API-nyckel',\n      deleteAttachment_title: 'Ta bort bilaga',\n      deleteBackgroundImage_title: 'Ta bort bakgrundsbild',\n      deleteBoard_title: 'Ta bort tavla',\n      deleteCardForever_title: 'Ta bort kort för alltid',\n      deleteCard_title: 'Ta bort kort',\n      deleteComment_title: 'Ta bort kommentar',\n      deleteCustomFieldGroup_title: 'Ta bort anpassad fältgrupp',\n      deleteCustomField_title: 'Ta bort anpassat fält',\n      deleteLabel_title: 'Ta bort etikett',\n      deleteList_title: 'Ta bort lista',\n      deleteNotificationService_title: 'Ta bort aviseringstjänst',\n      deleteProject_title: 'Ta bort projekt',\n      deleteTaskList_title: 'Ta bort uppgiftslista',\n      deleteTask_title: 'Ta bort uppgift',\n      deleteUser_title: 'Ta bort användare',\n      deleteWebhook_title: 'Ta bort webhook',\n      deletedUser_title: 'Borttagen användare',\n      description: 'Beskrivning',\n      display: 'Visa',\n      displayCardAges: 'Visa kortålder',\n      dropFileToUpload: 'Släpp en fil för att ladda upp',\n      dueDate_title: 'Förfallodatum',\n      dynamicAndUnevenlySpacedLayout: 'Dynamisk och ojämnt fördelad layout.',\n      editAttachment_title: 'Redigera bilaga',\n      editAvatar_title: 'Redigera avatar',\n      editColor_title: 'Redigera färg',\n      editCustomFieldGroup_title: 'Redigera anpassad fältgrupp',\n      editCustomField_title: 'Redigera anpassat fält',\n      editDueDate_title: 'Redigera förfallodatum',\n      editEmail_title: 'Redigera e-mail',\n      editInformation_title: 'Redigera information',\n      editLabel_title: 'Redigera etikett',\n      editPassword_title: 'Redigera lösenord',\n      editPermissions_title: 'Redigera behörigheter',\n      editRole_title: 'Redigera roll',\n      editStopwatch_title: 'Redigera timer',\n      editType_title: 'Redigera typ',\n      editUsername_title: 'Redigera användarnamn',\n      editor: 'Redigerare',\n      editors: 'Redigerare',\n      email: 'E-mail',\n      emptyTrash_title: 'Töm papperskorg',\n      enterCardTitle: 'Ange kortets titel...',\n      enterDescription: 'Ange beskrivning...',\n      enterFilename: 'Ange filnamn',\n      enterListTitle: 'Ange listans titel...',\n      enterTaskDescription: 'Ange uppgiftsbeskrivning...',\n      events: 'Händelser',\n      excludedEvents: 'Uteslutna händelser',\n      expandTaskListsByDefault: 'Expandera uppgiftslistor som standard',\n      filterByLabels_title: 'Filtrera efter etiketter',\n      filterByMembers_title: 'Filtrera efter medlemmar',\n      forPersonalProjects: 'För personliga projekt.',\n      forTeamBasedProjects: 'För teambaserade projekt.',\n      fromComputer_title: 'Från dator',\n      fromTrello: 'Från Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Den fullständiga nyckeln är dold av säkerhetsskäl. Regenerera den för att skapa en ny.',\n      general: 'Allmänt',\n      gradients: 'Gradienter',\n      grid: 'Rutnät',\n      hideCompletedTasks: 'Dölj slutförda uppgifter',\n      hideFromProjectListAndFavorites: 'Dölj från projektlista och favoriter',\n      host: 'Värd',\n      hours: 'Timmar',\n      identity: 'Identitet',\n      importBoard_title: 'Importera tavla',\n      information: 'Information',\n      invalidCurrentPassword: 'Ogiltigt nuvarande lösenord',\n      kanban: 'Kanban',\n      labels: 'Etiketter',\n      language: 'Språk',\n      leaveBoard_title: 'Lämna tavla',\n      leaveProject_title: 'Lämna projekt',\n      limitCardTypesToDefaultOne: 'Begränsa korttyper till standardtyp',\n      linkToCard: 'Länk till kort',\n      list: 'Lista',\n      listActions_title: 'Liståtgärder',\n      lists: 'Listor',\n      makeProjectPrivate_title: 'Gör projekt privat',\n      makeProjectShared_title: 'Dela projekt',\n      managers: 'Projektledare',\n      memberActions_title: 'Medlemsåtgärder',\n      members: 'Medlemmar',\n      minutes: 'Minuter',\n      moreActions: 'Fler åtgärder',\n      moreActions_title: 'Fler åtgärder',\n      moveCard_title: 'Flytta kort',\n      moveList_title: 'Flytta lista',\n      myOwn_title: 'Mina egna',\n      name: 'Namn',\n      newEmail: 'Ny e-mail',\n      newPassword: 'Nytt lösenord',\n      newUsername: 'Nytt användarnamn',\n      newVersionAvailable: 'Ny version tillgänglig',\n      newestFirst: 'Nyaste först',\n      noApiKeyCreated: 'Ingen API-nyckel skapad.',\n      noBoards: 'Inga tavlor',\n      noCardsFound: 'Inga kort hittades.',\n      noConnectionToServer: 'Ingen anslutning till servern',\n      noLists: 'Inga listor',\n      noProjects: 'Inga projekt',\n      noUnreadNotifications: 'Inga olästa notifikationer.',\n      notifications: 'Notifikationer',\n      oldestFirst: 'Äldsta först',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Endast en projektledare bör kvarstå för att göra detta projekt privat',\n      openBoard_title: 'Öppna tavla',\n      optional_inline: 'valfri',\n      organization: 'Organisation',\n      others: 'Andra',\n      passwordIsSet: 'Lösenord är inställt',\n      phone: 'Telefon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA använder <1><0>Apprise</0></1> för att skicka aviseringar till över 100 populära tjänster.',\n      port: 'Port',\n      preferences: 'Preferenser',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Tips: tryck på Ctrl-V (Cmd-V på Mac) för att lägga till en bilaga från urklipp.',\n      private: 'Privat',\n      project: 'Projekt',\n      projectNotFound_title: 'Projekt hittades inte',\n      projectOwner: 'Projektägare',\n      referenceDataAndKnowledgeStorage: 'Referensdata och kunskapslagring.',\n      regenerateApiKey_title: 'Regenerera API-nyckel',\n      rejectUnauthorizedTlsCertificates: 'Avvisa obehöriga TLS-certifikat',\n      removeManager_title: 'Ta bort projektledare',\n      removeMember_title: 'Ta bort medlem',\n      role: 'Roll',\n      saveThisKeyItWillNotBeShownAgain: 'Spara denna nyckel — den visas inte igen!',\n      searchCards: 'Sök kort...',\n      searchCustomFieldGroups: 'Sök anpassade fältgrupper...',\n      searchCustomFields: 'Sök anpassade fält...',\n      searchLabels: 'Sök etiketter...',\n      searchLists: 'Sök listor...',\n      searchMembers: 'Sök medlemmar...',\n      searchProjects: 'Sök projekt...',\n      searchUsers: 'Sök användare...',\n      seconds: 'Sekunder',\n      selectAssignee_title: 'Välj tilldelad',\n      selectBoard: 'Välj tavla',\n      selectList: 'Välj lista',\n      selectListToRestoreThisCard: 'Välj lista för att återställa detta kort',\n      selectOrder_title: 'Välj ordning',\n      selectPermissions_title: 'Välj behörigheter',\n      selectProject: 'Välj projekt',\n      selectRole_title: 'Välj roll',\n      selectType_title: 'Välj typ',\n      sequentialDisplayOfCards: 'Sekventiell visning av kort.',\n      settings: 'Inställningar',\n      shared: 'Delad',\n      sharedWithMe_title: 'Delad med mig',\n      showOnFrontOfCard: 'Visa på framsidan av kort',\n      smtp: 'SMTP',\n      sortList_title: 'Sortera lista',\n      sourceCardIsNoLongerAvailableForCopying:\n        'Källkortet är inte längre tillgängligt för kopiering.',\n      sourceCardIsNoLongerAvailableForMoving:\n        'Källkortet är inte längre tillgängligt för flyttning.',\n      stopwatch: 'Timer',\n      story: 'Berättelse',\n      subscribeToCardWhenCommenting: 'Prenumerera på kort vid kommentering',\n      subscribeToMyOwnCardsByDefault: 'Prenumerera på mina egna kort som standard',\n      taskActions_title: 'Uppgiftsåtgärder',\n      taskAssignmentAndProjectCompletion: 'Uppgiftstilldelning och projektavslutning.',\n      taskListActions_title: 'Uppgiftslistsåtgärder',\n      taskList_title: 'Uppgiftslista',\n      team: 'Team',\n      termsOfService_title: 'Användarvillkor',\n      testLog_title: 'Testlogg',\n      thereIsNoPreviewAvailableForThisAttachment:\n        'Det finns ingen förhandsvisning tillgänglig för denna bilaga.',\n      time: 'Tid',\n      title: 'Titel',\n      trash: 'Papperskorg',\n      trashHasBeenSuccessfullyEmptied: 'Papperskorgen har tömts framgångsrikt.',\n      turnOffRecentCardHighlighting: 'Stäng av markering av senaste kort',\n      typeNameToConfirm: 'Skriv namn för att bekräfta.',\n      typeTitleToConfirm: 'Skriv titel för att bekräfta.',\n      unlinkSso_title: 'Avlänkning av SSO',\n      unsavedChanges: 'Osparade ändringar',\n      uploadFailedFileIsTooBig: 'Uppladdning misslyckades: filen är för stor.',\n      uploadFailedNotEnoughStorageSpace:\n        'Uppladdning misslyckades: inte tillräckligt med lagringsutrymme.',\n      uploadedImages: 'Uppladdade bilder',\n      url: 'URL',\n      useSecureConnection: 'Använd säker anslutning',\n      userActions_title: 'Användaråtgärder',\n      userAddedCardToList: '<0>{{user}}</0> lade till <2>{{card}}</2> i {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> lade till detta kort i {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> lade till {{addedUser}} i <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> lade till {{addedUser}} i detta kort',\n      userAddedYouToCard: '<0>{{user}}</0> lade till dig i <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> slutförde {{task}} på <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> slutförde {{task}} på detta kort',\n      userJoinedCard: '<0>{{user}}</0> gick med i <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> gick med i detta kort',\n      userLeftCard: '<0>{{user}}</0> lämnade <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> lämnade en ny kommentar «{{comment}}» på <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> lämnade detta kort',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> markerade {{task}} som ofullständig på <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> markerade {{task}} som ofullständig på detta kort',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> nämnde dig i en kommentar «{{comment}}» på <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> flyttade <2>{{card}}</2> från {{fromList}} till {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> flyttade detta kort från {{fromList}} till {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> tog bort {{removedUser}} från <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> tog bort {{removedUser}} från detta kort',\n      username: 'Användarnamn',\n      users: 'Användare',\n      viewer: 'Visare',\n      viewers: 'Visare',\n      visualTaskManagementWithLists: 'Visuell uppgiftshantering med listor.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Vad är nytt',\n      withoutBaseGroup: 'Utan basgrupp',\n      writeComment: 'Skriv en kommentar...',\n    },\n\n    action: {\n      activateUser: 'Aktivera användare',\n      activateUser_title: 'Aktivera användare',\n      addAnotherCard: 'Lägg till ytterligare ett kort',\n      addAnotherList: 'Lägg till ytterligare en lista',\n      addAnotherTask: 'Lägg till ytterligare en uppgift',\n      addCard: 'Lägg till kort',\n      addCard_title: 'Lägg till kort',\n      addComment: 'Lägg till kommentar',\n      addCustomField: 'Lägg till anpassat fält',\n      addCustomFieldGroup: 'Lägg till anpassad fältgrupp',\n      addList: 'Lägg till lista',\n      addMember: 'Lägg till medlem',\n      addMoreDetailedDescription: 'Lägg till en mer detaljerad beskrivning',\n      addTask: 'Lägg till uppgift',\n      addTaskList: 'Lägg till uppgiftslista',\n      addToCard: 'Lägg till i kort',\n      addUser: 'Lägg till användare',\n      addWebhook: 'Lägg till webhook',\n      archive: 'Arkivera',\n      archiveCard: 'Arkivera kort',\n      archiveCard_title: 'Arkivera kort',\n      archiveCards: 'Arkivera kort',\n      archiveCards_title: 'Arkivera kort',\n      assignAsOwner: 'Tilldela som ägare',\n      cancel: 'Avbryt',\n      copy: 'Kopiera',\n      copyCard_title: 'Kopiera kort',\n      createApiKey: 'Skapa API-nyckel',\n      createBoard: 'Skapa tavla',\n      createCustomFieldGroup: 'Skapa anpassad fältgrupp',\n      createFile: 'Skapa fil',\n      createLabel: 'Skapa etikett',\n      createNewLabel: 'Skapa ny etikett',\n      createProject: 'Skapa projekt',\n      cut: 'Klipp ut',\n      cutCard_title: 'Klipp ut kort',\n      deactivateUser: 'Inaktivera användare',\n      deactivateUser_title: 'Inaktivera användare',\n      delete: 'Ta bort',\n      deleteApiKey: 'Ta bort API-nyckel',\n      deleteAttachment: 'Ta bort bilaga',\n      deleteAvatar: 'Ta bort avatar',\n      deleteBackgroundImage: 'Ta bort bakgrundsbild',\n      deleteBoard: 'Ta bort tavla',\n      deleteBoard_title: 'Ta bort tavla',\n      deleteCard: 'Ta bort kort',\n      deleteCardForever: 'Ta bort kort för alltid',\n      deleteCard_title: 'Ta bort kort',\n      deleteComment: 'Ta bort kommentar',\n      deleteCustomField: 'Ta bort anpassat fält',\n      deleteCustomFieldGroup: 'Ta bort anpassad fältgrupp',\n      deleteForever_title: 'Ta bort för alltid',\n      deleteGroup: 'Ta bort grupp',\n      deleteLabel: 'Ta bort etikett',\n      deleteList: 'Ta bort lista',\n      deleteList_title: 'Ta bort lista',\n      deleteNotificationService: 'Ta bort aviseringstjänst',\n      deleteProject: 'Ta bort projekt',\n      deleteProject_title: 'Ta bort projekt',\n      deleteTask: 'Ta bort uppgift',\n      deleteTaskList: 'Ta bort uppgiftslista',\n      deleteTask_title: 'Ta bort uppgift',\n      deleteUser: 'Ta bort användare',\n      deleteUser_title: 'Ta bort användare',\n      deleteWebhook: 'Ta bort webhook',\n      dismissAll: 'Avvisa alla',\n      download: 'Ladda ner',\n      duplicateCard_title: 'Duplicera kort',\n      edit: 'Redigera',\n      editColor_title: 'Redigera färg',\n      editDescription_title: 'Redigera beskrivning',\n      editDueDate_title: 'Redigera förfallodatum',\n      editEmail_title: 'Redigera e-mail',\n      editGroup: 'Redigera grupp',\n      editInformation_title: 'Redigera information',\n      editPassword_title: 'Redigera lösenord',\n      editPermissions: 'Redigera behörigheter',\n      editRole_title: 'Redigera roll',\n      editStopwatch_title: 'Redigera timer',\n      editTitle_title: 'Redigera titel',\n      editType_title: 'Redigera typ',\n      editUsername_title: 'Redigera användarnamn',\n      emptyTrash: 'Töm papperskorg',\n      emptyTrash_title: 'Töm papperskorg',\n      import: 'Importera',\n      join: 'Gå med',\n      leave: 'Lämna',\n      leaveBoard: 'Lämna tavla',\n      leaveProject: 'Lämna projekt',\n      logOut_title: 'Logga ut',\n      makeCover_title: 'Skapa omslagsbild',\n      makeProjectPrivate: 'Gör projekt privat',\n      makeProjectPrivate_title: 'Gör projekt privat',\n      makeProjectShared: 'Dela projekt',\n      makeProjectShared_title: 'Dela projekt',\n      move: 'Flytta',\n      moveCard_title: 'Flytta kort',\n      moveList_title: 'Flytta lista',\n      regenerateApiKey: 'Regenerera API-nyckel',\n      remove: 'Ta bort',\n      removeAssignee: 'Ta bort tilldelad',\n      removeColor: 'Ta bort färg',\n      removeCover_title: 'Ta bort omslagsbild',\n      removeFromBoard: 'Ta bort from tavla',\n      removeFromProject: 'Ta bort from projekt',\n      removeManager: 'Ta bort projektledare',\n      removeMember: 'Ta bort medlem',\n      restoreToList: 'Återställ till {{list}}',\n      returnToBoard: 'Återgå till tavla',\n      save: 'Spara',\n      sendTestEmail: 'Skicka test-e-post',\n      showActive: 'Visa aktiva',\n      showAllAttachments: 'Visa alla bilagor ({{hidden}} dolda)',\n      showCardsWithThisUser: 'Visa kort med denna användare',\n      showDeactivated: 'Visa inaktiverade',\n      showFewerAttachments: 'Visa färre bilagor',\n      showLess: 'Visa mindre',\n      showMore: 'Visa mer',\n      sortList_title: 'Sortera lista',\n      start: 'Starta',\n      stop: 'Stoppa',\n      subscribe: 'Prenumerera',\n      unlinkSso: 'Avlänka SSO',\n      unlinkSso_title: 'Avlänka SSO',\n      unsubscribe: 'Avprenumerera',\n      uploadNewAvatar: 'Ladda upp ny avatar',\n      uploadNewImage: 'Ladda upp ny bild',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/sv-SE/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'sv-SE',\n  country: 'se',\n  name: 'Svenska',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/sv-SE/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Gränsen för aktiva användare har nåtts',\n      adminLoginRequiredToInitializeInstance:\n        'Administratörsinloggning krävs för att initiera instansen',\n      emailAlreadyInUse: 'E-mail används redan',\n      emailOrUsername: 'E-mail eller användarnamn',\n      invalidCredentials: 'Ogiltiga inloggningsuppgifter',\n      invalidEmailOrUsername: 'Ogiltig e-mail eller användarnamn',\n      invalidPassword: 'Ogiltigt lösenord',\n      logIn_title: 'Logga in',\n      noInternetConnection: 'Ingen internetanslutning',\n      or: 'Eller',\n      pageNotFound_title: 'Sidan kunde inte hittas',\n      password: 'Lösenord',\n      poweredByPlanka: 'Drivs av <1>PLANKA</1>',\n      serverConnectionFailed: 'Server connection failed',\n      unknownError: 'Okänt fel, försök igen senare',\n      useSingleSignOn: 'Använd enkel inloggning',\n      usernameAlreadyInUse: 'Användarnamnet används redan',\n      whoops_title: 'Hoppsan!',\n    },\n\n    action: {\n      cancelAndClose: 'Avbryt och stäng',\n      continue: 'Fortsätt',\n      debugSso: 'Felsök SSO',\n      goBack: 'Gå tillbaka',\n      goHome: 'Gå hem',\n      logIn: 'Logga in',\n      logInWithSso: 'Logga in med SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/sv-SE/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Detta är en text utan titel.\\nBåde titeln och texten\\nkan markeras med fet, kursiv, färg,\\ngenomstruken och understruken.\",\n    \"text-with-head\": \"Detta är en text med titel.\\nBåde titeln och texten\\nkan markeras med fet, kursiv, färg,\\ngenomstruken och understruken.\",\n    \"heading\": \"Titel\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Fel i markdown-redigerare\",\n    \"settings_wysiwyg\": \"Visuell redigerare (wysiwyg)\",\n    \"settings_markup\": \"Markdown-märkning\",\n    \"markup_placeholder\": \"Ange markdown-märkning...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Ta bort\",\n    \"empty_option\": \"Inga träffar hittades\",\n    \"show_line_numbers\": \"Radnumrering\"\n  },\n  \"common\": {\n    \"delete\": \"Radera\",\n    \"edit\": \"Redigera\",\n    \"toolbar_action_disabled\": \"Inkompatibelt märkningselement\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Avbryt\",\n    \"common_action_submit\": \"Skicka\",\n    \"common_action_upload\": \"Välj\",\n    \"common_tab_attach\": \"Lägg till från enhet\",\n    \"common_tab_link\": \"Lägg till via länk\",\n    \"common_link\": \"Länk\",\n    \"common_sizes\": \"Storlek, px\",\n    \"image_name\": \"Titel\",\n    \"image_link_href\": \"Bildlänk\",\n    \"image_link_href_help\": \"Adress som bildlänken leder till.\",\n    \"image_alt\": \"Alt-text\",\n    \"image_alt_help\": \"Alt-text visas om bilden inte kan laddas.\",\n    \"image_upload_help\": \"JPEG-, GIF- eller PNG-bild som inte är större än 1 MB.\",\n    \"image_upload_failed\": \"Misslyckades med att lägga till bild\",\n    \"image_size_width\": \"Bredd\",\n    \"image_size_height\": \"Höjd\",\n    \"link_url_help\": \"Adress som länken leder till.\",\n    \"link_text\": \"Länktext\",\n    \"link_text_help\": \"Text som visas som länk.\",\n    \"link_open_help\": \"Öppna länken i ny flik\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Rubrik\",\n    \"header_hint\": \"# Din text\",\n    \"italic_title\": \"Kursiv\",\n    \"italic_hint\": \"_Din text_\",\n    \"bold_title\": \"Fet\",\n    \"bold_hint\": \"**Din text**\",\n    \"strikethrough_title\": \"Genomstruken\",\n    \"strikethrough_hint\": \"~~Din text~~\",\n    \"blockquote_title\": \"Blockcitat\",\n    \"blockquote_hint\": \"> Din text\",\n    \"code_title\": \"Kod\",\n    \"code_hint\": \"```Din text```\",\n    \"link_title\": \"Länk\",\n    \"link_hint\": \"[Din text](url)\",\n    \"image_title\": \"Bild\",\n    \"image_hint\": \"![Din text](url)\",\n    \"list_title\": \"Listobjekt\",\n    \"list_hint\": \"- Din text\",\n    \"numbered-list_title\": \"Numrerad lista\",\n    \"numbered-list_hint\": \"1. Din text\",\n    \"documentation\": \"Dokumentation\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Fet\",\n    \"code\": \"Kod\",\n    \"code_inline\": \"Inline-kod\",\n    \"codeblock\": \"Kodblock\",\n    \"colorify\": \"Textfärg\",\n    \"colorify__color_blue\": \"Blå\",\n    \"colorify__color_default\": \"Standard\",\n    \"colorify__color_gray\": \"Grå\",\n    \"colorify__color_green\": \"Grön\",\n    \"colorify__color_orange\": \"Orange\",\n    \"colorify__color_red\": \"Röd\",\n    \"colorify__color_violet\": \"Violett\",\n    \"colorify__color_yellow\": \"Gul\",\n    \"colorify__group_text\": \"Text\",\n    \"cut\": \"Klipp ut\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emojis kan läggas till i WYSIWYG eller manuellt med märkning\",\n    \"heading\": \"Rubrik\",\n    \"heading1\": \"Rubrik 1\",\n    \"heading2\": \"Rubrik 2\",\n    \"heading3\": \"Rubrik 3\",\n    \"heading4\": \"Rubrik 4\",\n    \"heading5\": \"Rubrik 5\",\n    \"heading6\": \"Rubrik 6\",\n    \"hrule\": \"Separator\",\n    \"image\": \"Bild\",\n    \"italic\": \"Kursiv\",\n    \"link\": \"Länk\",\n    \"list\": \"Lista\",\n    \"list__action_lift\": \"Lyft objekt\",\n    \"list__action_sink\": \"Sänk objekt\",\n    \"list_action_disabled\": \"Motsäger listans logik\",\n    \"mark\": \"Markerad\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Fler åtgärder\",\n    \"note\": \"Anteckning\",\n    \"olist\": \"Ordnad lista\",\n    \"quote\": \"Citat\",\n    \"redo\": \"Gör om\",\n    \"strike\": \"Genomstruken\",\n    \"table\": \"Tabell\",\n    \"text\": \"Text\",\n    \"ulist\": \"Punktlista\",\n    \"underline\": \"Understruken\",\n    \"undo\": \"Ångra\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Skriv / för att använda snedstreckskommandon...\",\n    \"checkbox\": \"Ange uppgiftsbeskrivning...\",\n    \"deflist_term\": \"Term\",\n    \"deflist_desc\": \"Definitionsbeskrivning\",\n    \"heading\": \"Rubrik\",\n    \"cut_title\": \"Titel\",\n    \"cut_content\": \"Innehåll som ska visas vid klick\",\n    \"note_title\": \"Titel\",\n    \"note_content\": \"Anteckningsinnehåll\",\n    \"table_cell\": \"Cellinnehåll\",\n    \"select_filter\": \"Sök språk...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Skiftlägeskänslig\",\n    \"label_whole-word\": \"Helt ord\",\n    \"title\": \"Sök i kod\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Hittades inte\"\n  },\n  \"widgets\": {\n    \"image\": \"Lägg till bild\",\n    \"link\": \"Lägg till länk\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Anteckning\",\n    \"tip\": \"Tips\",\n    \"warning\": \"Varning\",\n    \"alert\": \"Varning\",\n    \"remove\": \"Ta bort\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Lägg till kolumn före\",\n    \"column.add.after\": \"Lägg till kolumn efter\",\n    \"column.remove\": \"Ta bort kolumn\",\n    \"column.remove.multiple\": \"Ta bort kolumner\",\n    \"row.add.before\": \"Lägg till rad före\",\n    \"row.add.after\": \"Lägg till rad efter\",\n    \"row.remove\": \"Ta bort rad\",\n    \"row.remove.multiple\": \"Ta bort rader\",\n    \"cells.clear\": \"Rensa celler\",\n    \"table.remove\": \"Ta bort tabell\",\n    \"table.menu.cell.align.left\": \"Vänsterjustera cellinnehåll\",\n    \"table.menu.cell.align.right\": \"Högerjustera cellinnehåll\",\n    \"table.menu.cell.align.center\": \"Centrera cellinnehåll\",\n    \"table.menu.row.add\": \"Lägg till rad efter\",\n    \"table.menu.row.remove\": \"Ta bort rad\",\n    \"table.menu.column.add\": \"Lägg till kolumn efter\",\n    \"table.menu.column.remove\": \"Ta bort kolumn\",\n    \"table.menu.convert.yfm\": \"Konvertera till YFM-tabell\",\n    \"table.menu.table.remove\": \"Ta bort tabell\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/tr-TR/core.js",
    "content": "import dateFns from 'date-fns/locale/tr';\nimport timeAgo from 'javascript-time-ago/locale/tr';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd.MM.yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd. MMM',\n    longDateTime: \"d. MMMM 'Saat' p\",\n    fullDate: 'd. MMM. y',\n    fullDateTime: \"d. MMMM. y 'Saat' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Uygulama hakkında',\n      aboutPlanka_title: 'PLANKA Hakkında',\n      accessToken: 'Erişim jetonu',\n      account: 'Hesap',\n      actions: 'Eylemler',\n      activateUser_title: 'Kullanıcıyı etkinleştir',\n      active: 'Etkin',\n      addAttachment_title: 'Dosya ekle',\n      addCustomFieldGroup_title: 'Özel alan grubu ekle',\n      addCustomField_title: 'Özel alan ekle',\n      addManager_title: 'Yönetici ekle',\n      addMember_title: 'Üye ekle',\n      addTaskList_title: 'Görev listesi ekle',\n      addUser_title: 'Kullanıcı ekle',\n      admin: 'Yönetici',\n      administration: 'Yönetim',\n      all: 'Tümü',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Bağlantı yeniden kurulduğunda tüm değişiklikler kaydedilecektir.',\n      alphabetically: 'Alfabetik olarak',\n      alwaysDisplayCardCreator: 'Kart oluşturucusunu her zaman göster',\n      apiKeyCreated_title: 'API Anahtarı Oluşturuldu',\n      apiKey_title: 'API Anahtarı',\n      archive: 'Arşiv',\n      archiveCard_title: 'Kartı arşivle',\n      archiveCards_title: 'Kartları arşivle',\n      areYouSureYouWantToActivateThisUser:\n        'Bu kullanıcıyı etkinleştirmek istediğinizden emin misiniz?',\n      areYouSureYouWantToArchiveCards: 'Kartları arşivlemek istediğinizden emin misiniz?',\n      areYouSureYouWantToArchiveThisCard: 'Bu kartı arşivlemek istediğinizden emin misiniz?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Bu proje yöneticisini sahip olarak atamak istediğinizden emin misiniz?',\n      areYouSureYouWantToDeactivateThisUser:\n        'Bu kullanıcıyı devre dışı bırakmak istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisApiKey: 'Bu API anahtarını silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisAttachment: 'Bu eki silmek istediğinize emin misiniz?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Bu arka plan resmini silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisBoard: 'Bu panoyu silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisCard: 'Bu kartı silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Bu kartı kalıcı olarak silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisComment: 'Bu yorumu silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisCustomField: 'Bu özel alanı silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Bu özel alan grubunu silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisLabel: 'Bu etiketi silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisList:\n        'Bu listeyi silmek istediğinizden emin misiniz? Tüm kartlar çöp kutusuna taşınacaktır.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Bu bildirim servisini silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisProject: 'Bu projeyi silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisTask: 'Bu görevi silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisTaskList:\n        'Bu görev listesini silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisUser: 'Bu kullanıcıyı silmek istediğinizden emin misiniz?',\n      areYouSureYouWantToDeleteThisWebhook: \"Bu webhook'u silmek istediğinizden emin misiniz?\",\n      areYouSureYouWantToEmptyTrash: 'Çöp kutusunu boşaltmak istediğinizden emin misiniz?',\n      areYouSureYouWantToLeaveBoard: 'Panodan ayrılmak istediğinizden emin misiniz?',\n      areYouSureYouWantToLeaveProject: 'Projeden ayrılmak istediğinizden emin misiniz?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Bu projeyi özel yapmak istediğinizden emin misiniz?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Bu projeyi paylaşımlı yapmak istediğinizden emin misiniz?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Bu API anahtarını yeniden oluşturmak istediğinizden emin misiniz? Önceki anahtar artık çalışmayacak.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Bu yöneticiyi projeden çıkarmak istediğinizden emin misiniz?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Bu üyeyi panodan çıkarmak istediğinizden emin misiniz?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Bu kullanıcıdan SSO bağlantısını kaldırmak istediğinizden emin misiniz? Bu, kullanıcının şifre ile giriş yapmasına izin verecektir.',\n      assignAsOwner_title: 'Sahip olarak ata',\n      atLeastOneListMustBePresent: 'En az bir liste bulunmalıdır',\n      attachment: 'ek',\n      attachments: 'ekler',\n      authentication: 'kimlik doğrulama',\n      background: 'arka plan',\n      baseCustomFields_title: 'Temel özel alanlar',\n      baseGroup: 'Temel grup',\n      board: 'pano',\n      boardActions_title: 'Pano işlemleri',\n      boardNotFound_title: 'Pano bulunamadı',\n      boardSubscribed: 'Panoya abone olundu',\n      boardUser: 'Pano kullanıcısı',\n      byCreationTime: 'Oluşturma zamanına göre',\n      byDefault: 'Varsayılan olarak',\n      byDueDate: 'Termin tarihine göre',\n      canBeInvitedToWorkInBoards: 'Panolarda çalışmaya davet edilebilir.',\n      canComment: 'Yorum yapabilir',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Kendi projelerini oluşturabilir ve başkalarında çalışmaya davet edilebilir.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Pano düzenini düzenleyebilir ve kartlara üye atayabilir.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Sistem geneli ayarları yönetebilir ve proje sahibi olarak hareket edebilir.',\n      canOnlyViewBoard: 'Sadece panoyu görüntüleyebilir.',\n      cardActions_title: 'Kart işlemleri',\n      cardNotFound_title: 'Kart bulunamadı',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Bu listedeki kartlar tüm pano üyelerine açıktır.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Bu listedeki kartlar tamamlanmış ve arşivlenmeye hazırdır.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Bu listedeki kartlar üzerinde çalışılmaya hazırdır.',\n      clickHereOrRefreshPageToUpdate:\n        '<0>Güncellemek için buraya tıklayın</0> veya sayfayı yenileyin.',\n      clientHostnameInEhlo: \"EHLO'da istemci ana bilgisayar adı\",\n      closed: 'Kapalı',\n      color: 'renk',\n      comments: 'Yorumlar',\n      contentExceedsLimit: 'İçerik {{limit}} sınırını aşıyor',\n      contentOfThisAttachmentIsTooBigToDisplay: 'Bu ekin içeriği görüntülenemeyecek kadar büyük.',\n      copy_inline: 'kopya',\n      createBoard_title: 'Pano oluştur',\n      createCustomFieldGroup_title: 'Özel alan grubu oluştur',\n      createLabel_title: 'Etiket oluştur',\n      createNewOneOrSelectExistingOne: 'Yeni bir tane oluşturun veya mevcut bir tanesini seçin.',\n      createProject_title: 'Proje oluştur',\n      createTextFile_title: 'Metin dosyası oluştur',\n      creator: 'Oluşturan',\n      currentPassword: 'Geçerli şifre',\n      currentUser: 'Mevcut kullanıcı',\n      customFieldGroup_title: 'özel alan grubu',\n      customFieldGroups_title: 'özel alan grupları',\n      customField_title: 'Özel alan',\n      customFields_title: 'Özel alanlar',\n      customerPanel_title: 'Müşteri paneli',\n      dangerZone_title: 'Tehlikeli bölge',\n      date: 'tarih',\n      deactivateUser_title: 'Kullanıcıyı devre dışı bırak',\n      defaultCardType_title: 'Varsayılan kart türü',\n      defaultFrom: 'Varsayılan gönderen',\n      defaultView_title: 'Varsayılan görünüm',\n      deleteAllBoardsToBeAbleToDeleteThisProject: 'Bu projeyi silebilmek için tüm panoları silin',\n      deleteApiKey_title: 'API Anahtarını Sil',\n      deleteAttachment_title: 'Eki sil',\n      deleteBackgroundImage_title: 'Arka plan resmini sil',\n      deleteBoard_title: 'Panoyu sil',\n      deleteCardForever_title: 'Kartı kalıcı olarak sil',\n      deleteCard_title: 'Kartı sil',\n      deleteComment_title: 'Yorumu sil',\n      deleteCustomFieldGroup_title: 'Özel alan grubunu sil',\n      deleteCustomField_title: 'Özel alanı sil',\n      deleteLabel_title: 'Etiketi sil',\n      deleteList_title: 'Listeyi sil',\n      deleteNotificationService_title: 'Bildirim servisini sil',\n      deleteProject_title: 'Projeyi sil',\n      deleteTaskList_title: 'Görev listesini sil',\n      deleteTask_title: 'Görevi sil',\n      deleteUser_title: 'Kullanıcıyı sil',\n      deleteWebhook_title: \"Webhook'u sil\",\n      deletedUser_title: 'Silinmiş kullanıcı',\n      description: 'açıklama',\n      display: 'Görüntüle',\n      displayCardAges: 'Kart yaşlarını göster',\n      dropFileToUpload: 'Yüklenecek dosyayı buraya bırakın',\n      dueDate_title: 'Termin tarihi',\n      dynamicAndUnevenlySpacedLayout: 'Dinamik ve düzensiz aralıklı düzen.',\n      editAttachment_title: 'Eki düzenle',\n      editAvatar_title: 'Avatarı düzenle',\n      editColor_title: 'Rengi düzenle',\n      editCustomFieldGroup_title: 'Özel alan grubunu düzenle',\n      editCustomField_title: 'Özel alanı düzenle',\n      editDueDate_title: 'Son tarihi düzenle',\n      editEmail_title: 'E-posta adresini düzenle',\n      editInformation_title: 'Bilgileri düzenle',\n      editLabel_title: 'Etiketi düzenle',\n      editPassword_title: 'Şifreyi değiştir',\n      editPermissions_title: 'İzinleri düzenle',\n      editRole_title: 'Rolü düzenle',\n      editStopwatch_title: 'Kronometreyi düzenle',\n      editType_title: 'Türü düzenle',\n      editUsername_title: 'Kullanıcı adını düzenle',\n      editor: 'Düzenleyici',\n      editors: 'Düzenleyiciler',\n      email: 'e-posta',\n      emptyTrash_title: 'çöp kutusunu boşalt',\n      enterCardTitle: 'Kart başlığını girin...',\n      enterDescription: 'Açıklamayı girin...',\n      enterFilename: 'Dosya adını girin',\n      enterListTitle: 'Liste başlığını girin...',\n      enterTaskDescription: 'Görev açıklamasını girin...',\n      events: 'Olaylar',\n      excludedEvents: 'Hariç tutulan olaylar',\n      expandTaskListsByDefault: 'Görev listelerini varsayılan olarak genişlet',\n      filterByLabels_title: 'Etikete göre filtrele',\n      filterByMembers_title: 'Üyelere göre filtrele',\n      forPersonalProjects: 'Kişisel projeler için.',\n      forTeamBasedProjects: 'Takım tabanlı projeler için.',\n      fromComputer_title: 'Bilgisayardan',\n      fromTrello: \"Trello'dan\",\n      fullKeyIsHiddenForSecurityReasons:\n        'Tam anahtar güvenlik nedeniyle gizlenmiştir. Yeni bir tane oluşturmak için yeniden oluşturun.',\n      general: 'Genel',\n      gradients: 'Gradyanlar',\n      grid: 'Izgara',\n      hideCompletedTasks: 'Tamamlanan görevleri gizle',\n      hideFromProjectListAndFavorites: 'Proje listesi ve favorilerden gizle',\n      host: 'Ana bilgisayar',\n      hours: 'saat',\n      identity: 'Kimlik',\n      importBoard_title: 'Panoyu içe aktar',\n      information: 'Bilgi',\n      invalidCurrentPassword: 'Mevcut şifre yanlış',\n      kanban: 'Kanban',\n      labels: 'etiketler',\n      language: 'dil',\n      leaveBoard_title: 'Panodan ayrıl',\n      leaveProject_title: 'Projeden ayrıl',\n      limitCardTypesToDefaultOne: 'Kart türlerini varsayılan olanla sınırla',\n      linkToCard: 'Karta bağlantı',\n      list: 'Listeler',\n      listActions_title: 'Görevleri listele',\n      lists: 'Listeler',\n      makeProjectPrivate_title: 'Projeyi özel yap',\n      makeProjectShared_title: 'Projeyi paylaşımlı yap',\n      managers: 'Yöneticiler',\n      memberActions_title: 'Üye işlemleri',\n      members: 'Üyeler',\n      minutes: 'dakika',\n      moreActions: 'Daha fazla işlem',\n      moreActions_title: 'Daha fazla işlem',\n      moveCard_title: 'Kartı taşı',\n      moveList_title: 'Listeyi taşı',\n      myOwn_title: 'Kendimin',\n      name: 'isim',\n      newEmail: 'Yeni e-posta adresi',\n      newPassword: 'Yeni şifre',\n      newUsername: 'Yeni kullanıcı adı',\n      newVersionAvailable: 'Yeni sürüm mevcut',\n      newestFirst: 'Önce en yeni',\n      noApiKeyCreated: 'Oluşturulmuş API anahtarı yok.',\n      noBoards: 'Pano yok',\n      noCardsFound: 'Kart bulunamadı.',\n      noConnectionToServer: 'Sunucuya bağlantı yok',\n      noLists: 'Liste yok',\n      noProjects: 'Proje yok',\n      noUnreadNotifications: 'Okunmamış bildirim yok.',\n      notifications: 'Bildirimler',\n      oldestFirst: 'Önce en eski',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Bu projeyi özel yapmak için sadece bir yönetici kalmalıdır',\n      openBoard_title: 'Açık pano',\n      optional_inline: 'İsteğe bağlı',\n      organization: 'Organizasyon',\n      others: 'Diğerleri',\n      passwordIsSet: 'Şifre ayarlandı',\n      phone: 'telefon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        \"PLANKA, 100'den fazla popüler servise bildirim göndermek için <1><0>Apprise</0></1> kullanır.\",\n      port: 'Port',\n      preferences: 'Tercihler',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'İpucu: Panodan bir ek eklemek için CTRL-V ye (Macte Cmd-V) basın.',\n      private: 'Özel',\n      project: 'Proje',\n      projectNotFound_title: 'Proje bulunamadı',\n      projectOwner: 'Proje sahibi',\n      referenceDataAndKnowledgeStorage: 'Referans veri ve bilgi depolama.',\n      regenerateApiKey_title: 'API Anahtarını Yeniden Oluştur',\n      rejectUnauthorizedTlsCertificates: 'Yetkisiz TLS sertifikalarını reddet',\n      removeManager_title: 'Yöneticiyi kaldır',\n      removeMember_title: 'Üyeyi kaldır',\n      role: 'Rol',\n      saveThisKeyItWillNotBeShownAgain: 'Bu anahtarı kaydedin — tekrar gösterilmeyecek!',\n      searchCards: 'Kartları ara...',\n      searchCustomFieldGroups: 'Özel alan gruplarını ara...',\n      searchCustomFields: 'Özel alanları ara...',\n      searchLabels: 'Etiketleri ara...',\n      searchLists: 'Listeleri ara...',\n      searchMembers: 'Üyeleri ara...',\n      searchProjects: 'Projeleri ara...',\n      searchUsers: 'Kullanıcıları ara...',\n      seconds: 'saniye',\n      selectAssignee_title: 'Atanan kişiyi seç',\n      selectBoard: 'Pano seç',\n      selectList: 'Liste seç',\n      selectListToRestoreThisCard: 'Bu kartı geri yüklemek için liste seçin',\n      selectOrder_title: 'Sıralamayı seç',\n      selectPermissions_title: 'İzinleri seç',\n      selectProject: 'Proje seç',\n      selectRole_title: 'Rolü seç',\n      selectType_title: 'Türü seç',\n      sequentialDisplayOfCards: 'Kartların sıralı görüntülenmesi.',\n      settings: 'Ayarlar',\n      shared: 'Paylaşımlı',\n      sharedWithMe_title: 'Benimle paylaşılan',\n      showOnFrontOfCard: 'Kartın ön yüzünde göster',\n      smtp: 'SMTP',\n      sortList_title: 'Listeyi sırala',\n      sourceCardIsNoLongerAvailableForCopying: 'Kaynak kart artık kopyalama için kullanılamıyor.',\n      sourceCardIsNoLongerAvailableForMoving: 'Kaynak kart artık taşıma için kullanılamıyor.',\n      stopwatch: 'kronometre',\n      story: 'Hikaye',\n      subscribeToCardWhenCommenting: 'Yorum yaparken karta abone ol',\n      subscribeToMyOwnCardsByDefault: 'Varsayılan olarak kendi kartlarıma abone ol',\n      taskActions_title: 'Görev eylemleri',\n      taskAssignmentAndProjectCompletion: 'Görev atama ve proje tamamlama.',\n      taskListActions_title: 'Görev listesi işlemleri',\n      taskList_title: 'Görev listesi',\n      team: 'Takım',\n      termsOfService_title: 'Hizmet şartları',\n      testLog_title: 'Test günlüğü',\n      thereIsNoPreviewAvailableForThisAttachment: 'Bu ek için önizleme mevcut değil.',\n      time: 'zaman',\n      title: 'başlık',\n      trash: 'Çöp kutusu',\n      trashHasBeenSuccessfullyEmptied: 'Çöp kutusu başarıyla boşaltıldı.',\n      turnOffRecentCardHighlighting: 'Son kart vurgulamasını kapat',\n      typeNameToConfirm: 'Onaylamak için adı yazın.',\n      typeTitleToConfirm: 'Onaylamak için başlığı yazın.',\n      unlinkSso_title: 'SSO bağlantısını kaldırma',\n      unsavedChanges: 'Kaydedilmemiş değişiklikler',\n      uploadFailedFileIsTooBig: 'Yükleme başarısız: dosya çok büyük.',\n      uploadFailedNotEnoughStorageSpace: 'Yükleme başarısız: yeterli depolama alanı yok.',\n      uploadedImages: 'Yüklenen resimler',\n      url: 'URL',\n      useSecureConnection: 'Güvenli bağlantı kullan',\n      userActions_title: 'Kullanıcı işlemleri',\n      userAddedCardToList: '<0>{{user}}</0> <2>{{card}}</2> kartını {{list}} listesine ekledi',\n      userAddedThisCardToList: '<0>{{user}}</0> bu kartı {{list}} listesine ekledi',\n      userAddedUserToCard:\n        '<0>{{actorUser}}</0> {{addedUser}} kullanıcısını <4>{{card}}</4> kartına ekledi',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> {{addedUser}} kullanıcısını bu karta ekledi',\n      userAddedYouToCard: '<0>{{user}}</0> sizi <2>{{card}}</2> kartına ekledi',\n      userCompletedTaskOnCard:\n        '<0>{{user}}</0> {{task}} görevini <4>{{card}}</4> kartında tamamladı',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> {{task}} görevini bu kartta tamamladı',\n      userJoinedCard: '<0>{{user}}</0> <2>{{card}}</2> kartına katıldı',\n      userJoinedThisCard: '<0>{{user}}</0> bu karta katıldı',\n      userLeftCard: '<0>{{user}}</0> <2>{{card}}</2> kartından ayrıldı',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> <2>{{card}}</2> kartına yeni bir yorum yazdı: «{{comment}}»',\n      userLeftThisCard: '<0>{{user}}</0> bu karttan ayrıldı',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> {{task}} görevini <4>{{card}}</4> kartında tamamlanmamış olarak işaretledi',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> {{task}} görevini bu kartta tamamlanmamış olarak işaretledi',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> sizi <2>{{card}}</2> kartındaki «{{comment}}» yorumunda bahsetti',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> <2>{{card}}</2> kartını {{fromList}} listesinden {{toList}} listesine taşıdı',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> bu kartı {{fromList}} konumundan {{toList}} konumuna taşıdı',\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0> {{removedUser}} kullanıcısını <4>{{card}}</4> kartından çıkardı',\n      userRemovedUserFromThisCard:\n        '<0>{{actorUser}}</0> {{removedUser}} kullanıcısını bu karttan çıkardı',\n      username: 'kullanıcı adı',\n      users: 'kullanıcı',\n      viewer: 'Görüntüleyici',\n      viewers: 'Görüntüleyiciler',\n      visualTaskManagementWithLists: 'Listelerle görsel görev yönetimi.',\n      webhooks: \"Webhook'lar\",\n      whatsNew_title: 'Yenilikler',\n      withoutBaseGroup: 'Temel grup olmadan',\n      writeComment: 'Yorum yazın...',\n    },\n\n    action: {\n      activateUser: 'Kullanıcıyı etkinleştir',\n      activateUser_title: 'Kullanıcıyı etkinleştir',\n      addAnotherCard: 'Başka bir kart ekle',\n      addAnotherList: 'Başka bir liste ekle',\n      addAnotherTask: 'Başka bir görev ekle',\n      addCard: 'Kart ekle',\n      addCard_title: 'Kart ekle',\n      addComment: 'Yorum ekle',\n      addCustomField: 'Özel alan ekle',\n      addCustomFieldGroup: 'Özel alan grubu ekle',\n      addList: 'Liste ekle',\n      addMember: 'Üye ekle',\n      addMoreDetailedDescription: 'Ayrıntılı bir açıklama ekleyin',\n      addTask: 'Görev ekle',\n      addTaskList: 'Görev listesi ekle',\n      addToCard: 'Karta ekle',\n      addUser: 'Kullanıcı ekle',\n      addWebhook: 'Webhook ekle',\n      archive: 'Arşivle',\n      archiveCard: 'Kartı arşivle',\n      archiveCard_title: 'Kartı arşivle',\n      archiveCards: 'Kartları arşivle',\n      archiveCards_title: 'Kartları arşivle',\n      assignAsOwner: 'Sahip olarak ata',\n      cancel: 'İptal',\n      copy: 'Kopyala',\n      copyCard_title: 'Kartı kopyala',\n      createApiKey: 'API anahtarı oluştur',\n      createBoard: 'Pano oluştur',\n      createCustomFieldGroup: 'Özel alan grubu oluştur',\n      createFile: 'Dosya oluştur',\n      createLabel: 'Etiket oluştur',\n      createNewLabel: 'Yeni etiket oluştur',\n      createProject: 'Proje oluştur',\n      cut: 'Kes',\n      cutCard_title: 'Kartı kes',\n      deactivateUser: 'Kullanıcıyı devre dışı bırak',\n      deactivateUser_title: 'Kullanıcıyı devre dışı bırak',\n      delete: 'Sil',\n      deleteApiKey: 'API anahtarını sil',\n      deleteAttachment: 'Eki sil',\n      deleteAvatar: 'Avatarı sil',\n      deleteBackgroundImage: 'Arka plan resmini sil',\n      deleteBoard: 'Panoyu sil',\n      deleteBoard_title: 'Panoyu sil',\n      deleteCard: 'Kartı sil',\n      deleteCardForever: 'Kartı kalıcı olarak sil',\n      deleteCard_title: 'Kartı sil',\n      deleteComment: 'Yorumu sil',\n      deleteCustomField: 'Özel alanı sil',\n      deleteCustomFieldGroup: 'Özel alan grubunu sil',\n      deleteForever_title: 'Kalıcı olarak sil',\n      deleteGroup: 'Grubu sil',\n      deleteLabel: 'Etiketi sil',\n      deleteList: 'Listeyi sil',\n      deleteList_title: 'Listeyi sil',\n      deleteNotificationService: 'Bildirim servisini sil',\n      deleteProject: 'Projeyi sil',\n      deleteProject_title: 'Projeyi sil',\n      deleteTask: 'Görevi sil',\n      deleteTaskList: 'Görev listesini sil',\n      deleteTask_title: 'Görevi sil',\n      deleteUser: 'Kullanıcıyı sil',\n      deleteUser_title: 'Kullanıcıyı sil',\n      deleteWebhook: \"Webhook'u sil\",\n      dismissAll: 'Tümünü kapat',\n      download: 'İndir',\n      duplicateCard_title: 'Kartı kopyala',\n      edit: 'Düzenle',\n      editColor_title: 'Rengi düzenle',\n      editDescription_title: 'Açıklamayı düzenle',\n      editDueDate_title: 'Son tarihi düzenle',\n      editEmail_title: 'E-posta adresini düzenle',\n      editGroup: 'Grubu düzenle',\n      editInformation_title: 'Bilgileri düzenle',\n      editPassword_title: 'Şifreyi değiştir',\n      editPermissions: 'İzinleri düzenle',\n      editRole_title: 'Rolü düzenle',\n      editStopwatch_title: 'Kronometreyi düzenle',\n      editTitle_title: 'Başlığı düzenle',\n      editType_title: 'Türü düzenle',\n      editUsername_title: 'Kullanıcı adını düzenle',\n      emptyTrash: 'Çöp kutusunu boşalt',\n      emptyTrash_title: 'Çöp kutusunu boşalt',\n      import: 'İçe aktar',\n      join: 'Katıl',\n      leave: 'Ayrıl',\n      leaveBoard: 'Panodan ayrıl',\n      leaveProject: 'Projeden ayrıl',\n      logOut_title: 'Çıkış',\n      makeCover_title: 'Önizleme olarak ayarla',\n      makeProjectPrivate: 'Projeyi özel yap',\n      makeProjectPrivate_title: 'Projeyi özel yap',\n      makeProjectShared: 'Projeyi paylaşımlı yap',\n      makeProjectShared_title: 'Projeyi paylaşımlı yap',\n      move: 'Taşı',\n      moveCard_title: 'Kartı taşı',\n      moveList_title: 'Listeyi taşı',\n      regenerateApiKey: 'API anahtarını yeniden oluştur',\n      remove: 'Sil',\n      removeAssignee: 'Atanan kişiyi kaldır',\n      removeColor: 'Rengi kaldır',\n      removeCover_title: 'Önizlemeyi kaldır',\n      removeFromBoard: 'Panodan kaldır',\n      removeFromProject: 'Projeden kaldır',\n      removeManager: 'Yöneticiyi kaldır',\n      removeMember: 'Üyeyi kaldır',\n      restoreToList: '{{list}} listesine geri yükle',\n      returnToBoard: 'Panoya dön',\n      save: 'Kaydet',\n      sendTestEmail: 'Test e-postası gönder',\n      showActive: 'Etkinleri göster',\n      showAllAttachments: 'Tüm ekleri göster ({{hidden}} gizli)',\n      showCardsWithThisUser: 'Bu kullanıcıyla kartları göster',\n      showDeactivated: 'Devre dışı bırakılanları göster',\n      showFewerAttachments: 'Daha az ek göster',\n      showLess: 'Daha az göster',\n      showMore: 'Daha fazla göster',\n      sortList_title: 'Listeyi sırala',\n      start: 'başlat',\n      stop: 'dur',\n      subscribe: 'Abone ol',\n      unlinkSso: 'SSO bağlantısını kaldır',\n      unlinkSso_title: 'SSO bağlantısını kaldır',\n      unsubscribe: 'Abonelikten çık',\n      uploadNewAvatar: 'Yeni avatar yükle',\n      uploadNewImage: 'Yeni resim yükle',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/tr-TR/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'tr-TR',\n  country: 'tr',\n  name: 'Türkçe',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/tr-TR/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Aktif kullanıcı sınırına ulaşıldı',\n      adminLoginRequiredToInitializeInstance: 'Örneği başlatmak için yönetici girişi gerekli',\n      emailAlreadyInUse: 'E-posta adresi zaten kullanımda',\n      emailOrUsername: 'E-posta adresi veya kullanıcı adı',\n      invalidCredentials: 'Geçersiz kimlik bilgileri',\n      invalidEmailOrUsername: 'Geçersiz e-posta adresi veya kullanıcı adı',\n      invalidPassword: 'Hatalı şifre',\n      logIn_title: 'Giriş yap',\n      noInternetConnection: 'Internet bağlantısı yok',\n      or: 'Veya',\n      pageNotFound_title: 'Sayfa bulunamadı',\n      password: 'Şifre',\n      poweredByPlanka: 'PLANKA tarafından desteklenmektedir',\n      serverConnectionFailed: 'Sunucu bağlantı hatası',\n      unknownError: 'Bilinmeyen hata, daha sonra tekrar deneyin',\n      useSingleSignOn: 'Tek oturum açma kullan',\n      usernameAlreadyInUse: 'Kullanıcı adı zaten kullanımda',\n      whoops_title: 'Hata!',\n    },\n\n    action: {\n      cancelAndClose: 'İptal et ve kapat',\n      continue: 'Devam et',\n      debugSso: 'SSO hatalarını ayıkla',\n      goBack: 'Geri dön',\n      goHome: 'Ana sayfaya git',\n      logIn: 'Giriş yap',\n      logInWithSso: 'SSO ile giriş yap',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/tr-TR/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Bu başlıksız bir metindir.\\nHem başlık hem de metin\\nkalın, italik, renkli,\\nüstü çizili ve altı çizili olarak vurgulanabilir.\",\n    \"text-with-head\": \"Bu başlıklı bir metindir.\\nHem başlık hem de metin\\nkalın, italik, renkli,\\nüstü çizili ve altı çizili olarak vurgulanabilir.\",\n    \"heading\": \"Başlık\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Markdown editöründe hata\",\n    \"settings_wysiwyg\": \"Görsel editör (wysiwyg)\",\n    \"settings_markup\": \"Markdown işaretleme\",\n    \"markup_placeholder\": \"Markdown işaretlemesi girin...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Kaldır\",\n    \"empty_option\": \"Eşleşme bulunamadı\",\n    \"show_line_numbers\": \"Satır numaralandırma\"\n  },\n  \"common\": {\n    \"delete\": \"Sil\",\n    \"edit\": \"Düzenle\",\n    \"toolbar_action_disabled\": \"Uyumsuz işaretleme öğesi\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"İptal\",\n    \"common_action_submit\": \"Gönder\",\n    \"common_action_upload\": \"Seç\",\n    \"common_tab_attach\": \"Cihazdan ekle\",\n    \"common_tab_link\": \"Bağlantı ile ekle\",\n    \"common_link\": \"Bağlantı\",\n    \"common_sizes\": \"Boyut, px\",\n    \"image_name\": \"Başlık\",\n    \"image_link_href\": \"Resim bağlantısı\",\n    \"image_link_href_help\": \"Resim bağlantısının yönlendirdiği adres.\",\n    \"image_alt\": \"Alt metin\",\n    \"image_alt_help\": \"Resim yüklenemezse alt metin görüntülenir.\",\n    \"image_upload_help\": \"1 MB'dan büyük olmayan JPEG, GIF veya PNG resmi.\",\n    \"image_upload_failed\": \"Resim eklenemedi\",\n    \"image_size_width\": \"Genişlik\",\n    \"image_size_height\": \"Yükseklik\",\n    \"link_url_help\": \"Bağlantının yönlendirdiği adres.\",\n    \"link_text\": \"Bağlantı metni\",\n    \"link_text_help\": \"Bağlantı olarak görüntülenen metin.\",\n    \"link_open_help\": \"Bağlantıyı yeni sekmede aç\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Başlık\",\n    \"header_hint\": \"# Metniniz\",\n    \"italic_title\": \"İtalik\",\n    \"italic_hint\": \"_Metniniz_\",\n    \"bold_title\": \"Kalın\",\n    \"bold_hint\": \"**Metniniz**\",\n    \"strikethrough_title\": \"Üstü çizili\",\n    \"strikethrough_hint\": \"~~Metniniz~~\",\n    \"blockquote_title\": \"Blok alıntı\",\n    \"blockquote_hint\": \"> Metniniz\",\n    \"code_title\": \"Kod\",\n    \"code_hint\": \"```Metniniz```\",\n    \"link_title\": \"Bağlantı\",\n    \"link_hint\": \"[Metniniz](url)\",\n    \"image_title\": \"Resim\",\n    \"image_hint\": \"![Metniniz](url)\",\n    \"list_title\": \"Liste öğesi\",\n    \"list_hint\": \"- Metniniz\",\n    \"numbered-list_title\": \"Numaralı liste\",\n    \"numbered-list_hint\": \"1. Metniniz\",\n    \"documentation\": \"Dokümantasyon\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Kalın\",\n    \"code\": \"Kod\",\n    \"code_inline\": \"Satır içi kod\",\n    \"codeblock\": \"Kod bloğu\",\n    \"colorify\": \"Metin rengi\",\n    \"colorify__color_blue\": \"Mavi\",\n    \"colorify__color_default\": \"Varsayılan\",\n    \"colorify__color_gray\": \"Gri\",\n    \"colorify__color_green\": \"Yeşil\",\n    \"colorify__color_orange\": \"Turuncu\",\n    \"colorify__color_red\": \"Kırmızı\",\n    \"colorify__color_violet\": \"Mor\",\n    \"colorify__color_yellow\": \"Sarı\",\n    \"colorify__group_text\": \"Metin\",\n    \"cut\": \"Kes\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emojiler WYSIWYG'de veya işaretleme ile manuel olarak eklenebilir\",\n    \"heading\": \"Başlık\",\n    \"heading1\": \"Başlık 1\",\n    \"heading2\": \"Başlık 2\",\n    \"heading3\": \"Başlık 3\",\n    \"heading4\": \"Başlık 4\",\n    \"heading5\": \"Başlık 5\",\n    \"heading6\": \"Başlık 6\",\n    \"hrule\": \"Ayırıcı\",\n    \"image\": \"Resim\",\n    \"italic\": \"İtalik\",\n    \"link\": \"Bağlantı\",\n    \"list\": \"Liste\",\n    \"list__action_lift\": \"Öğeyi yukarı taşı\",\n    \"list__action_sink\": \"Öğeyi aşağı taşı\",\n    \"list_action_disabled\": \"Liste mantığıyla çelişiyor\",\n    \"mark\": \"İşaretli\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Daha fazla eylem\",\n    \"note\": \"Not\",\n    \"olist\": \"Sıralı liste\",\n    \"quote\": \"Alıntı\",\n    \"redo\": \"Yinele\",\n    \"strike\": \"Üstü çizili\",\n    \"table\": \"Tablo\",\n    \"text\": \"Metin\",\n    \"ulist\": \"Madde işaretli liste\",\n    \"underline\": \"Altı çizili\",\n    \"undo\": \"Geri al\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Eğik çizgi komutlarını kullanmak için / yazın...\",\n    \"checkbox\": \"Görev açıklaması girin...\",\n    \"deflist_term\": \"Terim\",\n    \"deflist_desc\": \"Tanım açıklaması\",\n    \"heading\": \"Başlık\",\n    \"cut_title\": \"Başlık\",\n    \"cut_content\": \"Tıklandığında görüntülenecek içerik\",\n    \"note_title\": \"Başlık\",\n    \"note_content\": \"Not içeriği\",\n    \"table_cell\": \"Hücre içeriği\",\n    \"select_filter\": \"Dilleri ara...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Büyük/küçük harf duyarlı\",\n    \"label_whole-word\": \"Tam kelime\",\n    \"title\": \"Kodda ara\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Bulunamadı\"\n  },\n  \"widgets\": {\n    \"image\": \"Resim ekle\",\n    \"link\": \"Bağlantı ekle\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Bilgi\",\n    \"tip\": \"İpucu\",\n    \"warning\": \"Uyarı\",\n    \"alert\": \"Alarm\",\n    \"remove\": \"Kaldır\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Öncesine sütun ekle\",\n    \"column.add.after\": \"Sonrasına sütun ekle\",\n    \"column.remove\": \"Sütunu kaldır\",\n    \"column.remove.multiple\": \"Sütunları kaldır\",\n    \"row.add.before\": \"Öncesine satır ekle\",\n    \"row.add.after\": \"Sonrasına satır ekle\",\n    \"row.remove\": \"Satırı kaldır\",\n    \"row.remove.multiple\": \"Satırları kaldır\",\n    \"cells.clear\": \"Hücreleri temizle\",\n    \"table.remove\": \"Tabloyu kaldır\",\n    \"table.menu.cell.align.left\": \"Hücre içeriğini sola hizala\",\n    \"table.menu.cell.align.right\": \"Hücre içeriğini sağa hizala\",\n    \"table.menu.cell.align.center\": \"Hücre içeriğini ortala\",\n    \"table.menu.row.add\": \"Sonrasına satır ekle\",\n    \"table.menu.row.remove\": \"Satırı kaldır\",\n    \"table.menu.column.add\": \"Sonrasına sütun ekle\",\n    \"table.menu.column.remove\": \"Sütunu kaldır\",\n    \"table.menu.convert.yfm\": \"YFM tablosuna dönüştür\",\n    \"table.menu.table.remove\": \"Tabloyu kaldır\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/uk-UA/core.js",
    "content": "import dateFns from 'date-fns/locale/uk';\nimport timeAgo from 'javascript-time-ago/locale/uk';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'd/M/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMMM',\n    longDateTime: \"d MMMM 'о' p\",\n    fullDate: 'd MMMM y',\n    fullDateTime: \"d MMMM y 'о' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Про додаток',\n      aboutPlanka_title: 'Про PLANKA',\n      accessToken: 'Токен доступу',\n      account: 'Обліковий запис',\n      actions: 'Дії',\n      activateUser_title: 'Активувати користувача',\n      active: 'Активний',\n      addAttachment_title: 'Додати вкладення',\n      addCustomFieldGroup_title: 'Додати групу користувацьких полів',\n      addCustomField_title: 'Додати користувацьке поле',\n      addManager_title: 'Додати менеджера',\n      addMember_title: 'Додати учасника',\n      addTaskList_title: 'Додати список завдань',\n      addUser_title: 'Додати користувача',\n      admin: 'Адмін',\n      administration: 'Адміністратор',\n      all: 'Все',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Всі зміни будуть автоматично збережені<br />після відновлення підключення.',\n      alphabetically: 'За алфавітом',\n      alwaysDisplayCardCreator: 'Завжди відображати творця картки',\n      apiKeyCreated_title: 'Ключ API створено',\n      apiKey_title: 'Ключ API',\n      archive: 'Архів',\n      archiveCard_title: 'Архівувати картку',\n      archiveCards_title: 'Архівувати картки',\n      areYouSureYouWantToActivateThisUser: 'Ви впевнені, що хочете активувати цього користувача?',\n      areYouSureYouWantToArchiveCards: 'Ви впевнені, що хочете архівувати картки?',\n      areYouSureYouWantToArchiveThisCard: 'Ви впевнені, що хочете заархівувати цю картку?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Ви впевнені, що хочете призначити цього менеджера проекту власником?',\n      areYouSureYouWantToDeactivateThisUser:\n        'Ви впевнені, що хочете деактивувати цього користувача?',\n      areYouSureYouWantToDeleteThisApiKey: 'Ви впевнені, що хочете видалити цей ключ API?',\n      areYouSureYouWantToDeleteThisAttachment: 'Ви впевнені, що хочете видалити це вкладення?',\n      areYouSureYouWantToDeleteThisBackgroundImage:\n        'Ви впевнені, що хочете видалити це фонове зображення?',\n      areYouSureYouWantToDeleteThisBoard: 'Ви впевнені, що хочете видалити цю дошку?',\n      areYouSureYouWantToDeleteThisCard: 'Ви впевнені, що хочете видалити цю картку?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Ви впевнені, що хочете видалити цю картку назавжди?',\n      areYouSureYouWantToDeleteThisComment: 'Ви впевнені, що хочете видалити цей коментар?',\n      areYouSureYouWantToDeleteThisCustomField: 'Ви впевнені, що хочете видалити це кастомне поле?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Ви впевнені, що хочете видалити цю групу кастомних полів?',\n      areYouSureYouWantToDeleteThisLabel: 'Ви впевнені, що хочете видалити цю мітку?',\n      areYouSureYouWantToDeleteThisList:\n        'Ви впевнені, що хочете видалити цей список? Усі картки будуть переміщені до смітника.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Ви впевнені, що хочете видалити цю службу сповіщень?',\n      areYouSureYouWantToDeleteThisProject: 'Ви впевнені, що хочете видалити цей проект?',\n      areYouSureYouWantToDeleteThisTask: 'Ви впевнені, що хочете видалити це завдання?',\n      areYouSureYouWantToDeleteThisTaskList: 'Ви впевнені, що хочете видалити цей список завдань?',\n      areYouSureYouWantToDeleteThisUser: 'Ви впевнені, що хочете видалити цього користувача?',\n      areYouSureYouWantToDeleteThisWebhook: 'Ви впевнені, що хочете видалити цей вебхук?',\n      areYouSureYouWantToEmptyTrash: 'Ви впевнені, що хочете очистити смітник?',\n      areYouSureYouWantToLeaveBoard: 'Ви впевнені, що хочете залишити дошку?',\n      areYouSureYouWantToLeaveProject: 'Ви впевнені, що хочете залишити проект?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Ви впевнені, що хочете зробити цей проект приватним?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Ви впевнені, що хочете зробити цей проект спільним?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Ви впевнені, що хочете перегенерувати цей ключ API? Попередній ключ більше не працюватиме.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Ви впевнені, що хочете видалити цього менеджера з проекту?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Ви впевнені, що хочете видалити цього учасника з дошки?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        \"Ви впевнені, що хочете відв'язати SSO від цього користувача? Це дозволить користувачу входити за допомогою пароля.\",\n      assignAsOwner_title: 'Призначити власником',\n      atLeastOneListMustBePresent: 'Принаймні один список повинен бути присутнім',\n      attachment: 'Вкладення',\n      attachments: 'Вкладення',\n      authentication: 'Аутентифікація',\n      background: 'Фон',\n      baseCustomFields_title: 'Базові користувацькі поля',\n      baseGroup: 'Базова група',\n      board: 'Дошка',\n      boardActions_title: 'Дії з дошкою',\n      boardNotFound_title: 'Дошку не знайдено',\n      boardSubscribed: 'Підписано на дошку',\n      boardUser: 'Користувач форуму',\n      byCreationTime: 'За часом створення',\n      byDefault: 'За замовчуванням',\n      byDueDate: 'За терміном',\n      canBeInvitedToWorkInBoards: 'Можуть бути запрошені до роботи в дошках.',\n      canComment: 'Може коментувати',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Може створювати власні проекти та бути запрошеним до роботи в інших.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Можна редагувати макет дошки та призначати учасників на картки.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Може керувати загальними налаштуваннями системи та виступати в ролі власника проекту.',\n      canOnlyViewBoard: 'Може лише переглядати дошку.',\n      cardActions_title: 'Дії з карткою',\n      cardNotFound_title: 'Картку не знайдено',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Картки з цього списку доступні всім членам правління.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Картки з цього списку завершені і готові до архівування.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Картки з цього списку готові до роботи.',\n      clickHereOrRefreshPageToUpdate: '<0>Натисніть тут</0> або оновіть сторінку для оновлення.',\n      clientHostnameInEhlo: \"Ім'я хоста клієнта в EHLO\",\n      closed: 'Закрито',\n      color: 'Колір',\n      comments: 'Коментарі',\n      contentExceedsLimit: 'Вміст перевищує {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Вміст цього вкладення занадто великий для відображення.',\n      copy_inline: 'копія',\n      createBoard_title: 'Створити дошку',\n      createCustomFieldGroup_title: 'Створити групу користувацьких полів',\n      createLabel_title: 'Створити мітку',\n      createNewOneOrSelectExistingOne: 'Створіть нову або виберіть<br />існуючу.',\n      createProject_title: 'Створити проект',\n      createTextFile_title: 'Створити текстовий файл',\n      creator: 'Творець',\n      currentPassword: 'Поточний пароль',\n      currentUser: 'Поточний користувач',\n      customFieldGroup_title: 'Користувацька група полів',\n      customFieldGroups_title: 'Користувацькі групи полів',\n      customField_title: 'Користувацьке поле',\n      customFields_title: 'Користувацькі поля',\n      customerPanel_title: 'Панель клієнта',\n      dangerZone_title: 'Небезпечна зона',\n      date: 'Дата',\n      deactivateUser_title: 'Деактивувати користувача',\n      defaultCardType_title: 'Тип картки за замовчуванням',\n      defaultFrom: 'За замовчуванням \"від\"',\n      defaultView_title: 'Вигляд за замовчуванням',\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        'Видаліть усі дошки, щоб мати змогу видалити цей проект',\n      deleteApiKey_title: 'Видалити ключ API',\n      deleteAttachment_title: 'Видалити вкладення',\n      deleteBackgroundImage_title: 'Видалити фонове зображення',\n      deleteBoard_title: 'Видалити дошку',\n      deleteCardForever_title: 'Видалити картку назавжди',\n      deleteCard_title: 'Видалити картку',\n      deleteComment_title: 'Видалити коментар',\n      deleteCustomFieldGroup_title: 'Видалити групу користувацьких полів',\n      deleteCustomField_title: 'Видалити користувацьке поле',\n      deleteLabel_title: 'Видалити мітку',\n      deleteList_title: 'Видалити список',\n      deleteNotificationService_title: 'Видалити службу сповіщень',\n      deleteProject_title: 'Видалити проект',\n      deleteTaskList_title: 'Видалити список завдань',\n      deleteTask_title: 'Видалити завдання',\n      deleteUser_title: 'Видалити користувача',\n      deleteWebhook_title: 'Видалити вебхук',\n      deletedUser_title: 'Видалений користувач',\n      description: 'Опис',\n      display: 'Дисплей',\n      displayCardAges: 'Відображати вік карток',\n      dropFileToUpload: 'Перетягніть файл для завантаження',\n      dueDate_title: 'Крайній термін',\n      dynamicAndUnevenlySpacedLayout: 'Динамічна та нерівномірна верстка.',\n      editAttachment_title: 'Редагувати вкладення',\n      editAvatar_title: 'Редагувати аватар',\n      editColor_title: 'Редагувати колір',\n      editCustomFieldGroup_title: 'Редагувати групу користувацьких полів',\n      editCustomField_title: 'Редагувати користувацьке поле',\n      editDueDate_title: 'Редагувати крайній термін',\n      editEmail_title: 'Редагувати електронну пошту',\n      editInformation_title: 'Редагувати інформацію',\n      editLabel_title: 'Редагувати мітку',\n      editPassword_title: 'Редагувати пароль',\n      editPermissions_title: 'Редагувати дозволи',\n      editRole_title: 'Редагувати роль',\n      editStopwatch_title: 'Редагувати секундомір',\n      editType_title: 'Редагувати тип',\n      editUsername_title: \"Редагувати ім'я користувача\",\n      editor: 'Редактор',\n      editors: 'Редактори',\n      email: 'Електронна пошта',\n      emptyTrash_title: 'Очистити смітник',\n      enterCardTitle: 'Введіть назву картки...',\n      enterDescription: 'Введіть опис...',\n      enterFilename: \"Введіть ім'я файлу\",\n      enterListTitle: 'Введіть назву списку...',\n      enterTaskDescription: 'Введіть опис завдання...',\n      events: 'Події',\n      excludedEvents: 'Виключені події',\n      expandTaskListsByDefault: 'Розгортати списки завдань за замовчуванням',\n      filterByLabels_title: 'Фільтрувати за мітками',\n      filterByMembers_title: 'Фільтрувати за учасниками',\n      forPersonalProjects: 'Для особистих проектів.',\n      forTeamBasedProjects: 'Для командних проектів.',\n      fromComputer_title: \"З комп'ютера\",\n      fromTrello: 'З Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Повний ключ приховано з міркувань безпеки. Перегенеруйте його, щоб створити новий.',\n      general: 'Загальне',\n      gradients: 'Градієнти',\n      grid: 'Сітка',\n      hideCompletedTasks: 'Приховати виконані завдання',\n      hideFromProjectListAndFavorites: 'Приховати зі списку проектів та обраного',\n      host: 'Хост',\n      hours: 'Години',\n      identity: 'Особистість',\n      importBoard_title: 'Імпортувати дошку',\n      information: 'Інформація',\n      invalidCurrentPassword: 'Невірний поточний пароль',\n      kanban: 'Канбан',\n      labels: 'Мітки',\n      language: 'Мова',\n      leaveBoard_title: 'Покинути дошку',\n      leaveProject_title: 'Покинути проект',\n      limitCardTypesToDefaultOne: 'Обмежте типи карток до одного за замовчуванням',\n      linkToCard: 'Посилання на картку',\n      list: 'Список',\n      listActions_title: 'Дії зі списком',\n      lists: 'Списки',\n      makeProjectPrivate_title: 'Зробити проект приватним',\n      makeProjectShared_title: 'Зробити проект спільним',\n      managers: 'Менеджери',\n      memberActions_title: 'Дії учасників',\n      members: 'Учасники',\n      minutes: 'Хвилини',\n      moreActions: 'Більше дій',\n      moreActions_title: 'Більше дій',\n      moveCard_title: 'Перемістити картку',\n      moveList_title: 'Перемістити список',\n      myOwn_title: 'Мої власні',\n      name: 'Назва',\n      newEmail: 'Нова електронна пошта',\n      newPassword: 'Новий пароль',\n      newUsername: \"Нове ім'я користувача\",\n      newVersionAvailable: 'Доступна нова версія',\n      newestFirst: 'Найновіші перші',\n      noApiKeyCreated: 'Ключ API не створено.',\n      noBoards: 'Немає дошок',\n      noCardsFound: 'Карток не знайдено.',\n      noConnectionToServer: 'Відсутнє підключення до сервера',\n      noLists: 'Немає списків',\n      noProjects: 'Немає проектів',\n      noUnreadNotifications: 'Немає непрочитаних сповіщень.',\n      notifications: 'Сповіщення',\n      oldestFirst: 'Найстарший перший',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Щоб зробити цей проект приватним, має залишитися лише один менеджер',\n      openBoard_title: 'Відкрити дошку',\n      optional_inline: 'опціонально',\n      organization: 'Організація',\n      others: 'Інші',\n      passwordIsSet: 'Пароль встановлено',\n      phone: 'Телефон',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA використовує <1><0>Apprise</0></1> для надсилання сповіщень на понад 100 популярних сервісів.',\n      port: 'Порт',\n      preferences: 'Уподобання',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Порада: натисніть Ctrl-V (⌘V на Mac), щоб додати вкладення з буфера обміну.',\n      private: 'Приватний',\n      project: 'Проект',\n      projectNotFound_title: 'Проект не знайдено',\n      projectOwner: 'Власник проекту',\n      referenceDataAndKnowledgeStorage: 'Довідкові дані та сховище знань.',\n      regenerateApiKey_title: 'Перегенерувати ключ API',\n      rejectUnauthorizedTlsCertificates: 'Відхиляти неавторизовані TLS-сертифікати',\n      removeManager_title: 'Видалити менеджера',\n      removeMember_title: 'Видалити учасника',\n      role: 'Роль',\n      saveThisKeyItWillNotBeShownAgain: 'Збережіть цей ключ — він більше не відображатиметься!',\n      searchCards: 'Картки пошуку...',\n      searchCustomFieldGroups: 'Пошук користувацьких груп полів...',\n      searchCustomFields: 'Пошук користувацьких полів...',\n      searchLabels: 'Пошук міток...',\n      searchLists: 'Пошук списків...',\n      searchMembers: 'Пошук учасників...',\n      searchProjects: 'Пошук проектів...',\n      searchUsers: 'Пошук користувачів...',\n      seconds: 'Секунди',\n      selectAssignee_title: 'Вибрати виконавця',\n      selectBoard: 'Вибрати дошку',\n      selectList: 'Вибрати список',\n      selectListToRestoreThisCard: 'Виберіть список для відновлення цієї картки',\n      selectOrder_title: 'Вибрати порядок',\n      selectPermissions_title: 'Вибрати дозволи',\n      selectProject: 'Вибрати проект',\n      selectRole_title: 'Вибрати роль',\n      selectType_title: 'Вибрати тип',\n      sequentialDisplayOfCards: 'Послідовне відображення карток.',\n      settings: 'Налаштування',\n      shared: 'Спільне',\n      sharedWithMe_title: 'Поділені зі мною',\n      showOnFrontOfCard: 'Показати на лицьовій стороні картки',\n      smtp: 'SMTP',\n      sortList_title: 'Сортувати список',\n      sourceCardIsNoLongerAvailableForCopying: 'Вихідна картка більше недоступна для копіювання.',\n      sourceCardIsNoLongerAvailableForMoving: 'Вихідна картка більше недоступна для переміщення.',\n      stopwatch: 'Секундомір',\n      story: 'Історія',\n      subscribeToCardWhenCommenting: 'Підпишіться на картку при коментуванні',\n      subscribeToMyOwnCardsByDefault: 'Підписатися на свої картки за замовчуванням',\n      taskActions_title: 'Дії з завданням',\n      taskAssignmentAndProjectCompletion: 'Постановка завдань і завершення проекту.',\n      taskListActions_title: 'Дії для списку завдань',\n      taskList_title: 'Список завдань',\n      team: 'Команда',\n      termsOfService_title: 'Умови використання',\n      testLog_title: 'Журнал тестування',\n      thereIsNoPreviewAvailableForThisAttachment: 'Для цього вкладення немає доступного перегляду.',\n      time: 'Час',\n      title: 'Назва',\n      trash: 'Сміття',\n      trashHasBeenSuccessfullyEmptied: 'Сміття успішно очищено.',\n      turnOffRecentCardHighlighting: 'Вимкнути підсвічування останніх карток',\n      typeNameToConfirm: \"Введіть ім'я для підтвердження.\",\n      typeTitleToConfirm: 'Введіть назву, щоб підтвердити.',\n      unlinkSso_title: \"Відв'язка SSO\",\n      unsavedChanges: 'Незбережені зміни',\n      uploadFailedFileIsTooBig: 'Помилка завантаження: файл занадто великий.',\n      uploadFailedNotEnoughStorageSpace: 'Помилка завантаження: недостатньо місця для зберігання.',\n      uploadedImages: 'Завантажені зображення',\n      url: 'Посилання',\n      useSecureConnection: \"Використовувати безпечне з'єднання\",\n      userActions_title: 'Дії користувача',\n      userAddedCardToList: '<0>{{user}}</0> додав(ла) <2>{{card}}</2> до {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> додав(ла) цю картку до {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> додав(ла) {{addedUser}} до <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> додав(ла) {{addedUser}} до цієї картки',\n      userAddedYouToCard: '<0>{{user}}</0> додав(ла) вас до <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> виконав(ла) {{task}} на <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> виконав(ла) {{task}} на цій картці',\n      userJoinedCard: '<0>{{user}}</0> приєднав(лася) до <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> приєднав(лася) до цієї картки',\n      userLeftCard: '<0>{{user}}</0> залишив(ла) <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> залишив(ла) новий коментар «{{comment}}» до <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> залишив(ла) цю картку',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> позначив(ла) {{task}} як незавершене на <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> позначив(ла) {{task}} як незавершене на цій картці',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> згадав(ла) вас у коментарі «{{comment}}» до <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> перемістив(ла) <2>{{card}}</2> з {{fromList}} в {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> перемістив(ла) цю картку з {{fromList}} в {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> видалив(ла) {{removedUser}} з <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> видалив(ла) {{removedUser}} з цієї картки',\n      username: \"Ім'я користувача\",\n      users: 'Користувачі',\n      viewer: 'Переглядач',\n      viewers: 'Переглядачі',\n      visualTaskManagementWithLists: 'Візуальне управління завданнями за допомогою списків.',\n      webhooks: 'Вебхуки',\n      whatsNew_title: 'Що нового',\n      withoutBaseGroup: 'Без базової групи',\n      writeComment: 'Написати коментар...',\n    },\n\n    action: {\n      activateUser: 'Активувати користувача',\n      activateUser_title: 'Активувати користувача',\n      addAnotherCard: 'Додати іншу картку',\n      addAnotherList: 'Додати інший список',\n      addAnotherTask: 'Додати інше завдання',\n      addCard: 'Додати картку',\n      addCard_title: 'Додати картку',\n      addComment: 'Додати коментар',\n      addCustomField: 'Додати користувацьке поле',\n      addCustomFieldGroup: 'Додати групу користувацьких полів',\n      addList: 'Додати список',\n      addMember: 'Додати учасника',\n      addMoreDetailedDescription: 'Додати більш детальний опис',\n      addTask: 'Додати завдання',\n      addTaskList: 'Додати список завдань',\n      addToCard: 'Додати до картки',\n      addUser: 'Додати користувача',\n      addWebhook: 'Додати вебхук',\n      archive: 'Архівувати',\n      archiveCard: 'Архівувати картку',\n      archiveCard_title: 'Архівувати картку',\n      archiveCards: 'Архівувати картки',\n      archiveCards_title: 'Архівувати картки',\n      assignAsOwner: 'Призначити власником',\n      cancel: 'Скасувати',\n      copy: 'Копіювати',\n      copyCard_title: 'Копіювати картку',\n      createApiKey: 'Створити ключ API',\n      createBoard: 'Створити дошку',\n      createCustomFieldGroup: 'Створити групу користувацьких полів',\n      createFile: 'Створити файл',\n      createLabel: 'Створити мітку',\n      createNewLabel: 'Створити нову мітку',\n      createProject: 'Створити проект',\n      cut: 'Вирізати',\n      cutCard_title: 'Вирізати картку',\n      deactivateUser: 'Деактивувати користувача',\n      deactivateUser_title: 'Деактивувати користувача',\n      delete: 'Видалити',\n      deleteApiKey: 'Видалити ключ API',\n      deleteAttachment: 'Видалити вкладення',\n      deleteAvatar: 'Видалити аватар',\n      deleteBackgroundImage: 'Видалити фонове зображення',\n      deleteBoard: 'Видалити дошку',\n      deleteBoard_title: 'Видалити дошку',\n      deleteCard: 'Видалити картку',\n      deleteCardForever: 'Видалити картку назавжди',\n      deleteCard_title: 'Видалити картку',\n      deleteComment: 'Видалити коментар',\n      deleteCustomField: 'Видалити користувацьке поле',\n      deleteCustomFieldGroup: 'Видалити групу користувацьких полів',\n      deleteForever_title: 'Видалити назавжди',\n      deleteGroup: 'Видалити групу',\n      deleteLabel: 'Видалити мітку',\n      deleteList: 'Видалити список',\n      deleteList_title: 'Видалити список',\n      deleteNotificationService: 'Видалити службу сповіщень',\n      deleteProject: 'Видалити проект',\n      deleteProject_title: 'Видалити проект',\n      deleteTask: 'Видалити завдання',\n      deleteTaskList: 'Видалити список завдань',\n      deleteTask_title: 'Видалити завдання',\n      deleteUser: 'Видалити користувача',\n      deleteUser_title: 'Видалити користувача',\n      deleteWebhook: 'Видалити вебхук',\n      dismissAll: 'Скасувати всі',\n      download: 'Завантажити',\n      duplicateCard_title: 'Дублювати картку',\n      edit: 'Редагувати',\n      editColor_title: 'Редагувати колір',\n      editDescription_title: 'Редагувати опис',\n      editDueDate_title: 'Редагувати термін виконання',\n      editEmail_title: 'Редагувати e-mail',\n      editGroup: 'Редагувати групу',\n      editInformation_title: 'Редагувати інформацію',\n      editPassword_title: 'Редагувати пароль',\n      editPermissions: 'Редагувати дозволи',\n      editRole_title: 'Редагувати роль',\n      editStopwatch_title: 'Редагувати секундомір',\n      editTitle_title: 'Редагувати заголовок',\n      editType_title: 'Редагувати тип',\n      editUsername_title: \"Редагувати ім'я користувача\",\n      emptyTrash: 'Очистити смітник',\n      emptyTrash_title: 'Очистити смітник',\n      import: 'Імпортувати',\n      join: 'Приєднатися',\n      leave: 'Покинути',\n      leaveBoard: 'Залишити дошку',\n      leaveProject: 'Залишити проект',\n      logOut_title: 'Вийти',\n      makeCover_title: 'Зробити обкладинкою',\n      makeProjectPrivate: 'Зробити проект приватним',\n      makeProjectPrivate_title: 'Зробити проект приватним',\n      makeProjectShared: 'Зробити проект спільним',\n      makeProjectShared_title: 'Зробити проект спільним',\n      move: 'Перемістити',\n      moveCard_title: 'Перемістити картку',\n      moveList_title: 'Перемістити список',\n      regenerateApiKey: 'Перегенерувати ключ API',\n      remove: 'Видалити',\n      removeAssignee: 'Видалити виконавця',\n      removeColor: 'Видалити колір',\n      removeCover_title: 'Видалити обкладинку',\n      removeFromBoard: 'Вилучити з дошки',\n      removeFromProject: 'Вилучити з проекту',\n      removeManager: 'Вилучити керівника',\n      removeMember: 'Вилучити учасника',\n      restoreToList: 'Відновити до {{list}}',\n      returnToBoard: 'Повернутися до дошки',\n      save: 'Зберегти',\n      sendTestEmail: 'Надіслати тестовий електронний лист',\n      showActive: 'Показати активний',\n      showAllAttachments: 'Показати всі вкладення ({{hidden}} приховані)',\n      showCardsWithThisUser: 'Показати картки з цим користувачем',\n      showDeactivated: 'Показати деактивовано',\n      showFewerAttachments: 'Показати менше вкладень',\n      showLess: 'Показати менше',\n      showMore: 'Показати більше',\n      sortList_title: 'Сортувати список',\n      start: 'Почати',\n      stop: 'Зупинити',\n      subscribe: 'Підписатися',\n      unlinkSso: \"Відв'язати SSO\",\n      unlinkSso_title: \"Відв'язати SSO\",\n      unsubscribe: 'Відписатися',\n      uploadNewAvatar: 'Завантажити новий аватар',\n      uploadNewImage: 'Завантажити нове зображення',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/uk-UA/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'uk-UA',\n  country: 'ua',\n  name: 'Українська',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/uk-UA/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Досягнуто ліміту активних користувачів',\n      adminLoginRequiredToInitializeInstance:\n        'Потрібен вхід адміністратора для ініціалізації екземпляра',\n      emailAlreadyInUse: 'Електронна пошта вже використовується',\n      emailOrUsername: \"Електронна пошта або ім'я користувача\",\n      invalidCredentials: 'Неправильні облікові дані',\n      invalidEmailOrUsername: \"Неправильна електронна пошта або ім'я користувача\",\n      invalidPassword: 'Неправильний пароль',\n      logIn_title: 'Увійти',\n      noInternetConnection: 'Відсутнє підключення до інтернету',\n      or: 'Або',\n      pageNotFound_title: 'Сторінку не знайдено',\n      password: 'Пароль',\n      poweredByPlanka: 'Працює на <1>PLANKA</1>',\n      serverConnectionFailed: 'Не вдалося підключитися до сервера',\n      unknownError: 'Невідома помилка, спробуйте ще раз пізніше',\n      useSingleSignOn: 'Використовувати одночасний вхід',\n      usernameAlreadyInUse: \"Ім'я користувача вже використовується\",\n      whoops_title: 'Ой!',\n    },\n\n    action: {\n      cancelAndClose: 'Скасувати та закрити',\n      continue: 'Продовжити',\n      debugSso: 'Відлагодити SSO',\n      goBack: 'Назад',\n      goHome: 'На головну',\n      logIn: 'Увійти',\n      logInWithSso: 'Увійти за допомогою SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/uk-UA/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Це текст без заголовка.\\nЯк заголовок, так і текст\\nможуть бути виділені жирним, курсивом, кольором,\\nзакресленим та підкресленим.\",\n    \"text-with-head\": \"Це текст із заголовком.\\nЯк заголовок, так і текст\\nможуть бути виділені жирним, курсивом, кольором,\\nзакресленим та підкресленим.\",\n    \"heading\": \"Заголовок\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Помилка в редакторі markdown\",\n    \"settings_wysiwyg\": \"Візуальний редактор (wysiwyg)\",\n    \"settings_markup\": \"Розмітка markdown\",\n    \"markup_placeholder\": \"Введіть розмітку markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Видалити\",\n    \"empty_option\": \"Збігів не знайдено\",\n    \"show_line_numbers\": \"Нумерація рядків\"\n  },\n  \"common\": {\n    \"delete\": \"Видалити\",\n    \"edit\": \"Редагувати\",\n    \"toolbar_action_disabled\": \"Несумісний елемент розмітки\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Скасувати\",\n    \"common_action_submit\": \"Надіслати\",\n    \"common_action_upload\": \"Вибрати\",\n    \"common_tab_attach\": \"Додати з пристрою\",\n    \"common_tab_link\": \"Додати за посиланням\",\n    \"common_link\": \"Посилання\",\n    \"common_sizes\": \"Розмір, px\",\n    \"image_name\": \"Заголовок\",\n    \"image_link_href\": \"Посилання на зображення\",\n    \"image_link_href_help\": \"Адреса, на яку веде посилання на зображення.\",\n    \"image_alt\": \"Alt текст\",\n    \"image_alt_help\": \"Alt текст відображається, якщо зображення не може бути завантажене.\",\n    \"image_upload_help\": \"Зображення в форматі JPEG, GIF або PNG розміром не більше 1 МБ.\",\n    \"image_upload_failed\": \"Не вдалося додати зображення\",\n    \"image_size_width\": \"Ширина\",\n    \"image_size_height\": \"Висота\",\n    \"link_url_help\": \"Адреса, на яку веде посилання.\",\n    \"link_text\": \"Текст посилання\",\n    \"link_text_help\": \"Текст, що відображається як посилання.\",\n    \"link_open_help\": \"Відкрити посилання в новій вкладці\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Заголовок\",\n    \"header_hint\": \"# Ваш текст\",\n    \"italic_title\": \"Курсив\",\n    \"italic_hint\": \"_Ваш текст_\",\n    \"bold_title\": \"Жирний\",\n    \"bold_hint\": \"**Ваш текст**\",\n    \"strikethrough_title\": \"Закреслений\",\n    \"strikethrough_hint\": \"~~Ваш текст~~\",\n    \"blockquote_title\": \"Цитата\",\n    \"blockquote_hint\": \"> Ваш текст\",\n    \"code_title\": \"Код\",\n    \"code_hint\": \"```Ваш текст```\",\n    \"link_title\": \"Посилання\",\n    \"link_hint\": \"[Ваш текст](url)\",\n    \"image_title\": \"Зображення\",\n    \"image_hint\": \"![Ваш текст](url)\",\n    \"list_title\": \"Елемент списку\",\n    \"list_hint\": \"- Ваш текст\",\n    \"numbered-list_title\": \"Нумерований список\",\n    \"numbered-list_hint\": \"1. Ваш текст\",\n    \"documentation\": \"Документація\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Жирний\",\n    \"code\": \"Код\",\n    \"code_inline\": \"Вбудований код\",\n    \"codeblock\": \"Блок коду\",\n    \"colorify\": \"Колір тексту\",\n    \"colorify__color_blue\": \"Синій\",\n    \"colorify__color_default\": \"За замовчуванням\",\n    \"colorify__color_gray\": \"Сірий\",\n    \"colorify__color_green\": \"Зелений\",\n    \"colorify__color_orange\": \"Помаранчевий\",\n    \"colorify__color_red\": \"Червоний\",\n    \"colorify__color_violet\": \"Фіолетовий\",\n    \"colorify__color_yellow\": \"Жовтий\",\n    \"colorify__group_text\": \"Текст\",\n    \"cut\": \"Вирізати\",\n    \"emoji\": \"Емодзі\",\n    \"emoji__hint\": \"Емодзі можна додати в WYSIWYG або вручну з розміткою\",\n    \"heading\": \"Заголовок\",\n    \"heading1\": \"Заголовок 1\",\n    \"heading2\": \"Заголовок 2\",\n    \"heading3\": \"Заголовок 3\",\n    \"heading4\": \"Заголовок 4\",\n    \"heading5\": \"Заголовок 5\",\n    \"heading6\": \"Заголовок 6\",\n    \"hrule\": \"Роздільник\",\n    \"image\": \"Зображення\",\n    \"italic\": \"Курсив\",\n    \"link\": \"Посилання\",\n    \"list\": \"Список\",\n    \"list__action_lift\": \"Підняти елемент\",\n    \"list__action_sink\": \"Опустити елемент\",\n    \"list_action_disabled\": \"Суперечить логіці списку\",\n    \"mark\": \"Виділити\",\n    \"mono\": \"Моноширинний\",\n    \"more_action\": \"Більше дій\",\n    \"note\": \"Замітка\",\n    \"olist\": \"Упорядкований список\",\n    \"quote\": \"Цитата\",\n    \"redo\": \"Повторити\",\n    \"strike\": \"Закреслений\",\n    \"table\": \"Таблиця\",\n    \"text\": \"Текст\",\n    \"ulist\": \"Маркований список\",\n    \"underline\": \"Підкреслений\",\n    \"undo\": \"Скасувати\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Введіть / для використання команд через слеш...\",\n    \"checkbox\": \"Введіть опис завдання...\",\n    \"deflist_term\": \"Термін\",\n    \"deflist_desc\": \"Опис визначення\",\n    \"heading\": \"Заголовок\",\n    \"cut_title\": \"Заголовок\",\n    \"cut_content\": \"Вміст для відображення при кліку\",\n    \"note_title\": \"Заголовок\",\n    \"note_content\": \"Вміст замітки\",\n    \"table_cell\": \"Вміст комірки\",\n    \"select_filter\": \"Пошук мов...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Чутливість до регістру\",\n    \"label_whole-word\": \"Повне слово\",\n    \"title\": \"Пошук у коді\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Не знайдено\"\n  },\n  \"widgets\": {\n    \"image\": \"Додати зображення\",\n    \"link\": \"Додати посилання\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Інформація\",\n    \"tip\": \"Порада\",\n    \"warning\": \"Попередження\",\n    \"alert\": \"Сповіщення\",\n    \"remove\": \"Видалити\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Додати стовпець перед\",\n    \"column.add.after\": \"Додати стовпець після\",\n    \"column.remove\": \"Видалити стовпець\",\n    \"column.remove.multiple\": \"Видалити стовпці\",\n    \"row.add.before\": \"Додати рядок перед\",\n    \"row.add.after\": \"Додати рядок після\",\n    \"row.remove\": \"Видалити рядок\",\n    \"row.remove.multiple\": \"Видалити рядки\",\n    \"cells.clear\": \"Очистити комірки\",\n    \"table.remove\": \"Видалити таблицю\",\n    \"table.menu.cell.align.left\": \"Вирівняти вміст комірки по лівому краю\",\n    \"table.menu.cell.align.right\": \"Вирівняти вміст комірки по правому краю\",\n    \"table.menu.cell.align.center\": \"Вирівняти вміст комірки по центру\",\n    \"table.menu.row.add\": \"Додати рядок після\",\n    \"table.menu.row.remove\": \"Видалити рядок\",\n    \"table.menu.column.add\": \"Додати стовпець після\",\n    \"table.menu.column.remove\": \"Видалити стовпець\",\n    \"table.menu.convert.yfm\": \"Перетворити в таблицю YFM\",\n    \"table.menu.table.remove\": \"Видалити таблицю\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/uz-UZ/core.js",
    "content": "import dateFns from 'date-fns/locale/uz';\nimport timeAgo from 'javascript-time-ago/locale/uz';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'M/d/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'MMM d',\n    longDateTime: \"MMMM d 'at' p\",\n    fullDate: 'MMM d, y',\n    fullDateTime: \"MMMM d, y 'at' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Ilova haqida',\n      aboutPlanka_title: 'PLANKA haqida',\n      accessToken: 'Kirish tokeni',\n      account: 'Profil',\n      actions: 'Amallar',\n      activateUser_title: 'Foydalanuvchini faollashtirish',\n      active: 'Faol',\n      addAttachment_title: \"Ilova qo'shish\",\n      addCustomFieldGroup_title: \"Maxsus maydon guruhini qo'shish\",\n      addCustomField_title: \"Maxsus maydon qo'shish\",\n      addManager_title: \"Boshqaruvchi qo'shish\",\n      addMember_title: \"Yangi a'zo qo'shish\",\n      addTaskList_title: \"Vazifalar ro'yxatini qo'shish\",\n      addUser_title: \"Foydalanuvchi qo'shish\",\n      admin: 'Administrator',\n      administration: 'Boshqaruv',\n      all: 'Barchasi',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        \"Barcha o'zgarishlar tarmoq ulanishi tiklangandan so'ng<br />avtomatik saqlanadi.\",\n      alphabetically: 'Alifbo tartibida',\n      alwaysDisplayCardCreator: \"Karta yaratuvchisini doim ko'rsatish\",\n      apiKeyCreated_title: 'API kaliti yaratildi',\n      apiKey_title: 'API kaliti',\n      archive: 'Arxiv',\n      archiveCard_title: 'Kartani arxivlash',\n      archiveCards_title: 'Kartalarni arxivlash',\n      areYouSureYouWantToActivateThisUser: 'Ushbu foydalanuvchini faollashtirishni xohlaysizmi?',\n      areYouSureYouWantToArchiveCards: 'Kartalarni arxivlashni xohlaysizmi?',\n      areYouSureYouWantToArchiveThisCard: 'Ushbu kartani arxivlashni xohlaysizmi?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Ushbu loyiha menejerini egasi sifatida tayinlashni xohlaysizmi?',\n      areYouSureYouWantToDeactivateThisUser:\n        'Ushbu foydalanuvchini faolsizlantirishni xohlaysizmi?',\n      areYouSureYouWantToDeleteThisApiKey: \"Ushbu API kalitini o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisAttachment: \"Ushbu biriktirmani o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisBackgroundImage: \"Ushbu fon rasmini o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisBoard: \"Ushbu doskani o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisCard: \"Ushbu kartani o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisCardForever: \"Ushbu kartani butunlay o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisComment: \"Ushbu izohni o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisCustomField: \"Ushbu maxsus maydonni o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        \"Ushbu maxsus maydon guruhini o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisLabel: \"Ushbu yorliqni o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisList:\n        \"Ushbu ro'yxatni o'chirmoqchimisiz? Barcha kartalar axlat qutisiga ko'chiriladi.\",\n      areYouSureYouWantToDeleteThisNotificationService:\n        \"Ushbu bildirishnoma xizmatini o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisProject: \"Ushbu loyihani o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisTask: \"Ushbu topshiriqni o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisTaskList: \"Ushbu vazifalar ro'yxatini o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisUser: \"Ushbu foydalanuvchini o'chirmoqchimisiz?\",\n      areYouSureYouWantToDeleteThisWebhook: \"Ushbu webhookni o'chirmoqchimisiz?\",\n      areYouSureYouWantToEmptyTrash: \"Axlatni bo'shatmoqchimisiz?\",\n      areYouSureYouWantToLeaveBoard: 'Ushbu doskadan chiqmoqchimisiz?',\n      areYouSureYouWantToLeaveProject: 'Ushbu loyihadan chiqmoqchimisiz?',\n      areYouSureYouWantToMakeThisProjectPrivate: 'Ushbu loyihani shaxsiy qilmoqchimisiz?',\n      areYouSureYouWantToMakeThisProjectShared: 'Ushbu loyihani umumiy qilmoqchimisiz?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Ushbu API kalitini qayta yaratmoqchimisiz? Oldingi kalit endi ishlamaydi.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        \"Ushbu boshqaruvchini loyihadan o'chirmoqchimisiz?\",\n      areYouSureYouWantToRemoveThisMemberFromBoard: \"Ushbu a'zoni doskadan o'chirmoqchimisiz?\",\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        \"Haqiqatan ham bu foydalanuvchidan SSO bog'lanishini uzishni xohlaysizmi? Bu foydalanuvchiga parol bilan kirishga imkon beradi.\",\n      assignAsOwner_title: 'Egasi sifatida tayinlash',\n      atLeastOneListMustBePresent: \"Kamida bitta ro'yxat bo'lishi kerak\",\n      attachment: 'Ilova',\n      attachments: 'Ilovalar',\n      authentication: 'Autentifikatsiya',\n      background: 'Orqa fon',\n      baseCustomFields_title: 'Asosiy maxsus maydonlar',\n      baseGroup: 'Asosiy guruh',\n      board: 'Doska',\n      boardActions_title: 'Doska amallari',\n      boardNotFound_title: 'Doska topilmadi',\n      boardSubscribed: \"Doskaga obuna bo'lindi\",\n      boardUser: 'Doska foydalanuvchisi',\n      byCreationTime: \"Yaratilish vaqti bo'yicha\",\n      byDefault: \"Standart bo'yicha\",\n      byDueDate: \"Muddat bo'yicha\",\n      canBeInvitedToWorkInBoards: 'Doskalarda ishlash uchun taklif qilinishi mumkin.',\n      canComment: 'Izoh qoldirishi mumkin',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        \"O'z loyihalarini yaratishi va boshqalarida ishlash uchun taklif qilinishi mumkin.\",\n      canEditBoardLayoutAndAssignMembersToCards:\n        \"Doska tartibini tahrirlashi va a'zolarni kartalarga tayinlashi mumkin.\",\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        \"Tizim bo'ylab sozlamalarni boshqarishi va loyiha egasi sifatida harakat qilishi mumkin.\",\n      canOnlyViewBoard: \"Faqat doskani ko'rishi mumkin.\",\n      cardActions_title: 'Karta amallari',\n      cardNotFound_title: 'Karta topilmadi',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        \"Ushbu ro'yxatdagi kartalar barcha doska a'zolari uchun mavjud.\",\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        \"Ushbu ro'yxatdagi kartalar tugallangan va arxivlashga tayyor.\",\n      cardsOnThisListAreReadyToBeWorkedOn: \"Ushbu ro'yxatdagi kartalar ustida ishlashga tayyor.\",\n      clickHereOrRefreshPageToUpdate:\n        '<0>Yangilash uchun bu yerga bosing</0> yoki sahifani yangilang.',\n      clientHostnameInEhlo: 'EHLO da mijoz host nomi',\n      closed: 'Yopiq',\n      color: 'Rang',\n      comments: 'Izohlar',\n      contentExceedsLimit: 'Kontent {{limit}} chegarasini oshirdi',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        \"Ushbu ilovaning mazmuni ko'rsatish uchun juda katta.\",\n      copy_inline: 'nusxa',\n      createBoard_title: 'Doska yaratish',\n      createCustomFieldGroup_title: 'Maxsus maydon guruhi yaratish',\n      createLabel_title: 'Yorliq yaratish',\n      createNewOneOrSelectExistingOne: \"Yangisini tanlash yoki<br />mavjud bo'lganini tanlash.\",\n      createProject_title: 'Loyiha yaratish',\n      createTextFile_title: 'Matnli fayl yaratish',\n      creator: 'Yaratuvchi',\n      currentPassword: 'Hozirgi parol',\n      currentUser: 'Joriy foydalanuvchi',\n      customFieldGroup_title: 'Maxsus maydon guruhi',\n      customFieldGroups_title: 'Maxsus maydon guruhlari',\n      customField_title: 'Maxsus maydon',\n      customFields_title: 'Maxsus maydonlar',\n      customerPanel_title: 'Mijoz paneli',\n      dangerZone_title: 'Xavfli hudud',\n      date: 'Sana',\n      deactivateUser_title: 'Foydalanuvchini faolsizlantirish',\n      defaultCardType_title: 'Standart karta turi',\n      defaultFrom: 'Standart dan',\n      defaultView_title: \"Standart ko'rinish\",\n      deleteAllBoardsToBeAbleToDeleteThisProject:\n        \"Ushbu loyihani o'chirish uchun barcha doskalarni o'chiring\",\n      deleteApiKey_title: \"API kalitini o'chirish\",\n      deleteAttachment_title: \"Ilovani o'chirish\",\n      deleteBackgroundImage_title: \"Orqa fon rasmini o'chirish\",\n      deleteBoard_title: \"Doskani o'chirish\",\n      deleteCardForever_title: \"Kartani butunlay o'chirish\",\n      deleteCard_title: \"Kartani o'chirish\",\n      deleteComment_title: \"Izohni o'chirish\",\n      deleteCustomFieldGroup_title: \"Maxsus maydon guruhini o'chirish\",\n      deleteCustomField_title: \"Maxsus maydonni o'chirish\",\n      deleteLabel_title: \"Yorliqni o'chirish\",\n      deleteList_title: \"Ro'yxatni o'chirish\",\n      deleteNotificationService_title: \"Bildirishnoma xizmatini o'chirish\",\n      deleteProject_title: \"Loyihani o'chirish\",\n      deleteTaskList_title: \"Vazifalar ro'yxatini o'chirish\",\n      deleteTask_title: \"Vazifani o'chirish\",\n      deleteUser_title: \"Foydalanuvchini o'chirish\",\n      deleteWebhook_title: \"Webhookni o'chirish\",\n      deletedUser_title: \"O'chirilgan foydalanuvchi\",\n      description: 'Tavsif',\n      display: \"Ko'rsatish\",\n      displayCardAges: \"Kartalar yoshini ko'rsatish\",\n      dropFileToUpload: 'Faylni yuklash uchun qoldiring',\n      dueDate_title: 'Muddati',\n      dynamicAndUnevenlySpacedLayout: 'Dinamik va notekis joylashtirilgan tartib.',\n      editAttachment_title: 'Ilovani tahrirlash',\n      editAvatar_title: 'Avatarni tahrirlash',\n      editColor_title: 'Rangni tahrirlash',\n      editCustomFieldGroup_title: 'Maxsus maydon guruhini tahrirlash',\n      editCustomField_title: 'Maxsus maydonni tahrirlash',\n      editDueDate_title: 'Muddatni tahrirlash',\n      editEmail_title: 'E-mail ni tahrirlash',\n      editInformation_title: \"Ma'lumotni tahrirlash\",\n      editLabel_title: 'Yorliqni tahrirlash',\n      editPassword_title: 'Parolni tahrirlash',\n      editPermissions_title: 'Ruxsatlarni tahrirlash',\n      editRole_title: 'Rolni tahrirlash',\n      editStopwatch_title: 'Taymerni tahrirlash',\n      editType_title: 'Turni tahrirlash',\n      editUsername_title: 'Foydalanuvchi nomini tahrirlash',\n      editor: 'Muharrir',\n      editors: 'Muharrirlar',\n      email: 'E-mail',\n      emptyTrash_title: \"Axlatni bo'shatish\",\n      enterCardTitle: 'Karta sarlavhasini kiriting...',\n      enterDescription: 'Tavsif kiriting...',\n      enterFilename: 'Fayl nomini kiriting',\n      enterListTitle: \"Ro'yxat sarlavhasini kiriting...\",\n      enterTaskDescription: 'Topshiriq sarlavhasini kiriting...',\n      events: 'Hodisalar',\n      excludedEvents: 'Istisno qilingan hodisalar',\n      expandTaskListsByDefault: \"Vazifalar ro'yxatini standart bo'yicha kengaytirish\",\n      filterByLabels_title: \"Yorliq bo'yicha filter\",\n      filterByMembers_title: \"A'zolar bo'yicha filter\",\n      forPersonalProjects: 'Shaxsiy loyihalar uchun.',\n      forTeamBasedProjects: 'Jamoa loyihalari uchun.',\n      fromComputer_title: 'Kompyuterdan',\n      fromTrello: 'Trello dan',\n      fullKeyIsHiddenForSecurityReasons:\n        \"To'liq kalit xavfsizlik sabablariga ko'ra yashirilgan. Yangi yaratish uchun uni qayta yarating.\",\n      general: 'Umumiy',\n      gradients: 'Gradientlar',\n      grid: 'Panjara',\n      hideCompletedTasks: 'Tugallangan vazifalarni yashirish',\n      hideFromProjectListAndFavorites: \"Loyihalar ro'yxati va sevimlilardan yashirish\",\n      host: 'Host',\n      hours: 'Soat',\n      identity: 'Shaxs',\n      importBoard_title: 'Doskani import qilish',\n      information: 'Maʻlumot',\n      invalidCurrentPassword: 'Hozirgi parol xato',\n      kanban: 'Kanban',\n      labels: 'Yorliqlar',\n      language: 'Til',\n      leaveBoard_title: 'Doskadan chiqish',\n      leaveProject_title: 'Loyihadan chiqish',\n      limitCardTypesToDefaultOne: 'Karta turlarini standart turiga cheklash',\n      linkToCard: 'Kartaga havola',\n      list: \"Ro'yxat\",\n      listActions_title: \"Ro'yxat amallari\",\n      lists: \"Ro'yxatlar\",\n      makeProjectPrivate_title: 'Loyihani shaxsiy qilish',\n      makeProjectShared_title: 'Loyihani umumiy qilish',\n      managers: 'Boshqaruvchilar',\n      memberActions_title: \"A'zo amallari\",\n      members: \"A'zolar\",\n      minutes: 'Minut',\n      moreActions: \"Ko'proq amallar\",\n      moreActions_title: \"Ko'proq amallar\",\n      moveCard_title: \"Kartani ko'chirish\",\n      moveList_title: \"Ro'yxatni ko'chirish\",\n      myOwn_title: 'Mening',\n      name: 'Ism',\n      newEmail: 'Yangi e-mail',\n      newPassword: 'Yangi parol',\n      newUsername: 'Yangi foydalanuvchi nomi',\n      newVersionAvailable: 'Yangi versiya mavjud',\n      newestFirst: 'Avval yangisi',\n      noApiKeyCreated: 'API kaliti yaratilmagan.',\n      noBoards: \"Doskalar yo'q\",\n      noCardsFound: 'Kartalar topilmadi.',\n      noConnectionToServer: \"Server bilan bog'lanish yo'q\",\n      noLists: \"Ro'yxatlar yo'q\",\n      noProjects: \"Loyihalar yo'q\",\n      noUnreadNotifications: \"O'qilmagan bildirishnomalar yo'q.\",\n      notifications: 'Bildirishnomalar',\n      oldestFirst: 'Avval eskisi',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Ushbu loyihani shaxsiy qilish uchun faqat bitta boshqaruvchi qolishi kerak',\n      openBoard_title: 'Doskani ochish',\n      optional_inline: 'ixtiyoriy',\n      organization: 'Tashkilot',\n      others: 'Boshqalar',\n      passwordIsSet: \"Parol o'rnatilgan\",\n      phone: 'Telefon',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA 100 dan ortiq mashhur xizmatlarga bildirishnomalar yuborish uchun <1><0>Apprise</0></1> dan foydalanadi.',\n      port: 'Port',\n      preferences: 'Afzalliklar',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        \"Tip: Buferdan ilova qo'shish uchun Ctrl-V (Mac da Cmd-V) ni bosing.\",\n      private: 'Shaxsiy',\n      project: 'Loyiha',\n      projectNotFound_title: 'Loyiha topilmadi',\n      projectOwner: 'Loyiha egasi',\n      referenceDataAndKnowledgeStorage: \"Ma'lumotnoma va bilim saqlash.\",\n      regenerateApiKey_title: 'API kalitini qayta yaratish',\n      rejectUnauthorizedTlsCertificates: 'Ruxsatsiz TLS sertifikatlarini rad etish',\n      removeManager_title: \"Boshqaruvchini o'chirish\",\n      removeMember_title: \"A'zoni o'chirish\",\n      role: 'Rol',\n      saveThisKeyItWillNotBeShownAgain: \"Ushbu kalitni saqlang — u boshqa ko'rsatilmaydi!\",\n      searchCards: 'Kartalarni qidirish...',\n      searchCustomFieldGroups: 'Maxsus maydon guruhlarini qidirish...',\n      searchCustomFields: 'Maxsus maydonlarni qidirish...',\n      searchLabels: 'Yorliqlarni qidirish...',\n      searchLists: \"Ro'yxatlarni qidirish...\",\n      searchMembers: \"A'zolarni qidirish...\",\n      searchProjects: 'Loyihalarni qidirish...',\n      searchUsers: 'Foydalanuvchilarni qidirish...',\n      seconds: 'Sekund',\n      selectAssignee_title: 'Ijrochini tanlash',\n      selectBoard: 'Doskani tanlash',\n      selectList: \"Ro'yxatni tanlash\",\n      selectListToRestoreThisCard: \"Ushbu kartani tiklash uchun ro'yxatni tanlang\",\n      selectOrder_title: 'Tartibni tanlash',\n      selectPermissions_title: 'Ruxsatlarni tanlash',\n      selectProject: \"Loyihani '\",\n      selectRole_title: 'Rolni tanlash',\n      selectType_title: 'Turni tanlash',\n      sequentialDisplayOfCards: \"Kartalarning ketma-ket ko'rsatilishi.\",\n      settings: 'Sozlamalar',\n      shared: 'Umumiy',\n      sharedWithMe_title: 'Men bilan umumiy',\n      showOnFrontOfCard: \"Karta old tomonida ko'rsatish\",\n      smtp: 'SMTP',\n      sortList_title: \"Ro'yxatni saralash\",\n      sourceCardIsNoLongerAvailableForCopying: 'Manba karta nusxalash uchun endi mavjud emas.',\n      sourceCardIsNoLongerAvailableForMoving: 'Manba karta koʻchirish uchun endi mavjud emas.',\n      stopwatch: 'Taymer',\n      story: 'Hikoya',\n      subscribeToCardWhenCommenting: \"Izoh qoldirganida kartaga obuna bo'lish\",\n      subscribeToMyOwnCardsByDefault: \"Odatiy holda o'z kartalarimga obuna bo'ling\",\n      taskActions_title: 'Vazifa amallari',\n      taskAssignmentAndProjectCompletion: 'Vazifa tayinlash va loyihani yakunlash.',\n      taskListActions_title: \"Vazifalar ro'yxati amallari\",\n      taskList_title: \"Vazifalar ro'yxati\",\n      team: 'Jamoa',\n      termsOfService_title: 'Xizmat shartlari',\n      testLog_title: 'Test jurnali',\n      thereIsNoPreviewAvailableForThisAttachment: \"Ushbu ilova uchun oldindan ko'rish mavjud emas.\",\n      time: 'Vaqt',\n      title: 'Sarlavha',\n      trash: 'Axlat',\n      trashHasBeenSuccessfullyEmptied: \"Axlat muvaffaqiyatli bo'shatildi.\",\n      turnOffRecentCardHighlighting: \"So'nggi kartalarni ajratib ko'rsatishni o'chirish\",\n      typeNameToConfirm: 'Tasdiqlash uchun nomni kiriting.',\n      typeTitleToConfirm: 'Tasdiqlash uchun sarlavhani kiriting.',\n      unlinkSso_title: \"SSO bog'lanishini uzish\",\n      unsavedChanges: \"Saqlanmagan o'zgarishlar\",\n      uploadFailedFileIsTooBig: 'Yuklash muvaffaqiyatsiz: fayl juda katta.',\n      uploadFailedNotEnoughStorageSpace: 'Yuklash muvaffaqiyatsiz: saqlash joyi yetarli emas.',\n      uploadedImages: 'Yuklangan rasmlar',\n      url: 'URL',\n      useSecureConnection: 'Xavfsiz ulanishdan foydalanish',\n      userActions_title: 'Foydalanuvchi amallari',\n      userAddedCardToList: \"<0>{{user}}</0> <2>{{card}}</2> kartani {{list}} ga qo'shdi\",\n      userAddedThisCardToList: \"<0>{{user}}</0> ushbu kartani {{list}} ga qo'shdi\",\n      userAddedUserToCard: \"<0>{{actorUser}}</0> {{addedUser}} ni <4>{{card}}</4> ga qo'shdi\",\n      userAddedUserToThisCard: \"<0>{{actorUser}}</0> {{addedUser}} ni ushbu kartaga qo'shdi\",\n      userAddedYouToCard: \"<0>{{user}}</0> sizni <2>{{card}}</2> ga qo'shdi\",\n      userCompletedTaskOnCard: '<0>{{user}}</0> {{task}} vazifasini <4>{{card}}</4> da yakunladi',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> {{task}} vazifasini ushbu kartada yakunladi',\n      userJoinedCard: \"<0>{{user}}</0> <2>{{card}}</2> ga qo'shildi\",\n      userJoinedThisCard: \"<0>{{user}}</0> ushbu kartaga qo'shildi\",\n      userLeftCard: '<0>{{user}}</0> <2>{{card}}</2> ni tark etdi',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> <2>{{card}}</2> ga yangi izoh qoldirdi «{{comment}}»',\n      userLeftThisCard: '<0>{{user}}</0> ushbu kartani tark etdi',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> {{task}} vazifasini <4>{{card}}</4> da tugallanmagan deb belgiladi',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> {{task}} vazifasini ushbu kartada tugallanmagan deb belgiladi',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> sizni <2>{{card}}</2> dagi «{{comment}}» izohda eslatdi',\n      userMovedCardFromListToList:\n        \"<0>{{user}}</0> <2>{{card}}</2> ni {{fromList}} dan {{toList}} ga ko'chirdi\",\n      userMovedThisCardFromListToList:\n        \"<0>{{user}}</0> ushbu kartani {{fromList}} dan {{toList}} ga ko'chirdi\",\n      userRemovedUserFromCard:\n        '<0>{{actorUser}}</0> {{removedUser}} ni <4>{{card}}</4> dan olib tashladi',\n      userRemovedUserFromThisCard:\n        '<0>{{actorUser}}</0> {{removedUser}} ni ushbu kartadan olib tashladi',\n      username: 'Foydalanuvchi nomi',\n      users: 'Foydalanuvchilar',\n      viewer: \"Ko'ruvchi\",\n      viewers: \"Ko'ruvchilar\",\n      visualTaskManagementWithLists: \"Ro'yxatlar bilan vizual vazifa boshqaruvi.\",\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Nima yangi',\n      withoutBaseGroup: 'Asosiy guruhsiz',\n      writeComment: 'Izoh yozish...',\n    },\n\n    action: {\n      activateUser: 'Foydalanuvchini faollashtirish',\n      activateUser_title: 'Foydalanuvchini faollashtirish',\n      addAnotherCard: \"Yana karta qo'shish\",\n      addAnotherList: \"Yana ro'yxat qo'shish\",\n      addAnotherTask: \"Yana vazifa qo'shish\",\n      addCard: \"Karta qo'shish\",\n      addCard_title: \"Karta qo'shish\",\n      addComment: \"Izoh qo'shish\",\n      addCustomField: \"Maxsus maydon qo'shish\",\n      addCustomFieldGroup: \"Maxsus maydon guruhi qo'shish\",\n      addList: \"Ro'yxat qo'shish\",\n      addMember: \"A'zo qo'shish\",\n      addMoreDetailedDescription: \"Batafsil izoh qo'shish\",\n      addTask: \"Vazifa qo'shish\",\n      addTaskList: \"Vazifalar ro'yxatini qo'shish\",\n      addToCard: \"Kartaga qo'shish\",\n      addUser: \"Foydalanuvchi qo'shish\",\n      addWebhook: \"Webhook qo'shish\",\n      archive: 'Arxivlash',\n      archiveCard: 'Kartani arxivlash',\n      archiveCard_title: 'Kartani arxivlash',\n      archiveCards: 'Kartalarni arxivlash',\n      archiveCards_title: 'Kartalarni arxivlash',\n      assignAsOwner: 'Egasi sifatida tayinlash',\n      cancel: 'Bekor qilish',\n      copy: 'Nusxalash',\n      copyCard_title: 'Kartani nusxalash',\n      createApiKey: 'API kalitini yaratish',\n      createBoard: 'Doska yaratish',\n      createCustomFieldGroup: 'Maxsus maydon guruhi yaratish',\n      createFile: 'Fayl yaratish',\n      createLabel: 'Yorliq yaratish',\n      createNewLabel: 'Yangi yorliq yaratish',\n      createProject: 'Loyiha yaratish',\n      cut: 'Kesish',\n      cutCard_title: 'Kartani kesish',\n      deactivateUser: 'Foydalanuvchini faolsizlantirish',\n      deactivateUser_title: 'Foydalanuvchini faolsizlantirish',\n      delete: \"O'chirish\",\n      deleteApiKey: \"API kalitini o'chirish\",\n      deleteAttachment: \"Ilovani o'chirish\",\n      deleteAvatar: \"Avatarni o'chirish\",\n      deleteBackgroundImage: \"Orqa fon rasmini o'chirish\",\n      deleteBoard: \"Doskani o'chirish\",\n      deleteBoard_title: \"Doskani o'chirish\",\n      deleteCard: \"Kartani o'chirish\",\n      deleteCardForever: \"Kartani butunlay o'chirish\",\n      deleteCard_title: \"Kartani o'chirish\",\n      deleteComment: \"Izohni o'chirish\",\n      deleteCustomField: \"Maxsus maydonni o'chirish\",\n      deleteCustomFieldGroup: \"Maxsus maydon guruhini o'chirish\",\n      deleteForever_title: \"Butunlay o'chirish\",\n      deleteGroup: \"Guruhni o'chirish\",\n      deleteLabel: \"Yorliqni o'chirish\",\n      deleteList: \"Ro'yxatni o'chirish\",\n      deleteList_title: \"Ro'yxatni o'chirish\",\n      deleteNotificationService: \"Bildirishnoma xizmatini o'chirish\",\n      deleteProject: \"Loyihani o'chirish\",\n      deleteProject_title: \"Loyihani o'chirish\",\n      deleteTask: \"Vazifani o'chirish\",\n      deleteTaskList: \"Vazifalar ro'yxatini o'chirish\",\n      deleteTask_title: \"Vazifani O'chirish\",\n      deleteUser: \"Foydalanuvchini o'chirish\",\n      deleteUser_title: \"Foydalanuvchini o'chirish\",\n      deleteWebhook: \"Webhookni o'chirish\",\n      dismissAll: 'Barchasini rad etish',\n      download: 'Yuklab olish',\n      duplicateCard_title: 'Kartani nusxalash',\n      edit: 'Tahrirlash',\n      editColor_title: 'Rangni tahrirlash',\n      editDescription_title: 'Izohni tahrirlash',\n      editDueDate_title: 'Muddatni tahrirlash',\n      editEmail_title: 'E-mailni tahrirlash',\n      editGroup: 'Guruhni tahrirlash',\n      editInformation_title: \"Ma'lumotni tahrirlash\",\n      editPassword_title: 'Parolni tahrirlash',\n      editPermissions: 'Ruxsatlarni tahrirlash',\n      editRole_title: 'Rolni tahrirlash',\n      editStopwatch_title: 'Taymerni tahrirlash',\n      editTitle_title: 'Sarlavhani tahrirlash',\n      editType_title: 'Turni tahrirlash',\n      editUsername_title: 'Foydalanuvchi nomini tahrirlash',\n      emptyTrash: \"Axlatni bo'shatish\",\n      emptyTrash_title: \"Axlatni bo'shatish\",\n      import: 'Import qilish',\n      join: \"Qo'shilish\",\n      leave: 'Tark etish',\n      leaveBoard: 'Doskadan chiqish',\n      leaveProject: 'Loyidan chiqish',\n      logOut_title: 'Chiqish',\n      makeCover_title: 'Muqova yasash',\n      makeProjectPrivate: 'Loyihani shaxsiy qilish',\n      makeProjectPrivate_title: 'Loyihani shaxsiy qilish',\n      makeProjectShared: 'Loyihani umumiy qilish',\n      makeProjectShared_title: 'Loyihani umumiy qilish',\n      move: \"Ko'chirish\",\n      moveCard_title: \"Kartani ko'chirish\",\n      moveList_title: \"Ro'yxatni ko'chirish\",\n      regenerateApiKey: 'API kalitini qayta yaratish',\n      remove: \"O'chirish\",\n      removeAssignee: 'Ijrochini olib tashlash',\n      removeColor: 'Rangni olib tashlash',\n      removeCover_title: \"Muqovani O'chirish\",\n      removeFromBoard: \"Doskadan o'chirish\",\n      removeFromProject: \"Loyihadan o'chirish\",\n      removeManager: \"Boshqaruvchini o'chirish\",\n      removeMember: \"A'zoni o'chirish\",\n      restoreToList: \"{{list}} ro'yxatiga qaytarish\",\n      returnToBoard: 'Doskaga qaytish',\n      save: 'Saqlash',\n      sendTestEmail: 'Test e-mail yuborish',\n      showActive: \"Faollarni ko'rsatish\",\n      showAllAttachments: \"Barcha ilovalarni ko'rsatish ({{hidden}} yashirilgan)\",\n      showCardsWithThisUser: \"Ushbu foydalanuvchi bilan kartalarni ko'rsatish\",\n      showDeactivated: \"Faolsizlantirilganlarni ko'rsatish\",\n      showFewerAttachments: \"Kamroq ilovalarni ko'rsatish\",\n      showLess: \"Kamroq ko'rsatish\",\n      showMore: \"Ko'proq ko'rsatish\",\n      sortList_title: \"Ro'yxatni saralash\",\n      start: 'Boshlash',\n      stop: \"To'xtatish\",\n      subscribe: \"Obuna bo'lish\",\n      unlinkSso: \"SSO bog'lanishini uzish\",\n      unlinkSso_title: \"SSO bog'lanishini uzish\",\n      unsubscribe: 'Obunani bekor qilish',\n      uploadNewAvatar: 'Yangi avatar yuklash',\n      uploadNewImage: 'Yangi rasm yuklash',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/uz-UZ/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'uz-UZ',\n  country: 'uz',\n  name: \"O'zbek\",\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/uz-UZ/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Faol foydalanuvchilar chegarasiga yetildi',\n      adminLoginRequiredToInitializeInstance:\n        'Tizimni ishga tushirish uchun admin kirishi talab qilinadi',\n      emailAlreadyInUse: 'E-mail allaqachon mavjud',\n      emailOrUsername: 'E-mail yoki foydalanuvchi nomi',\n      invalidCredentials: \"Noto'g'ri kirish ma'lumotlari\",\n      invalidEmailOrUsername: \"Noto'g'ri e-mail yoki foydalanuvchi nomi\",\n      invalidPassword: \"Noto'g'ri parol\",\n      logIn_title: 'Kirish',\n      noInternetConnection: \"Internet bog'lanishi yo'q\",\n      or: 'Yoki',\n      pageNotFound_title: 'Sahifa topilmadi',\n      password: 'Parol',\n      poweredByPlanka: 'PLANKA tomonidan quvvatlanadi',\n      serverConnectionFailed: \"Serverga bog'lanish xatosi\",\n      unknownError: \"Noma'lum xatolik, qaytadan urinib ko'ring\",\n      useSingleSignOn: 'Yagona kirish tizimidan foydalaning',\n      usernameAlreadyInUse: 'Foydalanuvchi nomi allaqachon mavjud',\n      whoops_title: 'Voy!',\n    },\n\n    action: {\n      cancelAndClose: 'Bekor qilish va yopish',\n      continue: 'Davom etish',\n      debugSso: 'SSO ni tuzatish',\n      goBack: 'Orqaga',\n      goHome: 'Bosh sahifaga',\n      logIn: 'Kirish',\n      logInWithSso: 'SSO orqali kirish',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/uz-UZ/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Bu sarlavhasiz matndir.\\nHam sarlavha, ham matn\\nqalin, qiyshiq, rangli,\\nchizilgan va tagiga chizilgan holda ajratilishi mumkin.\",\n    \"text-with-head\": \"Bu sarlavhali matndir.\\nHam sarlavha, ham matn\\nqalin, qiyshiq, rangli,\\nchizilgan va tagiga chizilgan holda ajratilishi mumkin.\",\n    \"heading\": \"Sarlavha\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Markdown muharririda xato\",\n    \"settings_wysiwyg\": \"Vizual muharrir (wysiwyg)\",\n    \"settings_markup\": \"Markdown belgilash\",\n    \"markup_placeholder\": \"Markdown belgilashni kiriting...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"O'chirish\",\n    \"empty_option\": \"Hech qanday mos kelish topilmadi\",\n    \"show_line_numbers\": \"Qator raqamlash\"\n  },\n  \"common\": {\n    \"delete\": \"O'chirish\",\n    \"edit\": \"Tahrirlash\",\n    \"toolbar_action_disabled\": \"Mos kelmaydigan belgilash elementi\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Bekor qilish\",\n    \"common_action_submit\": \"Yuborish\",\n    \"common_action_upload\": \"Tanlash\",\n    \"common_tab_attach\": \"Qurilmadan qo'shish\",\n    \"common_tab_link\": \"Havola orqali qo'shish\",\n    \"common_link\": \"Havola\",\n    \"common_sizes\": \"O'lcham, px\",\n    \"image_name\": \"Sarlavha\",\n    \"image_link_href\": \"Rasm havolasi\",\n    \"image_link_href_help\": \"Rasm havolasi olib boradigan manzil.\",\n    \"image_alt\": \"Alt matn\",\n    \"image_alt_help\": \"Agar rasm yuklanmasa, alt matn ko'rsatiladi.\",\n    \"image_upload_help\": \"1 MB dan katta bo'lmagan JPEG, GIF yoki PNG rasm.\",\n    \"image_upload_failed\": \"Rasmni qo'shib bo'lmadi\",\n    \"image_size_width\": \"Kenglik\",\n    \"image_size_height\": \"Balandlik\",\n    \"link_url_help\": \"Havola olib boradigan manzil.\",\n    \"link_text\": \"Havola matni\",\n    \"link_text_help\": \"Havola sifatida ko'rsatiladigan matn.\",\n    \"link_open_help\": \"Havolani yangi varaqda ochish\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Sarlavha\",\n    \"header_hint\": \"# Sizning matningiz\",\n    \"italic_title\": \"Qiyshiq\",\n    \"italic_hint\": \"_Sizning matningiz_\",\n    \"bold_title\": \"Qalin\",\n    \"bold_hint\": \"**Sizning matningiz**\",\n    \"strikethrough_title\": \"Chizilgan\",\n    \"strikethrough_hint\": \"~~Sizning matningiz~~\",\n    \"blockquote_title\": \"Blok iqtibos\",\n    \"blockquote_hint\": \"> Sizning matningiz\",\n    \"code_title\": \"Kod\",\n    \"code_hint\": \"```Sizning matningiz```\",\n    \"link_title\": \"Havola\",\n    \"link_hint\": \"[Sizning matningiz](url)\",\n    \"image_title\": \"Rasm\",\n    \"image_hint\": \"![Sizning matningiz](url)\",\n    \"list_title\": \"Ro'yxat elementi\",\n    \"list_hint\": \"- Sizning matningiz\",\n    \"numbered-list_title\": \"Raqamlangan ro'yxat\",\n    \"numbered-list_hint\": \"1. Sizning matningiz\",\n    \"documentation\": \"Hujjatlar\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"Qalin\",\n    \"code\": \"Kod\",\n    \"code_inline\": \"Ichki kod\",\n    \"codeblock\": \"Kod bloki\",\n    \"colorify\": \"Matn rangi\",\n    \"colorify__color_blue\": \"Ko'k\",\n    \"colorify__color_default\": \"Standart\",\n    \"colorify__color_gray\": \"Kulrang\",\n    \"colorify__color_green\": \"Yashil\",\n    \"colorify__color_orange\": \"To'q sariq\",\n    \"colorify__color_red\": \"Qizil\",\n    \"colorify__color_violet\": \"Binafsha\",\n    \"colorify__color_yellow\": \"Sariq\",\n    \"colorify__group_text\": \"Matn\",\n    \"cut\": \"Kesish\",\n    \"emoji\": \"Emoji\",\n    \"emoji__hint\": \"Emojilarni WYSIWYG da yoki belgilash bilan qo'lda qo'shish mumkin\",\n    \"heading\": \"Sarlavha\",\n    \"heading1\": \"Sarlavha 1\",\n    \"heading2\": \"Sarlavha 2\",\n    \"heading3\": \"Sarlavha 3\",\n    \"heading4\": \"Sarlavha 4\",\n    \"heading5\": \"Sarlavha 5\",\n    \"heading6\": \"Sarlavha 6\",\n    \"hrule\": \"Ajratuvchi\",\n    \"image\": \"Rasm\",\n    \"italic\": \"Qiyshiq\",\n    \"link\": \"Havola\",\n    \"list\": \"Ro'yxat\",\n    \"list__action_lift\": \"Elementni ko'tarish\",\n    \"list__action_sink\": \"Elementni tushirish\",\n    \"list_action_disabled\": \"Ro'yxat mantiqiga zid\",\n    \"mark\": \"Belgilangan\",\n    \"mono\": \"Monospace\",\n    \"more_action\": \"Ko'proq amal\",\n    \"note\": \"Eslatma\",\n    \"olist\": \"Tartiblangan ro'yxat\",\n    \"quote\": \"Iqtibos\",\n    \"redo\": \"Qaytarish\",\n    \"strike\": \"Chizilgan\",\n    \"table\": \"Jadval\",\n    \"text\": \"Matn\",\n    \"ulist\": \"Nuqtali ro'yxat\",\n    \"underline\": \"Tagiga chizilgan\",\n    \"undo\": \"Bekor qilish\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Slash buyruqlarini ishlatish uchun / yozing...\",\n    \"checkbox\": \"Vazifa tavsifini kiriting...\",\n    \"deflist_term\": \"Atama\",\n    \"deflist_desc\": \"Ta'rif tavsifi\",\n    \"heading\": \"Sarlavha\",\n    \"cut_title\": \"Sarlavha\",\n    \"cut_content\": \"Bosishda ko'rsatiladigan kontent\",\n    \"note_title\": \"Sarlavha\",\n    \"note_content\": \"Eslatma kontenti\",\n    \"table_cell\": \"Katak kontenti\",\n    \"select_filter\": \"Tillarni qidirish...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Katta-kichik harflarga sezgir\",\n    \"label_whole-word\": \"To'liq so'z\",\n    \"title\": \"Kodda qidirish\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Topilmadi\"\n  },\n  \"widgets\": {\n    \"image\": \"Rasm qo'shish\",\n    \"link\": \"Havola qo'shish\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Ma'lumot\",\n    \"tip\": \"Maslahat\",\n    \"warning\": \"Ogohlantirish\",\n    \"alert\": \"Signal\",\n    \"remove\": \"O'chirish\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Oldin ustun qo'shish\",\n    \"column.add.after\": \"Keyin ustun qo'shish\",\n    \"column.remove\": \"Ustunni o'chirish\",\n    \"column.remove.multiple\": \"Ustunlarni o'chirish\",\n    \"row.add.before\": \"Oldin qator qo'shish\",\n    \"row.add.after\": \"Keyin qator qo'shish\",\n    \"row.remove\": \"Qatorni o'chirish\",\n    \"row.remove.multiple\": \"Qatorlarni o'chirish\",\n    \"cells.clear\": \"Kataklarni tozalash\",\n    \"table.remove\": \"Jadvalni o'chirish\",\n    \"table.menu.cell.align.left\": \"Katak kontentini chapga tekislash\",\n    \"table.menu.cell.align.right\": \"Katak kontentini o'ngga tekislash\",\n    \"table.menu.cell.align.center\": \"Katak kontentini markazga tekislash\",\n    \"table.menu.row.add\": \"Keyin qator qo'shish\",\n    \"table.menu.row.remove\": \"Qatorni o'chirish\",\n    \"table.menu.column.add\": \"Keyin ustun qo'shish\",\n    \"table.menu.column.remove\": \"Ustunni o'chirish\",\n    \"table.menu.convert.yfm\": \"YFM jadvaliga aylantirish\",\n    \"table.menu.table.remove\": \"Jadvalni o'chirish\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/vi-VN/core.js",
    "content": "import dateFns from 'date-fns/locale/vi';\nimport timeAgo from 'javascript-time-ago/locale/vi';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'dd/MM/yyyy',\n    time: 'HH:mm',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'd MMM',\n    longDateTime: \"d MMMM 'lúc' HH:mm\",\n    fullDate: 'd MMM, y',\n    fullDateTime: \"d MMMM, y 'lúc' HH:mm\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: 'Về ứng dụng',\n      aboutPlanka_title: 'Về PLANKA',\n      accessToken: 'Access token',\n      account: 'Tài khoản',\n      actions: 'Thao tác',\n      activateUser_title: 'Kích hoạt người dùng',\n      active: 'Đang hoạt động',\n      addAttachment_title: 'Thêm tệp đính kèm',\n      addCustomFieldGroup_title: 'Thêm nhóm trường tùy chỉnh',\n      addCustomField_title: 'Thêm trường tùy chỉnh',\n      addManager_title: 'Thêm quản lý',\n      addMember_title: 'Thêm thành viên',\n      addTaskList_title: 'Thêm danh sách công việc',\n      addUser_title: 'Thêm người dùng',\n      admin: 'Quản trị viên',\n      administration: 'Quản trị hệ thống',\n      all: 'Tất cả',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored:\n        'Mọi thay đổi sẽ tự động được lưu<br />sau khi kết nối được khôi phục.',\n      alphabetically: 'Theo bảng chữ cái',\n      alwaysDisplayCardCreator: 'Luôn hiển thị trình tạo thẻ',\n      apiKeyCreated_title: 'Đã tạo API Key',\n      apiKey_title: 'API Key',\n      archive: 'Lưu trữ',\n      archiveCard_title: 'Lưu trữ thẻ',\n      archiveCards_title: 'Lưu trữ các thẻ',\n      areYouSureYouWantToActivateThisUser: 'Bạn có chắc chắn muốn kích hoạt người dùng này không?',\n      areYouSureYouWantToArchiveCards: 'Bạn có chắc chắn muốn lưu trữ các thẻ này không?',\n      areYouSureYouWantToArchiveThisCard: 'Bạn có chắc chắn muốn lưu trữ thẻ này không?',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner:\n        'Bạn có chắc chắn muốn chỉ định quản lý dự án này làm chủ sở hữu không?',\n      areYouSureYouWantToDeactivateThisUser:\n        'Bạn có chắc chắn muốn hủy kích hoạt người dùng này không?',\n      areYouSureYouWantToDeleteThisApiKey: 'Bạn có chắc chắn muốn xóa API Key này không?',\n      areYouSureYouWantToDeleteThisAttachment: 'Bạn có chắc chắn muốn xóa tệp đính kèm này không?',\n      areYouSureYouWantToDeleteThisBackgroundImage: 'Bạn có chắc chắn muốn xóa ảnh nền này không?',\n      areYouSureYouWantToDeleteThisBoard: 'Bạn có chắc chắn muốn xóa bảng này không?',\n      areYouSureYouWantToDeleteThisCard: 'Bạn có chắc chắn muốn xóa thẻ này không?',\n      areYouSureYouWantToDeleteThisCardForever:\n        'Bạn có chắc chắn muốn xóa vĩnh viễn thẻ này không?',\n      areYouSureYouWantToDeleteThisComment: 'Bạn có chắc chắn muốn xóa bình luận này không?',\n      areYouSureYouWantToDeleteThisCustomField:\n        'Bạn có chắc chắn muốn xóa trường tùy chỉnh này không?',\n      areYouSureYouWantToDeleteThisCustomFieldGroup:\n        'Bạn có chắc chắn muốn xóa nhóm trường tùy chỉnh này không?',\n      areYouSureYouWantToDeleteThisLabel: 'Bạn có chắc chắn muốn xóa nhãn này không?',\n      areYouSureYouWantToDeleteThisList:\n        'Bạn có chắc chắn muốn xóa danh sách này? Tất cả các thẻ sẽ được chuyển vào thùng rác.',\n      areYouSureYouWantToDeleteThisNotificationService:\n        'Bạn có chắc chắn muốn xóa dịch vụ thông báo này không?',\n      areYouSureYouWantToDeleteThisProject: 'Bạn có chắc chắn muốn xóa dự án này không?',\n      areYouSureYouWantToDeleteThisTask: 'Bạn có chắc chắn muốn xóa nhiệm vụ này không?',\n      areYouSureYouWantToDeleteThisTaskList:\n        'Bạn có chắc chắn muốn xóa danh sách công việc này không?',\n      areYouSureYouWantToDeleteThisUser: 'Bạn có chắc chắn muốn xóa người dùng này không?',\n      areYouSureYouWantToDeleteThisWebhook: 'Bạn có chắc chắn muốn xóa webhook này không?',\n      areYouSureYouWantToEmptyTrash: 'Bạn có chắc chắn muốn dọn sạch thùng rác không?',\n      areYouSureYouWantToLeaveBoard: 'Bạn có chắc chắn muốn rời khỏi bảng này không?',\n      areYouSureYouWantToLeaveProject: 'Bạn có chắc chắn muốn rời khỏi dự án này không?',\n      areYouSureYouWantToMakeThisProjectPrivate:\n        'Bạn có chắc chắn muốn chuyển dự án này sang riêng tư không?',\n      areYouSureYouWantToMakeThisProjectShared:\n        'Bạn có chắc chắn muốn chuyển dự án này sang công khai không?',\n      areYouSureYouWantToRegenerateThisApiKey:\n        'Bạn có chắc chắn muốn tạo lại API Key này không? Key cũ sẽ không còn hiệu lực.',\n      areYouSureYouWantToRemoveThisManagerFromProject:\n        'Bạn có chắc chắn muốn xóa quản lý này khỏi dự án không?',\n      areYouSureYouWantToRemoveThisMemberFromBoard:\n        'Bạn có chắc chắn muốn xóa thành viên này khỏi bảng không?',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        'Bạn có chắc chắn muốn hủy liên kết SSO khỏi người dùng này không? Điều này sẽ cho phép người dùng đăng nhập bằng mật khẩu.',\n      assignAsOwner_title: 'Chỉ định làm chủ sở hữu',\n      atLeastOneListMustBePresent: 'Phải có ít nhất một danh sách',\n      attachment: 'Tệp đính kèm',\n      attachments: 'Các tệp đính kèm',\n      authentication: 'Xác thực',\n      background: 'Nền',\n      baseCustomFields_title: 'Các trường tùy chỉnh cơ sở',\n      baseGroup: 'Nhóm cơ sở',\n      board: 'Bảng',\n      boardActions_title: 'Thao tác với bảng',\n      boardNotFound_title: 'Không tìm thấy bảng',\n      boardSubscribed: 'Đã đăng ký theo dõi bảng',\n      boardUser: 'Người dùng bảng',\n      byCreationTime: 'Theo thời gian tạo',\n      byDefault: 'Theo mặc định',\n      byDueDate: 'Theo hạn chót',\n      canBeInvitedToWorkInBoards: 'Có thể được mời làm việc trong các bảng.',\n      canComment: 'Có thể bình luận',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers:\n        'Có thể tạo dự án riêng và được mời làm việc trong các dự án khác.',\n      canEditBoardLayoutAndAssignMembersToCards:\n        'Có thể chỉnh sửa bố cục bảng và chỉ định thành viên cho các thẻ.',\n      canManageSystemWideSettingsAndActAsProjectOwner:\n        'Có thể quản lý các thiết lập hệ thống và đóng vai trò là chủ sở hữu dự án.',\n      canOnlyViewBoard: 'Chỉ có thể xem bảng.',\n      cardActions_title: 'Thao tác với thẻ',\n      cardNotFound_title: 'Không tìm thấy thẻ',\n      cardsOnThisListAreAvailableToAllBoardMembers:\n        'Các thẻ trong danh sách này khả dụng cho tất cả thành viên trong bảng.',\n      cardsOnThisListAreCompleteAndReadyToBeArchived:\n        'Các thẻ trong danh sách này đã hoàn thành và sẵn sàng để lưu trữ.',\n      cardsOnThisListAreReadyToBeWorkedOn: 'Các thẻ trong danh sách này đã sẵn sàng để thực hiện.',\n      clickHereOrRefreshPageToUpdate: '<0>Nhấp vào đây</0> hoặc tải lại trang để cập nhật.',\n      clientHostnameInEhlo: 'Tên máy chủ của máy khách (EHLO)',\n      closed: 'Đã đóng',\n      color: 'Màu sắc',\n      comments: 'Bình luận',\n      contentExceedsLimit: 'Nội dung vượt quá giới hạn {{limit}}',\n      contentOfThisAttachmentIsTooBigToDisplay:\n        'Nội dung của tệp đính kèm này quá lớn để hiển thị.',\n      copy_inline: 'sao chép',\n      createBoard_title: 'Tạo bảng',\n      createCustomFieldGroup_title: 'Tạo nhóm trường tùy chỉnh',\n      createLabel_title: 'Tạo nhãn',\n      createNewOneOrSelectExistingOne: 'Tạo cái mới hoặc chọn<br />cái đã có.',\n      createProject_title: 'Tạo dự án',\n      createTextFile_title: 'Tạo tệp văn bản',\n      creator: 'Người tạo',\n      currentPassword: 'Mật khẩu hiện tại',\n      currentUser: 'Người dùng hiện tại',\n      customFieldGroup_title: 'Nhóm trường tùy chỉnh',\n      customFieldGroups_title: 'Các nhóm trường tùy chỉnh',\n      customField_title: 'Trường tùy chỉnh',\n      customFields_title: 'Các trường tùy chỉnh',\n      customerPanel_title: 'Bảng điều khiển khách hàng',\n      dangerZone_title: 'Khu vực nguy hiểm',\n      date: 'Ngày',\n      deactivateUser_title: 'Hủy kích hoạt người dùng',\n      defaultCardType_title: 'Loại thẻ mặc định',\n      defaultFrom: 'Người gửi mặc định (\"from\")',\n      defaultView_title: 'Chế độ xem mặc định',\n      deleteAllBoardsToBeAbleToDeleteThisProject: 'Xóa tất cả các bảng để có thể xóa dự án này',\n      deleteApiKey_title: 'Xóa API Key',\n      deleteAttachment_title: 'Xóa tệp đính kèm',\n      deleteBackgroundImage_title: 'Xóa ảnh nền',\n      deleteBoard_title: 'Xóa bảng',\n      deleteCardForever_title: 'Xóa thẻ vĩnh viễn',\n      deleteCard_title: 'Xóa thẻ',\n      deleteComment_title: 'Xóa bình luận',\n      deleteCustomFieldGroup_title: 'Xóa nhóm trường tùy chỉnh',\n      deleteCustomField_title: 'Xóa trường tùy chỉnh',\n      deleteLabel_title: 'Xóa nhãn',\n      deleteList_title: 'Xóa danh sách',\n      deleteNotificationService_title: 'Xóa dịch vụ thông báo',\n      deleteProject_title: 'Xóa dự án',\n      deleteTaskList_title: 'Xóa danh sách công việc',\n      deleteTask_title: 'Xóa nhiệm vụ',\n      deleteUser_title: 'Xóa người dùng',\n      deleteWebhook_title: 'Xóa webhook',\n      deletedUser_title: 'Người dùng đã bị xóa',\n      description: 'Mô tả',\n      display: 'Hiển thị',\n      displayCardAges: 'Hiển thị tuổi thẻ',\n      dropFileToUpload: 'Thả tệp vào đây để tải lên',\n      dueDate_title: 'Hạn chót',\n      dynamicAndUnevenlySpacedLayout: 'Bố cục động và khoảng cách không đều.',\n      editAttachment_title: 'Chỉnh sửa tệp đính kèm',\n      editAvatar_title: 'Chỉnh sửa ảnh đại diện',\n      editColor_title: 'Chỉnh sửa màu sắc',\n      editCustomFieldGroup_title: 'Chỉnh sửa nhóm trường tùy chỉnh',\n      editCustomField_title: 'Chỉnh sửa trường tùy chỉnh',\n      editDueDate_title: 'Chỉnh sửa hạn chót',\n      editEmail_title: 'Chỉnh sửa E-mail',\n      editInformation_title: 'Chỉnh sửa thông tin',\n      editLabel_title: 'Chỉnh sửa nhãn',\n      editPassword_title: 'Chỉnh sửa mật khẩu',\n      editPermissions_title: 'Chỉnh sửa quyền hạn',\n      editRole_title: 'Chỉnh sửa vai trò',\n      editStopwatch_title: 'Chỉnh sửa đồng hồ bấm giờ',\n      editType_title: 'Chỉnh sửa loại',\n      editUsername_title: 'Chỉnh sửa tên người dùng',\n      editor: 'Trình chỉnh sửa',\n      editors: 'Những người chỉnh sửa',\n      email: 'E-mail',\n      emptyTrash_title: 'Dọn sạch thùng rác',\n      enterCardTitle: 'Nhập tiêu đề thẻ...',\n      enterDescription: 'Nhập mô tả...',\n      enterFilename: 'Nhập tên tệp',\n      enterListTitle: 'Nhập tiêu đề danh sách...',\n      enterTaskDescription: 'Nhập mô tả nhiệm vụ...',\n      events: 'Sự kiện',\n      excludedEvents: 'Các sự kiện loại trừ',\n      expandTaskListsByDefault: 'Mặc định mở rộng danh sách nhiệm vụ',\n      filterByLabels_title: 'Lọc theo nhãn',\n      filterByMembers_title: 'Lọc theo thành viên',\n      forPersonalProjects: 'Dành cho các dự án cá nhân.',\n      forTeamBasedProjects: 'Dành cho các dự án nhóm.',\n      fromComputer_title: 'Từ máy tính',\n      fromTrello: 'Từ Trello',\n      fullKeyIsHiddenForSecurityReasons:\n        'Mã đầy đủ bị ẩn vì lý do bảo mật. Hãy tạo lại để có mã mới.',\n      general: 'Chung',\n      gradients: 'Màu gradient',\n      grid: 'Lưới',\n      hideCompletedTasks: 'Ẩn các nhiệm vụ đã hoàn thành',\n      hideFromProjectListAndFavorites: 'Ẩn khỏi danh sách dự án và mục yêu thích',\n      host: 'Máy chủ (Host)',\n      hours: 'Giờ',\n      identity: 'Danh tính',\n      importBoard_title: 'Nhập bảng',\n      information: 'Thông tin',\n      invalidCurrentPassword: 'Mật khẩu hiện tại không hợp lệ',\n      kanban: 'Kanban',\n      labels: 'Nhãn',\n      language: 'Ngôn ngữ',\n      leaveBoard_title: 'Rời khỏi bảng',\n      leaveProject_title: 'Rời khỏi dự án',\n      limitCardTypesToDefaultOne: 'Giới hạn các loại thẻ về loại mặc định',\n      linkToCard: 'Liên kết tới thẻ',\n      list: 'Danh sách',\n      listActions_title: 'Thao tác với danh sách',\n      lists: 'Danh sách',\n      makeProjectPrivate_title: 'Chuyển dự án sang riêng tư',\n      makeProjectShared_title: 'Chuyển dự án sang công khai',\n      managers: 'Quản lý',\n      memberActions_title: 'Thao tác với thành viên',\n      members: 'Thành viên',\n      minutes: 'Phút',\n      moreActions: 'Thêm thao tác',\n      moreActions_title: 'Thêm thao tác',\n      moveCard_title: 'Di chuyển thẻ',\n      moveList_title: 'Di chuyển danh sách',\n      myOwn_title: 'Của tôi',\n      name: 'Tên',\n      newEmail: 'E-mail mới',\n      newPassword: 'Mật khẩu mới',\n      newUsername: 'Tên người dùng mới',\n      newVersionAvailable: 'Có phiên bản mới',\n      newestFirst: 'Mới nhất trước',\n      noApiKeyCreated: 'Chưa có API Key nào được tạo.',\n      noBoards: 'Không có bảng nào',\n      noCardsFound: 'Không tìm thấy thẻ nào.',\n      noConnectionToServer: 'Không có kết nối tới máy chủ',\n      noLists: 'Không có danh sách nào',\n      noProjects: 'Không có dự án nào',\n      noUnreadNotifications: 'Không có thông báo chưa đọc.',\n      notifications: 'Thông báo',\n      oldestFirst: 'Cũ nhất trước',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate:\n        'Chỉ được để lại một quản lý để chuyển dự án này sang riêng tư',\n      openBoard_title: 'Mở bảng',\n      optional_inline: 'tùy chọn',\n      organization: 'Tổ chức',\n      others: 'Khác',\n      passwordIsSet: 'Mật khẩu đã được thiết lập',\n      phone: 'Điện thoại',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA sử dụng <1><0>Apprise</0></1> để gửi thông báo tới hơn 100 dịch vụ phổ biến.',\n      port: 'Cổng',\n      preferences: 'Tùy chọn',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        'Mẹo: nhấn Ctrl-V (Cmd-V trên Mac) để thêm tệp đính kèm từ khay nhớ tạm.',\n      private: 'Riêng tư',\n      project: 'Dự án',\n      projectNotFound_title: 'Không tìm thấy dự án',\n      projectOwner: 'Chủ sở hữu dự án',\n      referenceDataAndKnowledgeStorage: 'Lưu trữ dữ liệu tham khảo và kiến thức.',\n      regenerateApiKey_title: 'Tạo lại API Key',\n      rejectUnauthorizedTlsCertificates: 'Từ chối chứng chỉ TLS không được xác thực',\n      removeManager_title: 'Xóa quản lý',\n      removeMember_title: 'Xóa thành viên',\n      role: 'Vai trò',\n      saveThisKeyItWillNotBeShownAgain: 'Lưu mã này — nó sẽ không được hiển thị lại!',\n      searchCards: 'Tìm kiếm thẻ...',\n      searchCustomFieldGroups: 'Tìm kiếm nhóm trường tùy chỉnh...',\n      searchCustomFields: 'Tìm kiếm trường tùy chỉnh...',\n      searchLabels: 'Tìm kiếm nhãn...',\n      searchLists: 'Tìm kiếm danh sách...',\n      searchMembers: 'Tìm kiếm thành viên...',\n      searchProjects: 'Tìm kiếm dự án...',\n      searchUsers: 'Tìm kiếm người dùng...',\n      seconds: 'Giây',\n      selectAssignee_title: 'Chọn người thực hiện',\n      selectBoard: 'Chọn bảng',\n      selectList: 'Chọn danh sách',\n      selectListToRestoreThisCard: 'Chọn danh sách để khôi phục thẻ này',\n      selectOrder_title: 'Chọn thứ tự',\n      selectPermissions_title: 'Chọn quyền hạn',\n      selectProject: 'Chọn dự án',\n      selectRole_title: 'Chọn vai trò',\n      selectType_title: 'Chọn loại',\n      sequentialDisplayOfCards: 'Hiển thị các thẻ theo trình tự.',\n      settings: 'Cài đặt',\n      shared: 'Đã chia sẻ',\n      sharedWithMe_title: 'Được chia sẻ với tôi',\n      showOnFrontOfCard: 'Hiển thị trên mặt trước của thẻ',\n      smtp: 'SMTP',\n      sortList_title: 'Sắp xếp danh sách',\n      sourceCardIsNoLongerAvailableForCopying: 'Thẻ nguồn không còn khả dụng để sao chép.',\n      sourceCardIsNoLongerAvailableForMoving: 'Thẻ nguồn không còn khả dụng để di chuyển.',\n      stopwatch: 'Đồng hồ bấm giờ',\n      story: 'Câu chuyện',\n      subscribeToCardWhenCommenting: 'Đăng ký theo dõi thẻ khi bình luận',\n      subscribeToMyOwnCardsByDefault: 'Mặc định đăng ký theo dõi các thẻ của tôi',\n      taskActions_title: 'Thao tác với nhiệm vụ',\n      taskAssignmentAndProjectCompletion: 'Phân công nhiệm vụ và hoàn thành dự án.',\n      taskListActions_title: 'Thao tác với danh sách nhiệm vụ',\n      taskList_title: 'Danh sách nhiệm vụ',\n      team: 'Nhóm',\n      termsOfService_title: 'Điều khoản dịch vụ',\n      testLog_title: 'Test log',\n      thereIsNoPreviewAvailableForThisAttachment: 'Không có bản xem trước cho tệp đính kèm này.',\n      time: 'Thời gian',\n      title: 'Tiêu đề',\n      trash: 'Thùng rác',\n      trashHasBeenSuccessfullyEmptied: 'Thùng rác đã được dọn sạch thành công.',\n      turnOffRecentCardHighlighting: 'Tắt làm nổi bật thẻ gần đây',\n      typeNameToConfirm: 'Nhập tên để xác nhận.',\n      typeTitleToConfirm: 'Nhập tiêu đề để xác nhận.',\n      unlinkSso_title: 'Hủy liên kết SSO',\n      unsavedChanges: 'Thay đổi chưa được lưu',\n      uploadFailedFileIsTooBig: 'Tải lên thất bại: Tệp quá lớn.',\n      uploadFailedNotEnoughStorageSpace: 'Tải lên thất bại: Không đủ không gian lưu trữ.',\n      uploadedImages: 'Ảnh đã tải lên',\n      url: 'URL',\n      useSecureConnection: 'Sử dụng kết nối bảo mật',\n      userActions_title: 'Thao tác với người dùng',\n      userAddedCardToList: '<0>{{user}}</0> đã thêm <2>{{card}}</2> vào {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> đã thêm thẻ này vào {{list}}',\n      userAddedUserToCard: '<0>{{actorUser}}</0> đã thêm {{addedUser}} vào <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> đã thêm {{addedUser}} vào thẻ này',\n      userAddedYouToCard: '<0>{{user}}</0> đã thêm bạn vào <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> đã hoàn thành {{task}} trên <4>{{card}}</4>',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> đã hoàn thành {{task}} trên thẻ này',\n      userJoinedCard: '<0>{{user}}</0> đã tham gia <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> đã tham gia thẻ này',\n      userLeftCard: '<0>{{user}}</0> đã rời khỏi <2>{{card}}</2>',\n      userLeftNewCommentToCard:\n        '<0>{{user}}</0> đã để lại bình luận mới «{{comment}}» tại <2>{{card}}</2>',\n      userLeftThisCard: '<0>{{user}}</0> đã rời khỏi thẻ này',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> đã đánh dấu {{task}} chưa hoàn thành trên <4>{{card}}</4>',\n      userMarkedTaskIncompleteOnThisCard:\n        '<0>{{user}}</0> đã đánh dấu {{task}} chưa hoàn thành trên thẻ này',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> đã nhắc đến bạn trong một bình luận «{{comment}}» tại <2>{{card}}</2>',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> đã di chuyển <2>{{card}}</2> từ {{fromList}} sang {{toList}}',\n      userMovedThisCardFromListToList:\n        '<0>{{user}}</0> đã di chuyển thẻ này từ {{fromList}} sang {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> đã xóa {{removedUser}} khỏi <4>{{card}}</4>',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> đã xóa {{removedUser}} khỏi thẻ này',\n      username: 'Tên đăng nhập',\n      users: 'Người dùng',\n      viewer: 'Người xem',\n      viewers: 'Người xem',\n      visualTaskManagementWithLists: 'Quản lý nhiệm vụ trực quan bằng danh sách.',\n      webhooks: 'Webhooks',\n      whatsNew_title: 'Có gì mới',\n      withoutBaseGroup: 'Không kèm nhóm cơ sở',\n      writeComment: 'Viết bình luận...',\n    },\n\n    action: {\n      activateUser: 'Kích hoạt người dùng',\n      activateUser_title: 'Kích hoạt Người dùng',\n      addAnotherCard: 'Thêm thẻ khác',\n      addAnotherList: 'Thêm danh sách khác',\n      addAnotherTask: 'Thêm nhiệm vụ khác',\n      addCard: 'Thêm thẻ',\n      addCard_title: 'Thêm Thẻ',\n      addComment: 'Thêm bình luận',\n      addCustomField: 'Thêm trường tùy chỉnh',\n      addCustomFieldGroup: 'Thêm nhóm trường tùy chỉnh',\n      addList: 'Thêm danh sách',\n      addMember: 'Thêm thành viên',\n      addMoreDetailedDescription: 'Thêm mô tả chi tiết hơn',\n      addTask: 'Thêm nhiệm vụ',\n      addTaskList: 'Thêm danh sách nhiệm vụ',\n      addToCard: 'Thêm vào thẻ',\n      addUser: 'Thêm người dùng',\n      addWebhook: 'Thêm webhook',\n      archive: 'Lưu trữ',\n      archiveCard: 'Lưu trữ thẻ',\n      archiveCard_title: 'Lưu trữ Thẻ',\n      archiveCards: 'Lưu trữ các thẻ',\n      archiveCards_title: 'Lưu trữ Các thẻ',\n      assignAsOwner: 'Chỉ định làm chủ sở hữu',\n      cancel: 'Hủy',\n      copy: 'Sao chép',\n      copyCard_title: 'Sao chép Thẻ',\n      createApiKey: 'Tạo API Key',\n      createBoard: 'Tạo bảng',\n      createCustomFieldGroup: 'Tạo nhóm trường tùy chỉnh',\n      createFile: 'Tạo tệp',\n      createLabel: 'Tạo nhãn',\n      createNewLabel: 'Tạo nhãn mới',\n      createProject: 'Tạo dự án',\n      cut: 'Cắt',\n      cutCard_title: 'Cắt Thẻ',\n      deactivateUser: 'Hủy kích hoạt người dùng',\n      deactivateUser_title: 'Hủy kích hoạt Người dùng',\n      delete: 'Xóa',\n      deleteApiKey: 'Xóa API Key',\n      deleteAttachment: 'Xóa tệp đính kèm',\n      deleteAvatar: 'Xóa ảnh đại diện',\n      deleteBackgroundImage: 'Xóa ảnh nền',\n      deleteBoard: 'Xóa bảng',\n      deleteBoard_title: 'Xóa Bảng',\n      deleteCard: 'Xóa thẻ',\n      deleteCardForever: 'Xóa thẻ vĩnh viễn',\n      deleteCard_title: 'Xóa Thẻ',\n      deleteComment: 'Xóa bình luận',\n      deleteCustomField: 'Xóa trường tùy chỉnh',\n      deleteCustomFieldGroup: 'Xóa nhóm trường tùy chỉnh',\n      deleteForever_title: 'Xóa Vĩnh viễn',\n      deleteGroup: 'Xóa nhóm',\n      deleteLabel: 'Xóa nhãn',\n      deleteList: 'Xóa danh sách',\n      deleteList_title: 'Xóa Danh sách',\n      deleteNotificationService: 'Xóa dịch vụ thông báo',\n      deleteProject: 'Xóa dự án',\n      deleteProject_title: 'Xóa Dự án',\n      deleteTask: 'Xóa nhiệm vụ',\n      deleteTaskList: 'Xóa danh sách nhiệm vụ',\n      deleteTask_title: 'Xóa Nhiệm vụ',\n      deleteUser: 'Xóa người dùng',\n      deleteUser_title: 'Xóa Người dùng',\n      deleteWebhook: 'Xóa webhook',\n      dismissAll: 'Bỏ qua tất cả',\n      download: 'Tải xuống',\n      duplicateCard_title: 'Nhân bản Thẻ',\n      edit: 'Chỉnh sửa',\n      editColor_title: 'Chỉnh sửa Màu sắc',\n      editDescription_title: 'Chỉnh sửa Mô tả',\n      editDueDate_title: 'Chỉnh sửa Hạn chót',\n      editEmail_title: 'Chỉnh sửa E-mail',\n      editGroup: 'Chỉnh sửa nhóm',\n      editInformation_title: 'Chỉnh sửa Thông tin',\n      editPassword_title: 'Chỉnh sửa Mật khẩu',\n      editPermissions: 'Chỉnh sửa quyền hạn',\n      editRole_title: 'Chỉnh sửa Vai trò',\n      editStopwatch_title: 'Chỉnh sửa Đồng hồ bấm giờ',\n      editTitle_title: 'Chỉnh sửa Tiêu đề',\n      editType_title: 'Chỉnh sửa Loại',\n      editUsername_title: 'Chỉnh sửa Tên người dùng',\n      emptyTrash: 'Dọn sạch thùng rác',\n      emptyTrash_title: 'Dọn sạch Thùng rác',\n      import: 'Nhập',\n      join: 'Tham gia',\n      leave: 'Rời khỏi',\n      leaveBoard: 'Rời khỏi bảng',\n      leaveProject: 'Rời khỏi dự án',\n      logOut_title: 'Đăng xuất',\n      makeCover_title: 'Đặt làm ảnh bìa',\n      makeProjectPrivate: 'Chuyển dự án sang riêng tư',\n      makeProjectPrivate_title: 'Chuyển Dự án sang Riêng tư',\n      makeProjectShared: 'Chuyển dự án sang công khai',\n      makeProjectShared_title: 'Chuyển Dự án sang Công khai',\n      move: 'Di chuyển',\n      moveCard_title: 'Di chuyển Thẻ',\n      moveList_title: 'Di chuyển Danh sách',\n      regenerateApiKey: 'Tạo lại API Key',\n      remove: 'Gỡ bỏ',\n      removeAssignee: 'Gỡ bỏ người thực hiện',\n      removeColor: 'Gỡ bỏ màu sắc',\n      removeCover_title: 'Gỡ bỏ ảnh bìa',\n      removeFromBoard: 'Gỡ bỏ khỏi bảng',\n      removeFromProject: 'Gỡ bỏ khỏi dự án',\n      removeManager: 'Gỡ bỏ quản lý',\n      removeMember: 'Gỡ bỏ thành viên',\n      restoreToList: 'Khôi phục về {{list}}',\n      returnToBoard: 'Quay lại bảng',\n      save: 'Lưu',\n      sendTestEmail: 'Gửi thử e-mail',\n      showActive: 'Hiển thị đang hoạt động',\n      showAllAttachments: 'Hiển thị tất cả tệp đính kèm ({{hidden}} bị ẩn)',\n      showCardsWithThisUser: 'Hiển thị các thẻ của người dùng này',\n      showDeactivated: 'Hiển thị đã hủy kích hoạt',\n      showFewerAttachments: 'Hiển thị ít tệp đính kèm hơn',\n      showLess: 'Ẩn bớt',\n      showMore: 'Xem thêm',\n      sortList_title: 'Sắp xếp Danh sách',\n      start: 'Bắt đầu',\n      stop: 'Dừng',\n      subscribe: 'Đăng ký theo dõi',\n      unlinkSso: 'Hủy liên kết SSO',\n      unlinkSso_title: 'Hủy liên kết SSO',\n      unsubscribe: 'Hủy đăng ký theo dõi',\n      uploadNewAvatar: 'Tải lên ảnh đại diện mới',\n      uploadNewImage: 'Tải lên ảnh mới',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/vi-VN/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'vi-VN',\n  country: 'vn',\n  name: 'Tiếng Việt',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/vi-VN/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: 'Đã đạt giới hạn người dùng hoạt động',\n      adminLoginRequiredToInitializeInstance: 'Cần đăng nhập quản trị để khởi tạo phiên bản',\n      emailAlreadyInUse: 'E-mail đã được sử dụng',\n      emailOrUsername: 'E-mail hoặc tên đăng nhập',\n      invalidCredentials: 'Thông tin đăng nhập không hợp lệ',\n      invalidEmailOrUsername: 'E-mail hoặc tên đăng nhập không hợp lệ',\n      invalidPassword: 'Mật khẩu không hợp lệ',\n      logIn_title: 'Đăng nhập',\n      noInternetConnection: 'Không có kết nối Internet',\n      or: 'Hoặc',\n      pageNotFound_title: 'Không tìm thấy trang',\n      password: 'Mật khẩu',\n      poweredByPlanka: 'Được phát triển bởi <1>PLANKA</1>',\n      serverConnectionFailed: 'Không kết nối được tới máy chủ',\n      unknownError: 'Lỗi không xác định, thử lại sau',\n      useSingleSignOn: 'Sử dụng đăng nhập một lần (SSO)',\n      usernameAlreadyInUse: 'Tên đăng nhập đã được sử dụng',\n      whoops_title: 'Ôi thôi chếcccc!',\n    },\n\n    action: {\n      cancelAndClose: 'Hủy và đóng',\n      continue: 'Tiếp tục',\n      debugSso: 'Gỡ lỗi SSO',\n      goBack: 'Quay lại',\n      goHome: 'Về trang chủ',\n      logIn: 'Đăng nhập',\n      logInWithSso: 'Đăng nhập bằng SSO',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/vi-VN/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"Đây là văn bản không có tiêu đề.\\nCả tiêu đề và nội dung\\ncó thể được in đậm, in nghiêng, đổi màu,\\ngạch ngang và gạch dưới.\",\n    \"text-with-head\": \"Đây là văn bản có tiêu đề.\\nCả tiêu đề và nội dung\\ncó thể được in đậm, in nghiêng, đổi màu,\\ngạch ngang và gạch dưới.\",\n    \"heading\": \"Tiêu đề\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Lỗi trong trình soạn thảo markdown\",\n    \"settings_wysiwyg\": \"Trình soạn thảo trực quan (WYSIWYG)\",\n    \"settings_markup\": \"Markdown\",\n    \"markup_placeholder\": \"Nhập markdown...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"Xóa\",\n    \"empty_option\": \"Không tìm thấy kết quả\",\n    \"show_line_numbers\": \"Số dòng\"\n  },\n  \"common\": {\n    \"delete\": \"Xóa\",\n    \"edit\": \"Chỉnh sửa\",\n    \"toolbar_action_disabled\": \"Phần tử đánh dấu không tương thích\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"Hủy\",\n    \"common_action_submit\": \"Gửi\",\n    \"common_action_upload\": \"Chọn\",\n    \"common_tab_attach\": \"Thêm từ thiết bị\",\n    \"common_tab_link\": \"Thêm theo liên kết\",\n    \"common_link\": \"Liên kết\",\n    \"common_sizes\": \"Kích thước, px\",\n    \"image_name\": \"Tiêu đề\",\n    \"image_link_href\": \"Liên kết ảnh\",\n    \"image_link_href_help\": \"Địa chỉ mà liên kết ảnh dẫn tới.\",\n    \"image_alt\": \"Văn bản thay thế (alt)\",\n    \"image_alt_help\": \"Văn bản thay thế hiển thị khi ảnh không thể tải.\",\n    \"image_upload_help\": \"Ảnh JPEG, GIF hoặc PNG không lớn hơn 1 MB.\",\n    \"image_upload_failed\": \"Thêm ảnh thất bại\",\n    \"image_size_width\": \"Chiều rộng\",\n    \"image_size_height\": \"Chiều cao\",\n    \"link_url_help\": \"Địa chỉ mà liên kết dẫn tới.\",\n    \"link_text\": \"Văn bản liên kết\",\n    \"link_text_help\": \"Văn bản hiển thị dưới dạng liên kết.\",\n    \"link_open_help\": \"Mở liên kết trong tab mới\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"Tiêu đề\",\n    \"header_hint\": \"# Văn bản của bạn\",\n    \"italic_title\": \"In nghiêng\",\n    \"italic_hint\": \"_Văn bản của bạn_\",\n    \"bold_title\": \"In đậm\",\n    \"bold_hint\": \"**Văn bản của bạn**\",\n    \"strikethrough_title\": \"Gạch ngang\",\n    \"strikethrough_hint\": \"~~Văn bản của bạn~~\",\n    \"blockquote_title\": \"Trích dẫn\",\n    \"blockquote_hint\": \"> Văn bản của bạn\",\n    \"code_title\": \"Mã nguồn\",\n    \"code_hint\": \"```Văn bản của bạn```\",\n    \"link_title\": \"Liên kết\",\n    \"link_hint\": \"[Văn bản của bạn](url)\",\n    \"image_title\": \"Ảnh\",\n    \"image_hint\": \"![Văn bản của bạn](url)\",\n    \"list_title\": \"Mục danh sách\",\n    \"list_hint\": \"- Văn bản của bạn\",\n    \"numbered-list_title\": \"Danh sách đánh số\",\n    \"numbered-list_hint\": \"1. Văn bản của bạn\",\n    \"documentation\": \"Tài liệu\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"In đậm\",\n    \"code\": \"Mã nguồn\",\n    \"code_inline\": \"Mã nội dòng\",\n    \"codeblock\": \"Khối mã\",\n    \"colorify\": \"Màu chữ\",\n    \"colorify__color_blue\": \"Xanh dương\",\n    \"colorify__color_default\": \"Mặc định\",\n    \"colorify__color_gray\": \"Xám\",\n    \"colorify__color_green\": \"Xanh lá\",\n    \"colorify__color_orange\": \"Cam\",\n    \"colorify__color_red\": \"Đỏ\",\n    \"colorify__color_violet\": \"Tím\",\n    \"colorify__color_yellow\": \"Vàng\",\n    \"colorify__group_text\": \"Văn bản\",\n    \"cut\": \"Cắt\",\n    \"emoji\": \"Biểu tượng cảm xúc\",\n    \"emoji__hint\": \"Biểu tượng có thể thêm ở WYSIWYG hoặc bằng markup\",\n    \"heading\": \"Tiêu đề\",\n    \"heading1\": \"Tiêu đề 1\",\n    \"heading2\": \"Tiêu đề 2\",\n    \"heading3\": \"Tiêu đề 3\",\n    \"heading4\": \"Tiêu đề 4\",\n    \"heading5\": \"Tiêu đề 5\",\n    \"heading6\": \"Tiêu đề 6\",\n    \"hrule\": \"Ngăn cách\",\n    \"image\": \"Ảnh\",\n    \"italic\": \"In nghiêng\",\n    \"link\": \"Liên kết\",\n    \"list\": \"Danh sách\",\n    \"list__action_lift\": \"Nâng mục\",\n    \"list__action_sink\": \"Hạ mục\",\n    \"list_action_disabled\": \"Mâu thuẫn với logic danh sách\",\n    \"mark\": \"Đánh dấu\",\n    \"mono\": \"Chữ monospace\",\n    \"more_action\": \"Thêm hành động\",\n    \"note\": \"Ghi chú\",\n    \"olist\": \"Danh sách đánh số\",\n    \"quote\": \"Trích dẫn\",\n    \"redo\": \"Làm lại\",\n    \"strike\": \"Gạch ngang\",\n    \"table\": \"Bảng\",\n    \"text\": \"Văn bản\",\n    \"ulist\": \"Danh sách gạch đầu dòng\",\n    \"underline\": \"Gạch dưới\",\n    \"undo\": \"Hoàn tác\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"Gõ / để dùng lệnh nhanh...\",\n    \"checkbox\": \"Nhập mô tả nhiệm vụ...\",\n    \"deflist_term\": \"Thuật ngữ\",\n    \"deflist_desc\": \"Mô tả định nghĩa\",\n    \"heading\": \"Tiêu đề\",\n    \"cut_title\": \"Tiêu đề\",\n    \"cut_content\": \"Nội dung hiển thị khi nhấp\",\n    \"note_title\": \"Tiêu đề\",\n    \"note_content\": \"Nội dung ghi chú\",\n    \"table_cell\": \"Nội dung ô\",\n    \"select_filter\": \"Tìm ngôn ngữ...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"Phân biệt chữ hoa/thường\",\n    \"label_whole-word\": \"Nguyên từ\",\n    \"title\": \"Tìm trong mã\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"Không tìm thấy\"\n  },\n  \"widgets\": {\n    \"image\": \"Thêm ảnh\",\n    \"link\": \"Thêm liên kết\"\n  },\n  \"yfm-note\": {\n    \"info\": \"Ghi chú\",\n    \"tip\": \"Mẹo\",\n    \"warning\": \"Cảnh báo\",\n    \"alert\": \"Thông báo\",\n    \"remove\": \"Xóa\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"Thêm cột trước\",\n    \"column.add.after\": \"Thêm cột sau\",\n    \"column.remove\": \"Xóa cột\",\n    \"column.remove.multiple\": \"Xóa các cột\",\n    \"row.add.before\": \"Thêm hàng trước\",\n    \"row.add.after\": \"Thêm hàng sau\",\n    \"row.remove\": \"Xóa hàng\",\n    \"row.remove.multiple\": \"Xóa các hàng\",\n    \"cells.clear\": \"Xóa ô\",\n    \"table.remove\": \"Xóa bảng\",\n    \"table.menu.cell.align.left\": \"Căn trái nội dung ô\",\n    \"table.menu.cell.align.right\": \"Căn phải nội dung ô\",\n    \"table.menu.cell.align.center\": \"Căn giữa nội dung ô\",\n    \"table.menu.row.add\": \"Thêm hàng sau\",\n    \"table.menu.row.remove\": \"Xóa hàng\",\n    \"table.menu.column.add\": \"Thêm cột sau\",\n    \"table.menu.column.remove\": \"Xóa cột\",\n    \"table.menu.convert.yfm\": \"Chuyển sang bảng YFM\",\n    \"table.menu.table.remove\": \"Xóa bảng\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/zh-CN/core.js",
    "content": "import dateFns from 'date-fns/locale/zh-CN';\nimport timeAgo from 'javascript-time-ago/locale/zh';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'M/d/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'MMM d',\n    longDateTime: \"MMMM d 'at' p\",\n    fullDate: 'MMM d, y',\n    fullDateTime: \"MMMM d, y 'at' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: '关于应用',\n      aboutPlanka_title: '关于 PLANKA',\n      accessToken: '访问令牌',\n      account: '账号',\n      actions: '操作',\n      activateUser_title: '激活用户',\n      active: '活跃',\n      addAttachment_title: '添加附件',\n      addCustomFieldGroup_title: '添加自定义字段组',\n      addCustomField_title: '添加自定义字段',\n      addManager_title: '添加管理员',\n      addMember_title: '添加成员',\n      addTaskList_title: '添加任务列表',\n      addUser_title: '添加用户',\n      admin: '管理员',\n      administration: '系统管理',\n      all: '全部',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored: '所有修改会在重连后自动保存',\n      alphabetically: '按字母顺序',\n      alwaysDisplayCardCreator: '始终显示卡片创建者',\n      apiKeyCreated_title: 'API密钥已创建',\n      apiKey_title: 'API密钥',\n      archive: '归档',\n      archiveCard_title: '归档卡片',\n      archiveCards_title: '归档多个卡片',\n      areYouSureYouWantToActivateThisUser: '您确定要激活此用户吗？',\n      areYouSureYouWantToArchiveCards: '您确定要归档这些卡片吗？',\n      areYouSureYouWantToArchiveThisCard: '您确定要归档此卡片吗？',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner: '您确定要指定此项目管理人作为所有者吗？',\n      areYouSureYouWantToDeactivateThisUser: '您确定要停用此用户吗？',\n      areYouSureYouWantToDeleteThisApiKey: '确认删除此API密钥吗？',\n      areYouSureYouWantToDeleteThisAttachment: '确认删除此附件吗？',\n      areYouSureYouWantToDeleteThisBackgroundImage: '您确定要删除此背景图片吗？',\n      areYouSureYouWantToDeleteThisBoard: '确认删除此面板吗？',\n      areYouSureYouWantToDeleteThisCard: '确认删除此卡片吗？',\n      areYouSureYouWantToDeleteThisCardForever: '您确定要永久删除此卡片吗？',\n      areYouSureYouWantToDeleteThisComment: '确认删除此评论吗？',\n      areYouSureYouWantToDeleteThisCustomField: '您确定要删除此自定义字段吗？',\n      areYouSureYouWantToDeleteThisCustomFieldGroup: '您确定要删除此自定义字段组吗？',\n      areYouSureYouWantToDeleteThisLabel: '确认删除此标签吗？',\n      areYouSureYouWantToDeleteThisList: '您确定要删除此列表吗？所有卡片将被移至回收站。',\n      areYouSureYouWantToDeleteThisNotificationService: '您确定要删除此通知服务吗？',\n      areYouSureYouWantToDeleteThisProject: '确认删除此项目吗？',\n      areYouSureYouWantToDeleteThisTask: '确认删除此任务吗？',\n      areYouSureYouWantToDeleteThisTaskList: '您确定要删除此任务列表吗？',\n      areYouSureYouWantToDeleteThisUser: '确认删除此用户吗？',\n      areYouSureYouWantToDeleteThisWebhook: '您确定要删除此Webhook吗？',\n      areYouSureYouWantToEmptyTrash: '您确定要清空回收站吗？',\n      areYouSureYouWantToLeaveBoard: '确认离开此面板吗？',\n      areYouSureYouWantToLeaveProject: '确认离开此项目吗？',\n      areYouSureYouWantToMakeThisProjectPrivate: '您确定要将此项目设为私有吗？',\n      areYouSureYouWantToMakeThisProjectShared: '您确定要将此项目设为共享吗？',\n      areYouSureYouWantToRegenerateThisApiKey: '确认重新生成此API密钥吗？之前的密钥将不再有效。',\n      areYouSureYouWantToRemoveThisManagerFromProject: '确认从本项目删除该管理员吗？',\n      areYouSureYouWantToRemoveThisMemberFromBoard: '确认本面板删除该成员吗？',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        '您确定要取消此用户的SSO链接吗？这将允许用户使用密码登录。',\n      assignAsOwner_title: '指定为所有者',\n      atLeastOneListMustBePresent: '至少需要存在一个列表',\n      attachment: '附件',\n      attachments: '多个附件',\n      authentication: '认证',\n      background: '背景',\n      baseCustomFields_title: '基础自定义字段',\n      baseGroup: '基础组',\n      board: '面板',\n      boardActions_title: '面板操作',\n      boardNotFound_title: '面板不存在',\n      boardSubscribed: '面板已订阅',\n      boardUser: '面板用户',\n      byCreationTime: '按创建时间',\n      byDefault: '默认',\n      byDueDate: '按截止日期',\n      canBeInvitedToWorkInBoards: '可被邀请在面板中工作。',\n      canComment: '可以评论',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers: '可创建自己的项目并被邀请参与其他项目。',\n      canEditBoardLayoutAndAssignMembersToCards: '可编辑面板布局并分配成员到卡片。',\n      canManageSystemWideSettingsAndActAsProjectOwner: '可管理系统设置并担任项目所有者。',\n      canOnlyViewBoard: '仅可查看面板。',\n      cardActions_title: '卡片操作',\n      cardNotFound_title: '卡片不存在',\n      cardsOnThisListAreAvailableToAllBoardMembers: '此列表中的卡片对所有面板成员可见。',\n      cardsOnThisListAreCompleteAndReadyToBeArchived: '此列表中的卡片已完成并准备归档。',\n      cardsOnThisListAreReadyToBeWorkedOn: '此列表中的卡片已准备就绪可开始工作。',\n      clickHereOrRefreshPageToUpdate: '<0>点击此处</0>或刷新页面更新。',\n      clientHostnameInEhlo: 'EHLO中的客户端主机名',\n      closed: '已关闭',\n      color: '颜色',\n      comments: '评论',\n      contentExceedsLimit: '内容超过{{limit}}限制',\n      contentOfThisAttachmentIsTooBigToDisplay: '此附件内容过大无法显示。',\n      copy_inline: '副本',\n      createBoard_title: '创建面板',\n      createCustomFieldGroup_title: '创建自定义字段组',\n      createLabel_title: '创建标签',\n      createNewOneOrSelectExistingOne: '创建一个新的或者选择<br />一个已创建的。',\n      createProject_title: '创建项目',\n      createTextFile_title: '创建文本文件',\n      creator: '创建者',\n      currentPassword: '当前密码',\n      currentUser: '当前用户',\n      customFieldGroup_title: '自定义字段组',\n      customFieldGroups_title: '自定义字段组',\n      customField_title: '自定义字段',\n      customFields_title: '自定义字段',\n      customerPanel_title: '客户面板',\n      dangerZone_title: '危险区域',\n      date: '日期',\n      deactivateUser_title: '停用用户',\n      defaultCardType_title: '默认卡片类型',\n      defaultFrom: '默认\"发件人\"',\n      defaultView_title: '默认视图',\n      deleteAllBoardsToBeAbleToDeleteThisProject: '删除所有面板后方可删除此项目',\n      deleteApiKey_title: '删除API密钥',\n      deleteAttachment_title: '删除附件',\n      deleteBackgroundImage_title: '删除背景图片',\n      deleteBoard_title: '删除面板',\n      deleteCardForever_title: '永久删除卡片',\n      deleteCard_title: '删除卡片',\n      deleteComment_title: '删除评论',\n      deleteCustomFieldGroup_title: '删除自定义字段组',\n      deleteCustomField_title: '删除自定义字段',\n      deleteLabel_title: '删除标签',\n      deleteList_title: '删除列表',\n      deleteNotificationService_title: '删除通知服务',\n      deleteProject_title: '删除项目',\n      deleteTaskList_title: '删除任务列表',\n      deleteTask_title: '删除任务',\n      deleteUser_title: '删除用户',\n      deleteWebhook_title: '删除Webhook',\n      deletedUser_title: '已删除用户',\n      description: '描述',\n      display: '显示',\n      displayCardAges: '显示卡片创建时间',\n      dropFileToUpload: '拖放文件以上传',\n      dueDate_title: '截止日期',\n      dynamicAndUnevenlySpacedLayout: '动态非均匀间隔布局。',\n      editAttachment_title: '编辑附件',\n      editAvatar_title: '编辑头像',\n      editColor_title: '编辑颜色',\n      editCustomFieldGroup_title: '编辑自定义字段组',\n      editCustomField_title: '编辑自定义字段',\n      editDueDate_title: '编辑截止时间',\n      editEmail_title: '编辑邮箱',\n      editInformation_title: '编辑信息',\n      editLabel_title: '编辑标签',\n      editPassword_title: '修改密码',\n      editPermissions_title: '修改权限',\n      editRole_title: '编辑角色',\n      editStopwatch_title: '修改时间',\n      editType_title: '编辑类型',\n      editUsername_title: '修改用户名',\n      editor: '编辑器',\n      editors: '编辑者',\n      email: '邮箱',\n      emptyTrash_title: '清空回收站',\n      enterCardTitle: '输入卡片标题...',\n      enterDescription: '输入描述...',\n      enterFilename: '输入文件名',\n      enterListTitle: '输入列表标题...',\n      enterTaskDescription: '输入任务描述...',\n      events: '事件',\n      excludedEvents: '排除事件',\n      expandTaskListsByDefault: '默认展开任务列表',\n      filterByLabels_title: '通过标签筛选',\n      filterByMembers_title: '通过成员筛选',\n      forPersonalProjects: '用于个人项目。',\n      forTeamBasedProjects: '用于团队项目。',\n      fromComputer_title: '来自计算机',\n      fromTrello: '来自 Trello',\n      fullKeyIsHiddenForSecurityReasons: '出于安全考虑，完整密钥已隐藏。重新生成以创建新密钥。',\n      general: '全体',\n      gradients: '渐变',\n      grid: '网格',\n      hideCompletedTasks: '隐藏已完成任务',\n      hideFromProjectListAndFavorites: '从项目列表和收藏中隐藏',\n      host: '主机',\n      hours: '小时',\n      identity: '身份',\n      importBoard_title: '导入面板',\n      information: '信息',\n      invalidCurrentPassword: '当前密码错误',\n      kanban: '看板',\n      labels: '标签',\n      language: '语言',\n      leaveBoard_title: '离开面板',\n      leaveProject_title: '离开项目',\n      limitCardTypesToDefaultOne: '限制卡片类型为默认类型',\n      linkToCard: '链接到卡片',\n      list: '列表',\n      listActions_title: '列表操作',\n      lists: '列表',\n      makeProjectPrivate_title: '将项目设为私有',\n      makeProjectShared_title: '将项目设为共享',\n      managers: '管理员',\n      memberActions_title: '成员操作',\n      members: '成员',\n      minutes: '分钟',\n      moreActions: '更多操作',\n      moreActions_title: '更多操作',\n      moveCard_title: '移动卡片',\n      moveList_title: '移动列表',\n      myOwn_title: '我的',\n      name: '姓名',\n      newEmail: '新邮箱',\n      newPassword: '新密码',\n      newUsername: '新用户名',\n      newVersionAvailable: '有新版本可用',\n      newestFirst: '最新优先',\n      noApiKeyCreated: '未创建API密钥。',\n      noBoards: '没有面板',\n      noCardsFound: '未找到卡片。',\n      noConnectionToServer: '未连接服务器',\n      noLists: '没有列表',\n      noProjects: '没有项目',\n      noUnreadNotifications: '没有未读通知。',\n      notifications: '通知',\n      oldestFirst: '最旧优先',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate: '只能保留一个管理员来将此项目设为私有',\n      openBoard_title: '打开面板',\n      optional_inline: '可选的',\n      organization: '组织机构',\n      others: '其他',\n      passwordIsSet: '密码已设置',\n      phone: '电话',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA使用<1><0>Apprise</0></1>向100多个流行服务发送通知。',\n      port: '端口',\n      preferences: '偏好',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        '提示: 按下 Ctrl-V (Mac: Cmd-V) 从剪切板添加附件。',\n      private: '私有',\n      project: '项目',\n      projectNotFound_title: '项目未找到',\n      projectOwner: '项目所有者',\n      referenceDataAndKnowledgeStorage: '参考数据和知识存储。',\n      regenerateApiKey_title: '重新生成API密钥',\n      rejectUnauthorizedTlsCertificates: '拒绝未授权的TLS证书',\n      removeManager_title: '删除管理员',\n      removeMember_title: '删除成员',\n      role: '角色',\n      saveThisKeyItWillNotBeShownAgain: '保存此密钥——它不会再次显示！',\n      searchCards: '搜索卡片...',\n      searchCustomFieldGroups: '搜索自定义字段组...',\n      searchCustomFields: '搜索自定义字段...',\n      searchLabels: '搜索标签...',\n      searchLists: '搜索列表...',\n      searchMembers: '搜索成员...',\n      searchProjects: '搜索项目...',\n      searchUsers: '搜索用户...',\n      seconds: '秒',\n      selectAssignee_title: '选择负责人',\n      selectBoard: '选择面板',\n      selectList: '选择列表',\n      selectListToRestoreThisCard: '选择列表以恢复此卡片',\n      selectOrder_title: '选择排序',\n      selectPermissions_title: '选择权限',\n      selectProject: '选择项目',\n      selectRole_title: '选择角色',\n      selectType_title: '选择类型',\n      sequentialDisplayOfCards: '卡片顺序显示。',\n      settings: '设置',\n      shared: '共享',\n      sharedWithMe_title: '与我共享',\n      showOnFrontOfCard: '在卡片正面显示',\n      smtp: 'SMTP',\n      sortList_title: '排序列表',\n      sourceCardIsNoLongerAvailableForCopying: '源卡片不再可供复制。',\n      sourceCardIsNoLongerAvailableForMoving: '源卡片不再可供移动。',\n      stopwatch: '计时器',\n      story: '故事',\n      subscribeToCardWhenCommenting: '评论时自动关注卡片',\n      subscribeToMyOwnCardsByDefault: '默认关注自己创建的卡片',\n      taskActions_title: '任务操作',\n      taskAssignmentAndProjectCompletion: '任务分配和项目完成。',\n      taskListActions_title: '任务列表操作',\n      taskList_title: '任务列表',\n      team: '团队',\n      termsOfService_title: '服务条款',\n      testLog_title: '测试日志',\n      thereIsNoPreviewAvailableForThisAttachment: '此附件无法预览。',\n      time: '时间',\n      title: '标题',\n      trash: '回收站',\n      trashHasBeenSuccessfullyEmptied: '回收站已成功清空。',\n      turnOffRecentCardHighlighting: '关闭最近卡片高亮',\n      typeNameToConfirm: '输入名称以确认。',\n      typeTitleToConfirm: '输入标题以确认。',\n      unlinkSso_title: '取消SSO链接',\n      unsavedChanges: '未保存的更改',\n      uploadFailedFileIsTooBig: '上传失败：文件太大。',\n      uploadFailedNotEnoughStorageSpace: '上传失败：存储空间不足。',\n      uploadedImages: '已上传图片',\n      url: '网址',\n      useSecureConnection: '使用安全连接',\n      userActions_title: '用户操作',\n      userAddedCardToList: '<0>{{user}}</0> 将 <2>{{card}}</2> 添加到 {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> 向列表 {{list}} 添加了该卡片',\n      userAddedUserToCard: '<0>{{actorUser}}</0> 将 {{addedUser}} 添加到 <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> 将 {{addedUser}} 添加到此卡片',\n      userAddedYouToCard: '<0>{{user}}</0> 将您添加到 <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> 在 <4>{{card}}</4> 上完成了 {{task}}',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> 在此卡片上完成了 {{task}}',\n      userJoinedCard: '<0>{{user}}</0> 加入了 <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> 加入了此卡片',\n      userLeftCard: '<0>{{user}}</0> 离开了 <2>{{card}}</2>',\n      userLeftNewCommentToCard: '<0>{{user}}</0> 给 <2>{{card}}</2> 添加了一个新评论 «{{comment}}»',\n      userLeftThisCard: '<0>{{user}}</0> 离开了此卡片',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> 在 <4>{{card}}</4> 上将 {{task}} 标记为未完成',\n      userMarkedTaskIncompleteOnThisCard: '<0>{{user}}</0> 在此卡片上将 {{task}} 标记为未完成',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> 在 <2>{{card}}</2> 的评论 «{{comment}}» 中提到了您',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> 将卡片 <2>{{card}}</2> 从 {{fromList}} 移动到 {{toList}}',\n      userMovedThisCardFromListToList: '<0>{{user}}</0> 将该卡片从 {{fromList}} 移动到 {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> 将 {{removedUser}} 从 <4>{{card}}</4> 移除',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> 将 {{removedUser}} 从此卡片移除',\n      username: '用户名',\n      users: '用户',\n      viewer: '视图',\n      viewers: '查看者',\n      visualTaskManagementWithLists: '使用列表进行可视化任务管理。',\n      webhooks: 'Webhooks',\n      whatsNew_title: '新功能',\n      withoutBaseGroup: '无基础组',\n      writeComment: '编写评论...',\n    },\n\n    action: {\n      activateUser: '激活用户',\n      activateUser_title: '激活用户',\n      addAnotherCard: '添加别的卡片',\n      addAnotherList: '添加别的列表',\n      addAnotherTask: '添加别的任务',\n      addCard: '添加卡片',\n      addCard_title: '添加卡片',\n      addComment: '添加评论',\n      addCustomField: '添加自定义字段',\n      addCustomFieldGroup: '添加自定义字段组',\n      addList: '添加列表',\n      addMember: '添加成员',\n      addMoreDetailedDescription: '添加更多详细描述',\n      addTask: '添加任务',\n      addTaskList: '添加任务列表',\n      addToCard: '添加到卡片',\n      addUser: '添加用户',\n      addWebhook: '添加Webhook',\n      archive: '归档',\n      archiveCard: '归档卡片',\n      archiveCard_title: '归档卡片',\n      archiveCards: '归档多个卡片',\n      archiveCards_title: '归档多个卡片',\n      assignAsOwner: '指定为所有者',\n      cancel: '取消',\n      copy: '复制',\n      copyCard_title: '复制卡片',\n      createApiKey: '创建API密钥',\n      createBoard: '创建面板',\n      createCustomFieldGroup: '创建自定义字段组',\n      createFile: '创建文件',\n      createLabel: '创建标签',\n      createNewLabel: '创建新标签',\n      createProject: '创建项目',\n      cut: '剪切',\n      cutCard_title: '剪切卡片',\n      deactivateUser: '停用用户',\n      deactivateUser_title: '停用用户',\n      delete: '删除',\n      deleteApiKey: '删除API密钥',\n      deleteAttachment: '删除附件',\n      deleteAvatar: '删除头像',\n      deleteBackgroundImage: '删除背景图片',\n      deleteBoard: '删除面板',\n      deleteBoard_title: '删除面板',\n      deleteCard: '删除卡片',\n      deleteCardForever: '永久删除卡片',\n      deleteCard_title: '删除卡片',\n      deleteComment: '删除评论',\n      deleteCustomField: '删除自定义字段',\n      deleteCustomFieldGroup: '删除自定义字段组',\n      deleteForever_title: '永久删除',\n      deleteGroup: '删除组',\n      deleteLabel: '删除标签',\n      deleteList: '删除列表',\n      deleteList_title: '删除列表',\n      deleteNotificationService: '删除通知服务',\n      deleteProject: '删除项目',\n      deleteProject_title: '删除项目',\n      deleteTask: '删除任务',\n      deleteTaskList: '删除任务列表',\n      deleteTask_title: '删除任务',\n      deleteUser: '删除用户',\n      deleteUser_title: '删除用户',\n      deleteWebhook: '删除Webhook',\n      dismissAll: '全部忽略',\n      download: '下载',\n      duplicateCard_title: '复制卡片',\n      edit: '编辑',\n      editColor_title: '编辑颜色',\n      editDescription_title: '编辑描述',\n      editDueDate_title: '编辑到期时间',\n      editEmail_title: '编辑邮箱',\n      editGroup: '编辑组',\n      editInformation_title: '编辑信息',\n      editPassword_title: '编辑密码',\n      editPermissions: '编辑权限',\n      editRole_title: '编辑角色',\n      editStopwatch_title: '编辑时间',\n      editTitle_title: '编辑标题',\n      editType_title: '编辑类型',\n      editUsername_title: '编辑用户名',\n      emptyTrash: '清空回收站',\n      emptyTrash_title: '清空回收站',\n      import: '导入',\n      join: '加入',\n      leave: '离开',\n      leaveBoard: '离开面板',\n      leaveProject: '离开项目',\n      logOut_title: '退出',\n      makeCover_title: '设置标题',\n      makeProjectPrivate: '将项目设为私有',\n      makeProjectPrivate_title: '将项目设为私有',\n      makeProjectShared: '将项目设为共享',\n      makeProjectShared_title: '将项目设为共享',\n      move: '移动',\n      moveCard_title: '移动卡片',\n      moveList_title: '移动列表',\n      regenerateApiKey: '重新生成API密钥',\n      remove: '删除',\n      removeAssignee: '移除负责人',\n      removeColor: '移除颜色',\n      removeCover_title: '删除封面',\n      removeFromBoard: '从面板中删除',\n      removeFromProject: '从项目中删除',\n      removeManager: '删除管理者',\n      removeMember: '删除成员',\n      restoreToList: '恢复到 {{list}}',\n      returnToBoard: '返回面板',\n      save: '保存',\n      sendTestEmail: '发送测试邮件',\n      showActive: '显示活跃',\n      showAllAttachments: '显示所有的附件 ({{hidden}} 隐藏)',\n      showCardsWithThisUser: '显示包含此用户的卡片',\n      showDeactivated: '显示已停用',\n      showFewerAttachments: '显示较少的附件',\n      showLess: '显示更少',\n      showMore: '显示更多',\n      sortList_title: '排序列表',\n      start: '开始',\n      stop: '结束',\n      subscribe: '关注',\n      unlinkSso: '取消SSO链接',\n      unlinkSso_title: '取消SSO链接',\n      unsubscribe: '取消关注',\n      uploadNewAvatar: '上传新头像',\n      uploadNewImage: '上传图片',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/zh-CN/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'zh-CN',\n  country: 'cn',\n  name: '中文',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/zh-CN/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: '活跃用户数已达上限',\n      adminLoginRequiredToInitializeInstance: '需要管理员登录以初始化实例',\n      emailAlreadyInUse: '邮箱已使用',\n      emailOrUsername: '邮箱或用户名',\n      invalidCredentials: '无效凭证',\n      invalidEmailOrUsername: '无效的邮箱或用户名',\n      invalidPassword: '密码错误',\n      logIn_title: '登录',\n      noInternetConnection: '没有网络连接',\n      or: '或',\n      pageNotFound_title: '找不到页面',\n      password: '密码',\n      poweredByPlanka: '技术支持由<1>PLANKA</1>提供',\n      serverConnectionFailed: '服务器连接失败',\n      unknownError: '未知错误，请稍后重试',\n      useSingleSignOn: '使用单点登录',\n      usernameAlreadyInUse: '用户名已被占用',\n      whoops_title: '哎呀！',\n    },\n\n    action: {\n      cancelAndClose: '取消并关闭',\n      continue: '继续',\n      debugSso: '调试SSO',\n      goBack: '返回',\n      goHome: '回到首页',\n      logIn: '登录',\n      logInWithSso: '使用SSO登录',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/zh-CN/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"这是一段无标题文本。\\n标题和文本都可以使用粗体、斜体、颜色、\\n删除线和下划线进行高亮显示。\",\n    \"text-with-head\": \"这是一段带标题的文本。\\n标题和文本都可以使用粗体、斜体、颜色、\\n删除线和下划线进行高亮显示。\",\n    \"heading\": \"标题\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Markdown编辑器错误\",\n    \"settings_wysiwyg\": \"可视化编辑器 (所见即所得)\",\n    \"settings_markup\": \"Markdown标记\",\n    \"markup_placeholder\": \"输入Markdown标记...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"移除\",\n    \"empty_option\": \"未找到匹配项\",\n    \"show_line_numbers\": \"行号\"\n  },\n  \"common\": {\n    \"delete\": \"删除\",\n    \"edit\": \"编辑\",\n    \"toolbar_action_disabled\": \"不兼容的标记元素\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"取消\",\n    \"common_action_submit\": \"提交\",\n    \"common_action_upload\": \"选择\",\n    \"common_tab_attach\": \"从设备添加\",\n    \"common_tab_link\": \"通过链接添加\",\n    \"common_link\": \"链接\",\n    \"common_sizes\": \"尺寸（像素）\",\n    \"image_name\": \"标题\",\n    \"image_link_href\": \"图片链接\",\n    \"image_link_href_help\": \"图片链接指向的地址。\",\n    \"image_alt\": \"替代文本\",\n    \"image_alt_help\": \"当图片无法加载时显示的替代文本。\",\n    \"image_upload_help\": \"JPEG、GIF或PNG格式图片，大小不超过1MB。\",\n    \"image_upload_failed\": \"添加图片失败\",\n    \"image_size_width\": \"宽度\",\n    \"image_size_height\": \"高度\",\n    \"link_url_help\": \"链接指向的地址。\",\n    \"link_text\": \"链接文本\",\n    \"link_text_help\": \"显示为链接的文本。\",\n    \"link_open_help\": \"在新标签页中打开链接\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"标题\",\n    \"header_hint\": \"# 您的文本\",\n    \"italic_title\": \"斜体\",\n    \"italic_hint\": \"_您的文本_\",\n    \"bold_title\": \"粗体\",\n    \"bold_hint\": \"**您的文本**\",\n    \"strikethrough_title\": \"删除线\",\n    \"strikethrough_hint\": \"~~您的文本~~\",\n    \"blockquote_title\": \"引用块\",\n    \"blockquote_hint\": \"> 您的文本\",\n    \"code_title\": \"代码\",\n    \"code_hint\": \"```您的文本```\",\n    \"link_title\": \"链接\",\n    \"link_hint\": \"[您的文本](url)\",\n    \"image_title\": \"图片\",\n    \"image_hint\": \"![您的文本](url)\",\n    \"list_title\": \"列表项\",\n    \"list_hint\": \"- 您的文本\",\n    \"numbered-list_title\": \"有序列表\",\n    \"numbered-list_hint\": \"1. 您的文本\",\n    \"documentation\": \"文档\",\n    \"documentation_link\": \"https://diplodoc.com/docs/zh/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"粗体\",\n    \"code\": \"代码\",\n    \"code_inline\": \"行内代码\",\n    \"codeblock\": \"代码块\",\n    \"colorify\": \"文本颜色\",\n    \"colorify__color_blue\": \"蓝色\",\n    \"colorify__color_default\": \"默认\",\n    \"colorify__color_gray\": \"灰色\",\n    \"colorify__color_green\": \"绿色\",\n    \"colorify__color_orange\": \"橙色\",\n    \"colorify__color_red\": \"红色\",\n    \"colorify__color_violet\": \"紫色\",\n    \"colorify__color_yellow\": \"黄色\",\n    \"colorify__group_text\": \"文本\",\n    \"cut\": \"分割线\",\n    \"emoji\": \"表情符号\",\n    \"emoji__hint\": \"表情符号可在所见即所得编辑器中添加或通过标记手动添加\",\n    \"heading\": \"标题\",\n    \"heading1\": \"标题1\",\n    \"heading2\": \"标题2\",\n    \"heading3\": \"标题3\",\n    \"heading4\": \"标题4\",\n    \"heading5\": \"标题5\",\n    \"heading6\": \"标题6\",\n    \"hrule\": \"分隔线\",\n    \"image\": \"图片\",\n    \"italic\": \"斜体\",\n    \"link\": \"链接\",\n    \"list\": \"列表\",\n    \"list__action_lift\": \"提升层级\",\n    \"list__action_sink\": \"降低层级\",\n    \"list_action_disabled\": \"与列表逻辑冲突\",\n    \"mark\": \"标记\",\n    \"mono\": \"等宽字体\",\n    \"more_action\": \"更多操作\",\n    \"note\": \"注释\",\n    \"olist\": \"有序列表\",\n    \"quote\": \"引用\",\n    \"redo\": \"重做\",\n    \"strike\": \"删除线\",\n    \"table\": \"表格\",\n    \"text\": \"文本\",\n    \"ulist\": \"无序列表\",\n    \"underline\": \"下划线\",\n    \"undo\": \"撤销\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"输入 / 使用快捷命令...\",\n    \"checkbox\": \"输入任务描述...\",\n    \"deflist_term\": \"术语\",\n    \"deflist_desc\": \"定义描述\",\n    \"heading\": \"标题\",\n    \"cut_title\": \"标题\",\n    \"cut_content\": \"点击后显示的内容\",\n    \"note_title\": \"标题\",\n    \"note_content\": \"注释内容\",\n    \"table_cell\": \"单元格内容\",\n    \"select_filter\": \"搜索语言...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"区分大小写\",\n    \"label_whole-word\": \"全字匹配\",\n    \"title\": \"在代码中搜索\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"未找到\"\n  },\n  \"widgets\": {\n    \"image\": \"添加图片\",\n    \"link\": \"添加链接\"\n  },\n  \"yfm-note\": {\n    \"info\": \"信息\",\n    \"tip\": \"提示\",\n    \"warning\": \"警告\",\n    \"alert\": \"警报\",\n    \"remove\": \"移除\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"在前方添加列\",\n    \"column.add.after\": \"在后方添加列\",\n    \"column.remove\": \"移除列\",\n    \"column.remove.multiple\": \"移除列\",\n    \"row.add.before\": \"在上方添加行\",\n    \"row.add.after\": \"在下方添加行\",\n    \"row.remove\": \"移除行\",\n    \"row.remove.multiple\": \"移除行\",\n    \"cells.clear\": \"清空单元格\",\n    \"table.remove\": \"移除表格\",\n    \"table.menu.cell.align.left\": \"单元格内容左对齐\",\n    \"table.menu.cell.align.right\": \"单元格内容右对齐\",\n    \"table.menu.cell.align.center\": \"单元格内容居中对齐\",\n    \"table.menu.row.add\": \"在下方添加行\",\n    \"table.menu.row.remove\": \"移除行\",\n    \"table.menu.column.add\": \"在右侧添加列\",\n    \"table.menu.column.remove\": \"移除列\",\n    \"table.menu.convert.yfm\": \"转换为YFM表格\",\n    \"table.menu.table.remove\": \"移除表格\"\n  }\n}\n"
  },
  {
    "path": "client/src/locales/zh-TW/core.js",
    "content": "import dateFns from 'date-fns/locale/zh-TW';\nimport timeAgo from 'javascript-time-ago/locale/zh';\n\nimport markdownEditor from './markdown-editor.json';\n\nexport default {\n  dateFns,\n  timeAgo,\n  markdownEditor,\n\n  format: {\n    date: 'M/d/yyyy',\n    time: 'p',\n    dateTime: '$t(format:date) $t(format:time)',\n    longDate: 'MMM d',\n    longDateTime: \"MMMM d 'at' p\",\n    fullDate: 'MMM d, y',\n    fullDateTime: \"MMMM d, y 'at' p\",\n  },\n\n  translation: {\n    common: {\n      aboutApp_title: '關於應用程式',\n      aboutPlanka_title: '關於 PLANKA',\n      accessToken: '存取權杖',\n      account: '帳號',\n      actions: '操作',\n      activateUser_title: '啟用使用者',\n      active: '啟用',\n      addAttachment_title: '添加附件',\n      addCustomFieldGroup_title: '添加自定義欄位群組',\n      addCustomField_title: '添加自定義欄位',\n      addManager_title: '添加管理員',\n      addMember_title: '添加成員',\n      addTaskList_title: '添加任務列表',\n      addUser_title: '添加使用者',\n      admin: '管理員',\n      administration: '管理',\n      all: '全部',\n      allChangesWillBeAutomaticallySavedAfterConnectionRestored: '所有修改會在重新連線後自動保存',\n      alphabetically: '按字母順序',\n      alwaysDisplayCardCreator: '總是顯示卡片建立者',\n      apiKeyCreated_title: 'API金鑰已建立',\n      apiKey_title: 'API金鑰',\n      archive: '封存',\n      archiveCard_title: '封存卡片',\n      archiveCards_title: '封存卡片',\n      areYouSureYouWantToActivateThisUser: '確認啟用此使用者嗎？',\n      areYouSureYouWantToArchiveCards: '確認封存這些卡片嗎？',\n      areYouSureYouWantToArchiveThisCard: '確認封存此卡片嗎？',\n      areYouSureYouWantToAssignThisProjectManagerAsOwner: '確認將此專案管理員指派為擁有者嗎？',\n      areYouSureYouWantToDeactivateThisUser: '確認停用此使用者嗎？',\n      areYouSureYouWantToDeleteThisApiKey: '確認刪除此API金鑰嗎？',\n      areYouSureYouWantToDeleteThisAttachment: '確認刪除此附件嗎？',\n      areYouSureYouWantToDeleteThisBackgroundImage: '確認刪除此背景圖片嗎？',\n      areYouSureYouWantToDeleteThisBoard: '確認刪除此看板嗎？',\n      areYouSureYouWantToDeleteThisCard: '確認刪除此卡片嗎？',\n      areYouSureYouWantToDeleteThisCardForever: '確認永久刪除此卡片嗎？',\n      areYouSureYouWantToDeleteThisComment: '確認刪除此評論嗎？',\n      areYouSureYouWantToDeleteThisCustomField: '確認刪除此自定義欄位嗎？',\n      areYouSureYouWantToDeleteThisCustomFieldGroup: '確認刪除此自定義欄位群組嗎？',\n      areYouSureYouWantToDeleteThisLabel: '確認刪除此標籤嗎？',\n      areYouSureYouWantToDeleteThisList: '確認刪除此列表嗎？所有卡片將被移至垃圾桶。',\n      areYouSureYouWantToDeleteThisNotificationService: '確認刪除此通知服務嗎？',\n      areYouSureYouWantToDeleteThisProject: '確認刪除此專案嗎？',\n      areYouSureYouWantToDeleteThisTask: '確認刪除此任務嗎？',\n      areYouSureYouWantToDeleteThisTaskList: '確認刪除此任務列表嗎？',\n      areYouSureYouWantToDeleteThisUser: '確認刪除此使用者嗎？',\n      areYouSureYouWantToDeleteThisWebhook: '確認刪除此 Webhook 嗎？',\n      areYouSureYouWantToEmptyTrash: '確認清空垃圾桶嗎？',\n      areYouSureYouWantToLeaveBoard: '確認離開此看板嗎？',\n      areYouSureYouWantToLeaveProject: '確認離開此專案嗎？',\n      areYouSureYouWantToMakeThisProjectPrivate: '確認將此專案設為私人嗎？',\n      areYouSureYouWantToMakeThisProjectShared: '確認將此專案設為共享嗎？',\n      areYouSureYouWantToRegenerateThisApiKey: '確認重新產生此API金鑰嗎？之前的金鑰將不再有效。',\n      areYouSureYouWantToRemoveThisManagerFromProject: '確認從此專案中刪除該管理員嗎？',\n      areYouSureYouWantToRemoveThisMemberFromBoard: '確認從此看板中刪除該成員嗎？',\n      areYouSureYouWantToUnlinkSsoFromThisUser:\n        '您確定要取消此使用者的SSO連結嗎？這將允許使用者使用密碼登入。',\n      assignAsOwner_title: '指派為擁有者',\n      atLeastOneListMustBePresent: '至少必須有一個列表',\n      attachment: '附件',\n      attachments: '附件',\n      authentication: '驗證',\n      background: '背景',\n      baseCustomFields_title: '基礎自定義欄位',\n      baseGroup: '基礎群組',\n      board: '看板',\n      boardActions_title: '看板操作',\n      boardNotFound_title: '看板不存在',\n      boardSubscribed: '已訂閱看板',\n      boardUser: '看板使用者',\n      byCreationTime: '按創建時間',\n      byDefault: '預設',\n      byDueDate: '按到期日',\n      canBeInvitedToWorkInBoards: '可以被邀請在看板中工作。',\n      canComment: '可以評論',\n      canCreateOwnProjectsAndBeInvitedToWorkInOthers: '可以創建自己的專案並被邀請參與其他專案。',\n      canEditBoardLayoutAndAssignMembersToCards: '可以編輯看板佈局並將成員指派給卡片。',\n      canManageSystemWideSettingsAndActAsProjectOwner: '可以管理系統設定並作為專案擁有者。',\n      canOnlyViewBoard: '僅可查看看板。',\n      cardActions_title: '卡片操作',\n      cardNotFound_title: '卡片不存在',\n      cardsOnThisListAreAvailableToAllBoardMembers: '此列表中的卡片對所有看板成員可見。',\n      cardsOnThisListAreCompleteAndReadyToBeArchived: '此列表中的卡片已完成並準備封存。',\n      cardsOnThisListAreReadyToBeWorkedOn: '此列表中的卡片準備開始工作。',\n      clickHereOrRefreshPageToUpdate: '<0>點擊此處</0>或重新整理頁面以更新。',\n      clientHostnameInEhlo: 'EHLO 中的客戶端主機名',\n      closed: '已關閉',\n      color: '顏色',\n      comments: '評論',\n      contentExceedsLimit: '內容超過{{limit}}限制',\n      contentOfThisAttachmentIsTooBigToDisplay: '此附件的內容太大無法顯示。',\n      copy_inline: '副本',\n      createBoard_title: '創建看板',\n      createCustomFieldGroup_title: '創建自定義欄位群組',\n      createLabel_title: '創建標籤',\n      createNewOneOrSelectExistingOne: '創建一個新的或選擇<br />一個已創建的。',\n      createProject_title: '創建專案',\n      createTextFile_title: '創建文本文件',\n      creator: '建立者',\n      currentPassword: '當前密碼',\n      currentUser: '目前使用者',\n      customFieldGroup_title: '自定義欄位群組',\n      customFieldGroups_title: '自定義欄位群組',\n      customField_title: '自定義欄位',\n      customFields_title: '自定義欄位',\n      customerPanel_title: '客戶面板',\n      dangerZone_title: '危險區域',\n      date: '日期',\n      deactivateUser_title: '停用使用者',\n      defaultCardType_title: '預設卡片類型',\n      defaultFrom: '預設發送者',\n      defaultView_title: '預設檢視',\n      deleteAllBoardsToBeAbleToDeleteThisProject: '刪除所有看板以便刪除此專案',\n      deleteApiKey_title: '刪除API金鑰',\n      deleteAttachment_title: '刪除附件',\n      deleteBackgroundImage_title: '刪除背景圖片',\n      deleteBoard_title: '刪除看板',\n      deleteCardForever_title: '永久刪除卡片',\n      deleteCard_title: '刪除卡片',\n      deleteComment_title: '刪除評論',\n      deleteCustomFieldGroup_title: '刪除自定義欄位群組',\n      deleteCustomField_title: '刪除自定義欄位',\n      deleteLabel_title: '刪除標籤',\n      deleteList_title: '刪除列表',\n      deleteNotificationService_title: '刪除通知服務',\n      deleteProject_title: '刪除專案',\n      deleteTaskList_title: '刪除任務列表',\n      deleteTask_title: '刪除任務',\n      deleteUser_title: '刪除使用者',\n      deleteWebhook_title: '刪除 Webhook',\n      deletedUser_title: '已刪除的使用者',\n      description: '描述',\n      display: '顯示',\n      displayCardAges: '顯示卡片建立時間',\n      dropFileToUpload: '拖放文件以上傳',\n      dueDate_title: '截止日期',\n      dynamicAndUnevenlySpacedLayout: '動態不均勻間距佈局。',\n      editAttachment_title: '編輯附件',\n      editAvatar_title: '編輯頭像',\n      editColor_title: '編輯顏色',\n      editCustomFieldGroup_title: '編輯自定義欄位群組',\n      editCustomField_title: '編輯自定義欄位',\n      editDueDate_title: '編輯截止時間',\n      editEmail_title: '編輯郵箱',\n      editInformation_title: '編輯信息',\n      editLabel_title: '編輯標籤',\n      editPassword_title: '修改密碼',\n      editPermissions_title: '修改權限',\n      editRole_title: '編輯角色',\n      editStopwatch_title: '修改計時',\n      editType_title: '編輯類型',\n      editUsername_title: '修改使用者名稱',\n      editor: '編輯器',\n      editors: '編輯者',\n      email: '郵箱',\n      emptyTrash_title: '清空垃圾桶',\n      enterCardTitle: '輸入卡片標題...',\n      enterDescription: '輸入描述...',\n      enterFilename: '輸入文件名',\n      enterListTitle: '輸入列表標題...',\n      enterTaskDescription: '輸入任務描述...',\n      events: '事件',\n      excludedEvents: '排除的事件',\n      expandTaskListsByDefault: '預設展開任務列表',\n      filterByLabels_title: '通過標籤篩選',\n      filterByMembers_title: '通過成員篩選',\n      forPersonalProjects: '用於個人專案。',\n      forTeamBasedProjects: '用於團隊專案。',\n      fromComputer_title: '來自電腦',\n      fromTrello: '來自 Trello',\n      fullKeyIsHiddenForSecurityReasons: '出於安全考量，完整金鑰已隱藏。重新產生以建立新金鑰。',\n      general: '通用',\n      gradients: '漸層',\n      grid: '網格',\n      hideCompletedTasks: '隱藏已完成的任務',\n      hideFromProjectListAndFavorites: '從專案列表和收藏夾中隱藏',\n      host: '主機',\n      hours: '小時',\n      identity: '身分',\n      importBoard_title: '導入看板',\n      information: '資訊',\n      invalidCurrentPassword: '當前密碼錯誤',\n      kanban: '看板',\n      labels: '標籤',\n      language: '語言',\n      leaveBoard_title: '離開看板',\n      leaveProject_title: '離開專案',\n      limitCardTypesToDefaultOne: '將卡片類型限制為預設',\n      linkToCard: '連結到卡片',\n      list: '列表',\n      listActions_title: '列表操作',\n      lists: '列表',\n      makeProjectPrivate_title: '將專案設為私人',\n      makeProjectShared_title: '將專案設為共享',\n      managers: '管理員',\n      memberActions_title: '成員操作',\n      members: '成員',\n      minutes: '分鐘',\n      moreActions: '更多操作',\n      moreActions_title: '更多操作',\n      moveCard_title: '移動卡片',\n      moveList_title: '移動列表',\n      myOwn_title: '我的',\n      name: '姓名',\n      newEmail: '新郵箱',\n      newPassword: '新密碼',\n      newUsername: '新使用者名稱',\n      newVersionAvailable: '有新版本可用',\n      newestFirst: '最新優先',\n      noApiKeyCreated: '未建立API金鑰。',\n      noBoards: '沒有看板',\n      noCardsFound: '未找到卡片。',\n      noConnectionToServer: '未連接到伺服器',\n      noLists: '沒有列表',\n      noProjects: '沒有專案',\n      noUnreadNotifications: '沒有未讀通知。',\n      notifications: '通知',\n      oldestFirst: '最舊優先',\n      onlyOneManagerShouldRemainToMakeThisProjectPrivate: '要將此專案設為私人，只能保留一個管理員',\n      openBoard_title: '打開看板',\n      optional_inline: '可選的',\n      organization: '組織機構',\n      others: '其他',\n      passwordIsSet: '密碼已設定',\n      phone: '電話',\n      plankaUsesAppriseToSendNotificationsToOver100PopularServices:\n        'PLANKA 使用 <1><0>Apprise</0></1> 向超過 100 個熱門服務發送通知。',\n      port: '連接埠',\n      preferences: '偏好設定',\n      pressPasteShortcutToAddAttachmentFromClipboard:\n        '提示：按下 Ctrl-V (Mac 上為 Cmd-V) 從剪貼簿添加附件。',\n      private: '私人',\n      project: '專案',\n      projectNotFound_title: '專案未找到',\n      projectOwner: '專案擁有者',\n      referenceDataAndKnowledgeStorage: '參考資料和知識儲存。',\n      regenerateApiKey_title: '重新產生API金鑰',\n      rejectUnauthorizedTlsCertificates: '拒絕未授權的 TLS 憑證',\n      removeManager_title: '刪除管理員',\n      removeMember_title: '刪除成員',\n      role: '角色',\n      saveThisKeyItWillNotBeShownAgain: '儲存此金鑰——它不會再次顯示！',\n      searchCards: '搜尋卡片...',\n      searchCustomFieldGroups: '搜尋自定義欄位群組...',\n      searchCustomFields: '搜尋自定義欄位...',\n      searchLabels: '搜尋標籤...',\n      searchLists: '搜尋列表...',\n      searchMembers: '搜尋成員...',\n      searchProjects: '搜尋專案...',\n      searchUsers: '搜尋使用者...',\n      seconds: '秒',\n      selectAssignee_title: '選擇受派者',\n      selectBoard: '選擇看板',\n      selectList: '選擇列表',\n      selectListToRestoreThisCard: '選擇列表以恢復此卡片',\n      selectOrder_title: '選擇順序',\n      selectPermissions_title: '選擇權限',\n      selectProject: '選擇專案',\n      selectRole_title: '選擇角色',\n      selectType_title: '選擇類型',\n      sequentialDisplayOfCards: '卡片順序顯示。',\n      settings: '設置',\n      shared: '共享',\n      sharedWithMe_title: '與我共享',\n      showOnFrontOfCard: '在卡片正面顯示',\n      smtp: 'SMTP',\n      sortList_title: '排序列表',\n      sourceCardIsNoLongerAvailableForCopying: '來源卡片不再可供複製。',\n      sourceCardIsNoLongerAvailableForMoving: '來源卡片不再可供移動。',\n      stopwatch: '碼表',\n      story: '故事',\n      subscribeToCardWhenCommenting: '評論時訂閱卡片',\n      subscribeToMyOwnCardsByDefault: '默認訂閱自己創建的卡片',\n      taskActions_title: '任務操作',\n      taskAssignmentAndProjectCompletion: '任務分配和專案完成。',\n      taskListActions_title: '任務列表操作',\n      taskList_title: '任務列表',\n      team: '團隊',\n      termsOfService_title: '服務條款',\n      testLog_title: '測試日誌',\n      thereIsNoPreviewAvailableForThisAttachment: '此附件無法預覽。',\n      time: '時間',\n      title: '標題',\n      trash: '垃圾桶',\n      trashHasBeenSuccessfullyEmptied: '垃圾桶已成功清空。',\n      turnOffRecentCardHighlighting: '關閉最近卡片高亮顯示',\n      typeNameToConfirm: '輸入名稱以確認。',\n      typeTitleToConfirm: '輸入標題以確認。',\n      unlinkSso_title: '取消SSO連結',\n      unsavedChanges: '未保存的更改',\n      uploadFailedFileIsTooBig: '上傳失敗：檔案太大。',\n      uploadFailedNotEnoughStorageSpace: '上傳失敗：儲存空間不足。',\n      uploadedImages: '已上傳的圖片',\n      url: 'URL',\n      useSecureConnection: '使用安全連接',\n      userActions_title: '使用者操作',\n      userAddedCardToList: '<0>{{user}}</0> 將 <2>{{card}}</2> 添加到 {{list}}',\n      userAddedThisCardToList: '<0>{{user}}</0> 向列表 {{list}} 添加了該卡片',\n      userAddedUserToCard: '<0>{{actorUser}}</0> 將 {{addedUser}} 添加到 <4>{{card}}</4>',\n      userAddedUserToThisCard: '<0>{{actorUser}}</0> 將 {{addedUser}} 添加到此卡片',\n      userAddedYouToCard: '<0>{{user}}</0> 將您添加到 <2>{{card}}</2>',\n      userCompletedTaskOnCard: '<0>{{user}}</0> 在 <4>{{card}}</4> 中完成了 {{task}}',\n      userCompletedTaskOnThisCard: '<0>{{user}}</0> 在此卡片中完成了 {{task}}',\n      userJoinedCard: '<0>{{user}}</0> 加入了 <2>{{card}}</2>',\n      userJoinedThisCard: '<0>{{user}}</0> 加入了此卡片',\n      userLeftCard: '<0>{{user}}</0> 離開了 <2>{{card}}</2>',\n      userLeftNewCommentToCard: '<0>{{user}}</0> 給 <2>{{card}}</2> 添加了一條新評論 «{{comment}}»',\n      userLeftThisCard: '<0>{{user}}</0> 離開了此卡片',\n      userMarkedTaskIncompleteOnCard:\n        '<0>{{user}}</0> 在 <4>{{card}}</4> 中將 {{task}} 標記為未完成',\n      userMarkedTaskIncompleteOnThisCard: '<0>{{user}}</0> 在此卡片中將 {{task}} 標記為未完成',\n      userMentionedYouInCommentOnCard:\n        '<0>{{user}}</0> 在 <2>{{card}}</2> 的評論 「{{comment}}」 中提到了您',\n      userMovedCardFromListToList:\n        '<0>{{user}}</0> 將卡片 <2>{{card}}</2> 從 {{fromList}} 移動到 {{toList}}',\n      userMovedThisCardFromListToList: '<0>{{user}}</0> 將該卡片從 {{fromList}} 移動到 {{toList}}',\n      userRemovedUserFromCard: '<0>{{actorUser}}</0> 從 <4>{{card}}</4> 中移除了 {{removedUser}}',\n      userRemovedUserFromThisCard: '<0>{{actorUser}}</0> 從此卡片中移除了 {{removedUser}}',\n      username: '使用者名稱',\n      users: '使用者',\n      viewer: '檢視',\n      viewers: '檢視者',\n      visualTaskManagementWithLists: '使用列表進行視覺化任務管理。',\n      webhooks: 'Webhook',\n      whatsNew_title: '最新消息',\n      withoutBaseGroup: '無基礎群組',\n      writeComment: '編寫評論...',\n    },\n\n    action: {\n      activateUser: '啟用使用者',\n      activateUser_title: '啟用使用者',\n      addAnotherCard: '添加另一個卡片',\n      addAnotherList: '添加另一個列表',\n      addAnotherTask: '添加另一個任務',\n      addCard: '添加卡片',\n      addCard_title: '添加卡片',\n      addComment: '添加評論',\n      addCustomField: '添加自定義欄位',\n      addCustomFieldGroup: '添加自定義欄位群組',\n      addList: '添加列表',\n      addMember: '添加成員',\n      addMoreDetailedDescription: '添加更多詳細描述',\n      addTask: '添加任務',\n      addTaskList: '添加任務列表',\n      addToCard: '添加到卡片',\n      addUser: '添加使用者',\n      addWebhook: '添加 Webhook',\n      archive: '封存',\n      archiveCard: '封存卡片',\n      archiveCard_title: '封存卡片',\n      archiveCards: '封存卡片',\n      archiveCards_title: '封存卡片',\n      assignAsOwner: '指派為擁有者',\n      cancel: '取消',\n      copy: '複製',\n      copyCard_title: '複製卡片',\n      createApiKey: '建立API金鑰',\n      createBoard: '創建看板',\n      createCustomFieldGroup: '創建自定義欄位群組',\n      createFile: '創建文件',\n      createLabel: '創建標籤',\n      createNewLabel: '創建新標籤',\n      createProject: '創建專案',\n      cut: '剪下',\n      cutCard_title: '剪下卡片',\n      deactivateUser: '停用使用者',\n      deactivateUser_title: '停用使用者',\n      delete: '刪除',\n      deleteApiKey: '刪除API金鑰',\n      deleteAttachment: '刪除附件',\n      deleteAvatar: '刪除頭像',\n      deleteBackgroundImage: '刪除背景圖片',\n      deleteBoard: '刪除看板',\n      deleteBoard_title: '刪除看板',\n      deleteCard: '刪除卡片',\n      deleteCardForever: '永久刪除卡片',\n      deleteCard_title: '刪除卡片',\n      deleteComment: '刪除評論',\n      deleteCustomField: '刪除自定義欄位',\n      deleteCustomFieldGroup: '刪除自定義欄位群組',\n      deleteForever_title: '永久刪除',\n      deleteGroup: '刪除群組',\n      deleteLabel: '刪除標籤',\n      deleteList: '刪除列表',\n      deleteList_title: '刪除列表',\n      deleteNotificationService: '刪除通知服務',\n      deleteProject: '刪除專案',\n      deleteProject_title: '刪除專案',\n      deleteTask: '刪除任務',\n      deleteTaskList: '刪除任務列表',\n      deleteTask_title: '刪除任務',\n      deleteUser: '刪除使用者',\n      deleteUser_title: '刪除使用者',\n      deleteWebhook: '刪除 Webhook',\n      dismissAll: '全部關閉',\n      download: '下載',\n      duplicateCard_title: '複製卡片',\n      edit: '編輯',\n      editColor_title: '編輯顏色',\n      editDescription_title: '編輯描述',\n      editDueDate_title: '編輯截止時間',\n      editEmail_title: '編輯郵箱',\n      editGroup: '編輯群組',\n      editInformation_title: '編輯信息',\n      editPassword_title: '編輯密碼',\n      editPermissions: '編輯權限',\n      editRole_title: '編輯角色',\n      editStopwatch_title: '編輯碼表',\n      editTitle_title: '編輯標題',\n      editType_title: '編輯類型',\n      editUsername_title: '編輯使用者名稱',\n      emptyTrash: '清空垃圾桶',\n      emptyTrash_title: '清空垃圾桶',\n      import: '導入',\n      join: '加入',\n      leave: '離開',\n      leaveBoard: '離開看板',\n      leaveProject: '離開專案',\n      logOut_title: '登出',\n      makeCover_title: '設置封面',\n      makeProjectPrivate: '將專案設為私人',\n      makeProjectPrivate_title: '將專案設為私人',\n      makeProjectShared: '將專案設為共享',\n      makeProjectShared_title: '將專案設為共享',\n      move: '移動',\n      moveCard_title: '移動卡片',\n      moveList_title: '移動列表',\n      regenerateApiKey: '重新產生API金鑰',\n      remove: '刪除',\n      removeAssignee: '移除受派者',\n      removeColor: '移除顏色',\n      removeCover_title: '刪除封面',\n      removeFromBoard: '從看板中刪除',\n      removeFromProject: '從專案中刪除',\n      removeManager: '刪除管理員',\n      removeMember: '刪除成員',\n      restoreToList: '恢復到 {{list}}',\n      returnToBoard: '返回看板',\n      save: '保存',\n      sendTestEmail: '發送測試郵件',\n      showActive: '顯示啟用',\n      showAllAttachments: '顯示所有附件 ({{hidden}} 隱藏)',\n      showCardsWithThisUser: '顯示此使用者的卡片',\n      showDeactivated: '顯示已停用',\n      showFewerAttachments: '顯示較少附件',\n      showLess: '顯示較少',\n      showMore: '顯示更多',\n      sortList_title: '排序列表',\n      start: '開始',\n      stop: '結束',\n      subscribe: '訂閱',\n      unlinkSso: '取消SSO連結',\n      unlinkSso_title: '取消SSO連結',\n      unsubscribe: '取消訂閱',\n      uploadNewAvatar: '上傳新頭像',\n      uploadNewImage: '上傳圖片',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/zh-TW/index.js",
    "content": "import login from './login';\n\nexport default {\n  language: 'zh-TW',\n  country: 'tw',\n  name: '繁體中文',\n  embeddedLocale: login,\n};\n"
  },
  {
    "path": "client/src/locales/zh-TW/login.js",
    "content": "export default {\n  translation: {\n    common: {\n      activeUsersLimitReached: '活躍使用者數已達上限',\n      adminLoginRequiredToInitializeInstance: '需要管理員登入以初始化實例',\n      emailAlreadyInUse: '郵箱已被使用',\n      emailOrUsername: '郵箱或使用者名稱',\n      invalidCredentials: '無效憑證',\n      invalidEmailOrUsername: '無效的郵箱或使用者名稱',\n      invalidPassword: '密碼錯誤',\n      logIn_title: '登入',\n      noInternetConnection: '沒有網路連接',\n      or: '或',\n      pageNotFound_title: '找不到頁面',\n      password: '密碼',\n      poweredByPlanka: '技術支援由<1>PLANKA</1>提供',\n      serverConnectionFailed: '伺服器連接失敗',\n      unknownError: '未知錯誤，請稍後重試',\n      useSingleSignOn: '使用單一登入',\n      usernameAlreadyInUse: '使用者名稱已被佔用',\n      whoops_title: '哎呀！',\n    },\n\n    action: {\n      cancelAndClose: '取消並關閉',\n      continue: '繼續',\n      debugSso: '偵錯SSO',\n      goBack: '返回',\n      goHome: '回到首頁',\n      logIn: '登入',\n      logInWithSso: '使用SSO登入',\n    },\n  },\n};\n"
  },
  {
    "path": "client/src/locales/zh-TW/markdown-editor.json",
    "content": "{\n  \"action-previews\": {\n    \"text\": \"這是一個沒有標題的文字。\\n標題和文字\\n都可以用粗體、斜體、顏色、\\n刪除線和底線來突出顯示。\",\n    \"text-with-head\": \"這是一個有標題的文字。\\n標題和文字\\n都可以用粗體、斜體、顏色、\\n刪除線和底線來突出顯示。\",\n    \"heading\": \"標題\"\n  },\n  \"bundle\": {\n    \"error-title\": \"Markdown 編輯器錯誤\",\n    \"settings_wysiwyg\": \"視覺編輯器 (wysiwyg)\",\n    \"settings_markup\": \"Markdown 標記\",\n    \"markup_placeholder\": \"輸入 markdown 標記...\"\n  },\n  \"codeblock\": {\n    \"remove\": \"移除\",\n    \"empty_option\": \"找不到匹配項\",\n    \"show_line_numbers\": \"行號\"\n  },\n  \"common\": {\n    \"delete\": \"刪除\",\n    \"edit\": \"編輯\",\n    \"toolbar_action_disabled\": \"不相容的標記元素\"\n  },\n  \"forms\": {\n    \"common_action_cancel\": \"取消\",\n    \"common_action_submit\": \"提交\",\n    \"common_action_upload\": \"選擇\",\n    \"common_tab_attach\": \"從裝置新增\",\n    \"common_tab_link\": \"透過連結新增\",\n    \"common_link\": \"連結\",\n    \"common_sizes\": \"大小，px\",\n    \"image_name\": \"標題\",\n    \"image_link_href\": \"圖片連結\",\n    \"image_link_href_help\": \"圖片連結指向的地址。\",\n    \"image_alt\": \"替代文字\",\n    \"image_alt_help\": \"如果圖片無法載入，會顯示替代文字。\",\n    \"image_upload_help\": \"不大於 1 MB 的 JPEG、GIF 或 PNG 圖片。\",\n    \"image_upload_failed\": \"新增圖片失敗\",\n    \"image_size_width\": \"寬度\",\n    \"image_size_height\": \"高度\",\n    \"link_url_help\": \"連結指向的地址。\",\n    \"link_text\": \"連結文字\",\n    \"link_text_help\": \"顯示為連結的文字。\",\n    \"link_open_help\": \"在新分頁中開啟連結\"\n  },\n  \"md-hints\": {\n    \"header_title\": \"標題\",\n    \"header_hint\": \"# 您的文字\",\n    \"italic_title\": \"斜體\",\n    \"italic_hint\": \"_您的文字_\",\n    \"bold_title\": \"粗體\",\n    \"bold_hint\": \"**您的文字**\",\n    \"strikethrough_title\": \"刪除線\",\n    \"strikethrough_hint\": \"~~您的文字~~\",\n    \"blockquote_title\": \"區塊引用\",\n    \"blockquote_hint\": \"> 您的文字\",\n    \"code_title\": \"程式碼\",\n    \"code_hint\": \"```您的文字```\",\n    \"link_title\": \"連結\",\n    \"link_hint\": \"[您的文字](url)\",\n    \"image_title\": \"圖片\",\n    \"image_hint\": \"![您的文字](url)\",\n    \"list_title\": \"清單項目\",\n    \"list_hint\": \"- 您的文字\",\n    \"numbered-list_title\": \"編號清單\",\n    \"numbered-list_hint\": \"1. 您的文字\",\n    \"documentation\": \"文件\",\n    \"documentation_link\": \"https://diplodoc.com/docs/en/syntax/\"\n  },\n  \"menubar\": {\n    \"bold\": \"粗體\",\n    \"code\": \"程式碼\",\n    \"code_inline\": \"行內程式碼\",\n    \"codeblock\": \"程式碼區塊\",\n    \"colorify\": \"文字顏色\",\n    \"colorify__color_blue\": \"藍色\",\n    \"colorify__color_default\": \"預設\",\n    \"colorify__color_gray\": \"灰色\",\n    \"colorify__color_green\": \"綠色\",\n    \"colorify__color_orange\": \"橙色\",\n    \"colorify__color_red\": \"紅色\",\n    \"colorify__color_violet\": \"紫色\",\n    \"colorify__color_yellow\": \"黃色\",\n    \"colorify__group_text\": \"文字\",\n    \"cut\": \"剪下\",\n    \"emoji\": \"表情符號\",\n    \"emoji__hint\": \"表情符號可以在 WYSIWYG 中新增或手動使用標記新增\",\n    \"heading\": \"標題\",\n    \"heading1\": \"標題 1\",\n    \"heading2\": \"標題 2\",\n    \"heading3\": \"標題 3\",\n    \"heading4\": \"標題 4\",\n    \"heading5\": \"標題 5\",\n    \"heading6\": \"標題 6\",\n    \"hrule\": \"分隔線\",\n    \"image\": \"圖片\",\n    \"italic\": \"斜體\",\n    \"link\": \"連結\",\n    \"list\": \"清單\",\n    \"list__action_lift\": \"提升項目\",\n    \"list__action_sink\": \"降低項目\",\n    \"list_action_disabled\": \"與清單邏輯衝突\",\n    \"mark\": \"標記\",\n    \"mono\": \"等寬字體\",\n    \"more_action\": \"更多動作\",\n    \"note\": \"備註\",\n    \"olist\": \"有序清單\",\n    \"quote\": \"引用\",\n    \"redo\": \"重做\",\n    \"strike\": \"刪除線\",\n    \"table\": \"表格\",\n    \"text\": \"文字\",\n    \"ulist\": \"項目符號清單\",\n    \"underline\": \"底線\",\n    \"undo\": \"復原\"\n  },\n  \"placeholder\": {\n    \"doc_empty\": \"輸入 / 使用斜線命令...\",\n    \"checkbox\": \"輸入任務描述...\",\n    \"deflist_term\": \"術語\",\n    \"deflist_desc\": \"定義描述\",\n    \"heading\": \"標題\",\n    \"cut_title\": \"標題\",\n    \"cut_content\": \"點擊時顯示的內容\",\n    \"note_title\": \"標題\",\n    \"note_content\": \"備註內容\",\n    \"table_cell\": \"儲存格內容\",\n    \"select_filter\": \"搜尋語言...\"\n  },\n  \"search\": {\n    \"label_case-sensitive\": \"區分大小寫\",\n    \"label_whole-word\": \"整個單字\",\n    \"title\": \"在程式碼中搜尋\"\n  },\n  \"suggest\": {\n    \"empty-msg\": \"找不到\"\n  },\n  \"widgets\": {\n    \"image\": \"新增圖片\",\n    \"link\": \"新增連結\"\n  },\n  \"yfm-note\": {\n    \"info\": \"資訊\",\n    \"tip\": \"提示\",\n    \"warning\": \"警告\",\n    \"alert\": \"警報\",\n    \"remove\": \"移除\"\n  },\n  \"yfm-table\": {\n    \"column.add.before\": \"在前面新增欄\",\n    \"column.add.after\": \"在後面新增欄\",\n    \"column.remove\": \"移除欄\",\n    \"column.remove.multiple\": \"移除欄\",\n    \"row.add.before\": \"在前面新增列\",\n    \"row.add.after\": \"在後面新增列\",\n    \"row.remove\": \"移除列\",\n    \"row.remove.multiple\": \"移除列\",\n    \"cells.clear\": \"清空儲存格\",\n    \"table.remove\": \"移除表格\",\n    \"table.menu.cell.align.left\": \"將儲存格內容靠左對齊\",\n    \"table.menu.cell.align.right\": \"將儲存格內容靠右對齊\",\n    \"table.menu.cell.align.center\": \"將儲存格內容置中對齊\",\n    \"table.menu.row.add\": \"在後面新增列\",\n    \"table.menu.row.remove\": \"移除列\",\n    \"table.menu.column.add\": \"在後面新增欄\",\n    \"table.menu.column.remove\": \"移除欄\",\n    \"table.menu.convert.yfm\": \"轉換為 YFM 表格\",\n    \"table.menu.table.remove\": \"移除表格\"\n  }\n}\n"
  },
  {
    "path": "client/src/models/Activity.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'Activity';\n\n  static fields = {\n    id: attr(),\n    type: attr(),\n    data: attr(),\n    createdAt: attr({\n      getDefault: () => new Date(),\n    }),\n    boardId: fk({\n      to: 'Board',\n      as: 'board',\n      relatedName: 'activities',\n    }),\n    cardId: fk({\n      to: 'Card',\n      as: 'card',\n      relatedName: 'activities',\n    }),\n    userId: fk({\n      to: 'User',\n      as: 'user',\n      relatedName: 'activities',\n    }),\n  };\n\n  static reducer({ type, payload }, Activity) {\n    switch (type) {\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        Activity.all().delete();\n\n        break;\n      case ActionTypes.LIST_CARDS_MOVE__SUCCESS:\n      case ActionTypes.ACTIVITIES_IN_BOARD_FETCH__SUCCESS:\n      case ActionTypes.ACTIVITIES_IN_CARD_FETCH__SUCCESS:\n        payload.activities.forEach((activity) => {\n          Activity.upsert(activity);\n        });\n\n        break;\n      case ActionTypes.CARDS_UPDATE_HANDLE:\n        if (payload.activities) {\n          payload.activities.forEach((activity) => {\n            Activity.upsert(activity);\n          });\n        }\n\n        break;\n      case ActionTypes.ACTIVITY_CREATE_HANDLE:\n        Activity.upsert(payload.activity);\n\n        break;\n      default:\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/models/Attachment.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport getFilenameAndExtension from '../utils/get-filename-and-extension';\nimport ActionTypes from '../constants/ActionTypes';\nimport { AttachmentTypes } from '../constants/Enums';\n\nconst prepareAttachment = (attachment) => {\n  if (attachment.type !== AttachmentTypes.FILE || !attachment.data) {\n    return attachment;\n  }\n\n  const { filename, extension } = getFilenameAndExtension(attachment.data.url);\n\n  return {\n    ...attachment,\n    data: {\n      ...attachment.data,\n      filename,\n      extension,\n    },\n  };\n};\n\nexport default class extends BaseModel {\n  static modelName = 'Attachment';\n\n  static fields = {\n    id: attr(),\n    type: attr(),\n    data: attr(),\n    name: attr(),\n    createdAt: attr({\n      getDefault: () => new Date(),\n    }),\n    cardId: fk({\n      to: 'Card',\n      as: 'card',\n      relatedName: 'attachments',\n    }),\n    creatorUserId: fk({\n      to: 'User',\n      as: 'creatorUser',\n      relatedName: 'createdAttachments',\n    }),\n  };\n\n  static reducer({ type, payload }, Attachment) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n      case ActionTypes.LIST_UPDATE_HANDLE:\n      case ActionTypes.CARD_UPDATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__FAILURE:\n        if (payload.attachments) {\n          payload.attachments.forEach((attachment) => {\n            Attachment.upsert(prepareAttachment(attachment));\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        Attachment.all().delete();\n\n        if (payload.attachments) {\n          payload.attachments.forEach((attachment) => {\n            Attachment.upsert(prepareAttachment(attachment));\n          });\n        }\n\n        break;\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n      case ActionTypes.CARDS_FETCH__SUCCESS:\n      case ActionTypes.CARD_CREATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__SUCCESS:\n      case ActionTypes.CARD_DUPLICATE__SUCCESS:\n        payload.attachments.forEach((attachment) => {\n          Attachment.upsert(prepareAttachment(attachment));\n        });\n\n        break;\n      case ActionTypes.ATTACHMENT_CREATE:\n      case ActionTypes.ATTACHMENT_CREATE_HANDLE:\n      case ActionTypes.ATTACHMENT_UPDATE__SUCCESS:\n      case ActionTypes.ATTACHMENT_UPDATE_HANDLE:\n        Attachment.upsert(prepareAttachment(payload.attachment));\n\n        break;\n      case ActionTypes.ATTACHMENT_CREATE__SUCCESS:\n        Attachment.withId(payload.localId).delete();\n        Attachment.upsert(prepareAttachment(payload.attachment));\n\n        break;\n      case ActionTypes.ATTACHMENT_CREATE__FAILURE:\n        Attachment.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.ATTACHMENT_UPDATE:\n        Attachment.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.ATTACHMENT_DELETE:\n        Attachment.withId(payload.id).delete();\n\n        break;\n      case ActionTypes.ATTACHMENT_DELETE__SUCCESS:\n      case ActionTypes.ATTACHMENT_DELETE_HANDLE: {\n        const attachmentModel = Attachment.withId(payload.attachment.id);\n\n        if (attachmentModel) {\n          attachmentModel.delete();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  duplicate(id, data) {\n    return this.getClass().create({\n      id,\n      cardId: this.cardId,\n      creatorUserId: this.creatorUserId,\n      type: this.type,\n      data: this.data,\n      name: this.name,\n      ...data,\n    });\n  }\n}\n"
  },
  {
    "path": "client/src/models/BackgroundImage.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'BackgroundImage';\n\n  static fields = {\n    id: attr(),\n    url: attr(),\n    thumbnailUrls: attr(),\n    projectId: fk({\n      to: 'Project',\n      as: 'project',\n      relatedName: 'backgroundImages',\n    }),\n  };\n\n  static reducer({ type, payload }, BackgroundImage) {\n    switch (type) {\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        BackgroundImage.all().delete();\n\n        payload.backgroundImages.forEach((backgroundImage) => {\n          BackgroundImage.upsert(backgroundImage);\n        });\n\n        break;\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.PROJECT_CREATE_HANDLE:\n        payload.backgroundImages.forEach((backgroundImage) => {\n          BackgroundImage.upsert(backgroundImage);\n        });\n\n        break;\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n        if (payload.backgroundImages) {\n          payload.backgroundImages.forEach((backgroundImage) => {\n            BackgroundImage.upsert(backgroundImage);\n          });\n        }\n\n        break;\n      case ActionTypes.BACKGROUND_IMAGE_CREATE:\n      case ActionTypes.BACKGROUND_IMAGE_CREATE_HANDLE:\n        BackgroundImage.upsert(payload.backgroundImage);\n\n        break;\n      case ActionTypes.BACKGROUND_IMAGE_CREATE__SUCCESS:\n        BackgroundImage.withId(payload.localId).delete();\n        BackgroundImage.upsert(payload.backgroundImage);\n\n        break;\n      case ActionTypes.BACKGROUND_IMAGE_CREATE__FAILURE:\n        BackgroundImage.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.BACKGROUND_IMAGE_DELETE:\n        BackgroundImage.withId(payload.id).deleteWithRelated();\n\n        break;\n      case ActionTypes.BACKGROUND_IMAGE_DELETE__SUCCESS:\n      case ActionTypes.BACKGROUND_IMAGE_DELETE_HANDLE: {\n        const backgroundImageModel = BackgroundImage.withId(payload.backgroundImage.id);\n\n        if (backgroundImageModel) {\n          backgroundImageModel.deleteWithRelated();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  deleteRelated() {\n    if (this.backgroundedProject) {\n      this.backgroundedProject.update({\n        backgroundType: null,\n        backgroundImageId: null,\n      });\n    }\n  }\n\n  deleteWithRelated() {\n    this.deleteRelated();\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/BaseCustomFieldGroup.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'BaseCustomFieldGroup';\n\n  static fields = {\n    id: attr(),\n    name: attr(),\n    projectId: fk({\n      to: 'Project',\n      as: 'project',\n      relatedName: 'baseCustomFieldGroups',\n    }),\n  };\n\n  static reducer({ type, payload }, BaseCustomFieldGroup) {\n    switch (type) {\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        BaseCustomFieldGroup.all().delete();\n\n        payload.baseCustomFieldGroups.forEach((baseCustomFieldGroup) => {\n          BaseCustomFieldGroup.upsert(baseCustomFieldGroup);\n        });\n\n        break;\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.PROJECT_CREATE_HANDLE:\n        payload.baseCustomFieldGroups.forEach((baseCustomFieldGroup) => {\n          BaseCustomFieldGroup.upsert(baseCustomFieldGroup);\n        });\n\n        break;\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n        if (payload.baseCustomFieldGroups) {\n          payload.baseCustomFieldGroups.forEach((baseCustomFieldGroup) => {\n            BaseCustomFieldGroup.upsert(baseCustomFieldGroup);\n          });\n        }\n\n        break;\n      case ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE:\n      case ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE:\n      case ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE__SUCCESS:\n      case ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE:\n        BaseCustomFieldGroup.upsert(payload.baseCustomFieldGroup);\n\n        break;\n      case ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE__SUCCESS:\n        BaseCustomFieldGroup.withId(payload.localId).delete();\n        BaseCustomFieldGroup.upsert(payload.baseCustomFieldGroup);\n\n        break;\n      case ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE__FAILURE:\n        BaseCustomFieldGroup.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE:\n        BaseCustomFieldGroup.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE:\n        BaseCustomFieldGroup.withId(payload.id).deleteWithRelated();\n\n        break;\n      case ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE__SUCCESS:\n      case ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE: {\n        const baseCustomFieldGroupModel = BaseCustomFieldGroup.withId(\n          payload.baseCustomFieldGroup.id,\n        );\n\n        if (baseCustomFieldGroupModel) {\n          baseCustomFieldGroupModel.deleteWithRelated();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  getCustomFieldsQuerySet() {\n    return this.customFields.orderBy(['position', 'id.length', 'id']);\n  }\n\n  deleteRelated() {\n    this.customFields.delete();\n\n    this.customFieldGroups.toModelArray().forEach((customFieldGroupModel) => {\n      customFieldGroupModel.deleteWithRelated();\n    });\n  }\n\n  deleteWithRelated() {\n    this.deleteRelated();\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/BaseModel.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { Model } from 'redux-orm';\n\nexport default class BaseModel extends Model {\n  // eslint-disable-next-line no-underscore-dangle, class-methods-use-this\n  _onDelete() {}\n}\n"
  },
  {
    "path": "client/src/models/Board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk, many } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport buildSearchParts from '../utils/build-search-parts';\nimport { isListKanban } from '../utils/record-helpers';\nimport ActionTypes from '../constants/ActionTypes';\nimport Config from '../constants/Config';\nimport { BoardContexts, BoardViews } from '../constants/Enums';\n\nconst prepareFetchedBoard = (board) => ({\n  ...board,\n  isFetching: false,\n  context: BoardContexts.BOARD,\n  view: board.defaultView,\n  search: '',\n});\n\nexport default class extends BaseModel {\n  static modelName = 'Board';\n\n  static fields = {\n    id: attr(),\n    position: attr(),\n    name: attr(),\n    defaultView: attr(),\n    defaultCardType: attr(),\n    limitCardTypesToDefaultOne: attr(),\n    alwaysDisplayCardCreator: attr(),\n    displayCardAges: attr(),\n    expandTaskListsByDefault: attr(),\n    context: attr(),\n    view: attr(),\n    search: attr(),\n    isSubscribed: attr({\n      getDefault: () => false,\n    }),\n    isFetching: attr({\n      getDefault: () => null,\n    }),\n    lastActivityId: attr({\n      getDefault: () => null,\n    }),\n    isActivitiesFetching: attr({\n      getDefault: () => false,\n    }),\n    isAllActivitiesFetched: attr({\n      getDefault: () => null,\n    }),\n    projectId: fk({\n      to: 'Project',\n      as: 'project',\n      relatedName: 'boards',\n    }),\n    memberUsers: many({\n      to: 'User',\n      through: 'BoardMembership',\n      relatedName: 'boards',\n    }),\n    filterUsers: many('User', 'filterBoards'),\n    filterLabels: many('Label', 'filterBoards'),\n  };\n\n  static reducer({ type, payload }, Board) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n        if (payload.board) {\n          Board.upsert(prepareFetchedBoard(payload.board));\n        }\n\n        break;\n      case ActionTypes.LOCATION_CHANGE_HANDLE__BOARD_FETCH:\n      case ActionTypes.BOARD_FETCH:\n        Board.withId(payload.id).update({\n          isFetching: true,\n        });\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE: {\n        const boardIds = payload.boards.map(({ id }) => id);\n\n        Board.all()\n          .toModelArray()\n          .forEach((boardModel) => {\n            if (boardModel.isFetching === null || !boardIds.includes(boardModel.id)) {\n              boardModel.deleteWithClearable();\n            }\n          });\n\n        if (payload.board) {\n          const boardModel = Board.withId(payload.board.id);\n\n          if (boardModel) {\n            boardModel.update(payload.board);\n          } else {\n            Board.upsert(prepareFetchedBoard(payload.board));\n          }\n        }\n\n        payload.boards.forEach((board) => {\n          Board.upsert(board);\n        });\n\n        break;\n      }\n      case ActionTypes.SOCKET_RECONNECT_HANDLE__CORE_FETCH:\n        Board.all()\n          .toModelArray()\n          .forEach((boardModel) => {\n            if (boardModel.id !== payload.currentBoardId) {\n              boardModel.update({\n                isFetching: null,\n              });\n\n              boardModel.deleteRelated(payload.currentUserId, true);\n            }\n          });\n\n        break;\n      case ActionTypes.CORE_INITIALIZE:\n        if (payload.board) {\n          Board.upsert(prepareFetchedBoard(payload.board));\n        }\n\n        payload.boards.forEach((board) => {\n          Board.upsert(board);\n        });\n\n        break;\n      case ActionTypes.USER_UPDATE_HANDLE:\n        Board.all()\n          .toModelArray()\n          .forEach((boardModel) => {\n            if (!payload.boardIds.includes(boardModel.id)) {\n              boardModel.deleteWithRelated(true);\n            }\n          });\n\n        if (payload.board) {\n          Board.upsert(prepareFetchedBoard(payload.board));\n        }\n\n        if (payload.boards) {\n          payload.boards.forEach((board) => {\n            Board.upsert(board);\n          });\n        }\n\n        break;\n      case ActionTypes.USER_TO_BOARD_FILTER_ADD: {\n        const boardModel = Board.withId(payload.boardId);\n\n        if (payload.replace) {\n          boardModel.filterUsers.clear();\n        }\n\n        boardModel.filterUsers.add(payload.id);\n\n        break;\n      }\n      case ActionTypes.USER_FROM_BOARD_FILTER_REMOVE:\n        Board.withId(payload.boardId).filterUsers.remove(payload.id);\n\n        break;\n      case ActionTypes.PROJECT_CREATE_HANDLE:\n        payload.boards.forEach((board) => {\n          Board.upsert(board);\n        });\n\n        break;\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n        if (payload.board) {\n          Board.upsert(prepareFetchedBoard(payload.board));\n        }\n\n        if (payload.boards) {\n          payload.boards.forEach((board) => {\n            Board.upsert(board);\n          });\n        }\n\n        break;\n      case ActionTypes.BOARD_CREATE:\n      case ActionTypes.BOARD_CREATE_HANDLE:\n      case ActionTypes.BOARD_UPDATE__SUCCESS:\n      case ActionTypes.BOARD_UPDATE_HANDLE:\n        Board.upsert(payload.board);\n\n        break;\n      case ActionTypes.BOARD_CREATE__SUCCESS:\n        Board.withId(payload.localId).delete();\n        Board.upsert(payload.board);\n\n        break;\n      case ActionTypes.BOARD_CREATE__FAILURE:\n        Board.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n        Board.upsert(prepareFetchedBoard(payload.board));\n\n        break;\n      case ActionTypes.BOARD_FETCH__FAILURE:\n        Board.withId(payload.id).update({\n          isFetching: null,\n        });\n\n        break;\n      case ActionTypes.BOARD_UPDATE:\n        Board.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.BOARD_CONTEXT_UPDATE: {\n        const boardModel = Board.withId(payload.id);\n\n        boardModel.update({\n          context: payload.value,\n          view: payload.value === BoardContexts.BOARD ? boardModel.defaultView : BoardViews.LIST,\n        });\n\n        break;\n      }\n      case ActionTypes.IN_BOARD_SEARCH:\n        Board.withId(payload.id).update({\n          search: payload.value,\n        });\n\n        break;\n      case ActionTypes.BOARD_DELETE:\n        Board.withId(payload.id).deleteWithRelated();\n\n        break;\n      case ActionTypes.BOARD_DELETE__SUCCESS:\n      case ActionTypes.BOARD_DELETE_HANDLE: {\n        const boardModel = Board.withId(payload.board.id);\n\n        if (boardModel) {\n          boardModel.deleteWithRelated();\n        }\n\n        break;\n      }\n      case ActionTypes.LABEL_TO_BOARD_FILTER_ADD:\n        Board.withId(payload.boardId).filterLabels.add(payload.id);\n\n        break;\n      case ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE:\n        Board.withId(payload.boardId).filterLabels.remove(payload.id);\n\n        break;\n      case ActionTypes.ACTIVITIES_IN_BOARD_FETCH:\n        Board.withId(payload.boardId).update({\n          isActivitiesFetching: true,\n        });\n\n        break;\n      case ActionTypes.ACTIVITIES_IN_BOARD_FETCH__SUCCESS:\n        Board.withId(payload.boardId).update({\n          isActivitiesFetching: false,\n          isAllActivitiesFetched: payload.activities.length < Config.ACTIVITIES_LIMIT,\n          ...(payload.activities.length > 0 && {\n            lastActivityId: payload.activities[payload.activities.length - 1].id,\n          }),\n        });\n\n        break;\n      default:\n    }\n  }\n\n  getMembershipsQuerySet() {\n    return this.memberships.orderBy(['id.length', 'id']);\n  }\n\n  getLabelsQuerySet() {\n    return this.labels.orderBy(['position', 'id.length', 'id']);\n  }\n\n  getListsQuerySet() {\n    return this.lists.orderBy(['position', 'id.length', 'id']);\n  }\n\n  getKanbanListsQuerySet() {\n    return this.getListsQuerySet().filter((list) => isListKanban(list));\n  }\n\n  getCustomFieldGroupsQuerySet() {\n    return this.customFieldGroups.orderBy(['position', 'id.length', 'id']);\n  }\n\n  getActivitiesQuerySet() {\n    return this.activities.orderBy(['id.length', 'id'], ['desc', 'desc']);\n  }\n\n  getUnreadNotificationsQuerySet() {\n    return this.notifications.filter({\n      isRead: false,\n    });\n  }\n\n  getNotificationServicesQuerySet() {\n    return this.notificationServices.orderBy(['id.length', 'id']);\n  }\n\n  getMembershipModelByUserId(userId) {\n    return this.memberships\n      .filter({\n        userId,\n      })\n      .first();\n  }\n\n  getCardsModelArray() {\n    return this.getKanbanListsQuerySet()\n      .toModelArray()\n      .flatMap((listModel) => listModel.getCardsModelArray());\n  }\n\n  getFilteredCardsModelArray() {\n    let cardModels = this.getCardsModelArray();\n\n    if (cardModels.length === 0) {\n      return cardModels;\n    }\n\n    if (this.search) {\n      if (this.search.startsWith('/')) {\n        let searchRegex;\n        try {\n          searchRegex = new RegExp(this.search.substring(1), 'i');\n        } catch {\n          return [];\n        }\n\n        cardModels = cardModels.filter(\n          (cardModel) =>\n            searchRegex.test(cardModel.name) ||\n            (cardModel.description && searchRegex.test(cardModel.description)),\n        );\n      } else {\n        const searchParts = buildSearchParts(this.search);\n\n        cardModels = cardModels.filter((cardModel) => {\n          const name = cardModel.name.toLowerCase();\n          const description = cardModel.description && cardModel.description.toLowerCase();\n\n          return searchParts.every(\n            (searchPart) =>\n              name.includes(searchPart) || (description && description.includes(searchPart)),\n          );\n        });\n      }\n    }\n\n    const filterUserIds = this.filterUsers.toRefArray().map((user) => user.id);\n\n    if (filterUserIds.length > 0) {\n      cardModels = cardModels.filter((cardModel) => {\n        const users = cardModel.users.toRefArray();\n\n        if (users.some((user) => filterUserIds.includes(user.id))) {\n          return true;\n        }\n\n        return cardModel\n          .getTaskListsQuerySet()\n          .toModelArray()\n          .some((taskListModel) =>\n            taskListModel\n              .getTasksQuerySet()\n              .toRefArray()\n              .some((task) => task.assigneeUserId && filterUserIds.includes(task.assigneeUserId)),\n          );\n      });\n    }\n\n    const filterLabelIds = this.filterLabels.toRefArray().map((label) => label.id);\n\n    if (filterLabelIds.length > 0) {\n      cardModels = cardModels.filter((cardModel) => {\n        const labels = cardModel.labels.toRefArray();\n        return labels.some((label) => filterLabelIds.includes(label.id));\n      });\n    }\n\n    return cardModels;\n  }\n\n  getActivitiesModelArray() {\n    if (this.isAllActivitiesFetched === null) {\n      return [];\n    }\n\n    const activityModels = this.getActivitiesQuerySet().toModelArray();\n\n    if (this.lastActivityId && this.isAllActivitiesFetched === false) {\n      return activityModels.filter((activityModel) => {\n        if (activityModel.id.length > this.lastActivityId.length) {\n          return true;\n        }\n\n        if (activityModel.id.length < this.lastActivityId.length) {\n          return false;\n        }\n\n        return activityModel.id >= this.lastActivityId;\n      });\n    }\n\n    return activityModels;\n  }\n\n  hasMembershipWithUserId(userId) {\n    return this.memberships\n      .filter({\n        userId,\n      })\n      .exists();\n  }\n\n  isAvailableForUser(userModel) {\n    if (!this.project) {\n      return false;\n    }\n\n    return (\n      this.project.isExternalAccessibleForUser(userModel) ||\n      this.hasMembershipWithUserId(userModel.id)\n    );\n  }\n\n  deleteListsWithRelated(soft) {\n    this.lists.toModelArray().forEach((listModel) => {\n      listModel.deleteWithRelated(soft);\n    });\n  }\n\n  deleteClearable() {\n    this.filterUsers.clear();\n    this.filterLabels.clear();\n  }\n\n  deleteRelated(exceptMemberUserId, soft) {\n    this.deleteClearable();\n\n    this.memberships.toModelArray().forEach((boardMembershipModel) => {\n      if (boardMembershipModel.userId !== exceptMemberUserId) {\n        boardMembershipModel.deleteWithRelated();\n      }\n    });\n\n    this.labels.toModelArray().forEach((labelModel) => {\n      labelModel.deleteWithRelated();\n    });\n\n    this.deleteListsWithRelated(soft);\n    this.notificationServices.delete();\n  }\n\n  deleteWithClearable() {\n    this.deleteClearable();\n    this.delete();\n  }\n\n  deleteWithRelated(soft) {\n    this.deleteRelated(undefined, soft);\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/BoardMembership.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'BoardMembership';\n\n  static fields = {\n    id: attr(),\n    role: attr(),\n    canComment: attr(),\n    boardId: fk({\n      to: 'Board',\n      as: 'board',\n      relatedName: 'memberships',\n    }),\n    userId: fk({\n      to: 'User',\n      as: 'user',\n      relatedName: 'boardMemberships',\n    }),\n  };\n\n  static reducer({ type, payload }, BoardMembership) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n        if (payload.boardMemberships) {\n          payload.boardMemberships.forEach((boardMembership) => {\n            BoardMembership.upsert(boardMembership);\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        BoardMembership.all().delete();\n\n        payload.boardMemberships.forEach((boardMembership) => {\n          BoardMembership.upsert(boardMembership);\n        });\n\n        break;\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.PROJECT_CREATE_HANDLE:\n      case ActionTypes.BOARD_CREATE__SUCCESS:\n      case ActionTypes.BOARD_CREATE_HANDLE:\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n        payload.boardMemberships.forEach((boardMembership) => {\n          BoardMembership.upsert(boardMembership);\n        });\n\n        break;\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE:\n      case ActionTypes.BOARD_MEMBERSHIP_UPDATE__SUCCESS:\n      case ActionTypes.BOARD_MEMBERSHIP_UPDATE_HANDLE:\n        BoardMembership.upsert(payload.boardMembership);\n\n        break;\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE__SUCCESS:\n        BoardMembership.withId(payload.localId).delete();\n        BoardMembership.upsert(payload.boardMembership);\n\n        break;\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE__FAILURE:\n        BoardMembership.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n        BoardMembership.upsert(payload.boardMembership);\n\n        if (payload.boardMemberships) {\n          payload.boardMemberships.forEach((boardMembership) => {\n            BoardMembership.upsert(boardMembership);\n          });\n        }\n\n        break;\n      case ActionTypes.BOARD_MEMBERSHIP_UPDATE:\n        BoardMembership.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.BOARD_MEMBERSHIP_DELETE:\n        BoardMembership.withId(payload.id).deleteWithRelated(payload.isCurrentUser);\n\n        break;\n      case ActionTypes.BOARD_MEMBERSHIP_DELETE__SUCCESS:\n      case ActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE: {\n        const boardMembershipModel = BoardMembership.withId(payload.boardMembership.id);\n\n        if (boardMembershipModel) {\n          boardMembershipModel.deleteWithRelated(payload.isCurrentUser);\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  deleteRelated(isCurrentUser = false) {\n    if (isCurrentUser) {\n      this.board.isSubscribed = false;\n    }\n\n    this.board.cards.toModelArray().forEach((cardModel) => {\n      if (isCurrentUser) {\n        cardModel.update({\n          isSubscribed: false,\n        });\n      }\n\n      try {\n        cardModel.users.remove(this.userId);\n      } catch {\n        /* empty */\n      }\n\n      cardModel.taskLists.toModelArray().forEach((taskListModel) => {\n        taskListModel.tasks.toModelArray().forEach((taskModel) => {\n          if (taskModel.assigneeUserId === this.userId) {\n            taskModel.update({\n              assigneeUserId: null,\n            });\n          }\n        });\n      });\n    });\n\n    try {\n      this.board.filterUsers.remove(this.userId);\n    } catch {\n      /* empty */\n    }\n  }\n\n  deleteWithRelated(isCurrentUser) {\n    this.deleteRelated(isCurrentUser);\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/Card.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport keyBy from 'lodash/keyBy';\nimport { attr, fk, many, oneToOne } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\nimport Config from '../constants/Config';\n\nexport default class extends BaseModel {\n  static modelName = 'Card';\n\n  static fields = {\n    id: attr(),\n    type: attr(),\n    position: attr(),\n    name: attr(),\n    description: attr(),\n    dueDate: attr(),\n    isDueCompleted: attr(),\n    stopwatch: attr(),\n    isClosed: attr(),\n    commentsTotal: attr({\n      getDefault: () => 0,\n    }),\n    createdAt: attr({\n      getDefault: () => new Date(),\n    }),\n    listChangedAt: attr({\n      getDefault: () => new Date(),\n    }),\n    isSubscribed: attr({\n      getDefault: () => false,\n    }),\n    lastCommentId: attr({\n      getDefault: () => null,\n    }),\n    isCommentsFetching: attr({\n      getDefault: () => false,\n    }),\n    isAllCommentsFetched: attr({\n      getDefault: () => null,\n    }),\n    lastActivityId: attr({\n      getDefault: () => null,\n    }),\n    isActivitiesFetching: attr({\n      getDefault: () => false,\n    }),\n    isAllActivitiesFetched: attr({\n      getDefault: () => null,\n    }),\n    boardId: fk({\n      to: 'Board',\n      as: 'board',\n      relatedName: 'cards',\n    }),\n    listId: fk({\n      to: 'List',\n      as: 'list',\n      relatedName: 'cards',\n    }),\n    creatorUserId: fk({\n      to: 'User',\n      as: 'creatorUser',\n      relatedName: 'createdCards',\n    }),\n    prevListId: fk({\n      to: 'List',\n      as: 'prevList',\n      relatedName: 'prevCards',\n    }),\n    coverAttachmentId: oneToOne({\n      to: 'Attachment',\n      as: 'coverAttachment',\n      relatedName: 'coveredCard',\n    }),\n    users: many('User', 'cards'),\n    labels: many('Label', 'cards'),\n  };\n\n  static reducer({ type, payload }, Card) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n      case ActionTypes.LIST_UPDATE_HANDLE:\n        if (payload.cards) {\n          payload.cards.forEach((card) => {\n            Card.upsert(card);\n          });\n        }\n\n        if (payload.cardMemberships) {\n          payload.cardMemberships.forEach(({ cardId, userId }) => {\n            Card.withId(cardId).users.add(userId);\n          });\n        }\n\n        if (payload.cardLabels) {\n          payload.cardLabels.forEach(({ cardId, labelId }) => {\n            Card.withId(cardId).labels.add(labelId);\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        Card.all()\n          .toModelArray()\n          .forEach((cardModel) => {\n            cardModel.deleteWithClearable();\n          });\n\n        if (payload.cards) {\n          payload.cards.forEach((card) => {\n            Card.upsert(card);\n          });\n        }\n\n        if (payload.cardMemberships) {\n          payload.cardMemberships.forEach(({ cardId, userId }) => {\n            Card.withId(cardId).users.add(userId);\n          });\n        }\n\n        if (payload.cardLabels) {\n          payload.cardLabels.forEach(({ cardId, labelId }) => {\n            Card.withId(cardId).labels.add(labelId);\n          });\n        }\n\n        break;\n      case ActionTypes.USER_TO_CARD_ADD: {\n        const cardModel = Card.withId(payload.cardId);\n        cardModel.users.add(payload.id);\n\n        if (payload.isCurrent) {\n          cardModel.isSubscribed = true;\n        }\n\n        break;\n      }\n      case ActionTypes.USER_TO_CARD_ADD__SUCCESS:\n      case ActionTypes.USER_TO_CARD_ADD_HANDLE:\n        try {\n          Card.withId(payload.cardMembership.cardId).users.add(payload.cardMembership.userId);\n        } catch {\n          /* empty */\n        }\n\n        break;\n      case ActionTypes.USER_FROM_CARD_REMOVE:\n        Card.withId(payload.cardId).users.remove(payload.id);\n\n        break;\n      case ActionTypes.USER_FROM_CARD_REMOVE__SUCCESS:\n      case ActionTypes.USER_FROM_CARD_REMOVE_HANDLE:\n        try {\n          Card.withId(payload.cardMembership.cardId).users.remove(payload.cardMembership.userId);\n        } catch {\n          /* empty */\n        }\n\n        break;\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n        payload.cards.forEach((card) => {\n          Card.upsert(card);\n        });\n\n        payload.cardMemberships.forEach(({ cardId, userId }) => {\n          Card.withId(cardId).users.add(userId);\n        });\n\n        payload.cardLabels.forEach(({ cardId, labelId }) => {\n          Card.withId(cardId).labels.add(labelId);\n        });\n\n        break;\n      case ActionTypes.LABEL_FROM_CARD_CREATE:\n        Card.withId(payload.cardId).labels.add(payload.label.id);\n\n        break;\n      case ActionTypes.LABEL_FROM_CARD_CREATE__SUCCESS: {\n        const cardModel = Card.withId(payload.cardLabel.cardId);\n\n        cardModel.labels.remove(payload.localId);\n        cardModel.labels.add(payload.label.id);\n\n        break;\n      }\n      case ActionTypes.LABEL_FROM_CARD_CREATE__FAILURE:\n        Card.withId(payload.cardId).labels.remove(payload.localId);\n\n        break;\n      case ActionTypes.LABEL_TO_CARD_ADD:\n        Card.withId(payload.cardId).labels.add(payload.id);\n\n        break;\n      case ActionTypes.LABEL_TO_CARD_ADD__SUCCESS:\n      case ActionTypes.LABEL_TO_CARD_ADD_HANDLE:\n        try {\n          Card.withId(payload.cardLabel.cardId).labels.add(payload.cardLabel.labelId);\n        } catch {\n          /* empty */\n        }\n\n        break;\n      case ActionTypes.LABEL_FROM_CARD_REMOVE:\n        Card.withId(payload.cardId).labels.remove(payload.id);\n\n        break;\n      case ActionTypes.LABEL_FROM_CARD_REMOVE__SUCCESS:\n      case ActionTypes.LABEL_FROM_CARD_REMOVE_HANDLE:\n        try {\n          Card.withId(payload.cardLabel.cardId).labels.remove(payload.cardLabel.labelId);\n        } catch {\n          /* empty */\n        }\n\n        break;\n      case ActionTypes.LIST_SORT__SUCCESS:\n      case ActionTypes.LIST_CARDS_MOVE__SUCCESS:\n      case ActionTypes.LIST_DELETE__SUCCESS:\n      case ActionTypes.CARDS_UPDATE_HANDLE:\n        payload.cards.forEach((card) => {\n          Card.upsert(card);\n        });\n\n        break;\n      case ActionTypes.LIST_CARDS_MOVE: {\n        const listChangedAt = new Date();\n\n        payload.cardIds.forEach((cardId) => {\n          const cardModel = Card.withId(cardId);\n\n          cardModel.update({\n            listChangedAt,\n            listId: payload.nextId,\n            prevListId: cardModel.listId,\n          });\n        });\n\n        break;\n      }\n      case ActionTypes.LIST_DELETE: {\n        const listChangedAt = new Date();\n\n        payload.cardIds.forEach((cardId) => {\n          Card.withId(cardId).update({\n            listChangedAt,\n            listId: payload.trashId,\n          });\n        });\n\n        break;\n      }\n      case ActionTypes.LIST_DELETE_HANDLE:\n        if (payload.cards) {\n          payload.cards.forEach((card) => {\n            Card.upsert(card);\n          });\n        }\n\n        break;\n      case ActionTypes.CARDS_FETCH__SUCCESS:\n        payload.cards.forEach((card) => {\n          const cardModel = Card.withId(card.id);\n\n          if (cardModel) {\n            cardModel.deleteWithRelated(true);\n          }\n\n          Card.upsert(card);\n        });\n\n        payload.cardMemberships.forEach(({ cardId, userId }) => {\n          Card.withId(cardId).users.add(userId);\n        });\n\n        payload.cardLabels.forEach(({ cardId, labelId }) => {\n          Card.withId(cardId).labels.add(labelId);\n        });\n\n        break;\n      case ActionTypes.CARD_CREATE:\n      case ActionTypes.CARD_UPDATE__SUCCESS:\n        Card.upsert(payload.card);\n\n        break;\n      case ActionTypes.CARD_CREATE__SUCCESS:\n        Card.withId(payload.localId).deleteWithClearable();\n        Card.upsert(payload.card);\n\n        break;\n      case ActionTypes.CARD_CREATE__FAILURE:\n        Card.withId(payload.localId).deleteWithClearable();\n\n        break;\n      case ActionTypes.CARD_CREATE_HANDLE:\n        Card.upsert(payload.card);\n\n        payload.cardMemberships.forEach(({ cardId, userId }) => {\n          Card.withId(cardId).users.add(userId);\n        });\n\n        payload.cardLabels.forEach(({ cardId, labelId }) => {\n          Card.withId(cardId).labels.add(labelId);\n        });\n\n        break;\n      case ActionTypes.CARD_UPDATE: {\n        const cardModel = Card.withId(payload.id);\n\n        if (payload.data.listId && payload.data.listId !== cardModel.listId) {\n          payload.data.listChangedAt = new Date(); // eslint-disable-line no-param-reassign\n        }\n\n        if (payload.data.dueDate !== undefined) {\n          if (payload.data.dueDate) {\n            if (!cardModel.dueDate) {\n              payload.data.isDueCompleted = false; // eslint-disable-line no-param-reassign\n            }\n          } else {\n            payload.data.isDueCompleted = null; // eslint-disable-line no-param-reassign\n          }\n        }\n\n        if (payload.data.isClosed !== undefined && payload.data.isClosed !== cardModel.isClosed) {\n          cardModel.linkedTasks.update({\n            isCompleted: payload.data.isClosed,\n          });\n        }\n\n        cardModel.update(payload.data);\n\n        break;\n      }\n      case ActionTypes.CARD_UPDATE_HANDLE: {\n        const cardModel = Card.withId(payload.card.id);\n\n        if (payload.card.boardId === null || payload.isFetched) {\n          if (cardModel) {\n            cardModel.deleteWithRelated(true);\n          }\n        }\n\n        if (payload.card.boardId !== null) {\n          Card.upsert(payload.card);\n\n          if (cardModel && payload.card.isClosed !== cardModel.isClosed) {\n            cardModel.linkedTasks.update({\n              isCompleted: payload.card.isClosed,\n            });\n          }\n        }\n\n        if (payload.cardMemberships) {\n          payload.cardMemberships.forEach(({ cardId, userId }) => {\n            Card.withId(cardId).users.add(userId);\n          });\n        }\n\n        if (payload.cardLabels) {\n          payload.cardLabels.forEach(({ cardId, labelId }) => {\n            Card.withId(cardId).labels.add(labelId);\n          });\n        }\n\n        break;\n      }\n      case ActionTypes.CARD_TRANSFER: {\n        const cardModel = Card.withId(payload.id);\n\n        if (cardModel) {\n          cardModel.update(payload.data);\n          cardModel.syncAfterBoardChange();\n        }\n\n        break;\n      }\n      case ActionTypes.CARD_TRANSFER__SUCCESS: {\n        const cardModel = Card.withId(payload.card.id);\n\n        if (cardModel) {\n          cardModel.deleteWithRelated(true);\n        }\n\n        Card.upsert(payload.card);\n\n        if (payload.cardMemberships) {\n          payload.cardMemberships.forEach(({ cardId, userId }) => {\n            Card.withId(cardId).users.add(userId);\n          });\n        }\n\n        if (payload.cardLabels) {\n          payload.cardLabels.forEach(({ cardId, labelId }) => {\n            Card.withId(cardId).labels.add(labelId);\n          });\n        }\n\n        break;\n      }\n      case ActionTypes.CARD_TRANSFER__FAILURE: {\n        const cardModel = Card.withId(payload.id);\n\n        if (cardModel) {\n          cardModel.deleteWithRelated(true);\n        }\n\n        if (payload.card) {\n          Card.upsert(payload.card);\n        }\n\n        if (payload.cardMemberships) {\n          payload.cardMemberships.forEach(({ cardId, userId }) => {\n            Card.withId(cardId).users.add(userId);\n          });\n        }\n\n        if (payload.cardLabels) {\n          payload.cardLabels.forEach(({ cardId, labelId }) => {\n            Card.withId(cardId).labels.add(labelId);\n          });\n        }\n\n        break;\n      }\n      case ActionTypes.CARD_DUPLICATE: {\n        let cardModel = Card.withId(payload.id);\n\n        if (cardModel) {\n          cardModel = cardModel.duplicate(payload.localId, {\n            ...payload.data,\n            listChangedAt: new Date(),\n          });\n\n          cardModel.syncAfterBoardChange();\n        }\n\n        break;\n      }\n      case ActionTypes.CARD_DUPLICATE__SUCCESS: {\n        let cardModel = Card.withId(payload.localId);\n\n        if (cardModel) {\n          cardModel.deleteWithRelated();\n        }\n\n        cardModel = Card.upsert(payload.card);\n\n        payload.cardMemberships.forEach(({ userId }) => {\n          cardModel.users.add(userId);\n        });\n\n        payload.cardLabels.forEach(({ labelId }) => {\n          cardModel.labels.add(labelId);\n        });\n\n        break;\n      }\n      case ActionTypes.CARD_DUPLICATE__FAILURE: {\n        const cardModel = Card.withId(payload.localId);\n\n        if (cardModel) {\n          cardModel.deleteWithRelated();\n        }\n\n        break;\n      }\n      case ActionTypes.CARD_DELETE:\n        Card.withId(payload.id).deleteWithRelated();\n\n        break;\n      case ActionTypes.CARD_DELETE__SUCCESS:\n      case ActionTypes.CARD_DELETE_HANDLE: {\n        const cardModel = Card.withId(payload.card.id);\n\n        if (cardModel) {\n          cardModel.deleteWithRelated();\n        }\n\n        break;\n      }\n      case ActionTypes.COMMENTS_FETCH:\n        Card.withId(payload.cardId).update({\n          isCommentsFetching: true,\n        });\n\n        break;\n      case ActionTypes.COMMENTS_FETCH__SUCCESS:\n        Card.withId(payload.cardId).update({\n          isCommentsFetching: false,\n          isAllCommentsFetched: payload.comments.length < Config.COMMENTS_LIMIT,\n          ...(payload.comments.length > 0 && {\n            lastCommentId: payload.comments[payload.comments.length - 1].id,\n          }),\n        });\n\n        break;\n      case ActionTypes.COMMENT_CREATE:\n      case ActionTypes.COMMENT_CREATE_HANDLE: {\n        const cardModel = Card.withId(payload.comment.cardId);\n\n        if (cardModel) {\n          cardModel.commentsTotal += 1;\n        }\n\n        break;\n      }\n      case ActionTypes.COMMENT_DELETE_HANDLE: {\n        const cardModel = Card.withId(payload.comment.cardId);\n\n        if (cardModel) {\n          cardModel.commentsTotal -= 1;\n        }\n\n        break;\n      }\n      case ActionTypes.ACTIVITIES_IN_CARD_FETCH:\n        Card.withId(payload.cardId).update({\n          isActivitiesFetching: true,\n        });\n\n        break;\n      case ActionTypes.ACTIVITIES_IN_CARD_FETCH__SUCCESS:\n        Card.withId(payload.cardId).update({\n          isActivitiesFetching: false,\n          isAllActivitiesFetched: payload.activities.length < Config.ACTIVITIES_LIMIT,\n          ...(payload.activities.length > 0 && {\n            lastActivityId: payload.activities[payload.activities.length - 1].id,\n          }),\n        });\n\n        break;\n      default:\n    }\n  }\n\n  getTaskListsQuerySet() {\n    return this.taskLists.orderBy(['position', 'id.length', 'id']);\n  }\n\n  getAttachmentsQuerySet() {\n    return this.attachments.orderBy(['id.length', 'id'], ['desc', 'desc']);\n  }\n\n  getCustomFieldGroupsQuerySet() {\n    return this.customFieldGroups.orderBy(['position', 'id.length', 'id']);\n  }\n\n  getCommentsQuerySet() {\n    return this.comments.orderBy(['id.length', 'id'], ['desc', 'desc']);\n  }\n\n  getActivitiesQuerySet() {\n    return this.activities.orderBy(['id.length', 'id'], ['desc', 'desc']);\n  }\n\n  getUnreadNotificationsQuerySet() {\n    return this.notifications.filter({\n      isRead: false,\n    });\n  }\n\n  getShownOnFrontOfCardTaskListsModelArray() {\n    return this.getTaskListsQuerySet()\n      .toModelArray()\n      .filter((taskListModel) => taskListModel.showOnFrontOfCard);\n  }\n\n  getCommentsModelArray() {\n    if (this.isAllCommentsFetched === null) {\n      return [];\n    }\n\n    const commentModels = this.getCommentsQuerySet().toModelArray();\n\n    if (this.lastCommentId && this.isAllCommentsFetched === false) {\n      return commentModels.filter((commentModel) => {\n        if (commentModel.id.length > this.lastCommentId.length) {\n          return true;\n        }\n\n        if (commentModel.id.length < this.lastCommentId.length) {\n          return false;\n        }\n\n        return commentModel.id >= this.lastCommentId;\n      });\n    }\n\n    return commentModels;\n  }\n\n  getActivitiesModelArray() {\n    if (this.isAllActivitiesFetched === null) {\n      return [];\n    }\n\n    const activityModels = this.getActivitiesQuerySet().toModelArray();\n\n    if (this.lastActivityId && this.isAllActivitiesFetched === false) {\n      return activityModels.filter((activityModel) => {\n        if (activityModel.id.length > this.lastActivityId.length) {\n          return true;\n        }\n\n        if (activityModel.id.length < this.lastActivityId.length) {\n          return false;\n        }\n\n        return activityModel.id >= this.lastActivityId;\n      });\n    }\n\n    return activityModels;\n  }\n\n  hasUserWithId(userId) {\n    return this.cardusersSet\n      .filter({\n        toUserId: userId,\n      })\n      .exists();\n  }\n\n  isAvailableForUser(userModel) {\n    return !!this.list && this.list.isAvailableForUser(userModel);\n  }\n\n  duplicate(id, data, rootId) {\n    if (rootId === undefined) {\n      rootId = id; // eslint-disable-line no-param-reassign\n    }\n\n    const cardModel = this.getClass().create({\n      id,\n      boardId: this.boardId,\n      listId: this.listId,\n      creatorUserId: this.creatorUserId,\n      prevListId: this.prevListId,\n      coverAttachmentId: this.coverAttachmentId && `${this.coverAttachmentId}-${rootId}`,\n      type: this.type,\n      position: this.position,\n      name: this.name,\n      description: this.description,\n      dueDate: this.dueDate,\n      isDueCompleted: this.isDueCompleted,\n      stopwatch: this.stopwatch,\n      isClosed: this.isClosed,\n      ...data,\n    });\n\n    this.users.toRefArray().forEach((user) => {\n      cardModel.users.add(user.id);\n    });\n\n    this.labels.toRefArray().forEach((label) => {\n      cardModel.labels.add(label.id);\n    });\n\n    this.taskLists.toModelArray().forEach((taskListModel) => {\n      taskListModel.duplicate(`${taskListModel.id}-${rootId}`, {\n        cardId: cardModel.id,\n      });\n    });\n\n    this.attachments.toModelArray().forEach((attachmentModel) => {\n      attachmentModel.duplicate(`${attachmentModel.id}-${rootId}`, {\n        cardId: cardModel.id,\n        ...(data.creatorUserId && {\n          creatorUserId: data.creatorUserId,\n        }),\n      });\n    });\n\n    this.customFieldGroups.toModelArray().forEach((customFieldGroupModel) => {\n      customFieldGroupModel.duplicate(\n        `${customFieldGroupModel.id}-${rootId}`,\n        {\n          cardId: cardModel.id,\n        },\n        rootId,\n      );\n    });\n\n    this.customFieldValues.toModelArray().forEach((customFieldValueModel) => {\n      const customFieldValueData = {\n        cardId: cardModel.id,\n      };\n\n      if (customFieldValueModel.customFieldGroup.cardId) {\n        customFieldValueData.customFieldGroupId = `${customFieldValueModel.customFieldGroupId}-${rootId}`;\n\n        if (customFieldValueModel.customField.customFieldGroupId) {\n          customFieldValueData.customFieldId = `${customFieldValueModel.customFieldId}-${rootId}`;\n        }\n      }\n\n      customFieldValueModel.duplicate(customFieldValueData);\n    });\n\n    return cardModel;\n  }\n\n  syncAfterBoardChange() {\n    if (!this.board) {\n      return;\n    }\n\n    const boardMemberships = this.board.memberships.toRefArray();\n    const userIdsSet = new Set(boardMemberships.map(({ userId }) => userId));\n\n    this.users.toRefArray().forEach((user) => {\n      if (userIdsSet.has(user.id)) {\n        return;\n      }\n\n      this.users.remove(user.id);\n    });\n\n    this.taskLists.toModelArray().forEach((taskListModel) => {\n      taskListModel.tasks.toModelArray().forEach((taskModel) => {\n        if (!taskModel.assigneeUserId || userIdsSet.has(taskModel.assigneeUserId)) {\n          return;\n        }\n\n        taskModel.update({\n          assigneeUserId: null,\n        });\n      });\n    });\n\n    const labels = this.board.labels.toRefArray();\n    const labelByName = keyBy(labels, 'name');\n\n    this.labels.toRefArray().forEach((label) => {\n      if (!labelByName[label.name]) {\n        return;\n      }\n\n      this.labels.remove(label.id);\n      this.labels.add(labelByName[label.name].id);\n    });\n  }\n\n  deleteClearable() {\n    this.users.clear();\n    this.labels.clear();\n  }\n\n  deleteRelated(soft = false) {\n    this.deleteClearable();\n\n    this.taskLists.toModelArray().forEach((taskListModel) => {\n      taskListModel.deleteWithRelated();\n    });\n\n    if (!soft) {\n      this.linkedTasks.toModelArray().forEach((taskModel) => {\n        taskModel.update({\n          linkedCardId: null,\n        });\n      });\n    }\n\n    this.attachments.delete();\n\n    this.customFieldGroups.toModelArray().forEach((customFieldGroupModel) => {\n      customFieldGroupModel.deleteWithRelated();\n    });\n\n    this.customFieldValues.delete();\n    this.comments.delete();\n  }\n\n  deleteWithClearable() {\n    this.deleteClearable();\n    this.delete();\n  }\n\n  deleteWithRelated(soft) {\n    this.deleteRelated(soft);\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/Comment.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'Comment';\n\n  static fields = {\n    id: attr(),\n    text: attr(),\n    createdAt: attr({\n      getDefault: () => new Date(),\n    }),\n    cardId: fk({\n      to: 'Card',\n      as: 'card',\n      relatedName: 'comments',\n    }),\n    userId: fk({\n      to: 'User',\n      as: 'user',\n      relatedName: 'comments',\n    }),\n  };\n\n  static reducer({ type, payload }, Comment) {\n    switch (type) {\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        Comment.all().delete();\n\n        break;\n      case ActionTypes.COMMENTS_FETCH__SUCCESS:\n        payload.comments.forEach((comment) => {\n          Comment.upsert(comment);\n        });\n\n        break;\n      case ActionTypes.COMMENT_CREATE:\n      case ActionTypes.COMMENT_CREATE_HANDLE:\n      case ActionTypes.COMMENT_UPDATE__SUCCESS:\n      case ActionTypes.COMMENT_UPDATE_HANDLE:\n        Comment.upsert(payload.comment);\n\n        break;\n      case ActionTypes.COMMENT_CREATE__SUCCESS:\n        Comment.withId(payload.localId).delete();\n        Comment.upsert(payload.comment);\n\n        break;\n      case ActionTypes.COMMENT_CREATE__FAILURE: {\n        const commentModel = Comment.withId(payload.localId);\n        commentModel.delete();\n\n        if (commentModel.card) {\n          commentModel.card.commentsTotal -= 1;\n        }\n\n        break;\n      }\n      case ActionTypes.COMMENT_UPDATE:\n        Comment.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.COMMENT_DELETE: {\n        const commentModel = Comment.withId(payload.id);\n        commentModel.delete();\n        commentModel.card.commentsTotal -= 1;\n\n        break;\n      }\n      case ActionTypes.COMMENT_DELETE__SUCCESS:\n      case ActionTypes.COMMENT_DELETE_HANDLE: {\n        const commentModel = Comment.withId(payload.comment.id);\n\n        if (commentModel) {\n          commentModel.delete();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/models/CustomField.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'CustomField';\n\n  static fields = {\n    id: attr(),\n    position: attr(),\n    name: attr(),\n    showOnFrontOfCard: attr(),\n    baseCustomFieldGroupId: fk({\n      to: 'BaseCustomFieldGroup',\n      as: 'baseGroup',\n      relatedName: 'customFields',\n    }),\n    customFieldGroupId: fk({\n      to: 'CustomFieldGroup',\n      as: 'group',\n      relatedName: 'customFields',\n    }),\n  };\n\n  static reducer({ type, payload }, CustomField) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n      case ActionTypes.LIST_UPDATE_HANDLE:\n      case ActionTypes.CARD_UPDATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__FAILURE:\n        if (payload.customFields) {\n          payload.customFields.forEach((customField) => {\n            CustomField.upsert(customField);\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        CustomField.all().delete();\n\n        if (payload.customFields) {\n          payload.customFields.forEach((customField) => {\n            CustomField.upsert(customField);\n          });\n        }\n\n        break;\n      case ActionTypes.PROJECT_CREATE_HANDLE:\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n      case ActionTypes.CARDS_FETCH__SUCCESS:\n      case ActionTypes.CARD_CREATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__SUCCESS:\n      case ActionTypes.CARD_DUPLICATE__SUCCESS:\n        payload.customFields.forEach((customField) => {\n          CustomField.upsert(customField);\n        });\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_CREATE:\n      case ActionTypes.CUSTOM_FIELD_CREATE_HANDLE:\n      case ActionTypes.CUSTOM_FIELD_UPDATE__SUCCESS:\n      case ActionTypes.CUSTOM_FIELD_UPDATE_HANDLE:\n        CustomField.upsert(payload.customField);\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_CREATE__SUCCESS:\n        CustomField.withId(payload.localId).delete();\n        CustomField.upsert(payload.customField);\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_CREATE__FAILURE:\n        CustomField.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_UPDATE:\n        CustomField.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_DELETE:\n        CustomField.withId(payload.id).deleteWithRelated();\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_DELETE__SUCCESS:\n      case ActionTypes.CUSTOM_FIELD_DELETE_HANDLE: {\n        const customFieldModel = CustomField.withId(payload.customField.id);\n\n        if (customFieldModel) {\n          customFieldModel.deleteWithRelated();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  duplicate(id, data) {\n    return this.getClass().create({\n      id,\n      baseCustomFieldGroupId: this.baseCustomFieldGroupId,\n      customFieldGroupId: this.customFieldGroupId,\n      position: this.position,\n      name: this.name,\n      showOnFrontOfCard: this.showOnFrontOfCard,\n      ...data,\n    });\n  }\n\n  deleteRelated() {\n    this.customFieldValues.delete();\n  }\n\n  deleteWithRelated() {\n    this.deleteRelated();\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/CustomFieldGroup.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'CustomFieldGroup';\n\n  static fields = {\n    id: attr(),\n    position: attr(),\n    name: attr(),\n    boardId: fk({\n      to: 'Board',\n      as: 'board',\n      relatedName: 'customFieldGroups',\n    }),\n    cardId: fk({\n      to: 'Card',\n      as: 'card',\n      relatedName: 'customFieldGroups',\n    }),\n    baseCustomFieldGroupId: fk({\n      to: 'BaseCustomFieldGroup',\n      as: 'baseCustomFieldGroup',\n      relatedName: 'customFieldGroups',\n    }),\n  };\n\n  static reducer({ type, payload }, CustomFieldGroup) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n      case ActionTypes.LIST_UPDATE_HANDLE:\n      case ActionTypes.CARD_UPDATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__FAILURE:\n        if (payload.customFieldGroups) {\n          payload.customFieldGroups.forEach((customFieldGroup) => {\n            CustomFieldGroup.upsert(customFieldGroup);\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        CustomFieldGroup.all().delete();\n\n        if (payload.customFieldGroups) {\n          payload.customFieldGroups.forEach((customFieldGroup) => {\n            CustomFieldGroup.upsert(customFieldGroup);\n          });\n        }\n\n        break;\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n      case ActionTypes.CARDS_FETCH__SUCCESS:\n      case ActionTypes.CARD_CREATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__SUCCESS:\n      case ActionTypes.CARD_DUPLICATE__SUCCESS:\n        payload.customFieldGroups.forEach((customFieldGroup) => {\n          CustomFieldGroup.upsert(customFieldGroup);\n        });\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_GROUP_CREATE:\n      case ActionTypes.CUSTOM_FIELD_GROUP_CREATE_HANDLE:\n      case ActionTypes.CUSTOM_FIELD_GROUP_UPDATE__SUCCESS:\n      case ActionTypes.CUSTOM_FIELD_GROUP_UPDATE_HANDLE:\n        CustomFieldGroup.upsert(payload.customFieldGroup);\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_GROUP_CREATE__SUCCESS:\n        CustomFieldGroup.withId(payload.localId).delete();\n        CustomFieldGroup.upsert(payload.customFieldGroup);\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_GROUP_CREATE__FAILURE:\n        CustomFieldGroup.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_GROUP_UPDATE:\n        CustomFieldGroup.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_GROUP_DELETE:\n        CustomFieldGroup.withId(payload.id).deleteWithRelated();\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_GROUP_DELETE__SUCCESS:\n      case ActionTypes.CUSTOM_FIELD_GROUP_DELETE_HANDLE: {\n        const customFieldGroupModel = CustomFieldGroup.withId(payload.customFieldGroup.id);\n\n        if (customFieldGroupModel) {\n          customFieldGroupModel.deleteWithRelated();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  getCustomFieldsQuerySet() {\n    return this.customFields.orderBy(['position', 'id.length', 'id']);\n  }\n\n  getCustomFieldsModelArray() {\n    if (this.baseCustomFieldGroupId) {\n      return this.baseCustomFieldGroup.getCustomFieldsQuerySet().toModelArray();\n    }\n\n    return this.getCustomFieldsQuerySet().toModelArray();\n  }\n\n  getShownOnFrontOfCardCustomFieldsModelArray() {\n    return this.getCustomFieldsModelArray().filter(\n      (customFieldModel) => customFieldModel.showOnFrontOfCard,\n    );\n  }\n\n  duplicate(id, data, rootId) {\n    if (rootId === undefined) {\n      rootId = id; // eslint-disable-line no-param-reassign\n    }\n\n    const customFieldGroupModel = this.getClass().create({\n      id,\n      boardId: this.boardId,\n      cardId: this.cardId,\n      baseCustomFieldGroupId: this.baseCustomFieldGroupId,\n      position: this.position,\n      name: this.name,\n      ...data,\n    });\n\n    this.customFields.toModelArray().forEach((customFieldModel) => {\n      customFieldModel.duplicate(\n        `${customFieldModel.id}-${rootId}`,\n        {\n          customFieldGroupId: customFieldGroupModel.id,\n        },\n        rootId,\n      );\n    });\n\n    return customFieldGroupModel;\n  }\n\n  deleteRelated() {\n    this.customFields.delete();\n    this.customFieldValues.delete();\n  }\n\n  deleteWithRelated() {\n    this.deleteRelated();\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/CustomFieldValue.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport const buildCustomFieldValueId = (customFieldValue) =>\n  JSON.stringify({\n    cardId: customFieldValue.cardId,\n    customFieldGroupId: customFieldValue.customFieldGroupId,\n    customFieldId: customFieldValue.customFieldId,\n  });\n\nconst prepareCustomFieldValue = (customFieldValue) => ({\n  ...customFieldValue,\n  id: buildCustomFieldValueId(customFieldValue),\n});\n\nexport default class extends BaseModel {\n  static modelName = 'CustomFieldValue';\n\n  static fields = {\n    id: attr(),\n    content: attr(),\n    cardId: fk({\n      to: 'Card',\n      as: 'card',\n      relatedName: 'customFieldValues',\n    }),\n    customFieldGroupId: fk({\n      to: 'CustomFieldGroup',\n      as: 'customFieldGroup',\n      relatedName: 'customFieldValues',\n    }),\n    customFieldId: fk({\n      to: 'CustomField',\n      as: 'customField',\n      relatedName: 'customFieldValues',\n    }),\n  };\n\n  static reducer({ type, payload }, CustomFieldValue) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n      case ActionTypes.LIST_UPDATE_HANDLE:\n      case ActionTypes.CARD_UPDATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__FAILURE:\n        if (payload.customFieldValues) {\n          payload.customFieldValues.forEach((customFieldValue) => {\n            CustomFieldValue.upsert(prepareCustomFieldValue(customFieldValue));\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        CustomFieldValue.all().delete();\n\n        if (payload.customFieldValues) {\n          payload.customFieldValues.forEach((customFieldValue) => {\n            CustomFieldValue.upsert(prepareCustomFieldValue(customFieldValue));\n          });\n        }\n\n        break;\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n      case ActionTypes.CARDS_FETCH__SUCCESS:\n      case ActionTypes.CARD_CREATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__SUCCESS:\n      case ActionTypes.CARD_DUPLICATE__SUCCESS:\n        payload.customFieldValues.forEach((customFieldValue) => {\n          CustomFieldValue.upsert(prepareCustomFieldValue(customFieldValue));\n        });\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_VALUE_UPDATE:\n      case ActionTypes.CUSTOM_FIELD_VALUE_UPDATE__SUCCESS:\n      case ActionTypes.CUSTOM_FIELD_VALUE_UPDATE_HANDLE:\n        CustomFieldValue.upsert(prepareCustomFieldValue(payload.customFieldValue));\n\n        break;\n      case ActionTypes.CUSTOM_FIELD_VALUE_DELETE: {\n        const customFieldValueModel = CustomFieldValue.withId(payload.id);\n\n        if (customFieldValueModel) {\n          customFieldValueModel.delete();\n        }\n\n        break;\n      }\n      case ActionTypes.CUSTOM_FIELD_VALUE_DELETE__SUCCESS:\n      case ActionTypes.CUSTOM_FIELD_VALUE_DELETE_HANDLE: {\n        const customFieldValueModel = CustomFieldValue.withId(\n          buildCustomFieldValueId(payload.customFieldValue),\n        );\n\n        if (customFieldValueModel) {\n          customFieldValueModel.delete();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  duplicate(data) {\n    const customFieldValue = {\n      cardId: this.cardId,\n      customFieldGroupId: this.customFieldGroupId,\n      customFieldId: this.customFieldId,\n      content: this.content,\n      ...data,\n    };\n\n    return this.getClass().create({\n      id: buildCustomFieldValueId(customFieldValue),\n      ...customFieldValue,\n    });\n  }\n}\n"
  },
  {
    "path": "client/src/models/Label.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'Label';\n\n  static fields = {\n    id: attr(),\n    position: attr(),\n    name: attr(),\n    color: attr(),\n    boardId: fk({\n      to: 'Board',\n      as: 'board',\n      relatedName: 'labels',\n    }),\n  };\n\n  static reducer({ type, payload }, Label) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n        if (payload.labels) {\n          payload.labels.forEach((label) => {\n            Label.upsert(label);\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        Label.all().delete();\n\n        if (payload.labels) {\n          payload.labels.forEach((label) => {\n            Label.upsert(label);\n          });\n        }\n\n        break;\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n        payload.labels.forEach((label) => {\n          Label.upsert(label);\n        });\n\n        break;\n      case ActionTypes.LABEL_CREATE:\n      case ActionTypes.LABEL_FROM_CARD_CREATE:\n      case ActionTypes.LABEL_CREATE_HANDLE:\n      case ActionTypes.LABEL_UPDATE__SUCCESS:\n      case ActionTypes.LABEL_UPDATE_HANDLE:\n        Label.upsert(payload.label);\n\n        break;\n      case ActionTypes.LABEL_CREATE__SUCCESS:\n      case ActionTypes.LABEL_FROM_CARD_CREATE__SUCCESS:\n        Label.withId(payload.localId).delete();\n        Label.upsert(payload.label);\n\n        break;\n      case ActionTypes.LABEL_CREATE__FAILURE:\n      case ActionTypes.LABEL_FROM_CARD_CREATE__FAILURE:\n        Label.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.LABEL_UPDATE:\n        Label.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.LABEL_DELETE:\n        Label.withId(payload.id).deleteWithRelated();\n\n        break;\n      case ActionTypes.LABEL_DELETE__SUCCESS:\n      case ActionTypes.LABEL_DELETE_HANDLE: {\n        const labelModel = Label.withId(payload.label.id);\n\n        if (labelModel) {\n          labelModel.deleteWithRelated();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  deleteRelated() {\n    this.board.cards.toModelArray().forEach((cardModel) => {\n      try {\n        cardModel.labels.remove(this.id);\n      } catch {\n        /* empty */\n      }\n    });\n\n    try {\n      this.board.filterLabels.remove(this.id);\n    } catch {\n      /* empty */\n    }\n  }\n\n  deleteWithRelated() {\n    this.deleteRelated();\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/List.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport buildSearchParts from '../utils/build-search-parts';\nimport { isListFinite } from '../utils/record-helpers';\nimport ActionTypes from '../constants/ActionTypes';\nimport Config from '../constants/Config';\nimport { ListSortFieldNames, ListTypes, ListTypeStates, SortOrders } from '../constants/Enums';\nimport LIST_TYPE_STATE_BY_TYPE from '../constants/ListTypeStateByType';\n\nconst POSITION_BY_LIST_TYPE = {\n  [ListTypes.ARCHIVE]: Number.MAX_SAFE_INTEGER - 1,\n  [ListTypes.TRASH]: Number.MAX_SAFE_INTEGER,\n};\n\nconst prepareList = (list) => {\n  if (list.position === undefined) {\n    return list;\n  }\n\n  return {\n    ...list,\n    position: list.position === null ? POSITION_BY_LIST_TYPE[list.type] : list.position,\n  };\n};\n\nconst getChangedTypeState = (prevList, list) => {\n  const prevTypeState = LIST_TYPE_STATE_BY_TYPE[prevList.type];\n  const typeState = LIST_TYPE_STATE_BY_TYPE[list.type];\n\n  if (prevTypeState === ListTypeStates.OPENED && typeState === ListTypeStates.CLOSED) {\n    return ListTypeStates.CLOSED;\n  }\n\n  if (prevTypeState === ListTypeStates.CLOSED && typeState === ListTypeStates.OPENED) {\n    return ListTypeStates.OPENED;\n  }\n\n  return null;\n};\n\nexport default class extends BaseModel {\n  static modelName = 'List';\n\n  static fields = {\n    id: attr(),\n    type: attr(),\n    position: attr(),\n    name: attr(),\n    color: attr(),\n    lastCard: attr({\n      getDefault: () => null,\n    }),\n    isCardsFetching: attr({\n      getDefault: () => false,\n    }),\n    isAllCardsFetched: attr({\n      getDefault: () => null,\n    }),\n    boardId: fk({\n      to: 'Board',\n      as: 'board',\n      relatedName: 'lists',\n    }),\n  };\n\n  static reducer({ type, payload }, List) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n        if (payload.lists) {\n          payload.lists.forEach((list) => {\n            List.upsert(prepareList(list));\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        List.all().delete();\n\n        if (payload.lists) {\n          payload.lists.forEach((list) => {\n            List.upsert(prepareList(list));\n          });\n        }\n\n        break;\n      case ActionTypes.USER_TO_BOARD_FILTER_ADD:\n      case ActionTypes.USER_FROM_BOARD_FILTER_REMOVE:\n      case ActionTypes.IN_BOARD_SEARCH:\n      case ActionTypes.LABEL_TO_BOARD_FILTER_ADD:\n      case ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE:\n        if (payload.currentListId) {\n          List.withId(payload.currentListId).update({\n            lastCard: null,\n            isCardsFetching: false,\n            isAllCardsFetched: null,\n          });\n        }\n\n        break;\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n        payload.lists.forEach((list) => {\n          List.upsert(prepareList(list));\n        });\n\n        break;\n      case ActionTypes.LIST_CREATE:\n      case ActionTypes.LIST_CREATE_HANDLE:\n      case ActionTypes.LIST_UPDATE__SUCCESS:\n      case ActionTypes.LIST_SORT__SUCCESS:\n      case ActionTypes.LIST_CARDS_MOVE__SUCCESS:\n      case ActionTypes.LIST_CLEAR__SUCCESS:\n        List.upsert(prepareList(payload.list));\n\n        break;\n      case ActionTypes.LIST_CREATE__SUCCESS:\n        List.withId(payload.localId).delete();\n        List.upsert(prepareList(payload.list));\n\n        break;\n      case ActionTypes.LIST_CREATE__FAILURE:\n        List.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.LIST_UPDATE: {\n        const listModel = List.withId(payload.id);\n\n        if (payload.data.boardId && payload.data.boardId !== listModel.boardId) {\n          listModel.deleteWithRelated(true);\n        } else {\n          let isClosed;\n          if (payload.data.type) {\n            const changedTypeState = getChangedTypeState(listModel, payload.data);\n\n            if (changedTypeState === ListTypeStates.OPENED) {\n              isClosed = false;\n            } else if (changedTypeState === ListTypeStates.CLOSED) {\n              isClosed = true;\n            }\n          }\n\n          listModel.update(payload.data);\n\n          if (isClosed !== undefined) {\n            listModel.cards.toModelArray().forEach((cardModel) => {\n              cardModel.update({\n                isClosed,\n              });\n\n              cardModel.linkedTasks.update({\n                isCompleted: isClosed,\n              });\n            });\n          }\n        }\n\n        break;\n      }\n      case ActionTypes.LIST_UPDATE_HANDLE: {\n        const listModel = List.withId(payload.list.id);\n\n        if (listModel) {\n          if (payload.list.boardId === null || payload.isFetched) {\n            listModel.deleteWithRelated(true);\n          }\n\n          if (payload.list.boardId !== null) {\n            const changedTypeState = getChangedTypeState(listModel, payload.list);\n\n            let isClosed;\n            if (changedTypeState === ListTypeStates.OPENED) {\n              isClosed = false;\n            } else if (changedTypeState === ListTypeStates.CLOSED) {\n              isClosed = true;\n            }\n\n            if (isClosed !== undefined) {\n              listModel.cards.toModelArray().forEach((cardModel) => {\n                cardModel.update({\n                  isClosed,\n                });\n\n                cardModel.linkedTasks.update({\n                  isCompleted: isClosed,\n                });\n              });\n            }\n          }\n        }\n\n        if (payload.list.boardId !== null) {\n          List.upsert(prepareList(payload.list));\n        }\n\n        break;\n      }\n      case ActionTypes.LIST_SORT:\n        List.withId(payload.id).sortCards(payload.data);\n\n        break;\n      case ActionTypes.LIST_CLEAR:\n        List.withId(payload.id).deleteRelated();\n\n        break;\n      case ActionTypes.LIST_CLEAR_HANDLE: {\n        const listModel = List.withId(payload.list.id);\n\n        if (listModel) {\n          listModel.deleteRelated();\n        }\n\n        List.upsert(prepareList(payload.list));\n\n        break;\n      }\n      case ActionTypes.LIST_DELETE:\n        List.withId(payload.id).delete();\n\n        break;\n      case ActionTypes.LIST_DELETE__SUCCESS: {\n        const listModel = List.withId(payload.list.id);\n\n        if (listModel) {\n          listModel.delete();\n        }\n\n        break;\n      }\n      case ActionTypes.LIST_DELETE_HANDLE: {\n        const listModel = List.withId(payload.list.id);\n\n        if (listModel) {\n          if (payload.cards) {\n            listModel.delete();\n          } else {\n            listModel.deleteWithRelated();\n          }\n        }\n\n        break;\n      }\n      case ActionTypes.CARDS_FETCH:\n        List.withId(payload.listId).update({\n          isCardsFetching: true,\n        });\n\n        break;\n      case ActionTypes.CARDS_FETCH__SUCCESS: {\n        const lastCard = payload.cards[payload.cards.length - 1];\n\n        List.withId(payload.listId).update({\n          isCardsFetching: false,\n          isAllCardsFetched: payload.cards.length < Config.CARDS_LIMIT,\n          ...(lastCard && {\n            lastCard: {\n              listChangedAt: lastCard.listChangedAt,\n              id: lastCard.id,\n            },\n          }),\n        });\n\n        break;\n      }\n      default:\n    }\n  }\n\n  getCardsQuerySet() {\n    const orderByArgs = isListFinite(this)\n      ? [['position', 'id.length', 'id']]\n      : [\n          ['listChangedAt', 'id.length', 'id'],\n          ['desc', 'desc', 'desc'],\n        ];\n\n    return this.cards.orderBy(...orderByArgs);\n  }\n\n  getCardsModelArray() {\n    const isFinite = isListFinite(this);\n\n    if (!isFinite && this.isAllCardsFetched === null) {\n      return [];\n    }\n\n    const cardModels = this.getCardsQuerySet().toModelArray();\n\n    if (!isFinite && this.lastCard && this.isAllCardsFetched === false) {\n      return cardModels.filter((cardModel) => {\n        if (cardModel.listChangedAt > this.lastCard.listChangedAt) {\n          return true;\n        }\n\n        if (cardModel.listChangedAt < this.lastCard.listChangedAt) {\n          return false;\n        }\n\n        if (cardModel.id.length > this.lastCard.id.length) {\n          return true;\n        }\n\n        if (cardModel.id.length < this.lastCard.id.length) {\n          return false;\n        }\n\n        return cardModel.id >= this.lastCard.id;\n      });\n    }\n\n    return cardModels;\n  }\n\n  getFilteredCardsModelArray() {\n    let cardModels = this.getCardsModelArray();\n\n    if (cardModels.length === 0) {\n      return cardModels;\n    }\n\n    if (this.board.search) {\n      if (this.board.search.startsWith('/')) {\n        let searchRegex;\n        try {\n          searchRegex = new RegExp(this.board.search.substring(1), 'i');\n        } catch {\n          return [];\n        }\n\n        cardModels = cardModels.filter(\n          (cardModel) =>\n            searchRegex.test(cardModel.name) ||\n            (cardModel.description && searchRegex.test(cardModel.description)),\n        );\n      } else {\n        const searchParts = buildSearchParts(this.board.search);\n\n        cardModels = cardModels.filter((cardModel) => {\n          const name = cardModel.name.toLowerCase();\n          const description = cardModel.description && cardModel.description.toLowerCase();\n\n          return searchParts.every(\n            (searchPart) =>\n              name.includes(searchPart) || (description && description.includes(searchPart)),\n          );\n        });\n      }\n    }\n\n    const filterUserIds = this.board.filterUsers.toRefArray().map((user) => user.id);\n\n    if (filterUserIds.length > 0) {\n      cardModels = cardModels.filter((cardModel) => {\n        const users = cardModel.users.toRefArray();\n\n        if (users.some((user) => filterUserIds.includes(user.id))) {\n          return true;\n        }\n\n        return cardModel\n          .getTaskListsQuerySet()\n          .toModelArray()\n          .some((taskListModel) =>\n            taskListModel\n              .getTasksQuerySet()\n              .toRefArray()\n              .some((task) => task.assigneeUserId && filterUserIds.includes(task.assigneeUserId)),\n          );\n      });\n    }\n\n    const filterLabelIds = this.board.filterLabels.toRefArray().map((label) => label.id);\n\n    if (filterLabelIds.length > 0) {\n      cardModels = cardModels.filter((cardModel) => {\n        const labels = cardModel.labels.toRefArray();\n        return labels.some((label) => filterLabelIds.includes(label.id));\n      });\n    }\n\n    return cardModels;\n  }\n\n  isAvailableForUser(userModel) {\n    return !!this.board && this.board.isAvailableForUser(userModel);\n  }\n\n  sortCards(options) {\n    const cardModels = this.getCardsQuerySet().toModelArray();\n\n    switch (options.fieldName) {\n      case ListSortFieldNames.NAME:\n        cardModels.sort((card1, card2) => card1.name.localeCompare(card2.name));\n\n        break;\n      case ListSortFieldNames.DUE_DATE:\n        cardModels.sort((card1, card2) => {\n          if (card1.dueDate === null) {\n            return 1;\n          }\n\n          if (card2.dueDate === null) {\n            return -1;\n          }\n\n          return card1.dueDate - card2.dueDate;\n        });\n\n        break;\n      case ListSortFieldNames.CREATED_AT:\n        cardModels.sort((card1, card2) => card1.createdAt - card2.createdAt);\n\n        break;\n      default:\n        break;\n    }\n\n    if (options.order === SortOrders.DESC) {\n      cardModels.reverse();\n    }\n\n    cardModels.forEach((cardModel, index) => {\n      cardModel.update({\n        position: Config.POSITION_GAP * (index + 1),\n      });\n    });\n  }\n\n  deleteRelated(soft) {\n    this.cards.toModelArray().forEach((cardModel) => {\n      cardModel.deleteWithRelated(soft);\n    });\n  }\n\n  deleteWithRelated(soft) {\n    this.deleteRelated(soft);\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/Notification.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk, oneToOne } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'Notification';\n\n  static fields = {\n    id: attr(),\n    type: attr(),\n    data: attr(),\n    isRead: attr(),\n    userId: fk({\n      to: 'User',\n      as: 'user',\n      relatedName: 'notifications',\n    }),\n    creatorUserId: fk({\n      to: 'User',\n      as: 'creatorUser',\n      relatedName: 'createdNotifications',\n    }),\n    boardId: fk({\n      to: 'Board',\n      as: 'board',\n      relatedName: 'notifications',\n    }),\n    cardId: fk({\n      to: 'Card',\n      as: 'card',\n      relatedName: 'notifications',\n    }),\n    commentId: oneToOne({\n      to: 'Comment',\n      as: 'comment',\n    }),\n    activityId: oneToOne({\n      to: 'Activity',\n      as: 'activity',\n    }),\n  };\n\n  static reducer({ type, payload }, Notification) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n        if (payload.notificationsToDelete) {\n          payload.notificationsToDelete.forEach((notification) => {\n            Notification.withId(notification.id).delete();\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        Notification.all().delete();\n\n        payload.notifications.forEach((notification) => {\n          Notification.upsert(notification);\n        });\n\n        break;\n      case ActionTypes.CORE_INITIALIZE:\n        payload.notifications.forEach((notification) => {\n          Notification.upsert(notification);\n        });\n\n        break;\n      case ActionTypes.ALL_NOTIFICATIONS_DELETE:\n        Notification.all().delete();\n\n        break;\n      case ActionTypes.ALL_NOTIFICATIONS_DELETE__SUCCESS:\n        payload.notifications.forEach((notification) => {\n          const notificationModel = Notification.withId(notification.id);\n\n          if (notificationModel) {\n            notificationModel.delete();\n          }\n        });\n\n        break;\n      case ActionTypes.NOTIFICATION_CREATE_HANDLE:\n        Notification.upsert(payload.notification);\n\n        break;\n      case ActionTypes.NOTIFICATION_DELETE:\n        Notification.withId(payload.id).delete();\n\n        break;\n      case ActionTypes.NOTIFICATION_DELETE__SUCCESS:\n      case ActionTypes.NOTIFICATION_DELETE_HANDLE: {\n        const notificationModel = Notification.withId(payload.notification.id);\n\n        if (notificationModel) {\n          notificationModel.delete();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/models/NotificationService.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'NotificationService';\n\n  static fields = {\n    id: attr(),\n    url: attr(),\n    format: attr(),\n    isTesting: attr({\n      getDefault: () => false,\n    }),\n    userId: fk({\n      to: 'User',\n      as: 'user',\n      relatedName: 'notificationServices',\n    }),\n    boardId: fk({\n      to: 'Board',\n      as: 'board',\n      relatedName: 'notificationServices',\n    }),\n  };\n\n  static reducer({ type, payload }, NotificationService) {\n    switch (type) {\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        NotificationService.all().delete();\n\n        payload.notificationServices.forEach((notificationService) => {\n          NotificationService.upsert(notificationService);\n        });\n\n        break;\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.PROJECT_CREATE_HANDLE:\n        payload.notificationServices.forEach((notificationService) => {\n          NotificationService.upsert(notificationService);\n        });\n\n        break;\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n        if (payload.notificationServices) {\n          payload.notificationServices.forEach((notificationService) => {\n            NotificationService.upsert(notificationService);\n          });\n        }\n\n        break;\n      case ActionTypes.NOTIFICATION_SERVICE_CREATE:\n      case ActionTypes.NOTIFICATION_SERVICE_CREATE_HANDLE:\n      case ActionTypes.NOTIFICATION_SERVICE_UPDATE__SUCCESS:\n      case ActionTypes.NOTIFICATION_SERVICE_UPDATE_HANDLE:\n        NotificationService.upsert(payload.notificationService);\n\n        break;\n      case ActionTypes.NOTIFICATION_SERVICE_CREATE__SUCCESS:\n        NotificationService.withId(payload.localId).delete();\n        NotificationService.upsert(payload.notificationService);\n\n        break;\n      case ActionTypes.NOTIFICATION_SERVICE_CREATE__FAILURE:\n        NotificationService.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.NOTIFICATION_SERVICE_UPDATE:\n        NotificationService.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.NOTIFICATION_SERVICE_TEST:\n        NotificationService.withId(payload.id).update({\n          isTesting: true,\n        });\n\n        break;\n      case ActionTypes.NOTIFICATION_SERVICE_TEST__SUCCESS:\n        NotificationService.upsert({\n          ...payload.notificationService,\n          isTesting: false,\n        });\n\n        break;\n      case ActionTypes.NOTIFICATION_SERVICE_TEST__FAILURE:\n        NotificationService.withId(payload.id).update({\n          isTesting: false,\n        });\n\n        break;\n      case ActionTypes.NOTIFICATION_SERVICE_DELETE:\n        NotificationService.withId(payload.id).delete();\n\n        break;\n      case ActionTypes.NOTIFICATION_SERVICE_DELETE__SUCCESS:\n      case ActionTypes.NOTIFICATION_SERVICE_DELETE_HANDLE: {\n        const notificationServiceModel = NotificationService.withId(payload.notificationService.id);\n\n        if (notificationServiceModel) {\n          notificationServiceModel.delete();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/models/Project.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, many, oneToOne } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\nimport { UserRoles } from '../constants/Enums';\n\nexport default class extends BaseModel {\n  static modelName = 'Project';\n\n  static fields = {\n    id: attr(),\n    name: attr(),\n    description: attr(),\n    backgroundType: attr(),\n    backgroundGradient: attr(),\n    isHidden: attr(),\n    isFavorite: attr({\n      getDefault: () => false,\n    }),\n    ownerProjectManagerId: oneToOne({\n      to: 'ProjectManager',\n      as: 'ownerProjectManager',\n      relatedName: 'ownedProject',\n    }),\n    backgroundImageId: oneToOne({\n      to: 'BackgroundImage',\n      as: 'backgroundImage',\n      relatedName: 'backgroundedProject', // TODO: rename?\n    }),\n    managerUsers: many({\n      to: 'User',\n      through: 'ProjectManager',\n      relatedName: 'managerProjects',\n    }),\n  };\n\n  static reducer({ type, payload }, Project) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n        if (payload.projects) {\n          payload.projects.forEach((project) => {\n            Project.upsert(project);\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        Project.all().delete();\n\n        payload.projects.forEach((project) => {\n          Project.upsert(project);\n        });\n\n        break;\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n        payload.projects.forEach((project) => {\n          Project.upsert(project);\n        });\n\n        break;\n      case ActionTypes.USER_UPDATE_HANDLE:\n        Project.all()\n          .toModelArray()\n          .forEach((projectModel) => {\n            if (!payload.projectIds.includes(projectModel.id)) {\n              projectModel.deleteWithRelated(true);\n            }\n          });\n\n        if (payload.projects) {\n          payload.projects.forEach((project) => {\n            Project.upsert(project);\n          });\n        }\n\n        break;\n      case ActionTypes.PROJECT_CREATE__SUCCESS:\n      case ActionTypes.PROJECT_CREATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE__SUCCESS:\n        Project.upsert(payload.project);\n\n        break;\n      case ActionTypes.PROJECT_UPDATE:\n        Project.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.PROJECT_UPDATE_HANDLE: {\n        const projectModel = Project.withId(payload.project.id);\n\n        if (projectModel) {\n          if (payload.isAvailable) {\n            projectModel.boards.toModelArray().forEach((boardModel) => {\n              if (!payload.boardIds.includes(boardModel.id)) {\n                boardModel.deleteWithRelated(true);\n              }\n            });\n          } else {\n            projectModel.deleteWithRelated(true);\n          }\n        }\n\n        Project.upsert(payload.project);\n\n        break;\n      }\n      case ActionTypes.PROJECT_DELETE:\n        Project.withId(payload.id).deleteWithRelated();\n\n        break;\n      case ActionTypes.PROJECT_DELETE__SUCCESS:\n      case ActionTypes.PROJECT_DELETE_HANDLE: {\n        const projectModel = Project.withId(payload.project.id);\n\n        if (projectModel) {\n          projectModel.deleteWithRelated();\n        }\n\n        break;\n      }\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE: {\n        const projectModel = Project.withId(payload.projectManager.projectId);\n\n        if (projectModel) {\n          if (payload.isProjectAvailable) {\n            projectModel.boards.toModelArray().forEach((boardModel) => {\n              if (payload.boardIds.includes(boardModel.id)) {\n                if (payload.isCurrentUser) {\n                  boardModel.notificationServices.delete();\n                }\n              } else {\n                boardModel.deleteWithRelated(true);\n              }\n            });\n          } else {\n            projectModel.deleteWithRelated(true);\n          }\n        }\n\n        if (payload.project) {\n          Project.upsert(payload.project);\n        }\n\n        break;\n      }\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n        if (!payload.isProjectAvailable) {\n          const projectModel = Project.withId(payload.boardMembership.projectId);\n\n          if (projectModel) {\n            projectModel.deleteWithRelated(true);\n          }\n        }\n\n        if (payload.project) {\n          Project.upsert(payload.project);\n        }\n\n        break;\n      default:\n    }\n  }\n\n  static getSharedQuerySet() {\n    return this.filter({\n      ownerProjectManagerId: null,\n    }).orderBy(['id.length', 'id']);\n  }\n\n  getManagersQuerySet() {\n    return this.managers.orderBy(['id.length', 'id']);\n  }\n\n  getBackgroundImagesQuerySet() {\n    return this.backgroundImages.orderBy(['id.length', 'id']);\n  }\n\n  getBaseCustomFieldGroupsQuerySet() {\n    return this.baseCustomFieldGroups.orderBy(['id.length', 'id']);\n  }\n\n  getBoardsQuerySet() {\n    return this.boards.orderBy(['position', 'id.length', 'id']);\n  }\n\n  getBoardsModelArrayForUserWithId(userId) {\n    return this.getBoardsQuerySet()\n      .toModelArray()\n      .filter((boardModel) => boardModel.hasMembershipWithUserId(userId));\n  }\n\n  getBoardsModelArrayAvailableForUser(userModel) {\n    if (this.isExternalAccessibleForUser(userModel)) {\n      return this.getBoardsQuerySet().toModelArray();\n    }\n\n    return this.getBoardsModelArrayForUserWithId(userModel.id);\n  }\n\n  hasManagerWithUserId(userId) {\n    return this.managers\n      .filter({\n        userId,\n      })\n      .exists();\n  }\n\n  hasMembershipWithUserIdInAnyBoard(userId) {\n    return this.boards\n      .toModelArray()\n      .some((boardModel) => boardModel.hasMembershipWithUserId(userId));\n  }\n\n  isExternalAccessibleForUser(userModel) {\n    if (!this.ownerProjectManagerId && userModel.role === UserRoles.ADMIN) {\n      return true;\n    }\n\n    return this.hasManagerWithUserId(userModel.id);\n  }\n\n  isAvailableForUser(userModel) {\n    return (\n      this.isExternalAccessibleForUser(userModel) ||\n      this.hasMembershipWithUserIdInAnyBoard(userModel.id)\n    );\n  }\n\n  deleteRelated(soft) {\n    this.managers.delete();\n\n    this.backgroundImages.toModelArray().forEach((backgroundImageModel) => {\n      backgroundImageModel.deleteWithRelated();\n    });\n\n    this.baseCustomFieldGroups.toModelArray().forEach((baseCustomFieldGroupModel) => {\n      baseCustomFieldGroupModel.deleteWithRelated();\n    });\n\n    this.boards.toModelArray().forEach((boardModel) => {\n      boardModel.deleteWithRelated(soft);\n    });\n  }\n\n  deleteWithRelated(soft) {\n    this.deleteRelated(soft);\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/ProjectManager.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'ProjectManager';\n\n  static fields = {\n    id: attr(),\n    projectId: fk({\n      to: 'Project',\n      as: 'project',\n      relatedName: 'managers',\n    }),\n    userId: fk({\n      to: 'User',\n      as: 'user',\n      relatedName: 'projectManagers',\n    }),\n  };\n\n  static reducer({ type, payload }, ProjectManager) {\n    switch (type) {\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        ProjectManager.all().delete();\n\n        payload.projectManagers.forEach((projectManager) => {\n          ProjectManager.upsert(projectManager);\n        });\n\n        break;\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.PROJECT_CREATE__SUCCESS:\n      case ActionTypes.PROJECT_CREATE_HANDLE:\n        payload.projectManagers.forEach((projectManager) => {\n          ProjectManager.upsert(projectManager);\n        });\n\n        break;\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n        if (payload.projectManagers) {\n          payload.projectManagers.forEach((projectManager) => {\n            ProjectManager.upsert(projectManager);\n          });\n        }\n\n        break;\n      case ActionTypes.PROJECT_MANAGER_CREATE:\n        ProjectManager.upsert(payload.projectManager);\n\n        break;\n      case ActionTypes.PROJECT_MANAGER_CREATE__SUCCESS:\n        ProjectManager.withId(payload.localId).delete();\n        ProjectManager.upsert(payload.projectManager);\n\n        break;\n      case ActionTypes.PROJECT_MANAGER_CREATE__FAILURE:\n        ProjectManager.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n        ProjectManager.upsert(payload.projectManager);\n\n        if (payload.projectManagers) {\n          payload.projectManagers.forEach((projectManager) => {\n            ProjectManager.upsert(projectManager);\n          });\n        }\n\n        break;\n      case ActionTypes.PROJECT_MANAGER_DELETE:\n        ProjectManager.withId(payload.id).delete();\n\n        break;\n      case ActionTypes.PROJECT_MANAGER_DELETE__SUCCESS:\n      case ActionTypes.PROJECT_MANAGER_DELETE_HANDLE: {\n        const projectManagerModel = ProjectManager.withId(payload.projectManager.id);\n\n        if (projectManagerModel) {\n          projectManagerModel.delete();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/models/Task.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'Task';\n\n  static fields = {\n    id: attr(),\n    position: attr(),\n    name: attr(),\n    isCompleted: attr({\n      getDefault: () => false,\n    }),\n    taskListId: fk({\n      to: 'TaskList',\n      as: 'taskList',\n      relatedName: 'tasks',\n    }),\n    linkedCardId: fk({\n      to: 'Card',\n      as: 'linkedCard',\n      relatedName: 'linkedTasks',\n    }),\n    assigneeUserId: fk({\n      to: 'User',\n      as: 'user',\n      relatedName: 'assignedTasks',\n    }),\n  };\n\n  static reducer({ type, payload }, Task) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n      case ActionTypes.LIST_UPDATE_HANDLE:\n      case ActionTypes.CARD_UPDATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__FAILURE:\n        if (payload.tasks) {\n          payload.tasks.forEach((task) => {\n            Task.upsert(task);\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        Task.all().delete();\n\n        if (payload.tasks) {\n          payload.tasks.forEach((task) => {\n            Task.upsert(task);\n          });\n        }\n\n        break;\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n      case ActionTypes.CARDS_FETCH__SUCCESS:\n      case ActionTypes.CARD_CREATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__SUCCESS:\n      case ActionTypes.CARD_DUPLICATE__SUCCESS:\n        payload.tasks.forEach((task) => {\n          Task.upsert(task);\n        });\n\n        break;\n      case ActionTypes.TASK_CREATE:\n      case ActionTypes.TASK_CREATE_HANDLE:\n      case ActionTypes.TASK_UPDATE__SUCCESS:\n      case ActionTypes.TASK_UPDATE_HANDLE:\n        Task.upsert(payload.task);\n\n        break;\n      case ActionTypes.TASK_CREATE__SUCCESS:\n        Task.withId(payload.localId).delete();\n        Task.upsert(payload.task);\n\n        break;\n      case ActionTypes.TASK_CREATE__FAILURE:\n        Task.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.TASK_UPDATE:\n        Task.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.TASK_DELETE:\n        Task.withId(payload.id).delete();\n\n        break;\n      case ActionTypes.TASK_DELETE__SUCCESS:\n      case ActionTypes.TASK_DELETE_HANDLE: {\n        const taskModel = Task.withId(payload.task.id);\n\n        if (taskModel) {\n          taskModel.delete();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  duplicate(id, data) {\n    return this.getClass().create({\n      id,\n      taskListId: this.taskListId,\n      assigneeUserId: this.assigneeUserId,\n      position: this.position,\n      name: this.name,\n      isCompleted: this.isCompleted,\n      ...data,\n    });\n  }\n}\n"
  },
  {
    "path": "client/src/models/TaskList.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'TaskList';\n\n  static fields = {\n    id: attr(),\n    position: attr(),\n    name: attr(),\n    showOnFrontOfCard: attr(),\n    hideCompletedTasks: attr(),\n    cardId: fk({\n      to: 'Card',\n      as: 'card',\n      relatedName: 'taskLists',\n    }),\n  };\n\n  static reducer({ type, payload }, TaskList) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n      case ActionTypes.LIST_UPDATE_HANDLE:\n      case ActionTypes.CARD_UPDATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__FAILURE:\n        if (payload.taskLists) {\n          payload.taskLists.forEach((taskList) => {\n            TaskList.upsert(taskList);\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        TaskList.all().delete();\n\n        if (payload.taskLists) {\n          payload.taskLists.forEach((taskList) => {\n            TaskList.upsert(taskList);\n          });\n        }\n\n        break;\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n      case ActionTypes.CARDS_FETCH__SUCCESS:\n      case ActionTypes.CARD_CREATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__SUCCESS:\n      case ActionTypes.CARD_DUPLICATE__SUCCESS:\n        payload.taskLists.forEach((taskList) => {\n          TaskList.upsert(taskList);\n        });\n\n        break;\n      case ActionTypes.TASK_LIST_CREATE:\n      case ActionTypes.TASK_LIST_CREATE_HANDLE:\n      case ActionTypes.TASK_LIST_UPDATE__SUCCESS:\n      case ActionTypes.TASK_LIST_UPDATE_HANDLE:\n        TaskList.upsert(payload.taskList);\n\n        break;\n      case ActionTypes.TASK_LIST_CREATE__SUCCESS:\n        TaskList.withId(payload.localId).delete();\n        TaskList.upsert(payload.taskList);\n\n        break;\n      case ActionTypes.TASK_LIST_CREATE__FAILURE:\n        TaskList.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.TASK_LIST_UPDATE:\n        TaskList.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.TASK_LIST_DELETE:\n        TaskList.withId(payload.id).deleteWithRelated();\n\n        break;\n      case ActionTypes.TASK_LIST_DELETE__SUCCESS:\n      case ActionTypes.TASK_LIST_DELETE_HANDLE: {\n        const taskListModel = TaskList.withId(payload.taskList.id);\n\n        if (taskListModel) {\n          taskListModel.deleteWithRelated();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  getTasksQuerySet() {\n    return this.tasks.orderBy(['position', 'id.length', 'id']);\n  }\n\n  duplicate(id, data, rootId) {\n    if (rootId === undefined) {\n      rootId = id; // eslint-disable-line no-param-reassign\n    }\n\n    const taskListModel = this.getClass().create({\n      id,\n      cardId: this.cardId,\n      position: this.position,\n      name: this.name,\n      showOnFrontOfCard: this.showOnFrontOfCard,\n      hideCompletedTasks: this.hideCompletedTasks,\n      ...data,\n    });\n\n    this.tasks.toModelArray().forEach((taskModel) => {\n      taskModel.duplicate(`${taskModel.id}-${rootId}`, {\n        taskListId: taskListModel.id,\n      });\n    });\n\n    return taskListModel;\n  }\n\n  deleteRelated() {\n    this.tasks.delete();\n  }\n\n  deleteWithRelated() {\n    this.deleteRelated();\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/User.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport orderBy from 'lodash/orderBy';\nimport { attr } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport buildSearchParts from '../utils/build-search-parts';\nimport ActionTypes from '../constants/ActionTypes';\nimport { UserRoles } from '../constants/Enums';\n\nconst DEFAULT_EMAIL_UPDATE_FORM = {\n  data: {\n    email: '',\n    currentPassword: '',\n  },\n  isSubmitting: false,\n  error: null,\n};\n\nconst DEFAULT_PASSWORD_UPDATE_FORM = {\n  data: {\n    password: '',\n    currentPassword: '',\n  },\n  isSubmitting: false,\n  error: null,\n};\n\nconst DEFAULT_USERNAME_UPDATE_FORM = {\n  data: {\n    username: '',\n    currentPassword: '',\n  },\n  isSubmitting: false,\n  error: null,\n};\n\nconst DEFAULT_API_KEY_STATE = {\n  value: null,\n  isCreating: false,\n  error: null,\n};\n\nconst filterProjectModels = (projectModels, search, isHidden) => {\n  let filteredProjectModels = projectModels.filter(\n    (projectModel) => projectModel.isHidden === isHidden,\n  );\n\n  if (filteredProjectModels.length > 0 && search) {\n    const searchParts = buildSearchParts(search);\n\n    filteredProjectModels = filteredProjectModels.filter((projectModel) =>\n      searchParts.every((searchPart) => projectModel.name.toLowerCase().includes(searchPart)),\n    );\n  }\n\n  return filteredProjectModels;\n};\n\nexport default class extends BaseModel {\n  static modelName = 'User';\n\n  static fields = {\n    id: attr(),\n    email: attr(),\n    role: attr(),\n    username: attr(),\n    name: attr(),\n    avatar: attr(),\n    phone: attr(),\n    organization: attr(),\n    language: attr(),\n    apiKeyPrefix: attr(),\n    subscribeToOwnCards: attr(),\n    subscribeToCardWhenCommenting: attr(),\n    turnOffRecentCardHighlighting: attr(),\n    isDefaultAdmin: attr(),\n    isSsoUser: attr(),\n    isDeactivated: attr(),\n    lockedFieldNames: attr(),\n    isAvatarUpdating: attr({\n      getDefault: () => false,\n    }),\n    emailUpdateForm: attr({\n      getDefault: () => DEFAULT_EMAIL_UPDATE_FORM,\n    }),\n    passwordUpdateForm: attr({\n      getDefault: () => DEFAULT_PASSWORD_UPDATE_FORM,\n    }),\n    usernameUpdateForm: attr({\n      getDefault: () => DEFAULT_USERNAME_UPDATE_FORM,\n    }),\n    apiKeyState: attr({\n      getDefault: () => DEFAULT_API_KEY_STATE,\n    }),\n  };\n\n  static reducer({ type, payload }, User) {\n    switch (type) {\n      case ActionTypes.LOCATION_CHANGE_HANDLE:\n      case ActionTypes.PROJECT_UPDATE_HANDLE:\n      case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:\n      case ActionTypes.LIST_UPDATE_HANDLE:\n      case ActionTypes.CARD_UPDATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__FAILURE:\n        if (payload.users) {\n          payload.users.forEach((user) => {\n            User.upsert(user);\n          });\n        }\n\n        break;\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        User.all().delete();\n        User.upsert(payload.user);\n\n        payload.users.forEach((user) => {\n          User.upsert(user);\n        });\n\n        break;\n      case ActionTypes.CORE_INITIALIZE:\n        User.upsert(payload.user);\n\n        payload.users.forEach((user) => {\n          User.upsert(user);\n        });\n\n        break;\n      case ActionTypes.USERS_RESET_HANDLE:\n      case ActionTypes.PROJECT_CREATE_HANDLE:\n      case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:\n      case ActionTypes.BOARD_FETCH__SUCCESS:\n      case ActionTypes.CARDS_FETCH__SUCCESS:\n      case ActionTypes.CARD_CREATE_HANDLE:\n      case ActionTypes.CARD_TRANSFER__SUCCESS:\n      case ActionTypes.COMMENTS_FETCH__SUCCESS:\n      case ActionTypes.COMMENT_CREATE_HANDLE:\n      case ActionTypes.ACTIVITIES_IN_BOARD_FETCH__SUCCESS:\n      case ActionTypes.ACTIVITIES_IN_CARD_FETCH__SUCCESS:\n      case ActionTypes.NOTIFICATION_CREATE_HANDLE:\n        payload.users.forEach((user) => {\n          User.upsert(user);\n        });\n\n        break;\n      case ActionTypes.USER_CREATE__SUCCESS:\n      case ActionTypes.USER_CREATE_HANDLE:\n      case ActionTypes.USER_UPDATE__SUCCESS:\n        User.upsert(payload.user);\n\n        break;\n      case ActionTypes.USER_UPDATE:\n        User.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.USER_UPDATE_HANDLE:\n        User.upsert(payload.user);\n\n        if (payload.users) {\n          payload.users.forEach((user) => {\n            User.upsert(user);\n          });\n        }\n\n        break;\n      case ActionTypes.USER_EMAIL_UPDATE: {\n        const userModel = User.withId(payload.id);\n\n        userModel.emailUpdateForm = {\n          ...userModel.emailUpdateForm,\n          data: payload.data,\n          isSubmitting: true,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_EMAIL_UPDATE__SUCCESS:\n        User.withId(payload.user.id).update({\n          ...payload.user,\n          emailUpdateForm: DEFAULT_EMAIL_UPDATE_FORM,\n        });\n\n        break;\n      case ActionTypes.USER_EMAIL_UPDATE__FAILURE: {\n        const userModel = User.withId(payload.id);\n\n        userModel.emailUpdateForm = {\n          ...userModel.emailUpdateForm,\n          isSubmitting: false,\n          error: payload.error,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR: {\n        const userModel = User.withId(payload.id);\n\n        userModel.emailUpdateForm = {\n          ...userModel.emailUpdateForm,\n          error: null,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_PASSWORD_UPDATE: {\n        const userModel = User.withId(payload.id);\n\n        userModel.passwordUpdateForm = {\n          ...userModel.passwordUpdateForm,\n          data: payload.data,\n          isSubmitting: true,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_PASSWORD_UPDATE__SUCCESS:\n        User.withId(payload.user.id).update({\n          ...payload.user,\n          passwordUpdateForm: DEFAULT_PASSWORD_UPDATE_FORM,\n        });\n\n        break;\n      case ActionTypes.USER_PASSWORD_UPDATE__FAILURE: {\n        const userModel = User.withId(payload.id);\n\n        userModel.passwordUpdateForm = {\n          ...userModel.passwordUpdateForm,\n          isSubmitting: false,\n          error: payload.error,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR: {\n        const userModel = User.withId(payload.id);\n\n        userModel.passwordUpdateForm = {\n          ...userModel.passwordUpdateForm,\n          error: null,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_USERNAME_UPDATE: {\n        const userModel = User.withId(payload.id);\n\n        userModel.usernameUpdateForm = {\n          ...userModel.usernameUpdateForm,\n          data: payload.data,\n          isSubmitting: true,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_USERNAME_UPDATE__SUCCESS:\n        User.withId(payload.user.id).update({\n          ...payload.user,\n          usernameUpdateForm: DEFAULT_USERNAME_UPDATE_FORM,\n        });\n\n        break;\n      case ActionTypes.USER_USERNAME_UPDATE__FAILURE: {\n        const userModel = User.withId(payload.id);\n\n        userModel.usernameUpdateForm = {\n          ...userModel.usernameUpdateForm,\n          isSubmitting: false,\n          error: payload.error,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR: {\n        const userModel = User.withId(payload.id);\n\n        userModel.usernameUpdateForm = {\n          ...userModel.usernameUpdateForm,\n          error: null,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_AVATAR_UPDATE:\n        User.withId(payload.id).update({\n          isAvatarUpdating: true,\n        });\n\n        break;\n      case ActionTypes.USER_AVATAR_UPDATE__SUCCESS:\n        User.withId(payload.user.id).update({\n          ...payload.user,\n          isAvatarUpdating: false,\n        });\n\n        break;\n      case ActionTypes.USER_AVATAR_UPDATE__FAILURE:\n        User.withId(payload.id).update({\n          isAvatarUpdating: false,\n        });\n\n        break;\n      case ActionTypes.USER_API_KEY_CREATE: {\n        const userModel = User.withId(payload.id);\n\n        userModel.apiKeyState = {\n          ...userModel.apiKeyState,\n          isCreating: true,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_API_KEY_CREATE__SUCCESS:\n        User.withId(payload.user.id).update({\n          ...payload.user,\n          apiKeyState: {\n            ...DEFAULT_API_KEY_STATE,\n            value: payload.apiKey,\n          },\n        });\n\n        break;\n      case ActionTypes.USER_API_KEY_CREATE__FAILURE: {\n        const userModel = User.withId(payload.id);\n\n        userModel.apiKeyState = {\n          ...userModel.apiKeyState,\n          isCreating: false,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_API_KEY_DELETE:\n        User.withId(payload.id).update({\n          apiKeyPrefix: null,\n          apiKeyState: DEFAULT_API_KEY_STATE,\n        });\n\n        break;\n      case ActionTypes.USER_API_KEY_VALUE_CLEAR: {\n        const userModel = User.withId(payload.id);\n\n        userModel.apiKeyState = {\n          ...userModel.apiKeyState,\n          value: null,\n        };\n\n        break;\n      }\n      case ActionTypes.USER_DELETE:\n        User.withId(payload.id).deleteWithRelated();\n\n        break;\n      case ActionTypes.USER_DELETE__SUCCESS:\n      case ActionTypes.USER_DELETE_HANDLE: {\n        const userModel = User.withId(payload.user.id);\n\n        if (userModel) {\n          userModel.deleteWithRelated();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  static getAllQuerySet() {\n    return this.orderBy([({ name }) => name.toLowerCase(), 'id.length', 'id']);\n  }\n\n  static getActiveQuerySet() {\n    return this.filter({\n      isDeactivated: false,\n    }).orderBy([({ name }) => name.toLowerCase(), 'id.length', 'id']);\n  }\n\n  getProjectManagersQuerySet() {\n    return this.projectManagers.orderBy(['id.length', 'id']);\n  }\n\n  getBoardMembershipsQuerySet() {\n    return this.boardMemberships.orderBy(['id.length', 'id']);\n  }\n\n  getUnreadNotificationsQuerySet() {\n    return this.notifications\n      .filter({\n        isRead: false,\n      })\n      .orderBy(['id.length', 'id'], ['desc', 'desc']);\n  }\n\n  getNotificationServicesQuerySet() {\n    return this.notificationServices.orderBy(['id.length', 'id']);\n  }\n\n  getManagerProjectsModelArray() {\n    return this.getProjectManagersQuerySet()\n      .toModelArray()\n      .map(({ project: projectModel }) => projectModel);\n  }\n\n  getMembershipProjectsModelArray() {\n    const projectIds = [];\n\n    return this.getBoardMembershipsQuerySet()\n      .toModelArray()\n      .flatMap(({ board: { project: projectModel } }) => {\n        if (!projectModel || projectIds.includes(projectModel.id)) {\n          return [];\n        }\n\n        projectIds.push(projectModel.id);\n        return projectModel;\n      });\n  }\n\n  getSeparatedProjectsModelArray() {\n    const projectIds = [];\n\n    const managerProjectModels = this.getManagerProjectsModelArray().map((projectModel) => {\n      projectIds.push(projectModel.id);\n      return projectModel;\n    });\n\n    const membershipProjectModels = this.getMembershipProjectsModelArray().flatMap(\n      (projectModel) => {\n        if (projectIds.includes(projectModel.id)) {\n          return [];\n        }\n\n        projectIds.push(projectModel.id);\n        return projectModel;\n      },\n    );\n\n    let adminProjectModels = [];\n    if (this.role === UserRoles.ADMIN) {\n      const {\n        session: { Project },\n      } = this.getClass();\n\n      adminProjectModels = Project.getSharedQuerySet()\n        .toModelArray()\n        .flatMap((projectModel) => {\n          if (projectIds.includes(projectModel.id)) {\n            return [];\n          }\n\n          projectIds.push(projectModel.id);\n          return projectModel;\n        });\n    }\n\n    return {\n      managerProjectModels,\n      membershipProjectModels,\n      adminProjectModels,\n    };\n  }\n\n  getProjectsModelArray() {\n    const { managerProjectModels, membershipProjectModels, adminProjectModels } =\n      this.getSeparatedProjectsModelArray();\n\n    return [...managerProjectModels, ...membershipProjectModels, ...adminProjectModels];\n  }\n\n  getFavoriteProjectsModelArray(orderByArgs) {\n    let projectModels = this.getProjectsModelArray();\n\n    projectModels = projectModels.filter(\n      (projectModel) => !projectModel.isHidden && projectModel.isFavorite,\n    );\n\n    if (orderByArgs) {\n      projectModels = orderBy(projectModels, ...orderByArgs);\n    }\n\n    return projectModels;\n  }\n\n  getFilteredSeparatedProjectsModelArray(search, isHidden, orderByArgs) {\n    const separatedProjectModels = this.getSeparatedProjectsModelArray();\n\n    return Object.entries(separatedProjectModels).reduce((result, [key, projectModels]) => {\n      let filteredProjectModels = filterProjectModels(projectModels, search, isHidden);\n\n      if (orderByArgs) {\n        filteredProjectModels = orderBy(filteredProjectModels, ...orderByArgs);\n      }\n\n      return {\n        ...result,\n        [key]: filteredProjectModels,\n      };\n    }, {});\n  }\n\n  getFilteredProjectsModelArray(search, isHidden, orderByArgs) {\n    let projectModels = this.getProjectsModelArray();\n    projectModels = filterProjectModels(projectModels, search, isHidden);\n\n    if (orderByArgs) {\n      projectModels = orderBy(projectModels, ...orderByArgs);\n    }\n\n    return projectModels;\n  }\n\n  deleteRelated() {\n    this.projectManagers.toModelArray().forEach((projectManagerModel) => {\n      if (projectManagerModel.ownedProject) {\n        projectManagerModel.ownedProject.deleteWithRelated();\n      } else {\n        projectManagerModel.delete();\n      }\n    });\n\n    this.boardMemberships.toModelArray().forEach((boardMembershipModel) => {\n      boardMembershipModel.deleteWithRelated();\n    });\n\n    this.createdCards.toModelArray().forEach((cardModel) => {\n      cardModel.update({\n        creatorUserId: null,\n      });\n    });\n\n    this.assignedTasks.toModelArray().forEach((taskModel) => {\n      taskModel.update({\n        assigneeUserId: null,\n      });\n    });\n\n    this.createdAttachments.toModelArray().forEach((attachmentModel) => {\n      attachmentModel.update({\n        creatorUserId: null,\n      });\n    });\n\n    this.comments.toModelArray().forEach((commentModel) => {\n      commentModel.update({\n        userId: null,\n      });\n    });\n\n    this.activities.toModelArray().forEach((activityModel) => {\n      activityModel.update({\n        userId: null,\n      });\n    });\n\n    this.createdNotifications.toModelArray().forEach((notificationModel) => {\n      notificationModel.update({\n        creatorUserId: null,\n      });\n    });\n\n    this.notificationServices.delete();\n  }\n\n  deleteWithRelated() {\n    this.deleteRelated();\n    this.delete();\n  }\n}\n"
  },
  {
    "path": "client/src/models/Webhook.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { attr, fk } from 'redux-orm';\n\nimport BaseModel from './BaseModel';\nimport ActionTypes from '../constants/ActionTypes';\n\nexport default class extends BaseModel {\n  static modelName = 'Webhook';\n\n  static fields = {\n    id: attr(),\n    name: attr(),\n    url: attr(),\n    accessToken: attr(),\n    events: attr(),\n    excludedEvents: attr(),\n    boardId: fk({\n      to: 'Board',\n      as: 'board',\n      relatedName: 'webhooks',\n    }),\n  };\n\n  static reducer({ type, payload }, Webhook) {\n    switch (type) {\n      case ActionTypes.SOCKET_RECONNECT_HANDLE:\n        Webhook.all().delete();\n\n        if (payload.webhooks) {\n          payload.webhooks.forEach((webhook) => {\n            Webhook.upsert(webhook);\n          });\n        }\n\n        break;\n      case ActionTypes.CORE_INITIALIZE:\n      case ActionTypes.USER_UPDATE_HANDLE:\n        if (payload.webhooks) {\n          payload.webhooks.forEach((webhook) => {\n            Webhook.upsert(webhook);\n          });\n        }\n\n        break;\n      case ActionTypes.WEBHOOK_CREATE:\n      case ActionTypes.WEBHOOK_CREATE_HANDLE:\n      case ActionTypes.WEBHOOK_UPDATE__SUCCESS:\n      case ActionTypes.WEBHOOK_UPDATE_HANDLE:\n        Webhook.upsert(payload.webhook);\n\n        break;\n      case ActionTypes.WEBHOOK_CREATE__SUCCESS:\n        Webhook.withId(payload.localId).delete();\n        Webhook.upsert(payload.webhook);\n\n        break;\n      case ActionTypes.WEBHOOK_CREATE__FAILURE:\n        Webhook.withId(payload.localId).delete();\n\n        break;\n      case ActionTypes.WEBHOOK_UPDATE:\n        Webhook.withId(payload.id).update(payload.data);\n\n        break;\n      case ActionTypes.WEBHOOK_DELETE:\n        Webhook.withId(payload.id).delete();\n\n        break;\n      case ActionTypes.WEBHOOK_DELETE__SUCCESS:\n      case ActionTypes.WEBHOOK_DELETE_HANDLE: {\n        const webhookModel = Webhook.withId(payload.webhook.id);\n\n        if (webhookModel) {\n          webhookModel.delete();\n        }\n\n        break;\n      }\n      default:\n    }\n  }\n\n  static getAllQuerySet() {\n    return this.orderBy(['id.length', 'id']);\n  }\n}\n"
  },
  {
    "path": "client/src/models/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Webhook from './Webhook';\nimport User from './User';\nimport Project from './Project';\nimport ProjectManager from './ProjectManager';\nimport BackgroundImage from './BackgroundImage';\nimport BaseCustomFieldGroup from './BaseCustomFieldGroup';\nimport Board from './Board';\nimport BoardMembership from './BoardMembership';\nimport Label from './Label';\nimport List from './List';\nimport Card from './Card';\nimport TaskList from './TaskList';\nimport Task from './Task';\nimport Attachment from './Attachment';\nimport CustomFieldGroup from './CustomFieldGroup';\nimport CustomField from './CustomField';\nimport CustomFieldValue from './CustomFieldValue';\nimport Comment from './Comment';\nimport Activity from './Activity';\nimport Notification from './Notification';\nimport NotificationService from './NotificationService';\n\nexport {\n  Webhook,\n  User,\n  Project,\n  ProjectManager,\n  BackgroundImage,\n  BaseCustomFieldGroup,\n  Board,\n  BoardMembership,\n  Label,\n  List,\n  Card,\n  TaskList,\n  Task,\n  Attachment,\n  CustomFieldGroup,\n  CustomField,\n  CustomFieldValue,\n  Comment,\n  Activity,\n  Notification,\n  NotificationService,\n};\n"
  },
  {
    "path": "client/src/orm.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { ORM } from 'redux-orm';\n\nimport {\n  Activity,\n  Attachment,\n  BackgroundImage,\n  BaseCustomFieldGroup,\n  Board,\n  BoardMembership,\n  Card,\n  Comment,\n  CustomField,\n  CustomFieldGroup,\n  CustomFieldValue,\n  Label,\n  List,\n  Notification,\n  NotificationService,\n  Project,\n  ProjectManager,\n  Task,\n  TaskList,\n  User,\n  Webhook,\n} from './models';\n\nconst orm = new ORM({\n  stateSelector: (state) => state.orm,\n});\n\norm.register(\n  Webhook,\n  User,\n  Project,\n  ProjectManager,\n  BackgroundImage,\n  BaseCustomFieldGroup,\n  Board,\n  BoardMembership,\n  Label,\n  List,\n  Card,\n  TaskList,\n  Task,\n  Attachment,\n  CustomFieldGroup,\n  CustomField,\n  CustomFieldValue,\n  Comment,\n  Activity,\n  Notification,\n  NotificationService,\n);\n\nexport default orm;\n"
  },
  {
    "path": "client/src/reducers/auth.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { getAccessToken } from '../utils/access-token-storage';\nimport ActionTypes from '../constants/ActionTypes';\n\nconst initialState = {\n  accessToken: getAccessToken(),\n  userId: null,\n};\n\n// eslint-disable-next-line default-param-last\nexport default (state = initialState, { type, payload }) => {\n  switch (type) {\n    case ActionTypes.AUTHENTICATE__SUCCESS:\n    case ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS:\n    case ActionTypes.TERMS_ACCEPT__SUCCESS:\n      return {\n        ...state,\n        accessToken: payload.accessToken,\n      };\n    case ActionTypes.SOCKET_RECONNECT_HANDLE:\n    case ActionTypes.CORE_INITIALIZE:\n      return {\n        ...state,\n        userId: payload.user.id,\n      };\n    case ActionTypes.USER_PASSWORD_UPDATE__SUCCESS:\n      if (payload.accessToken) {\n        return {\n          ...state,\n          accessToken: payload.accessToken,\n        };\n      }\n\n      return state;\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "client/src/reducers/common.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst initialState = {\n  isInitializing: true,\n  bootstrap: null,\n};\n\n// eslint-disable-next-line default-param-last\nexport default (state = initialState, { type, payload }) => {\n  switch (type) {\n    case ActionTypes.SOCKET_RECONNECT_HANDLE:\n      return {\n        ...state,\n        bootstrap: payload.bootstrap,\n      };\n    case ActionTypes.BOOTSTRAP_UPDATE_HANDLE:\n      return {\n        ...state,\n        bootstrap: {\n          ...state.bootstrap,\n          ...payload.bootstrap,\n        },\n      };\n    case ActionTypes.LOGIN_INITIALIZE:\n      return {\n        ...state,\n        isInitializing: false,\n        bootstrap: payload.bootstrap,\n      };\n    case ActionTypes.AUTHENTICATE__SUCCESS:\n    case ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS:\n    case ActionTypes.TERMS_ACCEPT__SUCCESS:\n      return {\n        ...state,\n        isInitializing: true,\n      };\n    case ActionTypes.CORE_INITIALIZE:\n      return {\n        ...state,\n        isInitializing: false,\n      };\n    case ActionTypes.CORE_INITIALIZE__BOOTSTRAP_FETCH:\n      return {\n        ...state,\n        bootstrap: payload.bootstrap,\n      };\n    case ActionTypes.USER_UPDATE_HANDLE:\n      if (payload.bootstrap) {\n        return {\n          ...state,\n          bootstrap: payload.bootstrap,\n        };\n      }\n\n      return state;\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "client/src/reducers/core.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { LOCATION_CHANGE_HANDLE } from '../lib/redux-router';\n\nimport ActionTypes from '../constants/ActionTypes';\nimport ModalTypes from '../constants/ModalTypes';\nimport ClipboardTypes from '../constants/ClipboardTypes';\nimport { HomeViews, ProjectOrders } from '../constants/Enums';\n\nconst initialState = {\n  isContentFetching: false,\n  isLogouting: false,\n  isFavoritesEnabled: false,\n  isEditModeEnabled: false,\n  modal: null,\n  clipboard: null,\n  config: null,\n  boardId: null,\n  cardId: null,\n  recentCardId: null,\n  prevCardIds: [],\n  homeView: HomeViews.GROUPED_PROJECTS,\n  projectsSearch: '',\n  projectsOrder: ProjectOrders.BY_DEFAULT,\n  isHiddenProjectsVisible: false, // TODO: refactor?\n};\n\n// eslint-disable-next-line default-param-last\nexport default (state = initialState, { type, payload }) => {\n  switch (type) {\n    case LOCATION_CHANGE_HANDLE:\n    case ActionTypes.MODAL_CLOSE:\n      return {\n        ...state,\n        modal: null,\n      };\n    case ActionTypes.LOCATION_CHANGE_HANDLE: {\n      const nextState = {\n        ...state,\n        isContentFetching: false,\n        boardId: payload.currentBoardId || null,\n        cardId: payload.currentCardId || null,\n      };\n\n      if (payload.currentCardId) {\n        nextState.recentCardId = payload.currentCardId;\n      } else if (nextState.boardId !== state.boardId) {\n        nextState.recentCardId = null;\n      }\n\n      if (payload.currentCardId) {\n        if (state.cardId) {\n          nextState.prevCardIds =\n            payload.currentCardId === nextState.prevCardIds.at(-1)\n              ? [...nextState.prevCardIds.slice(0, -1)]\n              : [...nextState.prevCardIds, state.cardId];\n        }\n      } else if (nextState.prevCardIds.length > 0) {\n        nextState.prevCardIds = [];\n      }\n\n      if (payload.isEditModeEnabled !== undefined) {\n        nextState.isEditModeEnabled = payload.isEditModeEnabled;\n      }\n\n      return nextState;\n    }\n    case ActionTypes.LOCATION_CHANGE_HANDLE__CONTENT_FETCH:\n      return {\n        ...state,\n        isContentFetching: true,\n      };\n    case ActionTypes.SOCKET_RECONNECT_HANDLE:\n    case ActionTypes.USER_UPDATE_HANDLE:\n      if (payload.config) {\n        return {\n          ...state,\n          config: payload.config,\n        };\n      }\n\n      return state;\n    case ActionTypes.CORE_INITIALIZE: {\n      const nextState = {\n        ...state,\n        isFavoritesEnabled: payload.user.enableFavoritesByDefault,\n        homeView: payload.user.defaultHomeView,\n        projectsOrder: payload.user.defaultProjectsOrder,\n      };\n\n      if (payload.config) {\n        nextState.config = payload.config;\n      }\n\n      return nextState;\n    }\n    case ActionTypes.FAVORITES_TOGGLE:\n      return {\n        ...state,\n        isFavoritesEnabled: payload.isEnabled,\n      };\n    case ActionTypes.EDIT_MODE_TOGGLE:\n      return {\n        ...state,\n        isEditModeEnabled: payload.isEnabled,\n      };\n    case ActionTypes.HOME_VIEW_UPDATE:\n      return {\n        ...state,\n        homeView: payload.value,\n      };\n    case ActionTypes.LOGOUT__ACCESS_TOKEN_REVOKE:\n      return {\n        ...state,\n        isLogouting: true,\n      };\n    case ActionTypes.MODAL_OPEN:\n      return {\n        ...state,\n        modal: payload,\n      };\n    case ActionTypes.CONFIG_UPDATE:\n      return {\n        ...state,\n        config: {\n          ...state.config,\n          ...payload.data,\n        },\n      };\n    case ActionTypes.CONFIG_UPDATE__SUCCESS:\n    case ActionTypes.CONFIG_UPDATE_HANDLE:\n      return {\n        ...state,\n        config: payload.config,\n      };\n    case ActionTypes.PROJECTS_SEARCH:\n      return {\n        ...state,\n        projectsSearch: payload.value,\n      };\n    case ActionTypes.PROJECTS_ORDER_UPDATE:\n      return {\n        ...state,\n        projectsOrder: payload.value,\n      };\n    case ActionTypes.HIDDEN_PROJECTS_TOGGLE:\n      return {\n        ...state,\n        isHiddenProjectsVisible: payload.isVisible,\n      };\n    case ActionTypes.BOARD_DELETE:\n      if (\n        state.modal &&\n        state.modal.type === ModalTypes.BOARD_SETTINGS &&\n        state.modal.params.id === payload.id\n      ) {\n        return {\n          ...state,\n          modal: null,\n        };\n      }\n\n      return state;\n    case ActionTypes.BOARD_DELETE_HANDLE:\n      if (\n        state.modal &&\n        state.modal.type === ModalTypes.BOARD_SETTINGS &&\n        state.modal.params.id === payload.board.id\n      ) {\n        return {\n          ...state,\n          modal: null,\n        };\n      }\n\n      return state;\n    case ActionTypes.CARD_DELETE:\n      if (payload.clipboard && payload.id === payload.clipboard.cardId) {\n        return {\n          ...state,\n          clipboard: null,\n        };\n      }\n\n      return state;\n    case ActionTypes.CARD_DELETE_HANDLE:\n      if (payload.clipboard && payload.card.id === payload.clipboard.cardId) {\n        return {\n          ...state,\n          clipboard: null,\n        };\n      }\n\n      return state;\n    case ActionTypes.CARD_COPY:\n      return {\n        ...state,\n        clipboard: {\n          type: ClipboardTypes.COPY,\n          cardId: payload.id,\n        },\n      };\n    case ActionTypes.CARD_CUT:\n      return {\n        ...state,\n        clipboard: {\n          type: ClipboardTypes.CUT,\n          cardId: payload.id,\n        },\n      };\n    case ActionTypes.CARD_PASTE:\n      return {\n        ...state,\n        clipboard: null,\n      };\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "client/src/reducers/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { combineReducers } from 'redux';\n\nimport router from './router';\nimport socket from './socket';\nimport orm from './orm';\nimport common from './common';\nimport auth from './auth';\nimport core from './core';\nimport ui from './ui';\n\nexport default combineReducers({\n  router,\n  socket,\n  orm,\n  common,\n  auth,\n  core,\n  ui,\n});\n"
  },
  {
    "path": "client/src/reducers/orm.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createReducer } from 'redux-orm';\n\nimport orm from '../orm';\n\nexport default createReducer(orm);\n"
  },
  {
    "path": "client/src/reducers/router.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createRouterReducer } from '../lib/redux-router';\n\nimport history from '../history';\n\nexport default createRouterReducer(history);\n"
  },
  {
    "path": "client/src/reducers/socket.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../constants/ActionTypes';\n\nconst initialState = {\n  isDisconnected: false,\n};\n\n// eslint-disable-next-line default-param-last\nexport default (state = initialState, { type }) => {\n  switch (type) {\n    case ActionTypes.SOCKET_DISCONNECT_HANDLE:\n      return {\n        ...state,\n        isDisconnected: true,\n      };\n    case ActionTypes.SOCKET_RECONNECT_HANDLE:\n      return {\n        ...state,\n        isDisconnected: false,\n      };\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "client/src/reducers/ui/authenticate-form.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { LOCATION_CHANGE_HANDLE } from '../../lib/redux-router';\n\nimport ActionTypes from '../../constants/ActionTypes';\nimport Paths from '../../constants/Paths';\n\nconst initialState = {\n  data: {\n    emailOrUsername: '',\n    password: '',\n  },\n  isSubmitting: false,\n  isSubmittingWithOidc: false,\n  error: null,\n  debugLogs: null,\n  pendingToken: null,\n  step: null,\n  termsForm: {\n    payload: null,\n    isSubmitting: false,\n    isCancelling: false,\n    isLanguageUpdating: false,\n  },\n};\n\n// eslint-disable-next-line default-param-last\nexport default (state = initialState, { type, payload }) => {\n  switch (type) {\n    case LOCATION_CHANGE_HANDLE:\n      if (payload.location.pathname === Paths.OIDC_CALLBACK) {\n        return {\n          ...state,\n          isSubmittingWithOidc: true,\n        };\n      }\n\n      return state;\n    case ActionTypes.AUTHENTICATE:\n      return {\n        ...state,\n        data: {\n          ...state.data,\n          ...payload.data,\n        },\n        isSubmitting: true,\n      };\n    case ActionTypes.AUTHENTICATE__SUCCESS:\n    case ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS:\n    case ActionTypes.TERMS_ACCEPT__SUCCESS:\n    case ActionTypes.TERMS_CANCEL__SUCCESS:\n    case ActionTypes.TERMS_CANCEL__FAILURE:\n      return initialState;\n    case ActionTypes.AUTHENTICATE__FAILURE:\n      if (payload.terms) {\n        return {\n          ...state,\n          data: initialState.data,\n          pendingToken: payload.error.pendingToken,\n          step: payload.error.step,\n          termsForm: {\n            ...state.termsForm,\n            payload: payload.terms,\n          },\n        };\n      }\n\n      return {\n        ...state,\n        isSubmitting: false,\n        error: payload.error,\n      };\n    case ActionTypes.WITH_OIDC_AUTHENTICATE__FAILURE:\n      if (payload.terms) {\n        return {\n          ...state,\n          data: initialState.data,\n          pendingToken: payload.error.pendingToken,\n          step: payload.error.step,\n          termsForm: {\n            ...state.termsForm,\n            payload: payload.terms,\n          },\n        };\n      }\n\n      return {\n        ...state,\n        isSubmittingWithOidc: false,\n        error: payload.error,\n      };\n    case ActionTypes.WITH_OIDC_AUTHENTICATE__DEBUG:\n      return {\n        ...state,\n        isSubmittingWithOidc: false,\n        debugLogs: payload.logs,\n      };\n    case ActionTypes.AUTHENTICATE_ERROR_CLEAR:\n      return {\n        ...state,\n        error: null,\n      };\n    case ActionTypes.TERMS_ACCEPT:\n      return {\n        ...state,\n        termsForm: {\n          ...state.termsForm,\n          isSubmitting: true,\n        },\n      };\n    case ActionTypes.TERMS_ACCEPT__FAILURE:\n      return {\n        ...initialState,\n        error: payload.error,\n      };\n    case ActionTypes.TERMS_CANCEL:\n      return {\n        ...state,\n        pendingToken: null,\n        termsForm: {\n          ...state.termsForm,\n          isCancelling: true,\n        },\n      };\n    case ActionTypes.TERMS_LANGUAGE_UPDATE:\n      return {\n        ...state,\n        termsForm: {\n          ...state.termsForm,\n          isLanguageUpdating: true,\n        },\n      };\n    case ActionTypes.TERMS_LANGUAGE_UPDATE__SUCCESS:\n      return {\n        ...state,\n        termsForm: {\n          ...state.termsForm,\n          payload: payload.terms,\n          isLanguageUpdating: false,\n        },\n      };\n    case ActionTypes.TERMS_LANGUAGE_UPDATE__FAILURE:\n      return {\n        ...state,\n        termsForm: {\n          ...state.termsForm,\n          isLanguageUpdating: false,\n        },\n      };\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "client/src/reducers/ui/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { combineReducers } from 'redux';\n\nimport authenticateForm from './authenticate-form';\nimport userCreateForm from './user-create-form';\nimport projectCreateForm from './project-create-form';\nimport smtpTestState from './smtp-test-state';\n\nexport default combineReducers({\n  authenticateForm,\n  userCreateForm,\n  projectCreateForm,\n  smtpTestState,\n});\n"
  },
  {
    "path": "client/src/reducers/ui/project-create-form.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../../constants/ActionTypes';\nimport { ProjectTypes } from '../../constants/Enums';\n\nconst initialState = {\n  data: {\n    name: '',\n    description: '',\n    type: ProjectTypes.PRIVATE,\n  },\n  isSubmitting: false,\n};\n\n// eslint-disable-next-line default-param-last\nexport default (state = initialState, { type, payload }) => {\n  switch (type) {\n    case ActionTypes.PROJECT_CREATE:\n      return {\n        ...state,\n        data: {\n          ...state.data,\n          ...payload.data,\n        },\n        isSubmitting: true,\n      };\n    case ActionTypes.PROJECT_CREATE__SUCCESS:\n      return initialState;\n    case ActionTypes.PROJECT_CREATE__FAILURE:\n      return {\n        ...state,\n        isSubmitting: false,\n      };\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "client/src/reducers/ui/smtp-test-state.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../../constants/ActionTypes';\n\nconst initialState = {\n  isLoading: false,\n  logs: null,\n  error: null,\n};\n\n// eslint-disable-next-line default-param-last\nexport default (state = initialState, { type, payload }) => {\n  switch (type) {\n    case ActionTypes.SMTP_CONFIG_TEST:\n      return {\n        ...state,\n        isLoading: true,\n      };\n    case ActionTypes.SMTP_CONFIG_TEST__SUCCESS:\n      return {\n        ...initialState,\n        logs: payload.logs,\n      };\n    case ActionTypes.SMTP_CONFIG_TEST__FAILURE:\n      return {\n        ...initialState,\n        error: payload.error,\n      };\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "client/src/reducers/ui/user-create-form.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport ActionTypes from '../../constants/ActionTypes';\n\nconst initialState = {\n  data: {\n    email: '',\n    name: '',\n    username: '',\n  },\n  isSubmitting: false,\n  error: null,\n};\n\n// eslint-disable-next-line default-param-last\nexport default (state = initialState, { type, payload }) => {\n  switch (type) {\n    case ActionTypes.USER_CREATE:\n      return {\n        ...state,\n        data: {\n          ...state.data,\n          ...payload.data,\n        },\n        isSubmitting: true,\n      };\n    case ActionTypes.USER_CREATE__SUCCESS:\n      return initialState;\n    case ActionTypes.USER_CREATE__FAILURE:\n      return {\n        ...state,\n        isSubmitting: false,\n        error: payload.error,\n      };\n    case ActionTypes.USER_CREATE_ERROR_CLEAR:\n      return {\n        ...state,\n        error: null,\n      };\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "client/src/sagas/core/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { apply, fork, select, take } from 'redux-saga/effects';\n\nimport watchers from './watchers';\nimport services from './services';\nimport runWatchers from '../run-watchers';\nimport selectors from '../../selectors';\nimport { socket } from '../../api';\nimport ActionTypes from '../../constants/ActionTypes';\nimport Paths from '../../constants/Paths';\n\nexport default function* coreSaga() {\n  yield runWatchers(watchers);\n\n  yield apply(socket, socket.connect);\n  yield fork(services.initializeCore);\n\n  yield take(ActionTypes.LOGOUT);\n\n  const oidcBootstrap = yield select(selectors.selectOidcBootstrap);\n\n  if (oidcBootstrap && oidcBootstrap.endSessionUrl !== null) {\n    const currentUser = yield select(selectors.selectCurrentUser);\n\n    if (!currentUser || currentUser.isSsoUser) {\n      // Redirect the user to the IDP to log out.\n      window.location.href = oidcBootstrap.endSessionUrl;\n      return;\n    }\n  }\n\n  window.location.href = Paths.LOGIN;\n}\n"
  },
  {
    "path": "client/src/sagas/core/request.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, join, put, select, spawn, take } from 'redux-saga/effects';\n\nimport selectors from '../../selectors';\nimport entryActions from '../../entry-actions';\nimport ErrorCodes from '../../constants/ErrorCodes';\n\nlet lastRequestTask;\n\nfunction* queueRequest(method, ...args) {\n  if (lastRequestTask) {\n    try {\n      yield join(lastRequestTask);\n    } catch {\n      /* empty */\n    }\n  }\n\n  const accessToken = yield select(selectors.selectAccessToken);\n\n  try {\n    return yield call(method, ...args, {\n      Authorization: `Bearer ${accessToken}`,\n    });\n  } catch (error) {\n    if (error.code === ErrorCodes.UNAUTHORIZED) {\n      yield put(entryActions.logout(false));\n      yield take();\n    }\n\n    throw error;\n  }\n}\n\nexport default function* request(method, ...args) {\n  lastRequestTask = yield spawn(queueRequest, method, ...args);\n\n  return yield join(lastRequestTask);\n}\n"
  },
  {
    "path": "client/src/sagas/core/requests/boards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport api from '../../../api';\nimport mergeRecords from '../../../utils/merge-records';\nimport Paths from '../../../constants/Paths';\n\nexport function* fetchBoardByCurrentPath() {\n  const pathsMatch = yield select(selectors.selectPathsMatch);\n\n  let board;\n  let card;\n  let users1;\n  let users2;\n  let projects;\n  let boardMemberships;\n  let labels;\n  let lists;\n  let cards;\n  let cardMemberships1;\n  let cardMemberships2;\n  let cardLabels1;\n  let cardLabels2;\n  let taskLists1;\n  let taskLists2;\n  let tasks1;\n  let tasks2;\n  let attachments1;\n  let attachments2;\n  let customFieldGroups1;\n  let customFieldGroups2;\n  let customFields1;\n  let customFields2;\n  let customFieldValues1;\n  let customFieldValues2;\n\n  if (pathsMatch) {\n    let boardId;\n    if (pathsMatch.pattern.path === Paths.BOARDS) {\n      boardId = pathsMatch.params.id;\n    } else if (pathsMatch.pattern.path === Paths.CARDS) {\n      ({\n        item: card,\n        item: { boardId },\n        included: {\n          users: users1,\n          cardMemberships: cardMemberships1,\n          cardLabels: cardLabels1,\n          taskLists: taskLists1,\n          tasks: tasks1,\n          attachments: attachments1,\n          customFieldGroups: customFieldGroups1,\n          customFields: customFields1,\n          customFieldValues: customFieldValues1,\n        },\n      } = yield call(request, api.getCard, pathsMatch.params.id));\n    }\n\n    if (boardId) {\n      ({\n        item: board,\n        included: {\n          projects,\n          boardMemberships,\n          labels,\n          lists,\n          cards,\n          users: users2,\n          cardMemberships: cardMemberships2,\n          cardLabels: cardLabels2,\n          taskLists: taskLists2,\n          tasks: tasks2,\n          attachments: attachments2,\n          customFieldGroups: customFieldGroups2,\n          customFields: customFields2,\n          customFieldValues: customFieldValues2,\n        },\n      } = yield call(request, api.getBoard, boardId, true));\n    }\n  }\n\n  return {\n    board,\n    card,\n    boardMemberships,\n    labels,\n    lists,\n    cards,\n    project: projects[0],\n    users: mergeRecords(users1, users2),\n    cardMemberships: mergeRecords(cardMemberships1, cardMemberships2),\n    cardLabels: mergeRecords(cardLabels1, cardLabels2),\n    taskLists: mergeRecords(taskLists1, taskLists2),\n    tasks: mergeRecords(tasks1, tasks2),\n    attachments: mergeRecords(attachments1, attachments2),\n    customFieldGroups: mergeRecords(customFieldGroups1, customFieldGroups2),\n    customFields: mergeRecords(customFields1, customFields2),\n    customFieldValues: mergeRecords(customFieldValues1, customFieldValues2),\n  };\n}\n\nexport default {\n  fetchBoardByCurrentPath,\n};\n"
  },
  {
    "path": "client/src/sagas/core/requests/core.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call } from 'redux-saga/effects';\n\nimport { fetchBoardByCurrentPath } from './boards';\nimport request from '../request';\nimport api from '../../../api';\nimport mergeRecords from '../../../utils/merge-records';\nimport { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';\nimport { UserRoles } from '../../../constants/Enums';\n\nexport function* fetchCore() {\n  const {\n    item: user,\n    included: { notificationServices: notificationServices1 },\n  } = yield call(request, api.getCurrentUser, true);\n\n  let config;\n  let webhooks;\n\n  if (user.role === UserRoles.ADMIN) {\n    ({ item: config } = yield call(request, api.getConfig));\n    ({ items: webhooks } = yield call(request, api.getWebhooks));\n  }\n\n  let users1;\n  if (isUserAdminOrProjectOwner(user)) {\n    ({ items: users1 } = yield call(request, api.getUsers));\n  }\n\n  const {\n    items: projects1,\n    included: {\n      projectManagers,\n      backgroundImages,\n      baseCustomFieldGroups,\n      boards,\n      users: users2,\n      boardMemberships: boardMemberships1,\n      customFields: customFields1,\n      notificationServices: notificationServices2,\n    },\n  } = yield call(request, api.getProjects);\n\n  let board;\n  let card;\n  let users3;\n  let projects2;\n  let boardMemberships2;\n  let labels;\n  let lists;\n  let cards1;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields2;\n  let customFieldValues;\n\n  try {\n    ({\n      board,\n      card,\n      labels,\n      lists,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFieldValues,\n      users: users3,\n      projects: projects2,\n      boardMemberships: boardMemberships2,\n      cards: cards1,\n      customFields: customFields2,\n    } = yield call(fetchBoardByCurrentPath));\n  } catch {\n    /* empty */\n  }\n\n  const body = yield call(request, api.getNotifications);\n\n  let { items: notifications } = body;\n\n  const {\n    included: { users: users4 },\n  } = body;\n\n  if (card) {\n    const notificationIds = notifications.flatMap((notification) =>\n      notification.cardId === card.id ? notification.id : [],\n    );\n\n    if (notificationIds.length > 0) {\n      yield call(request, api.readCardNotifications, card.id);\n\n      notifications = notifications.filter(\n        (notification) => !notificationIds.includes(notification.id),\n      );\n    }\n  }\n\n  return {\n    config,\n    user,\n    board,\n    webhooks,\n    projectManagers,\n    backgroundImages,\n    baseCustomFieldGroups,\n    boards,\n    labels,\n    lists,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFieldValues,\n    notifications,\n    users: mergeRecords(users1, users2, users3, users4),\n    projects: mergeRecords(projects1, projects2),\n    boardMemberships: mergeRecords(boardMemberships1, boardMemberships2),\n    cards: mergeRecords(card && [card], cards1),\n    customFields: mergeRecords(customFields1, customFields2),\n    notificationServices: mergeRecords(notificationServices1, notificationServices2),\n  };\n}\n\nexport default {\n  fetchCore,\n};\n"
  },
  {
    "path": "client/src/sagas/core/requests/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport core from './core';\nimport boards from './boards';\n\nexport default {\n  ...core,\n  ...boards,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/activities.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\n\nexport function* fetchActivitiesInBoard(boardId) {\n  const { lastActivityId } = yield select(selectors.selectBoardById, boardId);\n\n  yield put(actions.fetchActivitiesInBoard(boardId));\n\n  let activities;\n  let users;\n\n  try {\n    ({\n      items: activities,\n      included: { users },\n    } = yield call(request, api.getBoardActivities, boardId, {\n      beforeId: lastActivityId || undefined,\n    }));\n  } catch (error) {\n    yield put(actions.fetchActivitiesInBoard.failure(boardId, error));\n    return;\n  }\n\n  yield put(actions.fetchActivitiesInBoard.success(boardId, activities, users));\n}\n\nexport function* fetchActivitiesInCurrentBoard() {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(fetchActivitiesInBoard, boardId);\n}\n\nexport function* fetchActivitiesInCard(cardId) {\n  const { lastActivityId } = yield select(selectors.selectCardById, cardId);\n\n  yield put(actions.fetchActivitiesInCard(cardId));\n\n  let activities;\n  let users;\n\n  try {\n    ({\n      items: activities,\n      included: { users },\n    } = yield call(request, api.getCardActivities, cardId, {\n      beforeId: lastActivityId || undefined,\n    }));\n  } catch (error) {\n    yield put(actions.fetchActivitiesInCard.failure(cardId, error));\n    return;\n  }\n\n  yield put(actions.fetchActivitiesInCard.success(cardId, activities, users));\n}\n\nexport function* fetchActivitiesInCurrentCard() {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(fetchActivitiesInCard, cardId);\n}\n\nexport function* handleActivityCreate(activity) {\n  yield put(actions.handleActivityCreate(activity));\n}\n\nexport default {\n  fetchActivitiesInBoard,\n  fetchActivitiesInCurrentBoard,\n  fetchActivitiesInCard,\n  fetchActivitiesInCurrentCard,\n  handleActivityCreate,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/attachments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport omit from 'lodash/omit';\nimport truncate from 'lodash/truncate';\nimport { call, put, select } from 'redux-saga/effects';\nimport toast from 'react-hot-toast';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\nimport { AttachmentTypes } from '../../../constants/Enums';\nimport ToastTypes from '../../../constants/ToastTypes';\n\nexport function* createAttachment(cardId, data) {\n  const localId = yield call(createLocalId);\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  const nextData = {\n    ...data,\n    name: truncate(data.name, {\n      length: 128,\n    }),\n  };\n\n  yield put(\n    actions.createAttachment({\n      ...omit(nextData, ['file', 'url']),\n      cardId,\n      id: localId,\n      creatorUserId: currentUserId,\n    }),\n  );\n\n  let attachment;\n  try {\n    ({ item: attachment } = yield nextData.type === AttachmentTypes.FILE\n      ? call(request, api.createAttachmentWithFile, cardId, nextData, localId)\n      : call(request, api.createAttachment, cardId, nextData));\n  } catch (error) {\n    yield put(actions.createAttachment.failure(localId, error));\n\n    if (error.code === 'E_UNPROCESSABLE_ENTITY') {\n      let toastType;\n      if (error.message.startsWith('Upload limit')) {\n        toastType = ToastTypes.FILE_IS_TOO_BIG;\n      } else if (error.message === 'Storage limit reached') {\n        toastType = ToastTypes.NOT_ENOUGH_STORAGE;\n      }\n\n      if (toastType) {\n        yield call(toast, {\n          type: toastType,\n        });\n      }\n    }\n\n    return;\n  }\n\n  yield put(actions.createAttachment.success(localId, attachment));\n}\n\nexport function* createAttachmentInCurrentCard(data) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(createAttachment, cardId, data);\n}\n\nexport function* handleAttachmentCreate(attachment, requestId) {\n  const isExists = yield select(selectors.selectIsAttachmentWithIdExists, requestId);\n\n  if (!isExists) {\n    yield put(actions.handleAttachmentCreate(attachment));\n  }\n}\n\nexport function* updateAttachment(id, data) {\n  yield put(actions.updateAttachment(id, data));\n\n  let attachment;\n  try {\n    ({ item: attachment } = yield call(request, api.updateAttachment, id, data));\n  } catch (error) {\n    yield put(actions.updateAttachment.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateAttachment.success(attachment));\n}\n\nexport function* handleAttachmentUpdate(attachment) {\n  yield put(actions.handleAttachmentUpdate(attachment));\n}\n\nexport function* deleteAttachment(id) {\n  yield put(actions.deleteAttachment(id));\n\n  let attachment;\n  try {\n    ({ item: attachment } = yield call(request, api.deleteAttachment, id));\n  } catch (error) {\n    yield put(actions.deleteAttachment.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteAttachment.success(attachment));\n}\n\nexport function* handleAttachmentDelete(attachment) {\n  yield put(actions.handleAttachmentDelete(attachment));\n}\n\nexport default {\n  createAttachment,\n  createAttachmentInCurrentCard,\n  handleAttachmentCreate,\n  updateAttachment,\n  handleAttachmentUpdate,\n  deleteAttachment,\n  handleAttachmentDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/background-images.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport omit from 'lodash/omit';\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\n\nexport function* createBackgroundImage(projectId, data) {\n  const localId = yield call(createLocalId);\n\n  yield put(\n    actions.createBackgroundImage({\n      ...omit(data, ['file', 'url']),\n      projectId,\n      id: localId,\n    }),\n  );\n\n  let backgroundImage;\n  try {\n    ({ item: backgroundImage } = yield call(\n      request,\n      api.createBackgroundImage,\n      projectId,\n      data,\n      localId,\n    ));\n  } catch (error) {\n    yield put(actions.createBackgroundImage.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createBackgroundImage.success(localId, backgroundImage));\n}\n\nexport function* createBackgroundImageInCurrentProject(data) {\n  const { projectId } = yield select(selectors.selectPath);\n\n  yield call(createBackgroundImage, projectId, data);\n}\n\nexport function* handleBackgroundImageCreate(backgroundImage, requestId) {\n  const isExists = yield select(selectors.selectIsBackgroundImageWithIdExists, requestId);\n\n  if (!isExists) {\n    yield put(actions.handleBackgroundImageCreate(backgroundImage));\n  }\n}\n\nexport function* deleteBackgroundImage(id) {\n  yield put(actions.deleteBackgroundImage(id));\n\n  let backgroundImage;\n  try {\n    ({ item: backgroundImage } = yield call(request, api.deleteBackgroundImage, id));\n  } catch (error) {\n    yield put(actions.deleteBackgroundImage.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteBackgroundImage.success(backgroundImage));\n}\n\nexport function* handleBackgroundImageDelete(backgroundImage) {\n  yield put(actions.handleBackgroundImageDelete(backgroundImage));\n}\n\nexport default {\n  createBackgroundImage,\n  createBackgroundImageInCurrentProject,\n  handleBackgroundImageCreate,\n  deleteBackgroundImage,\n  handleBackgroundImageDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/base-custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\n\nexport function* createBaseCustomFieldGroup(projectId, data) {\n  const localId = yield call(createLocalId);\n\n  yield put(\n    actions.createBaseCustomFieldGroup({\n      ...data,\n      projectId,\n      id: localId,\n    }),\n  );\n\n  let baseCustomFieldGroup;\n  try {\n    ({ item: baseCustomFieldGroup } = yield call(\n      request,\n      api.createBaseCustomFieldGroup,\n      projectId,\n      data,\n    ));\n  } catch (error) {\n    yield put(actions.createBaseCustomFieldGroup.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createBaseCustomFieldGroup.success(localId, baseCustomFieldGroup));\n}\n\nexport function* createBaseCustomFieldGroupInCurrentProject(data) {\n  const { projectId } = yield select(selectors.selectPath);\n\n  yield call(createBaseCustomFieldGroup, projectId, data);\n}\n\nexport function* handleBaseCustomFieldGroupCreate(baseCustomFieldGroup) {\n  yield put(actions.handleBaseCustomFieldGroupCreate(baseCustomFieldGroup));\n}\n\nexport function* updateBaseCustomFieldGroup(id, data) {\n  yield put(actions.updateBaseCustomFieldGroup(id, data));\n\n  let baseCustomFieldGroup;\n  try {\n    ({ item: baseCustomFieldGroup } = yield call(\n      request,\n      api.updateBaseCustomFieldGroup,\n      id,\n      data,\n    ));\n  } catch (error) {\n    yield put(actions.updateBaseCustomFieldGroup.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateBaseCustomFieldGroup.success(baseCustomFieldGroup));\n}\n\nexport function* handleBaseCustomFieldGroupUpdate(baseCustomFieldGroup) {\n  yield put(actions.handleBaseCustomFieldGroupUpdate(baseCustomFieldGroup));\n}\n\nexport function* deleteBaseCustomFieldGroup(id) {\n  yield put(actions.deleteBaseCustomFieldGroup(id));\n\n  let baseCustomFieldGroup;\n  try {\n    ({ item: baseCustomFieldGroup } = yield call(request, api.deleteBaseCustomFieldGroup, id));\n  } catch (error) {\n    yield put(actions.deleteBaseCustomFieldGroup.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteBaseCustomFieldGroup.success(baseCustomFieldGroup));\n}\n\nexport function* handleBaseCustomFieldGroupDelete(baseCustomFieldGroup) {\n  yield put(actions.handleBaseCustomFieldGroupDelete(baseCustomFieldGroup));\n}\n\nexport default {\n  createBaseCustomFieldGroup,\n  createBaseCustomFieldGroupInCurrentProject,\n  handleBaseCustomFieldGroupCreate,\n  updateBaseCustomFieldGroup,\n  handleBaseCustomFieldGroupUpdate,\n  deleteBaseCustomFieldGroup,\n  handleBaseCustomFieldGroupDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/board-memberships.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport { goToProject } from './router';\nimport request from '../request';\nimport requests from '../requests';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\nimport mergeRecords from '../../../utils/merge-records';\n\nexport function* createBoardMembership(boardId, data) {\n  const localId = yield call(createLocalId);\n\n  yield put(\n    actions.createBoardMembership({\n      ...data,\n      boardId,\n      id: localId,\n    }),\n  );\n\n  let boardMembership;\n  try {\n    ({ item: boardMembership } = yield call(request, api.createBoardMembership, boardId, data));\n  } catch (error) {\n    yield put(actions.createBoardMembership.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createBoardMembership.success(localId, boardMembership));\n}\n\nexport function* createMembershipInCurrentBoard(data) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(createBoardMembership, boardId, data);\n}\n\nexport function* handleBoardMembershipCreate(boardMembership, users) {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n  const isCurrentUser = boardMembership.userId === currentUserId;\n\n  const isExternalAccessibleForCurrentUser = yield select(\n    selectors.selectIsProjectWithIdExternalAccessibleForCurrentUser,\n    boardMembership.projectId,\n  );\n\n  let project;\n  let board;\n  let users1;\n  let users2;\n  let projectManagers;\n  let backgroundImages;\n  let baseCustomFieldGroups;\n  let boards;\n  let boardMemberships1;\n  let boardMemberships2;\n  let labels;\n  let lists;\n  let cards;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields1;\n  let customFields2;\n  let customFieldValues;\n  let notificationsToDelete;\n  let notificationServices;\n\n  if (isCurrentUser && !isExternalAccessibleForCurrentUser) {\n    const { boardId } = yield select(selectors.selectPath);\n\n    try {\n      ({\n        item: project,\n        included: {\n          projectManagers,\n          backgroundImages,\n          baseCustomFieldGroups,\n          boards,\n          notificationServices,\n          users: users1,\n          boardMemberships: boardMemberships1,\n          customFields: customFields1,\n        },\n      } = yield call(request, api.getProject, boardMembership.projectId));\n    } catch {\n      return;\n    }\n\n    if (boardId === null) {\n      let body;\n      try {\n        body = yield call(requests.fetchBoardByCurrentPath);\n      } catch {\n        /* empty */\n      }\n\n      if (body) {\n        ({\n          project,\n          board,\n          labels,\n          lists,\n          cards,\n          cardMemberships,\n          cardLabels,\n          taskLists,\n          tasks,\n          attachments,\n          customFieldGroups,\n          customFieldValues,\n          users: users2,\n          boardMemberships: boardMemberships2,\n          customFields: customFields2,\n        } = body);\n\n        if (body.card) {\n          notificationsToDelete = yield select(selectors.selectNotificationsByCardId, body.card.id);\n        }\n      }\n    }\n  }\n\n  const isProjectAvailable = yield select(\n    selectors.selectIsProjectWithIdAvailableForCurrentUser,\n    boardMembership.projectId,\n  );\n\n  yield put(\n    actions.handleBoardMembershipCreate(\n      boardMembership,\n      isProjectAvailable,\n      project,\n      board,\n      mergeRecords(users, users1, users2),\n      projectManagers,\n      backgroundImages,\n      baseCustomFieldGroups,\n      boards,\n      mergeRecords(boardMemberships1, boardMemberships2),\n      labels,\n      lists,\n      cards,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      mergeRecords(customFields1, customFields2),\n      customFieldValues,\n      notificationsToDelete,\n      notificationServices,\n    ),\n  );\n}\n\nexport function* updateBoardMembership(id, data) {\n  yield put(actions.updateBoardMembership(id, data));\n\n  let boardMembership;\n  try {\n    ({ item: boardMembership } = yield call(request, api.updateBoardMembership, id, data));\n  } catch (error) {\n    yield put(actions.updateBoardMembership.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateBoardMembership.success(boardMembership));\n}\n\nexport function* handleBoardMembershipUpdate(boardMembership) {\n  yield put(actions.handleBoardMembershipUpdate(boardMembership));\n}\n\nexport function* deleteBoardMembership(id) {\n  let boardMembership = yield select(selectors.selectBoardMembershipById, id);\n\n  const { boardId, projectId } = yield select(selectors.selectPath);\n\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n  const isCurrentUser = boardMembership.userId === currentUserId;\n\n  yield put(actions.deleteBoardMembership(id, isCurrentUser));\n\n  if (isCurrentUser && boardMembership.boardId === boardId) {\n    const isAvailableForCurrentUser = yield select(\n      selectors.selectIsBoardWithIdAvailableForCurrentUser,\n      boardMembership.boardId,\n    );\n\n    if (!isAvailableForCurrentUser) {\n      yield call(goToProject, projectId);\n    }\n  }\n\n  try {\n    ({ item: boardMembership } = yield call(request, api.deleteBoardMembership, id));\n  } catch (error) {\n    yield put(actions.deleteBoardMembership.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteBoardMembership.success(boardMembership, isCurrentUser));\n}\n\nexport function* handleBoardMembershipDelete(boardMembership) {\n  const { boardId, projectId } = yield select(selectors.selectPath);\n\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n  const isCurrentUser = boardMembership.userId === currentUserId;\n\n  yield put(actions.handleBoardMembershipDelete(boardMembership, isCurrentUser));\n\n  if (isCurrentUser && boardMembership.boardId === boardId) {\n    const isAvailableForCurrentUser = yield select(\n      selectors.selectIsBoardWithIdAvailableForCurrentUser,\n      boardMembership.boardId,\n    );\n\n    if (!isAvailableForCurrentUser) {\n      yield call(goToProject, projectId);\n    }\n  }\n}\n\nexport default {\n  createBoardMembership,\n  createMembershipInCurrentBoard,\n  handleBoardMembershipCreate,\n  updateBoardMembership,\n  handleBoardMembershipUpdate,\n  deleteBoardMembership,\n  handleBoardMembershipDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/boards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, fork, put, select, take } from 'redux-saga/effects';\n\nimport { goToBoard, goToProject } from './router';\nimport { openModal } from './modals';\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\nimport ActionTypes from '../../../constants/ActionTypes';\nimport ModalTypes from '../../../constants/ModalTypes';\n\nexport function* createBoard(projectId, { import: boardImport, ...data }) {\n  const localId = yield call(createLocalId);\n\n  const nextData = {\n    ...data,\n    position: yield select(selectors.selectNextBoardPosition, projectId),\n  };\n\n  yield put(\n    actions.createBoard({\n      ...nextData,\n      projectId,\n      id: localId,\n    }),\n  );\n\n  // TODO: use race instead\n  const watchForCreateBoardActionTask = yield fork(function* watchForCreateBoardAction() {\n    yield take(ActionTypes.BOARD_CREATE);\n  });\n\n  let board;\n  let boardMemberships;\n\n  try {\n    ({\n      item: board,\n      included: { boardMemberships },\n    } = yield boardImport\n      ? call(\n          request,\n          api.createBoardWithImport,\n          projectId,\n          {\n            ...nextData,\n            importType: boardImport.type,\n            importFile: boardImport.file,\n          },\n          localId,\n        )\n      : call(request, api.createBoard, projectId, nextData));\n  } catch (error) {\n    yield put(actions.createBoard.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createBoard.success(localId, board, boardMemberships));\n\n  if (watchForCreateBoardActionTask.isRunning()) {\n    yield call(goToBoard, board.id);\n    yield call(openModal, ModalTypes.BOARD_SETTINGS, {\n      id: board.id,\n      openPreferences: true,\n    });\n  }\n}\n\nexport function* createBoardInCurrentProject(data) {\n  const { projectId } = yield select(selectors.selectPath);\n\n  yield call(createBoard, projectId, data);\n}\n\nexport function* handleBoardCreate(board, boardMemberships, requestId) {\n  const isExists = yield select(selectors.selectIsBoardWithIdExists, requestId);\n\n  if (!isExists) {\n    yield put(actions.handleBoardCreate(board, boardMemberships));\n  }\n}\n\nexport function* fetchBoard(id) {\n  yield put(actions.fetchBoard(id));\n\n  let board;\n  let users;\n  let projects;\n  let boardMemberships;\n  let labels;\n  let lists;\n  let cards;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields;\n  let customFieldValues;\n\n  try {\n    ({\n      item: board,\n      included: {\n        users,\n        projects,\n        boardMemberships,\n        labels,\n        lists,\n        cards,\n        cardMemberships,\n        cardLabels,\n        taskLists,\n        tasks,\n        attachments,\n        customFieldGroups,\n        customFields,\n        customFieldValues,\n      },\n    } = yield call(request, api.getBoard, id, true));\n  } catch (error) {\n    yield put(actions.fetchBoard.failure(id, error));\n    return;\n  }\n\n  yield put(\n    actions.fetchBoard.success(\n      board,\n      users,\n      projects,\n      boardMemberships,\n      labels,\n      lists,\n      cards,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFields,\n      customFieldValues,\n    ),\n  );\n}\n\nexport function* updateBoard(id, data) {\n  yield put(actions.updateBoard(id, data));\n\n  let board;\n  try {\n    ({ item: board } = yield call(request, api.updateBoard, id, data));\n  } catch (error) {\n    yield put(actions.updateBoard.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateBoard.success(board));\n}\n\nexport function* updateCurrentBoard(data) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(updateBoard, boardId, data);\n}\n\nexport function* handleBoardUpdate(board) {\n  yield put(actions.handleBoardUpdate(board));\n}\n\nexport function* moveBoard(id, index) {\n  const { projectId } = yield select(selectors.selectBoardById, id);\n  const position = yield select(selectors.selectNextBoardPosition, projectId, index, id);\n\n  yield call(updateBoard, id, {\n    position,\n  });\n}\n\nexport function* updateBoardContext(id, value) {\n  yield put(actions.updateBoardContext(id, value));\n}\n\nexport function* updateContextInCurrentBoard(value) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(updateBoardContext, boardId, value);\n}\n\nexport function* updateBoardView(id, value) {\n  yield put(\n    actions.updateBoard(id, {\n      view: value,\n    }),\n  );\n}\n\nexport function* updateViewInCurrentBoard(value) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(updateBoardView, boardId, value);\n}\n\nexport function* searchInCurrentBoard(value) {\n  const { boardId } = yield select(selectors.selectPath);\n  const currentListId = yield select(selectors.selectCurrentListId);\n\n  yield put(actions.searchInBoard(boardId, value, currentListId));\n}\n\nexport function* deleteBoard(id) {\n  const currentBoard = yield select(selectors.selectCurrentBoard);\n\n  yield put(actions.deleteBoard(id));\n\n  if (currentBoard && id === currentBoard.id) {\n    yield call(goToProject, currentBoard.projectId);\n  }\n\n  let board;\n  try {\n    ({ item: board } = yield call(request, api.deleteBoard, id));\n  } catch (error) {\n    yield put(actions.deleteBoard.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteBoard.success(board));\n}\n\nexport function* handleBoardDelete(board) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield put(actions.handleBoardDelete(board));\n\n  if (board.id === boardId) {\n    yield call(goToProject, board.projectId);\n  }\n}\n\nexport default {\n  createBoard,\n  createBoardInCurrentProject,\n  handleBoardCreate,\n  fetchBoard,\n  updateBoard,\n  updateCurrentBoard,\n  handleBoardUpdate,\n  moveBoard,\n  updateBoardContext,\n  updateContextInCurrentBoard,\n  updateBoardView,\n  updateViewInCurrentBoard,\n  searchInCurrentBoard,\n  deleteBoard,\n  handleBoardDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/bootstrap.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { put } from 'redux-saga/effects';\n\nimport actions from '../../../actions';\n\nexport function* handleBootstrapUpdate(bootstrap) {\n  yield put(actions.handleBootstrapUpdate(bootstrap));\n}\n\nexport default {\n  handleBootstrapUpdate,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/cards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, fork, join, put, race, select, take } from 'redux-saga/effects';\nimport toast from 'react-hot-toast';\nimport { LOCATION_CHANGE_HANDLE } from '../../../lib/redux-router';\n\nimport { goToBoard, goToCard } from './router';\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport i18n from '../../../i18n';\nimport { createLocalId } from '../../../utils/local-id';\nimport { isListArchiveOrTrash, isListFinite } from '../../../utils/record-helpers';\nimport ActionTypes from '../../../constants/ActionTypes';\nimport ClipboardTypes from '../../../constants/ClipboardTypes';\nimport ToastTypes from '../../../constants/ToastTypes';\nimport { BoardViews, ListTypes, ListTypeStates } from '../../../constants/Enums';\nimport LIST_TYPE_STATE_BY_TYPE from '../../../constants/ListTypeStateByType';\n\n// eslint-disable-next-line no-underscore-dangle\nconst _preloadImage = (url) =>\n  new Promise((resolve) => {\n    const image = new Image();\n\n    image.onload = resolve;\n    image.onerror = resolve;\n\n    image.src = url;\n  });\n\nexport function* fetchCards(listId) {\n  const { boardId, lastCard } = yield select(selectors.selectListById, listId);\n  const { search } = yield select(selectors.selectBoardById, boardId);\n  const filterUserIds = yield select(selectors.selectFilterUserIdsForCurrentBoard);\n  const filterLabelIds = yield select(selectors.selectFilterLabelIdsForCurrentBoard);\n\n  function* getCardsRequest() {\n    const response = {};\n\n    try {\n      response.body = yield call(request, api.getCards, listId, {\n        search: (search && search.trim()) || undefined,\n        userIds: filterUserIds.length > 0 ? filterUserIds.join(',') : undefined,\n        labelIds: filterLabelIds.length > 0 ? filterLabelIds.join(',') : undefined,\n        before: lastCard || undefined,\n      });\n    } catch (error) {\n      response.error = error;\n    }\n\n    return response;\n  }\n\n  yield put(actions.fetchCards(listId));\n\n  const getCardsRequestTask = yield fork(getCardsRequest);\n\n  const [response] = yield race([\n    join(getCardsRequestTask),\n    take((action) => action.type === ActionTypes.CARDS_FETCH && action.payload.listId === listId),\n  ]);\n\n  if (!response) {\n    return;\n  }\n\n  if (response.error) {\n    yield put(actions.fetchCards.failure(listId, response.error));\n    return;\n  }\n\n  const {\n    body: {\n      items: cards,\n      included: {\n        users,\n        cardMemberships,\n        cardLabels,\n        taskLists,\n        tasks,\n        attachments,\n        customFieldGroups,\n        customFields,\n        customFieldValues,\n      },\n    },\n  } = response;\n\n  yield put(\n    actions.fetchCards.success(\n      listId,\n      cards,\n      users,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFields,\n      customFieldValues,\n    ),\n  );\n}\n\nexport function* fetchCardsInCurrentList() {\n  const currentListId = yield select(selectors.selectCurrentListId);\n\n  yield call(fetchCards, currentListId);\n}\n\nexport function* handleCardsUpdate(cards, activities) {\n  yield put(actions.handleCardsUpdate(cards, activities));\n}\n\nexport function* createCard(listId, data, index, autoOpen) {\n  const localId = yield call(createLocalId);\n  const list = yield select(selectors.selectListById, listId);\n\n  const currentUserMembership = yield select(\n    selectors.selectCurrentUserMembershipByBoardId,\n    list.boardId,\n  );\n\n  const nextData = {\n    ...data,\n  };\n\n  if (isListFinite(list)) {\n    nextData.position = yield select(selectors.selectNextCardPosition, listId, index);\n  }\n\n  yield put(\n    actions.createCard(\n      {\n        ...nextData,\n        listId,\n        id: localId,\n        boardId: list.boardId,\n        creatorUserId: currentUserMembership.userId,\n        isClosed: LIST_TYPE_STATE_BY_TYPE[list.type] === ListTypeStates.CLOSED,\n      },\n      autoOpen,\n    ),\n  );\n\n  // TODO: use race instead\n  let watchForCreateCardActionTask;\n  if (autoOpen) {\n    watchForCreateCardActionTask = yield fork(function* watchForCreateCardAction() {\n      yield take((action) => action.type === ActionTypes.CARD_CREATE && action.payload.autoOpen);\n    });\n  }\n\n  let card;\n  try {\n    ({ item: card } = yield call(request, api.createCard, listId, nextData));\n  } catch (error) {\n    yield put(actions.createCard.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createCard.success(localId, card));\n\n  if (watchForCreateCardActionTask && watchForCreateCardActionTask.isRunning()) {\n    yield call(goToCard, card.id);\n  }\n}\n\nexport function* createCardInCurrentContext(data, index, autoOpen) {\n  const firstKanbanListId = yield select(selectors.selectFirstKanbanListId);\n\n  yield call(createCard, firstKanbanListId, data, index, autoOpen);\n}\n\nexport function* createCardInCurrentList(data, autoOpen) {\n  const currentListId = yield select(selectors.selectCurrentListId);\n\n  yield call(createCard, currentListId, data, undefined, autoOpen);\n}\n\nexport function* handleCardCreate(card) {\n  let users;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields;\n  let customFieldValues;\n\n  try {\n    ({\n      item: card, // eslint-disable-line no-param-reassign\n      included: {\n        users,\n        cardMemberships,\n        cardLabels,\n        taskLists,\n        tasks,\n        attachments,\n        customFieldGroups,\n        customFields,\n        customFieldValues,\n      },\n    } = yield call(request, api.getCard, card.id));\n  } catch {\n    return;\n  }\n\n  yield put(\n    actions.handleCardCreate(\n      card,\n      users,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFields,\n      customFieldValues,\n    ),\n  );\n}\n\nexport function* updateCard(id, data) {\n  let prevListId;\n  let isClosed;\n\n  if (data.listId) {\n    const list = yield select(selectors.selectListById, data.listId);\n\n    const card = yield select(selectors.selectCardById, id);\n    const prevList = yield select(selectors.selectListById, card.listId);\n\n    if (prevList.type === ListTypes.TRASH) {\n      prevListId = null;\n    } else if (isListArchiveOrTrash(list)) {\n      prevListId = prevList.id;\n    } else if (prevList.type === ListTypes.ARCHIVE) {\n      prevListId = null;\n    }\n\n    const typeState = LIST_TYPE_STATE_BY_TYPE[list.type];\n\n    if (card.isClosed) {\n      if (typeState === ListTypeStates.OPENED) {\n        isClosed = false;\n      }\n    } else if (typeState === ListTypeStates.CLOSED) {\n      isClosed = true;\n    }\n  }\n\n  yield put(\n    actions.updateCard(id, {\n      ...data,\n      ...(prevListId !== undefined && {\n        prevListId,\n      }),\n      ...(isClosed !== undefined && {\n        isClosed,\n      }),\n    }),\n  );\n\n  let card;\n  try {\n    ({ item: card } = yield call(request, api.updateCard, id, data));\n  } catch (error) {\n    yield put(actions.updateCard.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateCard.success(card));\n}\n\nexport function* updateCurrentCard(data) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(updateCard, cardId, data);\n}\n\nexport function* handleCardUpdate(card) {\n  const { cardId, boardId } = yield select(selectors.selectPath);\n\n  let fetch = false;\n  if (card.boardId) {\n    const isAvailableForCurrentUser = yield select(\n      selectors.selectIsCardWithIdAvailableForCurrentUser,\n      card.id,\n    );\n\n    fetch = !isAvailableForCurrentUser;\n  }\n\n  let users;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields;\n  let customFieldValues;\n\n  if (fetch) {\n    try {\n      ({\n        item: card, // eslint-disable-line no-param-reassign\n        included: {\n          users,\n          cardMemberships,\n          cardLabels,\n          taskLists,\n          tasks,\n          attachments,\n          customFieldGroups,\n          customFields,\n          customFieldValues,\n        },\n      } = yield call(request, api.getCard, card.id));\n    } catch {\n      fetch = false;\n    }\n  }\n\n  yield put(\n    actions.handleCardUpdate(\n      card,\n      fetch,\n      users,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFields,\n      customFieldValues,\n    ),\n  );\n\n  if (card.boardId === null && card.id === cardId) {\n    yield call(goToBoard, boardId);\n  }\n}\n\nexport function* moveCard(id, listId, index) {\n  const data = {};\n  if (listId) {\n    data.listId = listId;\n  } else {\n    // eslint-disable-next-line no-param-reassign\n    ({ listId } = yield select(selectors.selectCardById, id));\n  }\n\n  const list = yield select(selectors.selectListById, listId);\n\n  if (isListFinite(list)) {\n    data.position = yield select(selectors.selectNextCardPosition, listId, index, id);\n  }\n\n  yield call(updateCard, id, data);\n}\n\nexport function* moveCurrentCard(listId, index, autoClose) {\n  const { cardId, boardId } = yield select(selectors.selectPath);\n\n  if (autoClose) {\n    yield call(goToBoard, boardId);\n  }\n\n  yield call(moveCard, cardId, listId, index);\n}\n\nexport function* moveCardToArchive(id) {\n  const archiveListId = yield select(selectors.selectArchiveListIdForCurrentBoard);\n\n  yield call(moveCard, id, archiveListId);\n}\n\nexport function* moveCurrentCardToArchive() {\n  const archiveListId = yield select(selectors.selectArchiveListIdForCurrentBoard);\n\n  yield call(moveCurrentCard, archiveListId, undefined, true);\n}\n\nexport function* moveCardToTrash(id) {\n  const trashListId = yield select(selectors.selectTrashListIdForCurrentBoard);\n\n  yield call(moveCard, id, trashListId);\n}\n\nexport function* moveCurrentCardToTrash() {\n  const trashListId = yield select(selectors.selectTrashListIdForCurrentBoard);\n\n  yield call(moveCurrentCard, trashListId, undefined, true);\n}\n\nexport function* transferCard(id, boardId, listId, index) {\n  const { cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath);\n\n  // TODO: hack?\n  if (id === currentCardId) {\n    yield call(goToBoard, currentBoardId);\n  }\n\n  const list = yield select(selectors.selectListById, listId);\n\n  const data = {\n    listId,\n    boardId,\n  };\n\n  if (isListFinite(list)) {\n    data.position = yield select(selectors.selectNextCardPosition, listId, index, id);\n  }\n\n  const typeState = LIST_TYPE_STATE_BY_TYPE[list.type];\n\n  yield put(\n    actions.transferCard(id, {\n      ...data,\n      isClosed: typeState === ListTypeStates.CLOSED,\n    }),\n  );\n\n  let card;\n  let updateError;\n\n  try {\n    ({ item: card } = yield call(request, api.updateCard, id, data));\n  } catch (error) {\n    updateError = error;\n  }\n\n  let users;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields;\n  let customFieldValues;\n\n  try {\n    ({\n      item: card,\n      included: {\n        users,\n        cardMemberships,\n        cardLabels,\n        taskLists,\n        tasks,\n        attachments,\n        customFieldGroups,\n        customFields,\n        customFieldValues,\n      },\n    } = yield call(request, api.getCard, id));\n  } catch (error) {\n    yield put(actions.transferCard.failure(id, error));\n  }\n\n  if (updateError) {\n    yield put(\n      actions.transferCard.failure(\n        id,\n        updateError,\n        card,\n        users,\n        cardMemberships,\n        cardLabels,\n        taskLists,\n        tasks,\n        attachments,\n        customFieldGroups,\n        customFields,\n        customFieldValues,\n      ),\n    );\n\n    yield call(toast, {\n      type: ToastTypes.SOURCE_CARD_NOT_MOVABLE,\n    });\n\n    return;\n  }\n\n  yield put(\n    actions.transferCard.success(\n      card,\n      users,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFields,\n      customFieldValues,\n    ),\n  );\n}\n\nexport function* transferCurrentCard(boardId, listId, index) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(transferCard, cardId, boardId, listId, index);\n}\n\nexport function* duplicateCard(id, data) {\n  const localId = yield call(createLocalId);\n  const { cardId: currentCardId } = yield select(selectors.selectPath);\n  const sourceCard = yield select(selectors.selectCardById, id);\n\n  const boardId = data.boardId || sourceCard.boardId;\n  const listId = data.listId || sourceCard.listId;\n\n  const list = yield select(selectors.selectListById, listId);\n  const typeState = LIST_TYPE_STATE_BY_TYPE[list.type];\n\n  const nextData = {\n    ...data,\n  };\n\n  if (!nextData.position && isListFinite(list)) {\n    const index = yield select(selectors.selectCardIndexById, id);\n    nextData.position = yield select(selectors.selectNextCardPosition, listId, index + 1);\n  }\n\n  const currentUserMembership = yield select(\n    selectors.selectCurrentUserMembershipByBoardId,\n    boardId,\n  );\n\n  yield put(\n    actions.duplicateCard(id, localId, {\n      ...nextData,\n      creatorUserId: currentUserMembership.userId,\n      isClosed: typeState === ListTypeStates.CLOSED,\n      ...(sourceCard && {\n        name: `${sourceCard.name} (${i18n.t('common.copy', {\n          context: 'inline',\n        })})`,\n      }),\n    }),\n  );\n\n  if (id === currentCardId) {\n    yield call(goToBoard, boardId);\n  }\n\n  let card;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields;\n  let customFieldValues;\n\n  try {\n    ({\n      item: card,\n      included: {\n        cardMemberships,\n        cardLabels,\n        taskLists,\n        tasks,\n        attachments,\n        customFieldGroups,\n        customFields,\n        customFieldValues,\n      },\n    } = yield call(request, api.duplicateCard, id, nextData));\n  } catch (error) {\n    yield put(actions.duplicateCard.failure(localId, error));\n\n    yield call(toast, {\n      type: ToastTypes.SOURCE_CARD_NOT_COPYABLE,\n    });\n\n    return;\n  }\n\n  if (card.coverAttachmentId) {\n    const coverAttachment = attachments.find(\n      (attachment) => attachment.id === card.coverAttachmentId,\n    );\n\n    if (coverAttachment) {\n      yield call(_preloadImage, coverAttachment.data.thumbnailUrls.outside360);\n    }\n  }\n\n  yield put(\n    actions.duplicateCard.success(\n      localId,\n      card,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFields,\n      customFieldValues,\n    ),\n  );\n}\n\nexport function* duplicateCurrentCard(data) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(duplicateCard, cardId, data);\n}\n\nexport function* copyCard(id) {\n  yield put(actions.copyCard(id));\n}\n\nexport function* cutCard(id) {\n  yield put(actions.cutCard(id));\n}\n\nexport function* pasteCard(listId) {\n  const list = yield select(selectors.selectListById, listId);\n  const clipboard = yield select(selectors.selectClipboard);\n  const sourceCard = yield select(selectors.selectCardById, clipboard.cardId);\n\n  yield put(actions.pasteCard());\n\n  if (clipboard.type === ClipboardTypes.COPY) {\n    const data = {\n      listId,\n    };\n    if (!sourceCard || list.boardId !== sourceCard.boardId) {\n      data.boardId = list.boardId;\n    }\n    if (isListFinite(list)) {\n      data.position = yield select(selectors.selectNextCardPosition, listId);\n    }\n\n    yield call(duplicateCard, clipboard.cardId, data);\n  } else if (clipboard.type === ClipboardTypes.CUT) {\n    if (sourceCard && listId === sourceCard.listId) {\n      return;\n    }\n\n    yield call(transferCard, clipboard.cardId, list.boardId, list.id);\n  }\n}\n\nexport function* pasteCardInCurrentContext() {\n  const listId = yield select(selectors.selectFirstKanbanListId);\n\n  yield call(pasteCard, listId);\n}\n\nexport function* pasteCardInCurrentList() {\n  const currentListId = yield select(selectors.selectCurrentListId);\n\n  yield call(pasteCard, currentListId);\n}\n\nexport function* goToAdjacentCard(direction) {\n  const card = yield select(selectors.selectCurrentCard);\n  const list = yield select(selectors.selectListById, card.listId);\n\n  let cardIds;\n  if (isListFinite(list)) {\n    const { view } = yield select(selectors.selectCurrentBoard);\n\n    if (view === BoardViews.KANBAN) {\n      cardIds = yield select(selectors.selectFilteredCardIdsByListId, card.listId);\n    } else {\n      cardIds = yield select(selectors.selectFilteredCardIdsForCurrentBoard);\n    }\n  } else {\n    cardIds = yield select(selectors.selectFilteredCardIdsByListId, card.listId);\n\n    if (direction === 1 && card.id === cardIds[cardIds.length - 1]) {\n      if (list.isCardsFetching || list.isAllCardsFetched) {\n        return;\n      }\n\n      const [, cancelled] = yield race([call(fetchCards, list.id), take(LOCATION_CHANGE_HANDLE)]);\n\n      if (cancelled) {\n        return;\n      }\n\n      cardIds = yield select(selectors.selectFilteredCardIdsByListId, card.listId);\n    }\n  }\n\n  const index = cardIds.indexOf(card.id);\n\n  if (index === -1) {\n    return;\n  }\n\n  const adjacentCardId = cardIds[index + direction];\n\n  if (adjacentCardId) {\n    yield call(goToCard, adjacentCardId);\n  }\n}\n\nexport function* deleteCard(id) {\n  const { cardId, boardId } = yield select(selectors.selectPath);\n\n  yield put(actions.deleteCard(id));\n\n  if (id === cardId) {\n    yield call(goToBoard, boardId);\n  }\n\n  let card;\n  try {\n    ({ item: card } = yield call(request, api.deleteCard, id));\n  } catch (error) {\n    yield put(actions.deleteCard.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteCard.success(card));\n}\n\nexport function* deleteCurrentCard() {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(deleteCard, cardId);\n}\n\nexport function* handleCardDelete(card) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield put(actions.handleCardDelete(card));\n\n  if (card.id === cardId) {\n    yield call(goToBoard, card.boardId);\n  }\n}\n\nexport default {\n  fetchCards,\n  fetchCardsInCurrentList,\n  handleCardsUpdate,\n  createCard,\n  createCardInCurrentContext,\n  createCardInCurrentList,\n  handleCardCreate,\n  updateCard,\n  updateCurrentCard,\n  handleCardUpdate,\n  moveCard,\n  moveCurrentCard,\n  moveCardToArchive,\n  moveCurrentCardToArchive,\n  moveCardToTrash,\n  moveCurrentCardToTrash,\n  transferCard,\n  transferCurrentCard,\n  duplicateCard,\n  duplicateCurrentCard,\n  copyCard,\n  cutCard,\n  pasteCard,\n  pasteCardInCurrentContext,\n  pasteCardInCurrentList,\n  goToAdjacentCard,\n  deleteCard,\n  deleteCurrentCard,\n  handleCardDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/comments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\n\nexport function* fetchComments(cardId) {\n  const { lastCommentId } = yield select(selectors.selectCardById, cardId);\n\n  yield put(actions.fetchComments(cardId));\n\n  let comments;\n  let users;\n\n  try {\n    ({\n      items: comments,\n      included: { users },\n    } = yield call(request, api.getComments, cardId, {\n      beforeId: lastCommentId || undefined,\n    }));\n  } catch (error) {\n    yield put(actions.fetchComments.failure(cardId, error));\n    return;\n  }\n\n  yield put(actions.fetchComments.success(cardId, comments, users));\n}\n\nexport function* fetchCommentsInCurrentCard() {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(fetchComments, cardId);\n}\n\nexport function* createComment(cardId, data) {\n  const localId = yield call(createLocalId);\n  const currentUser = yield select(selectors.selectCurrentUser);\n\n  yield put(\n    actions.createComment({\n      ...data,\n      cardId,\n      id: localId,\n      userId: currentUser.id,\n    }),\n  );\n\n  let comment;\n  try {\n    ({ item: comment } = yield call(request, api.createComment, cardId, data));\n  } catch (error) {\n    yield put(actions.createComment.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createComment.success(localId, comment));\n}\n\nexport function* createCommentInCurrentCard(data) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(createComment, cardId, data);\n}\n\nexport function* handleCommentCreate(comment, users) {\n  yield put(actions.handleCommentCreate(comment, users));\n}\n\nexport function* updateComment(id, data) {\n  yield put(actions.updateComment(id, data));\n\n  let comment;\n  try {\n    ({ item: comment } = yield call(request, api.updateComment, id, data));\n  } catch (error) {\n    yield put(actions.updateComment.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateComment.success(comment));\n}\n\nexport function* handleCommentUpdate(comment) {\n  yield put(actions.handleCommentUpdate(comment));\n}\n\nexport function* deleteComment(id) {\n  yield put(actions.deleteComment(id));\n\n  let comment;\n  try {\n    ({ item: comment } = yield call(request, api.deleteComment, id));\n  } catch (error) {\n    yield put(actions.deleteComment.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteComment.success(comment));\n}\n\nexport function* handleCommentDelete(comment) {\n  yield put(actions.handleCommentDelete(comment));\n}\n\nexport default {\n  fetchComments,\n  fetchCommentsInCurrentCard,\n  createComment,\n  createCommentInCurrentCard,\n  handleCommentCreate,\n  updateComment,\n  handleCommentUpdate,\n  deleteComment,\n  handleCommentDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/config.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put } from 'redux-saga/effects';\n\nimport request from '../request';\nimport actions from '../../../actions';\nimport api from '../../../api';\n\nexport function* updateConfig(data) {\n  yield put(actions.updateConfig(data));\n\n  let config;\n  try {\n    ({ item: config } = yield call(request, api.updateConfig, data));\n  } catch (error) {\n    yield put(actions.updateConfig.failure(error));\n    return;\n  }\n\n  yield put(actions.updateConfig.success(config));\n}\n\nexport function* handleConfigUpdate(config) {\n  yield put(actions.handleConfigUpdate(config));\n}\n\nexport function* testSmtpConfig() {\n  yield put(actions.testSmtpConfig());\n\n  let logs;\n  try {\n    ({\n      included: { logs },\n    } = yield call(request, api.testSmtpConfig));\n  } catch (error) {\n    yield put(actions.testSmtpConfig.failure(error));\n  }\n\n  yield put(actions.testSmtpConfig.success(logs));\n}\n\nexport default {\n  updateConfig,\n  handleConfigUpdate,\n  testSmtpConfig,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/core.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport requests from '../requests';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport i18n from '../../../i18n';\nimport { removeAccessToken } from '../../../utils/access-token-storage';\n\nexport function* initializeCore() {\n  const { item: bootstrap } = yield call(request, api.getBootstrap); // TODO: handle error\n\n  yield put(actions.initializeCore.fetchBootstrap(bootstrap));\n\n  const {\n    config,\n    user,\n    board,\n    webhooks,\n    users,\n    projects,\n    projectManagers,\n    backgroundImages,\n    baseCustomFieldGroups,\n    boards,\n    boardMemberships,\n    labels,\n    lists,\n    cards,\n    cardMemberships,\n    cardLabels,\n    taskLists,\n    tasks,\n    attachments,\n    customFieldGroups,\n    customFields,\n    customFieldValues,\n    notifications,\n    notificationServices,\n  } = yield call(requests.fetchCore); // TODO: handle error\n\n  yield call(i18n.changeLanguage, user.language);\n  yield call(i18n.loadCoreLocale);\n\n  yield put(\n    actions.initializeCore(\n      config,\n      user,\n      board,\n      webhooks,\n      users,\n      projects,\n      projectManagers,\n      backgroundImages,\n      baseCustomFieldGroups,\n      boards,\n      boardMemberships,\n      labels,\n      lists,\n      cards,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFields,\n      customFieldValues,\n      notifications,\n      notificationServices,\n    ),\n  );\n}\n\nexport function* changeCoreLanguage(language) {\n  yield call(i18n.loadCoreLocale, language);\n  yield call(i18n.changeLanguage, language);\n}\n\nexport function* toggleFavorites(isEnabled) {\n  yield put(actions.toggleFavorites(isEnabled));\n\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  try {\n    yield call(request, api.updateUser, currentUserId, {\n      enableFavoritesByDefault: isEnabled,\n    });\n  } catch {\n    /* empty */\n  }\n}\n\nexport function* toggleEditMode(isEnabled) {\n  yield put(actions.toggleEditMode(isEnabled));\n}\n\nexport function* updateHomeView(value) {\n  yield put(actions.updateHomeView(value));\n\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  try {\n    yield call(request, api.updateUser, currentUserId, {\n      defaultHomeView: value,\n    });\n  } catch {\n    /* empty */\n  }\n}\n\nexport function* logout(revokeAccessToken) {\n  yield call(removeAccessToken);\n\n  if (revokeAccessToken) {\n    yield put(actions.logout.revokeAccessToken());\n\n    try {\n      yield call(request, api.deleteCurrentAccessToken);\n    } catch {\n      /* empty */\n    }\n  }\n\n  yield put(actions.logout()); // TODO: next url\n}\n\nexport default {\n  initializeCore,\n  changeCoreLanguage,\n  toggleFavorites,\n  toggleEditMode,\n  updateHomeView,\n  logout,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\n\nexport function* createCustomFieldGroupInBoard(boardId, data) {\n  const localId = yield call(createLocalId);\n\n  const nextData = {\n    ...data,\n    position: yield select(selectors.selectNextCustomFieldGroupPositionInBoard, boardId),\n  };\n\n  yield put(\n    actions.createCustomFieldGroup({\n      ...nextData,\n      boardId,\n      id: localId,\n    }),\n  );\n\n  let customFieldGroup;\n  try {\n    ({ item: customFieldGroup } = yield call(\n      request,\n      api.createBoardCustomFieldGroup,\n      boardId,\n      nextData,\n    ));\n  } catch (error) {\n    yield put(actions.createCustomFieldGroup.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createCustomFieldGroup.success(localId, customFieldGroup));\n}\n\nexport function* createCustomFieldGroupInCurrentBoard(data) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(createCustomFieldGroupInBoard, boardId, data);\n}\n\nexport function* createCustomFieldGroupInCard(cardId, data) {\n  const localId = yield call(createLocalId);\n\n  const nextData = {\n    ...data,\n    position: yield select(selectors.selectNextCustomFieldGroupPositionInCard, cardId),\n  };\n\n  yield put(\n    actions.createCustomFieldGroup({\n      ...nextData,\n      cardId,\n      id: localId,\n    }),\n  );\n\n  let customFieldGroup;\n  try {\n    ({ item: customFieldGroup } = yield call(\n      request,\n      api.createCardCustomFieldGroup,\n      cardId,\n      nextData,\n    ));\n  } catch (error) {\n    yield put(actions.createCustomFieldGroup.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createCustomFieldGroup.success(localId, customFieldGroup));\n}\n\nexport function* createCustomFieldGroupInCurrentCard(data) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(createCustomFieldGroupInCard, cardId, data);\n}\n\nexport function* handleCustomFieldGroupCreate(customFieldGroup) {\n  yield put(actions.handleCustomFieldGroupCreate(customFieldGroup));\n}\n\nexport function* updateCustomFieldGroup(id, data) {\n  yield put(actions.updateCustomFieldGroup(id, data));\n\n  let customFieldGroup;\n  try {\n    ({ item: customFieldGroup } = yield call(request, api.updateCustomFieldGroup, id, data));\n  } catch (error) {\n    yield put(actions.updateCustomFieldGroup.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateCustomFieldGroup.success(customFieldGroup));\n}\n\nexport function* handleCustomFieldGroupUpdate(customFieldGroup) {\n  yield put(actions.handleCustomFieldGroupUpdate(customFieldGroup));\n}\n\nexport function* moveCustomFieldGroup(id, index) {\n  const customFieldGroup = yield select(selectors.selectCustomFieldGroupById, id);\n\n  let position;\n  if (customFieldGroup.boardId) {\n    position = yield select(\n      selectors.selectNextCustomFieldGroupPositionInBoard,\n      customFieldGroup.boardId,\n      index,\n      id,\n    );\n  } else if (customFieldGroup.cardId) {\n    position = yield select(\n      selectors.selectNextCustomFieldGroupPositionInCard,\n      customFieldGroup.cardId,\n      index,\n      id,\n    );\n  }\n\n  yield call(updateCustomFieldGroup, id, {\n    position,\n  });\n}\n\nexport function* deleteCustomFieldGroup(id) {\n  yield put(actions.deleteCustomFieldGroup(id));\n\n  let customFieldGroup;\n  try {\n    ({ item: customFieldGroup } = yield call(request, api.deleteCustomFieldGroup, id));\n  } catch (error) {\n    yield put(actions.deleteCustomFieldGroup.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteCustomFieldGroup.success(customFieldGroup));\n}\n\nexport function* handleCustomFieldGroupDelete(customFieldGroup) {\n  yield put(actions.handleCustomFieldGroupDelete(customFieldGroup));\n}\n\nexport default {\n  createCustomFieldGroupInBoard,\n  createCustomFieldGroupInCurrentBoard,\n  createCustomFieldGroupInCard,\n  createCustomFieldGroupInCurrentCard,\n  handleCustomFieldGroupCreate,\n  updateCustomFieldGroup,\n  handleCustomFieldGroupUpdate,\n  moveCustomFieldGroup,\n  deleteCustomFieldGroup,\n  handleCustomFieldGroupDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/custom-field-values.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put } from 'redux-saga/effects';\n\nimport request from '../request';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { buildCustomFieldValueId } from '../../../models/CustomFieldValue';\nimport { createLocalId } from '../../../utils/local-id';\n\nexport function* updateCustomFieldValue(cardId, customFieldGroupId, customFieldId, data) {\n  const localId = yield call(createLocalId);\n\n  yield put(\n    actions.updateCustomFieldValue({\n      ...data,\n      cardId,\n      customFieldGroupId,\n      customFieldId,\n    }),\n  );\n\n  let customFieldValue;\n  try {\n    ({ item: customFieldValue } = yield call(\n      request,\n      api.updateCustomFieldValue,\n      cardId,\n      customFieldGroupId,\n      customFieldId,\n      data,\n    ));\n  } catch (error) {\n    yield put(actions.updateCustomFieldValue.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.updateCustomFieldValue.success(localId, customFieldValue));\n}\n\nexport function* handleCustomFieldValueUpdate(customFieldValue) {\n  yield put(actions.handleCustomFieldValueUpdate(customFieldValue));\n}\n\nexport function* deleteCustomFieldValue(cardId, customFieldGroupId, customFieldId) {\n  const id = buildCustomFieldValueId({\n    cardId,\n    customFieldGroupId,\n    customFieldId,\n  });\n\n  yield put(actions.deleteCustomFieldValue(id));\n\n  let customFieldValue;\n  try {\n    ({ item: customFieldValue } = yield call(\n      request,\n      api.deleteCustomFieldValue,\n      cardId,\n      customFieldGroupId,\n      customFieldId,\n    ));\n  } catch (error) {\n    yield put(actions.deleteCustomFieldValue.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteCustomFieldValue.success(customFieldValue));\n}\n\nexport function* handleCustomFieldValueDelete(customFieldValue) {\n  yield put(actions.handleCustomFieldValueDelete(customFieldValue));\n}\n\nexport default {\n  updateCustomFieldValue,\n  handleCustomFieldValueUpdate,\n  deleteCustomFieldValue,\n  handleCustomFieldValueDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/custom-fields.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\n\nexport function* createCustomFieldInBaseGroup(baseCustomFieldGroupId, data) {\n  const localId = yield call(createLocalId);\n\n  const nextData = {\n    ...data,\n    position: yield select(\n      selectors.selectNextCustomFieldPositionInBaseGroup,\n      baseCustomFieldGroupId,\n    ),\n  };\n\n  yield put(\n    actions.createCustomField({\n      ...nextData,\n      baseCustomFieldGroupId,\n      id: localId,\n    }),\n  );\n\n  let customField;\n  try {\n    ({ item: customField } = yield call(\n      request,\n      api.createCustomFieldInBaseGroup,\n      baseCustomFieldGroupId,\n      nextData,\n    ));\n  } catch (error) {\n    yield put(actions.createCustomField.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createCustomField.success(localId, customField));\n}\n\nexport function* createCustomFieldInGroup(customFieldGroupId, data) {\n  const localId = yield call(createLocalId);\n\n  const nextData = {\n    ...data,\n    position: yield select(selectors.selectNextCustomFieldPositionInGroup, customFieldGroupId),\n  };\n\n  yield put(\n    actions.createCustomField({\n      ...nextData,\n      customFieldGroupId,\n      id: localId,\n    }),\n  );\n\n  let customField;\n  try {\n    ({ item: customField } = yield call(\n      request,\n      api.createCustomFieldInGroup,\n      customFieldGroupId,\n      nextData,\n    ));\n  } catch (error) {\n    yield put(actions.createCustomField.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createCustomField.success(localId, customField));\n}\n\nexport function* handleCustomFieldCreate(customField) {\n  yield put(actions.handleCustomFieldCreate(customField));\n}\n\nexport function* updateCustomField(id, data) {\n  yield put(actions.updateCustomField(id, data));\n\n  let customField;\n  try {\n    ({ item: customField } = yield call(request, api.updateCustomField, id, data));\n  } catch (error) {\n    yield put(actions.updateCustomField.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateCustomField.success(customField));\n}\n\nexport function* handleCustomFieldUpdate(customField) {\n  yield put(actions.handleCustomFieldUpdate(customField));\n}\n\nexport function* moveCustomField(id, index) {\n  const customField = yield select(selectors.selectCustomFieldById, id);\n\n  let position;\n  if (customField.baseCustomFieldGroupId) {\n    position = yield select(\n      selectors.selectNextCustomFieldPositionInBaseGroup,\n      customField.baseCustomFieldGroupId,\n      index,\n      id,\n    );\n  } else if (customField.customFieldGroupId) {\n    position = yield select(\n      selectors.selectNextCustomFieldPositionInGroup,\n      customField.customFieldGroupId,\n      index,\n      id,\n    );\n  }\n\n  yield call(updateCustomField, id, {\n    position,\n  });\n}\n\nexport function* deleteCustomField(id) {\n  yield put(actions.deleteCustomField(id));\n\n  let customField;\n  try {\n    ({ item: customField } = yield call(request, api.deleteCustomField, id));\n  } catch (error) {\n    yield put(actions.deleteCustomField.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteCustomField.success(customField));\n}\n\nexport function* handleCustomFieldDelete(customField) {\n  yield put(actions.handleCustomFieldDelete(customField));\n}\n\nexport default {\n  createCustomFieldInBaseGroup,\n  createCustomFieldInGroup,\n  handleCustomFieldCreate,\n  updateCustomField,\n  handleCustomFieldUpdate,\n  moveCustomField,\n  deleteCustomField,\n  handleCustomFieldDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport router from './router';\nimport socket from './socket';\nimport bootstrap from './bootstrap';\nimport core from './core';\nimport modals from './modals';\nimport config from './config';\nimport webhooks from './webhooks';\nimport users from './users';\nimport projects from './projects';\nimport projectManagers from './project-managers';\nimport backgroundImages from './background-images';\nimport baseCustomFieldGroups from './base-custom-field-groups';\nimport boards from './boards';\nimport boardMemberships from './board-memberships';\nimport labels from './labels';\nimport lists from './lists';\nimport cards from './cards';\nimport taskLists from './task-lists';\nimport tasks from './tasks';\nimport attachments from './attachments';\nimport customFieldGroups from './custom-field-groups';\nimport customFields from './custom-fields';\nimport customFieldValues from './custom-field-values';\nimport comments from './comments';\nimport activities from './activities';\nimport notifications from './notifications';\nimport notificationServices from './notification-services';\n\nexport default {\n  ...router,\n  ...socket,\n  ...bootstrap,\n  ...core,\n  ...modals,\n  ...config,\n  ...webhooks,\n  ...users,\n  ...projects,\n  ...projectManagers,\n  ...backgroundImages,\n  ...baseCustomFieldGroups,\n  ...boards,\n  ...boardMemberships,\n  ...labels,\n  ...lists,\n  ...cards,\n  ...taskLists,\n  ...tasks,\n  ...attachments,\n  ...customFieldGroups,\n  ...customFields,\n  ...customFieldValues,\n  ...comments,\n  ...activities,\n  ...notifications,\n  ...notificationServices,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/labels.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\n\nexport function* createLabel(boardId, data) {\n  const localId = yield call(createLocalId);\n\n  const nextData = {\n    ...data,\n    position: yield select(selectors.selectNextLabelPosition, boardId),\n  };\n\n  yield put(\n    actions.createLabel({\n      ...nextData,\n      boardId,\n      id: localId,\n    }),\n  );\n\n  let label;\n  try {\n    ({ item: label } = yield call(request, api.createLabel, boardId, nextData));\n  } catch (error) {\n    yield put(actions.createLabel.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createLabel.success(localId, label));\n}\n\nexport function* createLabelInCurrentBoard(data) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(createLabel, boardId, data);\n}\n\nexport function* createLabelFromCard(cardId, data) {\n  const localId = yield call(createLocalId);\n  const { boardId } = yield select(selectors.selectCardById, cardId);\n\n  const nextData = {\n    ...data,\n    position: yield select(selectors.selectNextLabelPosition, boardId),\n  };\n\n  yield put(\n    actions.createLabelFromCard(cardId, {\n      ...nextData,\n      boardId,\n      id: localId,\n    }),\n  );\n\n  let label;\n  try {\n    ({ item: label } = yield call(request, api.createLabel, boardId, nextData));\n  } catch (error) {\n    yield put(actions.createLabelFromCard.failure(cardId, localId, error));\n    return;\n  }\n\n  let cardLabel;\n  try {\n    ({ item: cardLabel } = yield call(request, api.createCardLabel, cardId, {\n      labelId: label.id,\n    }));\n  } catch (error) {\n    yield put(actions.createLabelFromCard.failure(cardId, localId, error));\n    return;\n  }\n\n  yield put(actions.createLabelFromCard.success(localId, label, cardLabel));\n}\n\nexport function* handleLabelCreate(label) {\n  yield put(actions.handleLabelCreate(label));\n}\n\nexport function* updateLabel(id, data) {\n  yield put(actions.updateLabel(id, data));\n\n  let label;\n  try {\n    ({ item: label } = yield call(request, api.updateLabel, id, data));\n  } catch (error) {\n    yield put(actions.updateLabel.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateLabel.success(label));\n}\n\nexport function* handleLabelUpdate(label) {\n  yield put(actions.handleLabelUpdate(label));\n}\n\nexport function* moveLabel(id, index) {\n  const { boardId } = yield select(selectors.selectLabelById, id);\n  const position = yield select(selectors.selectNextLabelPosition, boardId, index, id);\n\n  yield call(updateLabel, id, {\n    position,\n  });\n}\n\nexport function* deleteLabel(id) {\n  yield put(actions.deleteLabel(id));\n\n  let label;\n  try {\n    ({ item: label } = yield call(request, api.deleteLabel, id));\n  } catch (error) {\n    yield put(actions.deleteLabel.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteLabel.success(label));\n}\n\nexport function* handleLabelDelete(label) {\n  yield put(actions.handleLabelDelete(label));\n}\n\nexport function* addLabelToCard(id, cardId) {\n  yield put(actions.addLabelToCard(id, cardId));\n\n  let cardLabel;\n  try {\n    ({ item: cardLabel } = yield call(request, api.createCardLabel, cardId, {\n      labelId: id,\n    }));\n  } catch (error) {\n    yield put(actions.addLabelToCard.failure(id, cardId, error));\n    return;\n  }\n\n  yield put(actions.addLabelToCard.success(cardLabel));\n}\n\nexport function* addLabelToCurrentCard(id) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(addLabelToCard, id, cardId);\n}\n\nexport function* handleLabelToCardAdd(cardLabel) {\n  yield put(actions.handleLabelToCardAdd(cardLabel));\n}\n\nexport function* removeLabelFromCard(id, cardId) {\n  yield put(actions.removeLabelFromCard(id, cardId));\n\n  let cardLabel;\n  try {\n    ({ item: cardLabel } = yield call(request, api.deleteCardLabel, cardId, id));\n  } catch (error) {\n    yield put(actions.removeLabelFromCard.failure(id, cardId, error));\n    return;\n  }\n\n  yield put(actions.removeLabelFromCard.success(cardLabel));\n}\n\nexport function* removeLabelFromCurrentCard(id) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(removeLabelFromCard, id, cardId);\n}\n\nexport function* handleLabelFromCardRemove(cardLabel) {\n  yield put(actions.handleLabelFromCardRemove(cardLabel));\n}\n\nexport function* addLabelToBoardFilter(id, boardId) {\n  const currentListId = yield select(selectors.selectCurrentListId);\n\n  yield put(actions.addLabelToBoardFilter(id, boardId, currentListId));\n}\n\nexport function* addLabelToFilterInCurrentBoard(id) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(addLabelToBoardFilter, id, boardId);\n}\n\nexport function* removeLabelFromBoardFilter(id, boardId) {\n  const currentListId = yield select(selectors.selectCurrentListId);\n\n  yield put(actions.removeLabelFromBoardFilter(id, boardId, currentListId));\n}\n\nexport function* removeLabelFromFilterInCurrentBoard(id) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(removeLabelFromBoardFilter, id, boardId);\n}\n\nexport default {\n  createLabel,\n  createLabelInCurrentBoard,\n  createLabelFromCard,\n  handleLabelCreate,\n  updateLabel,\n  handleLabelUpdate,\n  moveLabel,\n  deleteLabel,\n  handleLabelDelete,\n  addLabelToCard,\n  addLabelToCurrentCard,\n  handleLabelToCardAdd,\n  removeLabelFromCard,\n  removeLabelFromCurrentCard,\n  handleLabelFromCardRemove,\n  addLabelToBoardFilter,\n  addLabelToFilterInCurrentBoard,\n  removeLabelFromBoardFilter,\n  removeLabelFromFilterInCurrentBoard,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\nimport toast from 'react-hot-toast';\n\nimport { goToBoard } from './router';\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\nimport ToastTypes from '../../../constants/ToastTypes';\n\nexport function* createList(boardId, data) {\n  const localId = yield call(createLocalId);\n\n  const nextData = {\n    ...data,\n    position: yield select(selectors.selectNextListPosition, boardId),\n  };\n\n  yield put(\n    actions.createList({\n      ...nextData,\n      boardId,\n      id: localId,\n    }),\n  );\n\n  let list;\n  try {\n    ({ item: list } = yield call(request, api.createList, boardId, nextData));\n  } catch (error) {\n    yield put(actions.createList.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createList.success(localId, list));\n}\n\nexport function* createListInCurrentBoard(data) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(createList, boardId, data);\n}\n\nexport function* handleListCreate(list) {\n  yield put(actions.handleListCreate(list));\n}\n\nexport function* updateList(id, data) {\n  yield put(actions.updateList(id, data));\n\n  let list;\n  try {\n    ({ item: list } = yield call(request, api.updateList, id, data));\n  } catch (error) {\n    yield put(actions.updateList.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateList.success(list));\n}\n\nexport function* handleListUpdate(list) {\n  const currentCard = yield select(selectors.selectCurrentCard);\n\n  let fetch = false;\n  if (list.boardId) {\n    const isAvailableForCurrentUser = yield select(\n      selectors.selectIsListWithIdAvailableForCurrentUser,\n      list.id,\n    );\n\n    fetch = !isAvailableForCurrentUser;\n  }\n\n  let users;\n  let cards;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields;\n  let customFieldValues;\n\n  if (fetch) {\n    try {\n      ({\n        item: list, // eslint-disable-line no-param-reassign\n        included: {\n          users,\n          cards,\n          cardMemberships,\n          cardLabels,\n          taskLists,\n          tasks,\n          attachments,\n          customFieldGroups,\n          customFields,\n          customFieldValues,\n        },\n      } = yield call(request, api.getList, list.id));\n    } catch {\n      fetch = false;\n    }\n  }\n\n  yield put(\n    actions.handleListUpdate(\n      list,\n      fetch,\n      users,\n      cards,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFields,\n      customFieldValues,\n    ),\n  );\n\n  if (list.boardId === null && currentCard && list.id === currentCard.listId) {\n    yield call(goToBoard, currentCard.boardId);\n  }\n}\n\nexport function* moveList(id, index) {\n  const { boardId } = yield select(selectors.selectListById, id);\n  const position = yield select(selectors.selectNextListPosition, boardId, index, id);\n\n  yield call(updateList, id, {\n    position,\n  });\n}\n\nexport function* transferList(id, boardId) {\n  const currentCard = yield select(selectors.selectCurrentCard);\n\n  // TODO: hack?\n  if (currentCard && id === currentCard.listId) {\n    yield call(goToBoard, currentCard.boardId);\n  }\n\n  yield call(updateList, id, {\n    boardId,\n  });\n}\n\nexport function* sortList(id, data) {\n  yield put(actions.sortList(id, data));\n\n  let list;\n  let cards;\n\n  try {\n    ({\n      item: list,\n      included: { cards },\n    } = yield call(request, api.sortList, id, data));\n  } catch (error) {\n    yield put(actions.sortList.failure(id, error));\n    return;\n  }\n\n  yield put(actions.sortList.success(list, cards));\n}\n\nexport function* moveListCards(id, nextId) {\n  const cardIds = yield select(selectors.selectCardIdsByListId, id);\n\n  if (cardIds.length === 0) {\n    return;\n  }\n\n  yield put(actions.moveListCards(id, nextId, cardIds));\n\n  let list;\n  let cards;\n  let activities;\n\n  try {\n    ({\n      item: list,\n      included: { cards, activities },\n    } = yield call(request, api.moveListCards, id, {\n      listId: nextId,\n    }));\n  } catch (error) {\n    yield put(actions.moveListCards.failure(id, error));\n    return;\n  }\n\n  yield put(actions.moveListCards.success(list, cards, activities));\n}\n\nexport function* moveListCardsToArchiveList(id) {\n  const archiveListId = yield select(selectors.selectArchiveListIdForCurrentBoard);\n\n  yield call(moveListCards, id, archiveListId);\n}\n\nexport function* clearTrashListInCurrentBoard() {\n  const trashListId = yield select(selectors.selectTrashListIdForCurrentBoard);\n\n  yield put(actions.clearList(trashListId));\n\n  yield call(toast, {\n    type: ToastTypes.EMPTY_TRASH,\n    params: {\n      listId: trashListId,\n    },\n  });\n\n  let list;\n  try {\n    ({ item: list } = yield call(request, api.clearList, trashListId));\n  } catch (error) {\n    yield put(actions.clearList.failure(trashListId, error));\n    return;\n  }\n\n  yield put(actions.clearList.success(list));\n}\n\nexport function* handleListClear(list) {\n  yield put(actions.handleListClear(list));\n}\n\nexport function* deleteList(id) {\n  const trashListId = yield select(selectors.selectTrashListIdForCurrentBoard);\n  const cardIds = yield select(selectors.selectCardIdsByListId, id);\n\n  yield put(actions.deleteList(id, trashListId, cardIds));\n\n  let list;\n  let cards;\n\n  try {\n    ({\n      item: list,\n      included: { cards },\n    } = yield call(request, api.deleteList, id));\n  } catch (error) {\n    yield put(actions.deleteList.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteList.success(list, cards));\n}\n\nexport function* handleListDelete(list, cards) {\n  yield put(actions.handleListDelete(list, cards));\n}\n\nexport default {\n  createList,\n  createListInCurrentBoard,\n  handleListCreate,\n  updateList,\n  handleListUpdate,\n  moveList,\n  transferList,\n  sortList,\n  moveListCardsToArchiveList,\n  clearTrashListInCurrentBoard,\n  handleListClear,\n  deleteList,\n  handleListDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/modals.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { put } from 'redux-saga/effects';\n\nimport actions from '../../../actions';\n\nexport function* openModal(type, params) {\n  yield put(actions.openModal(type, params));\n}\n\nexport function* closeModal() {\n  yield put(actions.closeModal());\n}\n\nexport default {\n  openModal,\n  closeModal,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/notification-services.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\n\nexport function* createNotificationServiceInCurrentUser(data) {\n  const localId = yield call(createLocalId);\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield put(\n    actions.createNotificationService({\n      ...data,\n      id: localId,\n      userId: currentUserId,\n    }),\n  );\n\n  let notificationService;\n  try {\n    ({ item: notificationService } = yield call(\n      request,\n      api.createUserNotificationService,\n      currentUserId,\n      data,\n    ));\n  } catch (error) {\n    yield put(actions.createNotificationService.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createNotificationService.success(localId, notificationService));\n}\n\nexport function* createNotificationServiceInBoard(boardId, data) {\n  const localId = yield call(createLocalId);\n\n  yield put(\n    actions.createNotificationService({\n      ...data,\n      boardId,\n      id: localId,\n    }),\n  );\n\n  let notificationService;\n  try {\n    ({ item: notificationService } = yield call(\n      request,\n      api.createBoardNotificationService,\n      boardId,\n      data,\n    ));\n  } catch (error) {\n    yield put(actions.createNotificationService.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createNotificationService.success(localId, notificationService));\n}\n\nexport function* handleNotificationServiceCreate(notificationService) {\n  yield put(actions.handleNotificationServiceCreate(notificationService));\n}\n\nexport function* updateNotificationService(id, data) {\n  yield put(actions.updateNotificationService(id, data));\n\n  let notificationService;\n  try {\n    ({ item: notificationService } = yield call(request, api.updateNotificationService, id, data));\n  } catch (error) {\n    yield put(actions.updateNotificationService.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateNotificationService.success(notificationService));\n}\n\nexport function* handleNotificationServiceUpdate(notificationService) {\n  yield put(actions.handleNotificationServiceUpdate(notificationService));\n}\n\nexport function* testNotificationService(id) {\n  yield put(actions.testNotificationService(id));\n\n  let notificationService;\n  try {\n    ({ item: notificationService } = yield call(request, api.testNotificationService, id));\n  } catch (error) {\n    yield put(actions.testNotificationService.failure(id, error));\n    return;\n  }\n\n  yield put(actions.testNotificationService.success(notificationService));\n}\n\nexport function* deleteNotificationService(id) {\n  yield put(actions.deleteNotificationService(id));\n\n  let notificationService;\n  try {\n    ({ item: notificationService } = yield call(request, api.deleteNotificationService, id));\n  } catch (error) {\n    yield put(actions.deleteNotificationService.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteNotificationService.success(notificationService));\n}\n\nexport function* handleNotificationServiceDelete(notificationService) {\n  yield put(actions.handleNotificationServiceDelete(notificationService));\n}\n\nexport default {\n  createNotificationServiceInCurrentUser,\n  createNotificationServiceInBoard,\n  handleNotificationServiceCreate,\n  updateNotificationService,\n  handleNotificationServiceUpdate,\n  testNotificationService,\n  deleteNotificationService,\n  handleNotificationServiceDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/notifications.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\n\nexport function* deleteAllNotifications() {\n  yield put(actions.deleteAllNotifications());\n\n  let notifications;\n  try {\n    ({ items: notifications } = yield call(request, api.readAllNotifications));\n  } catch (error) {\n    yield put(actions.deleteAllNotifications.failure(error));\n    return;\n  }\n\n  yield put(actions.deleteAllNotifications.success(notifications));\n}\n\nexport function* handleNotificationCreate(notification, users) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  if (notification.cardId === cardId) {\n    try {\n      yield call(request, api.updateNotification, notification.id, {\n        isRead: true,\n      });\n    } catch {\n      /* empty */\n    }\n  } else {\n    yield put(actions.handleNotificationCreate(notification, users));\n  }\n}\n\nexport function* deleteNotification(id) {\n  yield put(actions.deleteNotification(id));\n\n  let notification;\n  try {\n    ({ item: notification } = yield call(request, api.updateNotification, id, {\n      isRead: true,\n    }));\n  } catch (error) {\n    yield put(actions.deleteNotification.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteNotification.success(notification));\n}\n\nexport function* handleNotificationDelete(notification) {\n  yield put(actions.handleNotificationDelete(notification));\n}\n\nexport default {\n  deleteAllNotifications,\n  handleNotificationCreate,\n  deleteNotification,\n  handleNotificationDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/project-managers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport requests from '../requests';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\nimport mergeRecords from '../../../utils/merge-records';\n\nexport function* createProjectManager(projectId, data) {\n  const localId = yield call(createLocalId);\n\n  yield put(\n    actions.createProjectManager({\n      ...data,\n      projectId,\n      id: localId,\n    }),\n  );\n\n  let projectManager;\n  try {\n    ({ item: projectManager } = yield call(request, api.createProjectManager, projectId, data));\n  } catch (error) {\n    yield put(actions.createProjectManager.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createProjectManager.success(localId, projectManager));\n}\n\nexport function* createManagerInCurrentProject(data) {\n  const { projectId } = yield select(selectors.selectPath);\n\n  yield call(createProjectManager, projectId, data);\n}\n\nexport function* handleProjectManagerCreate(projectManager, users) {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n  const isCurrentUser = projectManager.userId === currentUserId;\n\n  let project;\n  let board;\n  let users1;\n  let users2;\n  let projectManagers;\n  let backgroundImages;\n  let baseCustomFieldGroups;\n  let boards;\n  let boardMemberships1;\n  let boardMemberships2;\n  let labels;\n  let lists;\n  let cards;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields1;\n  let customFields2;\n  let customFieldValues;\n  let notificationsToDelete;\n  let notificationServices;\n\n  if (isCurrentUser) {\n    const { boardId } = yield select(selectors.selectPath);\n\n    const isExternalAccessibleForCurrentUser = yield select(\n      selectors.selectIsProjectWithIdExternalAccessibleForCurrentUser,\n      projectManager.projectId,\n    );\n\n    try {\n      ({\n        item: project,\n        included: {\n          projectManagers,\n          backgroundImages,\n          baseCustomFieldGroups,\n          boards,\n          notificationServices,\n          users: users1,\n          boardMemberships: boardMemberships1,\n          customFields: customFields1,\n        },\n      } = yield call(request, api.getProject, projectManager.projectId));\n    } catch {\n      return;\n    }\n\n    if (boardId === null && !isExternalAccessibleForCurrentUser) {\n      let body;\n      try {\n        body = yield call(requests.fetchBoardByCurrentPath);\n      } catch {\n        /* empty */\n      }\n\n      if (body) {\n        ({\n          project,\n          board,\n          labels,\n          lists,\n          cards,\n          cardMemberships,\n          cardLabels,\n          taskLists,\n          tasks,\n          attachments,\n          customFieldGroups,\n          customFieldValues,\n          users: users2,\n          boardMemberships: boardMemberships2,\n          customFields: customFields2,\n        } = body);\n\n        if (body.card) {\n          notificationsToDelete = yield select(selectors.selectNotificationsByCardId, body.card.id);\n        }\n      }\n    }\n  }\n\n  const boardIds = yield select(selectors.selectBoardIdsByProjectId, projectManager.projectId);\n\n  const isProjectAvailable = yield select(\n    selectors.selectIsProjectWithIdAvailableForCurrentUser,\n    projectManager.projectId,\n  );\n\n  yield put(\n    actions.handleProjectManagerCreate(\n      projectManager,\n      boardIds,\n      isCurrentUser,\n      isProjectAvailable,\n      project,\n      board,\n      mergeRecords(users, users1, users2),\n      projectManagers,\n      backgroundImages,\n      baseCustomFieldGroups,\n      boards,\n      mergeRecords(boardMemberships1, boardMemberships2),\n      labels,\n      lists,\n      cards,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      mergeRecords(customFields1, customFields2),\n      customFieldValues,\n      notificationsToDelete,\n      notificationServices,\n    ),\n  );\n}\n\nexport function* deleteProjectManager(id) {\n  let projectManager = yield select(selectors.selectProjectManagerById, id);\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield put(actions.deleteProjectManager(id));\n\n  if (projectManager.userId === currentUserId) {\n    const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);\n\n    if (!isAvailableForCurrentUser) {\n      yield put(actions.closeModal());\n    }\n  }\n\n  try {\n    ({ item: projectManager } = yield call(request, api.deleteProjectManager, id));\n  } catch (error) {\n    yield put(actions.deleteProjectManager.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteProjectManager.success(projectManager));\n}\n\nexport function* handleProjectManagerDelete(projectManager) {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield put(actions.handleProjectManagerDelete(projectManager));\n\n  if (projectManager.userId === currentUserId) {\n    const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);\n\n    if (!isAvailableForCurrentUser) {\n      yield put(actions.closeModal());\n    }\n  }\n}\n\nexport default {\n  createProjectManager,\n  createManagerInCurrentProject,\n  handleProjectManagerCreate,\n  deleteProjectManager,\n  handleProjectManagerDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/projects.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport omit from 'lodash/omit';\nimport { call, put, select } from 'redux-saga/effects';\n\nimport { goToProject, goToRoot } from './router';\nimport request from '../request';\nimport requests from '../requests';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport mergeRecords from '../../../utils/merge-records';\nimport { UserRoles } from '../../../constants/Enums';\n\nexport function* searchProjects(value) {\n  yield put(actions.searchProjects(value));\n}\n\nexport function* updateProjectsOrder(value) {\n  yield put(actions.updateProjectsOrder(value));\n\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  try {\n    yield call(request, api.updateUser, currentUserId, {\n      defaultProjectsOrder: value,\n    });\n  } catch {\n    /* empty */\n  }\n}\n\nexport function* toggleHiddenProjects(isVisible) {\n  yield put(actions.toggleHiddenProjects(isVisible));\n}\n\nexport function* createProject(data) {\n  yield put(actions.createProject(omit(data, 'type')));\n\n  let project;\n  let projectManagers;\n\n  try {\n    ({\n      item: project,\n      included: { projectManagers },\n    } = yield call(request, api.createProject, data));\n  } catch (error) {\n    yield put(actions.createProject.failure(error));\n    return;\n  }\n\n  yield put(actions.createProject.success(project, projectManagers));\n  yield call(goToProject, project.id);\n}\n\nexport function* handleProjectCreate({ id }) {\n  let project;\n  let users;\n  let projectManagers;\n  let backgroundImages;\n  let baseCustomFieldGroups;\n  let boards;\n  let boardMemberships;\n  let customFields;\n  let notificationServices;\n\n  try {\n    ({\n      item: project,\n      included: {\n        users,\n        projectManagers,\n        backgroundImages,\n        baseCustomFieldGroups,\n        boards,\n        boardMemberships,\n        customFields,\n        notificationServices,\n      },\n    } = yield call(request, api.getProject, id));\n  } catch {\n    return;\n  }\n\n  yield put(\n    actions.handleProjectCreate(\n      project,\n      users,\n      projectManagers,\n      backgroundImages,\n      baseCustomFieldGroups,\n      boards,\n      boardMemberships,\n      customFields,\n      notificationServices,\n    ),\n  );\n}\n\nexport function* updateProject(id, data) {\n  yield put(actions.updateProject(id, data));\n\n  const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);\n\n  if (!isAvailableForCurrentUser) {\n    yield put(actions.closeModal());\n  }\n\n  let project;\n  try {\n    ({ item: project } = yield call(request, api.updateProject, id, data));\n  } catch (error) {\n    yield put(actions.updateProject.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateProject.success(project));\n}\n\nexport function* updateCurrentProject(data) {\n  const { projectId } = yield select(selectors.selectPath);\n\n  yield call(updateProject, projectId, data);\n}\n\nexport function* handleProjectUpdate(project) {\n  const prevProject = yield select(selectors.selectProjectById, project.id);\n\n  const isChangedToShared =\n    (!prevProject || !!prevProject.ownerProjectManagerId) && !project.ownerProjectManagerId;\n\n  const currentUser = yield select(selectors.selectCurrentUser);\n  const isCurrentUserAdmin = currentUser.role === UserRoles.ADMIN;\n\n  const isExternalAccessibleForCurrentUser = yield select(\n    selectors.selectIsProjectWithIdExternalAccessibleForCurrentUser,\n    project.id,\n  );\n\n  let board;\n  let users1;\n  let users2;\n  let projectManagers;\n  let backgroundImages;\n  let baseCustomFieldGroups;\n  let boards;\n  let boardMemberships1;\n  let boardMemberships2;\n  let labels;\n  let lists;\n  let cards;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields1;\n  let customFields2;\n  let customFieldValues;\n  let notificationsToDelete;\n  let notificationServices;\n\n  if (isCurrentUserAdmin && isChangedToShared && !isExternalAccessibleForCurrentUser) {\n    const { boardId } = yield select(selectors.selectPath);\n\n    try {\n      ({\n        item: project, // eslint-disable-line no-param-reassign\n        included: {\n          projectManagers,\n          backgroundImages,\n          baseCustomFieldGroups,\n          boards,\n          notificationServices,\n          users: users1,\n          boardMemberships: boardMemberships1,\n          customFields: customFields1,\n        },\n      } = yield call(request, api.getProject, project.id));\n    } catch {\n      return;\n    }\n\n    if (boardId === null) {\n      let body;\n      try {\n        body = yield call(requests.fetchBoardByCurrentPath);\n      } catch {\n        /* empty */\n      }\n\n      if (body) {\n        ({\n          board,\n          labels,\n          lists,\n          cards,\n          cardMemberships,\n          cardLabels,\n          taskLists,\n          tasks,\n          attachments,\n          customFieldGroups,\n          customFieldValues,\n          users: users2,\n          boardMemberships: boardMemberships2,\n          customFields: customFields2,\n        } = body);\n\n        if (body.card) {\n          notificationsToDelete = yield select(selectors.selectNotificationsByCardId, body.card.id);\n        }\n      }\n    }\n  }\n\n  const boardIds = yield select(selectors.selectBoardIdsByProjectId, project.id);\n\n  const isAvailable = yield select(\n    selectors.selectIsProjectWithIdAvailableForCurrentUser,\n    project.id,\n  );\n\n  yield put(\n    actions.handleProjectUpdate(\n      project,\n      boardIds,\n      isAvailable,\n      board,\n      mergeRecords(users1, users2),\n      projectManagers,\n      backgroundImages,\n      baseCustomFieldGroups,\n      boards,\n      mergeRecords(boardMemberships1, boardMemberships2),\n      labels,\n      lists,\n      cards,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      mergeRecords(customFields1, customFields2),\n      customFieldValues,\n      notificationsToDelete,\n      notificationServices,\n    ),\n  );\n\n  const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);\n\n  if (!isAvailableForCurrentUser) {\n    yield put(actions.closeModal());\n  }\n}\n\nexport function* deleteProject(id) {\n  const { projectId } = yield select(selectors.selectPath);\n\n  yield put(actions.deleteProject(id));\n\n  if (id === projectId) {\n    yield call(goToRoot);\n  }\n\n  let project;\n  try {\n    ({ item: project } = yield call(request, api.deleteProject, id));\n  } catch (error) {\n    yield put(actions.deleteProject.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteProject.success(project));\n}\n\nexport function* deleteCurrentProject() {\n  const { projectId } = yield select(selectors.selectPath);\n\n  yield call(deleteProject, projectId);\n}\n\nexport function* handleProjectDelete(project) {\n  const { projectId } = yield select(selectors.selectPath);\n\n  yield put(actions.handleProjectDelete(project));\n\n  if (project.id === projectId) {\n    yield call(goToRoot);\n  }\n}\n\nexport default {\n  searchProjects,\n  updateProjectsOrder,\n  toggleHiddenProjects,\n  createProject,\n  handleProjectCreate,\n  updateProject,\n  updateCurrentProject,\n  handleProjectUpdate,\n  deleteProject,\n  deleteCurrentProject,\n  handleProjectDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/router.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select, take } from 'redux-saga/effects';\nimport { push } from '../../../lib/redux-router';\n\nimport { logout } from './core';\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { getAccessToken } from '../../../utils/access-token-storage';\nimport mergeRecords from '../../../utils/merge-records';\nimport ActionTypes from '../../../constants/ActionTypes';\nimport Paths from '../../../constants/Paths';\n\nexport function* goTo(pathname) {\n  yield put(push(pathname));\n}\n\nexport function* goToRoot() {\n  yield call(goTo, Paths.ROOT);\n}\n\nexport function* goToProject(projectId) {\n  yield call(goTo, Paths.PROJECTS.replace(':id', projectId));\n}\n\nexport function* goToBoard(boardId) {\n  yield call(goTo, Paths.BOARDS.replace(':id', boardId));\n}\n\nexport function* goToCard(cardId) {\n  yield call(goTo, Paths.CARDS.replace(':id', cardId));\n}\n\nexport function* handleLocationChange() {\n  const accessToken = yield call(getAccessToken);\n\n  if (!accessToken) {\n    yield call(logout, false);\n    return;\n  }\n\n  const pathsMatch = yield select(selectors.selectPathsMatch);\n\n  if (!pathsMatch) {\n    yield put(actions.handleLocationChange());\n    return;\n  }\n\n  switch (pathsMatch.pattern.path) {\n    case Paths.LOGIN:\n    case Paths.OIDC_CALLBACK:\n      yield call(goToRoot);\n\n      break;\n    default:\n  }\n\n  const isInitializing = yield select(selectors.selectIsInitializing);\n\n  if (isInitializing) {\n    yield take(ActionTypes.CORE_INITIALIZE);\n  }\n\n  let currentBoard = yield select(selectors.selectCurrentBoard);\n\n  let currentBoardId = null;\n  let currentCardId = null;\n  let isEditModeEnabled;\n  let board;\n  let card;\n  let users1;\n  let users2;\n  let projects;\n  let boardMemberships;\n  let labels;\n  let lists;\n  let cards;\n  let cardMemberships1;\n  let cardMemberships2;\n  let cardLabels1;\n  let cardLabels2;\n  let taskLists1;\n  let taskLists2;\n  let tasks1;\n  let tasks2;\n  let attachments1;\n  let attachments2;\n  let customFieldGroups1;\n  let customFieldGroups2;\n  let customFields1;\n  let customFields2;\n  let customFieldValues1;\n  let customFieldValues2;\n  let notificationsToDelete;\n\n  switch (pathsMatch.pattern.path) {\n    case Paths.ROOT:\n      isEditModeEnabled = false;\n\n      break;\n    case Paths.PROJECTS: {\n      const boardIds = yield select(selectors.selectBoardIdsForCurrentProject);\n\n      if (boardIds && boardIds.length === 0) {\n        isEditModeEnabled = true;\n      }\n\n      break;\n    }\n    case Paths.BOARDS:\n      if (currentBoard) {\n        ({ id: currentBoardId } = currentBoard);\n\n        if (currentBoard.isFetching === null) {\n          yield put(actions.handleLocationChange.fetchBoard(currentBoard.id));\n\n          try {\n            ({\n              item: board,\n              included: {\n                projects,\n                boardMemberships,\n                labels,\n                lists,\n                cards,\n                users: users1,\n                cardMemberships: cardMemberships1,\n                cardLabels: cardLabels1,\n                taskLists: taskLists1,\n                tasks: tasks1,\n                attachments: attachments1,\n                customFieldGroups: customFieldGroups1,\n                customFields: customFields1,\n                customFieldValues: customFieldValues1,\n              },\n            } = yield call(request, api.getBoard, currentBoard.id, true));\n          } catch {\n            /* empty */\n          }\n        }\n      }\n\n      break;\n    case Paths.CARDS:\n      ({ cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath));\n\n      if (!currentCardId) {\n        yield put(actions.handleLocationChange.fetchContent());\n\n        try {\n          ({\n            item: card,\n            included: {\n              users: users1,\n              cardMemberships: cardMemberships1,\n              cardLabels: cardLabels1,\n              taskLists: taskLists1,\n              tasks: tasks1,\n              attachments: attachments1,\n              customFieldGroups: customFieldGroups1,\n              customFields: customFields1,\n              customFieldValues: customFieldValues1,\n            },\n          } = yield call(request, api.getCard, pathsMatch.params.id));\n        } catch {\n          /* empty */\n        }\n\n        if (card) {\n          ({ id: currentCardId } = card);\n\n          currentBoard = yield select(selectors.selectBoardById, card.boardId);\n\n          if (currentBoard) {\n            ({ id: currentBoardId } = currentBoard);\n\n            if (currentBoard.isFetching === null) {\n              try {\n                ({\n                  item: board,\n                  included: {\n                    projects,\n                    boardMemberships,\n                    labels,\n                    lists,\n                    cards,\n                    users: users2,\n                    cardMemberships: cardMemberships2,\n                    cardLabels: cardLabels2,\n                    taskLists: taskLists2,\n                    tasks: tasks2,\n                    attachments: attachments2,\n                    customFieldGroups: customFieldGroups2,\n                    customFields: customFields2,\n                    customFieldValues: customFieldValues2,\n                  },\n                } = yield call(request, api.getBoard, card.boardId, true));\n              } catch {\n                /* empty */\n              }\n            }\n          }\n        }\n      }\n\n      if (currentCardId) {\n        const notificationIds = yield select(\n          selectors.selectNotificationIdsByCardId,\n          currentCardId,\n        );\n\n        if (notificationIds.length > 0) {\n          try {\n            ({\n              included: { notifications: notificationsToDelete },\n            } = yield call(request, api.readCardNotifications, currentCardId));\n          } catch {\n            /* empty */\n          }\n        }\n      }\n\n      break;\n    default:\n  }\n\n  yield put(\n    actions.handleLocationChange(\n      pathsMatch.pathname,\n      currentBoardId,\n      currentCardId,\n      isEditModeEnabled,\n      board,\n      mergeRecords(users1, users2),\n      projects,\n      boardMemberships,\n      labels,\n      lists,\n      mergeRecords(card && [card], cards),\n      mergeRecords(cardMemberships1, cardMemberships2),\n      mergeRecords(cardLabels1, cardLabels2),\n      mergeRecords(taskLists1, taskLists2),\n      mergeRecords(tasks1, tasks2),\n      mergeRecords(attachments1, attachments2),\n      mergeRecords(customFieldGroups1, customFieldGroups2),\n      mergeRecords(customFields1, customFields2),\n      mergeRecords(customFieldValues1, customFieldValues2),\n      notificationsToDelete,\n    ),\n  );\n}\n\nexport default {\n  goTo,\n  goToRoot,\n  goToProject,\n  goToBoard,\n  goToCard,\n  handleLocationChange,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/socket.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport requests from '../requests';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\n\nexport function* handleSocketDisconnect() {\n  yield put(actions.handleSocketDisconnect());\n}\n\nexport function* handleSocketReconnect() {\n  const { boardId } = yield select(selectors.selectPath);\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield put(actions.handleSocketReconnect.fetchCore(currentUserId, boardId));\n\n  let bootstrap;\n  let config;\n  let user;\n  let board;\n  let webhooks;\n  let users;\n  let projects;\n  let projectManagers;\n  let backgroundImages;\n  let baseCustomFieldGroups;\n  let boards;\n  let boardMemberships;\n  let labels;\n  let lists;\n  let cards;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields;\n  let customFieldValues;\n  let notifications;\n  let notificationServices;\n\n  try {\n    ({ item: bootstrap } = yield call(request, api.getBootstrap));\n\n    ({\n      user,\n      board,\n      webhooks,\n      users,\n      projects,\n      projectManagers,\n      backgroundImages,\n      baseCustomFieldGroups,\n      boards,\n      boardMemberships,\n      labels,\n      lists,\n      cards,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFields,\n      customFieldValues,\n      notifications,\n      notificationServices,\n    } = yield call(requests.fetchCore));\n  } catch {\n    return;\n  }\n\n  yield put(\n    actions.handleSocketReconnect(\n      bootstrap,\n      config,\n      user,\n      board,\n      webhooks,\n      users,\n      projects,\n      projectManagers,\n      backgroundImages,\n      baseCustomFieldGroups,\n      boards,\n      boardMemberships,\n      labels,\n      lists,\n      cards,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFields,\n      customFieldValues,\n      notifications,\n      notificationServices,\n    ),\n  );\n\n  const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);\n\n  if (!isAvailableForCurrentUser) {\n    yield put(actions.closeModal());\n  }\n}\n\nexport default {\n  handleSocketDisconnect,\n  handleSocketReconnect,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/task-lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\n\nexport function* createTaskList(cardId, data) {\n  const localId = yield call(createLocalId);\n\n  const nextData = {\n    ...data,\n    position: yield select(selectors.selectNextTaskListPosition, cardId),\n  };\n\n  yield put(\n    actions.createTaskList({\n      ...nextData,\n      cardId,\n      id: localId,\n    }),\n  );\n\n  let taskList;\n  try {\n    ({ item: taskList } = yield call(request, api.createTaskList, cardId, nextData));\n  } catch (error) {\n    yield put(actions.createTaskList.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createTaskList.success(localId, taskList));\n}\n\nexport function* createTaskListInCurrentCard(data) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(createTaskList, cardId, data);\n}\n\nexport function* handleTaskListCreate(taskList) {\n  yield put(actions.handleTaskListCreate(taskList));\n}\n\nexport function* updateTaskList(id, data) {\n  yield put(actions.updateTaskList(id, data));\n\n  let taskList;\n  try {\n    ({ item: taskList } = yield call(request, api.updateTaskList, id, data));\n  } catch (error) {\n    yield put(actions.updateTaskList.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateTaskList.success(taskList));\n}\n\nexport function* handleTaskListUpdate(taskList) {\n  yield put(actions.handleTaskListUpdate(taskList));\n}\n\nexport function* moveTaskList(id, index) {\n  const { cardId } = yield select(selectors.selectTaskListById, id);\n  const position = yield select(selectors.selectNextTaskListPosition, cardId, index, id);\n\n  yield call(updateTaskList, id, {\n    position,\n  });\n}\n\nexport function* deleteTaskList(id) {\n  yield put(actions.deleteTaskList(id));\n\n  let taskList;\n  try {\n    ({ item: taskList } = yield call(request, api.deleteTaskList, id));\n  } catch (error) {\n    yield put(actions.deleteTaskList.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteTaskList.success(taskList));\n}\n\nexport function* handleTaskListDelete(taskList) {\n  yield put(actions.handleTaskListDelete(taskList));\n}\n\nexport default {\n  createTaskList,\n  createTaskListInCurrentCard,\n  handleTaskListCreate,\n  updateTaskList,\n  handleTaskListUpdate,\n  moveTaskList,\n  deleteTaskList,\n  handleTaskListDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/tasks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport request from '../request';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\n\nexport function* createTask(taskListId, data) {\n  let isCompleted;\n  if (data.linkedCardId) {\n    ({ isClosed: isCompleted } = yield select(selectors.selectCardById, data.linkedCardId));\n  }\n\n  const localId = yield call(createLocalId);\n\n  const nextData = {\n    ...data,\n    position: yield select(selectors.selectNextTaskPosition, taskListId),\n  };\n\n  yield put(\n    actions.createTask({\n      ...nextData,\n      taskListId,\n      id: localId,\n      ...(isCompleted !== undefined && {\n        isCompleted,\n      }),\n    }),\n  );\n\n  let task;\n  try {\n    ({ item: task } = yield call(request, api.createTask, taskListId, nextData));\n  } catch (error) {\n    yield put(actions.createTask.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createTask.success(localId, task));\n}\n\nexport function* handleTaskCreate(task) {\n  yield put(actions.handleTaskCreate(task));\n}\n\nexport function* updateTask(id, data) {\n  yield put(actions.updateTask(id, data));\n\n  let task;\n  try {\n    ({ item: task } = yield call(request, api.updateTask, id, data));\n  } catch (error) {\n    yield put(actions.updateTask.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateTask.success(task));\n}\n\nexport function* handleTaskUpdate(task) {\n  yield put(actions.handleTaskUpdate(task));\n}\n\nexport function* moveTask(id, taskListId, index) {\n  const position = yield select(selectors.selectNextTaskPosition, taskListId, index, id);\n\n  yield call(updateTask, id, {\n    taskListId,\n    position,\n  });\n}\n\nexport function* deleteTask(id) {\n  yield put(actions.deleteTask(id));\n\n  let task;\n  try {\n    ({ item: task } = yield call(request, api.deleteTask, id));\n  } catch (error) {\n    yield put(actions.deleteTask.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteTask.success(task));\n}\n\nexport function* handleTaskDelete(task) {\n  yield put(actions.handleTaskDelete(task));\n}\n\nexport default {\n  createTask,\n  handleTaskCreate,\n  updateTask,\n  handleTaskUpdate,\n  moveTask,\n  deleteTask,\n  handleTaskDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/users.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select } from 'redux-saga/effects';\n\nimport { changeCoreLanguage, logout } from './core';\nimport request from '../request';\nimport requests from '../requests';\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { setAccessToken } from '../../../utils/access-token-storage';\nimport mergeRecords from '../../../utils/merge-records';\nimport { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';\nimport { UserRoles } from '../../../constants/Enums';\n\nexport function* handleUsersReset() {\n  let users;\n  try {\n    ({ items: users } = yield call(request, api.getUsers));\n  } catch {\n    return;\n  }\n\n  yield put(actions.handleUsersReset(users));\n}\n\nexport function* createUser(data) {\n  yield put(actions.createUser(data));\n\n  let user;\n  try {\n    ({ item: user } = yield call(request, api.createUser, data));\n  } catch (error) {\n    yield put(actions.createUser.failure(error));\n    return;\n  }\n\n  yield put(actions.createUser.success(user));\n}\n\nexport function* handleUserCreate(user) {\n  yield put(actions.handleUserCreate(user));\n}\n\nexport function* clearUserCreateError() {\n  yield put(actions.clearUserCreateError());\n}\n\nexport function* updateUser(id, data) {\n  yield put(actions.updateUser(id, data));\n\n  let user;\n  try {\n    ({ item: user } = yield call(request, api.updateUser, id, data));\n  } catch (error) {\n    yield put(actions.updateUser.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateUser.success(user));\n}\n\nexport function* updateCurrentUser(data) {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield call(updateUser, currentUserId, data);\n}\n\nexport function* handleUserUpdate(user) {\n  const prevUser = yield select(selectors.selectUserById, user.id);\n\n  const isChangedToAdminOrProjectOwner =\n    (!prevUser || (prevUser.role !== user.role && prevUser.role !== UserRoles.ADMIN)) &&\n    isUserAdminOrProjectOwner(user);\n\n  const currentUser = yield select(selectors.selectCurrentUser);\n  const isCurrentUser = user.id === currentUser.id;\n\n  let bootstrap;\n  let config;\n  let board;\n  let webhooks;\n  let users1;\n  let users2;\n  let users3;\n  let projects;\n  let projectManagers;\n  let backgroundImages;\n  let baseCustomFieldGroups;\n  let boards;\n  let boardMemberships1;\n  let boardMemberships2;\n  let labels;\n  let lists;\n  let cards;\n  let cardMemberships;\n  let cardLabels;\n  let taskLists;\n  let tasks;\n  let attachments;\n  let customFieldGroups;\n  let customFields1;\n  let customFields2;\n  let customFieldValues;\n  let notificationsToDelete;\n  let notificationServices;\n\n  if (isCurrentUser && isChangedToAdminOrProjectOwner) {\n    const { boardId } = yield select(selectors.selectPath);\n\n    ({ items: users1 } = yield call(request, api.getUsers));\n\n    if (user.role === UserRoles.ADMIN) {\n      ({ item: bootstrap } = yield call(request, api.getBootstrap));\n      ({ item: config } = yield call(request, api.getConfig));\n      ({ items: webhooks } = yield call(request, api.getWebhooks));\n\n      ({\n        items: projects,\n        included: {\n          projectManagers,\n          backgroundImages,\n          baseCustomFieldGroups,\n          boards,\n          notificationServices,\n          users: users2,\n          boardMemberships: boardMemberships1,\n          customFields: customFields1,\n        },\n      } = yield call(request, api.getProjects));\n\n      if (boardId === null) {\n        let body;\n        try {\n          body = yield call(requests.fetchBoardByCurrentPath);\n        } catch {\n          /* empty */\n        }\n\n        if (body) {\n          ({\n            board,\n            labels,\n            lists,\n            cards,\n            cardMemberships,\n            cardLabels,\n            taskLists,\n            tasks,\n            attachments,\n            customFieldGroups,\n            customFieldValues,\n            users: users3,\n            boardMemberships: boardMemberships2,\n            customFields: customFields2,\n          } = body);\n\n          if (body.card) {\n            notificationsToDelete = yield select(\n              selectors.selectNotificationsByCardId,\n              body.card.id,\n            );\n          }\n        }\n      }\n    }\n  }\n\n  const projectIds = yield select(selectors.selectProjectIdsForCurrentUser);\n  const boardIds = yield select(selectors.selectBoardIdsForCurrentUser);\n\n  yield put(\n    actions.handleUserUpdate(\n      user,\n      projectIds,\n      boardIds,\n      bootstrap,\n      config,\n      board,\n      webhooks,\n      mergeRecords(users1, users2, users3),\n      projects,\n      projectManagers,\n      backgroundImages,\n      baseCustomFieldGroups,\n      boards,\n      mergeRecords(boardMemberships1, boardMemberships2),\n      labels,\n      lists,\n      cards,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      mergeRecords(customFields1, customFields2),\n      customFieldValues,\n      notificationsToDelete,\n      notificationServices,\n    ),\n  );\n\n  if (isCurrentUser) {\n    const isAvailableForCurrentUser = yield select(selectors.isCurrentModalAvailableForCurrentUser);\n\n    if (!isAvailableForCurrentUser) {\n      yield put(actions.closeModal());\n    }\n  }\n}\n\n// TODO: add loading state\nexport function* updateUserLanguage(id, language) {\n  yield call(changeCoreLanguage, language);\n\n  yield call(updateUser, id, {\n    language,\n  });\n}\n\nexport function* updateCurrentUserLanguage(language) {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield call(updateUserLanguage, currentUserId, language);\n}\n\nexport function* updateUserEmail(id, data) {\n  yield put(actions.updateUserEmail(id, data));\n\n  let user;\n  try {\n    ({ item: user } = yield call(request, api.updateUserEmail, id, data));\n  } catch (error) {\n    yield put(actions.updateUserEmail.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateUserEmail.success(user));\n}\n\nexport function* updateCurrentUserEmail(data) {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield call(updateUserEmail, currentUserId, data);\n}\n\nexport function* clearUserEmailUpdateError(id) {\n  yield put(actions.clearUserEmailUpdateError(id));\n}\n\nexport function* clearCurrentUserEmailUpdateError() {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield call(clearUserEmailUpdateError, currentUserId);\n}\n\nexport function* updateUserPassword(id, data) {\n  yield put(actions.updateUserPassword(id, data));\n\n  let user;\n  let accessToken;\n\n  try {\n    ({ item: user, included: { accessToken } = {} } = yield call(\n      request,\n      api.updateUserPassword,\n      id,\n      data,\n    ));\n  } catch (error) {\n    yield put(actions.updateUserPassword.failure(id, error));\n    return;\n  }\n\n  if (accessToken) {\n    yield call(setAccessToken, accessToken);\n  }\n\n  yield put(actions.updateUserPassword.success(user, accessToken));\n}\n\nexport function* updateCurrentUserPassword(data) {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield call(updateUserPassword, currentUserId, data);\n}\n\nexport function* clearUserPasswordUpdateError(id) {\n  yield put(actions.clearUserPasswordUpdateError(id));\n}\n\nexport function* clearCurrentUserPasswordUpdateError() {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield call(clearUserPasswordUpdateError, currentUserId);\n}\n\nexport function* updateUserUsername(id, data) {\n  yield put(actions.updateUserUsername(id, data));\n\n  let user;\n  try {\n    ({ item: user } = yield call(request, api.updateUserUsername, id, data));\n  } catch (error) {\n    yield put(actions.updateUserUsername.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateUserUsername.success(user));\n}\n\nexport function* updateCurrentUserUsername(data) {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield call(updateUserUsername, currentUserId, data);\n}\n\nexport function* clearUserUsernameUpdateError(id) {\n  yield put(actions.clearUserUsernameUpdateError(id));\n}\n\nexport function* clearCurrentUserUsernameUpdateError() {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield call(clearUserUsernameUpdateError, currentUserId);\n}\n\nexport function* updateUserAvatar(id, data) {\n  yield put(actions.updateUserAvatar(id));\n\n  let user;\n  try {\n    ({ item: user } = yield call(request, api.updateUserAvatar, id, data));\n  } catch (error) {\n    yield put(actions.updateUserAvatar.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateUserAvatar.success(user));\n}\n\nexport function* updateCurrentUserAvatar(data) {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield call(updateUserAvatar, currentUserId, data);\n}\n\nexport function* createUserApiKey(id) {\n  yield put(actions.createUserApiKey(id));\n\n  let user;\n  let apiKey;\n\n  try {\n    ({\n      item: user,\n      included: { apiKey },\n    } = yield call(request, api.createUserApiKey, id));\n  } catch (error) {\n    yield put(actions.createUserApiKey.failure(id, error));\n    return;\n  }\n\n  yield put(actions.createUserApiKey.success(user, apiKey));\n}\n\nexport function* deleteUserApiKey(id) {\n  yield put(actions.deleteUserApiKey(id));\n\n  let user;\n  try {\n    ({ item: user } = yield call(request, api.updateUser, id, {\n      apiKey: null,\n    }));\n  } catch (error) {\n    yield put(actions.deleteUserApiKey.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteUserApiKey.success(user));\n}\n\nexport function* clearUserApiKeyValue(id) {\n  yield put(actions.clearUserApiKeyValue(id));\n}\n\nexport function* deleteUser(id) {\n  yield put(actions.deleteUser(id));\n\n  let user;\n  try {\n    ({ item: user } = yield call(request, api.deleteUser, id));\n  } catch (error) {\n    yield put(actions.deleteUser.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteUser.success(user));\n}\n\nexport function* handleUserDelete(user) {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  if (user.id === currentUserId) {\n    yield call(logout, false);\n    return;\n  }\n\n  yield put(actions.handleUserDelete(user));\n}\n\nexport function* addUserToCard(id, cardId) {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield put(actions.addUserToCard(id, cardId, id === currentUserId));\n\n  let cardMembership;\n  try {\n    ({ item: cardMembership } = yield call(request, api.createCardMembership, cardId, {\n      userId: id,\n    }));\n  } catch (error) {\n    yield put(actions.addUserToCard.failure(id, cardId, error));\n    return;\n  }\n\n  yield put(actions.addUserToCard.success(cardMembership));\n}\n\nexport function* addUserToCurrentCard(id) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(addUserToCard, id, cardId);\n}\n\nexport function* addCurrentUserToCurrentCard() {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield call(addUserToCurrentCard, currentUserId);\n}\n\nexport function* handleUserToCardAdd(cardMembership) {\n  yield put(actions.handleUserToCardAdd(cardMembership));\n}\n\nexport function* removeUserFromCard(id, cardId) {\n  yield put(actions.removeUserFromCard(id, cardId));\n\n  let cardMembership;\n  try {\n    ({ item: cardMembership } = yield call(request, api.deleteCardMembership, cardId, id));\n  } catch (error) {\n    yield put(actions.removeUserFromCard.failure(id, cardId, error));\n    return;\n  }\n\n  yield put(actions.removeUserFromCard.success(cardMembership));\n}\n\nexport function* removeUserFromCurrentCard(id) {\n  const { cardId } = yield select(selectors.selectPath);\n\n  yield call(removeUserFromCard, id, cardId);\n}\n\nexport function* removeCurrentUserFromCurrentCard() {\n  const currentUserId = yield select(selectors.selectCurrentUserId);\n\n  yield call(removeUserFromCurrentCard, currentUserId);\n}\n\nexport function* handleUserFromCardRemove(cardMembership) {\n  yield put(actions.handleUserFromCardRemove(cardMembership));\n}\n\nexport function* addUserToBoardFilter(id, boardId, replace) {\n  const currentListId = yield select(selectors.selectCurrentListId);\n\n  yield put(actions.addUserToBoardFilter(id, boardId, replace, currentListId));\n}\n\nexport function* addUserToFilterInCurrentBoard(id, replace) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(addUserToBoardFilter, id, boardId, replace);\n}\n\nexport function* removeUserFromBoardFilter(id, boardId) {\n  const currentListId = yield select(selectors.selectCurrentListId);\n\n  yield put(actions.removeUserFromBoardFilter(id, boardId, currentListId));\n}\n\nexport function* removeUserFromFilterInCurrentBoard(id) {\n  const { boardId } = yield select(selectors.selectPath);\n\n  yield call(removeUserFromBoardFilter, id, boardId);\n}\n\nexport default {\n  handleUsersReset,\n  createUser,\n  handleUserCreate,\n  clearUserCreateError,\n  updateUser,\n  updateCurrentUser,\n  handleUserUpdate,\n  updateUserLanguage,\n  updateCurrentUserLanguage,\n  updateUserEmail,\n  updateCurrentUserEmail,\n  clearUserEmailUpdateError,\n  clearCurrentUserEmailUpdateError,\n  updateUserPassword,\n  updateCurrentUserPassword,\n  clearUserPasswordUpdateError,\n  clearCurrentUserPasswordUpdateError,\n  updateUserUsername,\n  updateCurrentUserUsername,\n  clearUserUsernameUpdateError,\n  clearCurrentUserUsernameUpdateError,\n  updateUserAvatar,\n  updateCurrentUserAvatar,\n  createUserApiKey,\n  deleteUserApiKey,\n  clearUserApiKeyValue,\n  deleteUser,\n  handleUserDelete,\n  addUserToCard,\n  addUserToCurrentCard,\n  addCurrentUserToCurrentCard,\n  handleUserToCardAdd,\n  removeUserFromCard,\n  removeUserFromCurrentCard,\n  removeCurrentUserFromCurrentCard,\n  handleUserFromCardRemove,\n  addUserToBoardFilter,\n  addUserToFilterInCurrentBoard,\n  removeUserFromBoardFilter,\n  removeUserFromFilterInCurrentBoard,\n};\n"
  },
  {
    "path": "client/src/sagas/core/services/webhooks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put } from 'redux-saga/effects';\n\nimport request from '../request';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport { createLocalId } from '../../../utils/local-id';\n\nexport function* createWebhook(data) {\n  const localId = yield call(createLocalId);\n\n  yield put(\n    actions.createWebhook({\n      ...data,\n      id: localId,\n    }),\n  );\n\n  let webhook;\n  try {\n    ({ item: webhook } = yield call(request, api.createWebhook, {\n      ...data,\n      events: data.events && data.events.join(','),\n      excludedEvents: data.excludedEvents && data.excludedEvents.join(','),\n    }));\n  } catch (error) {\n    yield put(actions.createWebhook.failure(localId, error));\n    return;\n  }\n\n  yield put(actions.createWebhook.success(localId, webhook));\n}\n\nexport function* handleWebhookCreate(webhook) {\n  yield put(actions.handleWebhookCreate(webhook));\n}\n\nexport function* updateWebhook(id, data) {\n  yield put(actions.updateWebhook(id, data));\n\n  let webhook;\n  try {\n    ({ item: webhook } = yield call(request, api.updateWebhook, id, {\n      ...data,\n      events: data.events && data.events.join(','),\n      excludedEvents: data.excludedEvents && data.excludedEvents.join(','),\n    }));\n  } catch (error) {\n    yield put(actions.updateWebhook.failure(id, error));\n    return;\n  }\n\n  yield put(actions.updateWebhook.success(webhook));\n}\n\nexport function* handleWebhookUpdate(webhook) {\n  yield put(actions.handleWebhookUpdate(webhook));\n}\n\nexport function* deleteWebhook(id) {\n  yield put(actions.deleteWebhook(id));\n\n  let webhook;\n  try {\n    ({ item: webhook } = yield call(request, api.deleteWebhook, id));\n  } catch (error) {\n    yield put(actions.deleteWebhook.failure(id, error));\n    return;\n  }\n\n  yield put(actions.deleteWebhook.success(webhook));\n}\n\nexport function* handleWebhookDelete(webhook) {\n  yield put(actions.handleWebhookDelete(webhook));\n}\n\nexport default {\n  createWebhook,\n  handleWebhookCreate,\n  updateWebhook,\n  handleWebhookUpdate,\n  deleteWebhook,\n  handleWebhookDelete,\n};\n"
  },
  {
    "path": "client/src/sagas/core/watchers/activities.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* activitiesWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.ACTIVITIES_IN_CURRENT_BOARD_FETCH, () =>\n      services.fetchActivitiesInCurrentBoard(),\n    ),\n    takeEvery(EntryActionTypes.ACTIVITIES_IN_CURRENT_CARD_FETCH, () =>\n      services.fetchActivitiesInCurrentCard(),\n    ),\n    takeEvery(EntryActionTypes.ACTIVITY_CREATE_HANDLE, ({ payload: { activity } }) =>\n      services.handleActivityCreate(activity),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/attachments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* attachmentsWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.ATTACHMENT_IN_CURRENT_CARD_CREATE, ({ payload: { data } }) =>\n      services.createAttachmentInCurrentCard(data),\n    ),\n    takeEvery(EntryActionTypes.ATTACHMENT_CREATE_HANDLE, ({ payload: { attachment, requestId } }) =>\n      services.handleAttachmentCreate(attachment, requestId),\n    ),\n    takeEvery(EntryActionTypes.ATTACHMENT_UPDATE, ({ payload: { id, data } }) =>\n      services.updateAttachment(id, data),\n    ),\n    takeEvery(EntryActionTypes.ATTACHMENT_UPDATE_HANDLE, ({ payload: { attachment } }) =>\n      services.handleAttachmentUpdate(attachment),\n    ),\n    takeEvery(EntryActionTypes.ATTACHMENT_DELETE, ({ payload: { id } }) =>\n      services.deleteAttachment(id),\n    ),\n    takeEvery(EntryActionTypes.ATTACHMENT_DELETE_HANDLE, ({ payload: { attachment } }) =>\n      services.handleAttachmentDelete(attachment),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/background-images.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* backgroundImagesWatchers() {\n  yield all([\n    takeEvery(\n      EntryActionTypes.BACKGROUND_IMAGE_IN_CURRENT_PROJECT_CREATE,\n      ({ payload: { data } }) => services.createBackgroundImageInCurrentProject(data),\n    ),\n    takeEvery(\n      EntryActionTypes.BACKGROUND_IMAGE_CREATE_HANDLE,\n      ({ payload: { backgroundImage, requestId } }) =>\n        services.handleBackgroundImageCreate(backgroundImage, requestId),\n    ),\n    takeEvery(EntryActionTypes.BACKGROUND_IMAGE_DELETE, ({ payload: { id } }) =>\n      services.deleteBackgroundImage(id),\n    ),\n    takeEvery(EntryActionTypes.BACKGROUND_IMAGE_DELETE_HANDLE, ({ payload: { backgroundImage } }) =>\n      services.handleBackgroundImageDelete(backgroundImage),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/base-custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* baseCustomFieldGroupsWatchers() {\n  yield all([\n    takeEvery(\n      EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_IN_CURRENT_PROJECT_CREATE,\n      ({ payload: { data } }) => services.createBaseCustomFieldGroupInCurrentProject(data),\n    ),\n    takeEvery(\n      EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE,\n      ({ payload: { baseCustomFieldGroup } }) =>\n        services.handleBaseCustomFieldGroupCreate(baseCustomFieldGroup),\n    ),\n    takeEvery(EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE, ({ payload: { id, data } }) =>\n      services.updateBaseCustomFieldGroup(id, data),\n    ),\n    takeEvery(\n      EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE,\n      ({ payload: { baseCustomFieldGroup } }) =>\n        services.handleBaseCustomFieldGroupUpdate(baseCustomFieldGroup),\n    ),\n    takeEvery(EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE, ({ payload: { id } }) =>\n      services.deleteBaseCustomFieldGroup(id),\n    ),\n    takeEvery(\n      EntryActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE,\n      ({ payload: { baseCustomFieldGroup } }) =>\n        services.handleBaseCustomFieldGroupDelete(baseCustomFieldGroup),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/board-memberships.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* boardMembershipsWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.MEMBERSHIP_IN_CURRENT_BOARD_CREATE, ({ payload: { data } }) =>\n      services.createMembershipInCurrentBoard(data),\n    ),\n    takeEvery(\n      EntryActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE,\n      ({ payload: { boardMembership, users } }) =>\n        services.handleBoardMembershipCreate(boardMembership, users),\n    ),\n    takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_UPDATE, ({ payload: { id, data } }) =>\n      services.updateBoardMembership(id, data),\n    ),\n    takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_UPDATE_HANDLE, ({ payload: { boardMembership } }) =>\n      services.handleBoardMembershipUpdate(boardMembership),\n    ),\n    takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_DELETE, ({ payload: { id } }) =>\n      services.deleteBoardMembership(id),\n    ),\n    takeEvery(EntryActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE, ({ payload: { boardMembership } }) =>\n      services.handleBoardMembershipDelete(boardMembership),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/boards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* boardsWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.BOARD_IN_CURRENT_PROJECT_CREATE, ({ payload: { data } }) =>\n      services.createBoardInCurrentProject(data),\n    ),\n    takeEvery(\n      EntryActionTypes.BOARD_CREATE_HANDLE,\n      ({ payload: { board, boardMemberships, requestId } }) =>\n        services.handleBoardCreate(board, boardMemberships, requestId),\n    ),\n    takeEvery(EntryActionTypes.BOARD_FETCH, ({ payload: { id } }) => services.fetchBoard(id)),\n    takeEvery(EntryActionTypes.BOARD_UPDATE, ({ payload: { id, data } }) =>\n      services.updateBoard(id, data),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_BOARD_UPDATE, ({ payload: { data } }) =>\n      services.updateCurrentBoard(data),\n    ),\n    takeEvery(EntryActionTypes.BOARD_UPDATE_HANDLE, ({ payload: { board } }) =>\n      services.handleBoardUpdate(board),\n    ),\n    takeEvery(EntryActionTypes.BOARD_MOVE, ({ payload: { id, index } }) =>\n      services.moveBoard(id, index),\n    ),\n    takeEvery(EntryActionTypes.CONTEXT_IN_CURRENT_BOARD_UPDATE, ({ payload: { value } }) =>\n      services.updateContextInCurrentBoard(value),\n    ),\n    takeEvery(EntryActionTypes.VIEW_IN_CURRENT_BOARD_UPDATE, ({ payload: { value } }) =>\n      services.updateViewInCurrentBoard(value),\n    ),\n    takeEvery(EntryActionTypes.IN_CURRENT_BOARD_SEARCH, ({ payload: { value } }) =>\n      services.searchInCurrentBoard(value),\n    ),\n    takeEvery(EntryActionTypes.BOARD_DELETE, ({ payload: { id } }) => services.deleteBoard(id)),\n    takeEvery(EntryActionTypes.BOARD_DELETE_HANDLE, ({ payload: { board } }) =>\n      services.handleBoardDelete(board),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/bootstrap.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* bootstrapWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.BOOTSTRAP_UPDATE_HANDLE, ({ payload: { bootstrap } }) =>\n      services.handleBootstrapUpdate(bootstrap),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/cards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* cardsWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.CARDS_IN_CURRENT_LIST_FETCH, () =>\n      services.fetchCardsInCurrentList(),\n    ),\n    takeEvery(EntryActionTypes.CARDS_UPDATE_HANDLE, ({ payload: { cards, activities } }) =>\n      services.handleCardsUpdate(cards, activities),\n    ),\n    takeEvery(EntryActionTypes.CARD_CREATE, ({ payload: { listId, data, index, autoOpen } }) =>\n      services.createCard(listId, data, index, autoOpen),\n    ),\n    takeEvery(\n      EntryActionTypes.CARD_IN_CURRENT_CONTEXT_CREATE,\n      ({ payload: { data, index, autoOpen } }) =>\n        services.createCardInCurrentContext(data, index, autoOpen),\n    ),\n    takeEvery(EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, ({ payload: { data, autoOpen } }) =>\n      services.createCardInCurrentList(data, autoOpen),\n    ),\n    takeEvery(EntryActionTypes.CARD_CREATE_HANDLE, ({ payload: { card } }) =>\n      services.handleCardCreate(card),\n    ),\n    takeEvery(EntryActionTypes.CARD_UPDATE, ({ payload: { id, data } }) =>\n      services.updateCard(id, data),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_CARD_UPDATE, ({ payload: { data } }) =>\n      services.updateCurrentCard(data),\n    ),\n    takeEvery(EntryActionTypes.CARD_UPDATE_HANDLE, ({ payload: { card } }) =>\n      services.handleCardUpdate(card),\n    ),\n    takeEvery(EntryActionTypes.CARD_MOVE, ({ payload: { id, listId, index } }) =>\n      services.moveCard(id, listId, index),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_CARD_MOVE, ({ payload: { listId, index, autoClose } }) =>\n      services.moveCurrentCard(listId, index, autoClose),\n    ),\n    takeEvery(EntryActionTypes.CARD_TO_ARCHIVE_MOVE, ({ payload: { id } }) =>\n      services.moveCardToArchive(id),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_CARD_TO_ARCHIVE_MOVE, () =>\n      services.moveCurrentCardToArchive(),\n    ),\n    takeEvery(EntryActionTypes.CARD_TO_TRASH_MOVE, ({ payload: { id } }) =>\n      services.moveCardToTrash(id),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_CARD_TO_TRASH_MOVE, () => services.moveCurrentCardToTrash()),\n    takeEvery(EntryActionTypes.CARD_TRANSFER, ({ payload: { id, boardId, listId, index } }) =>\n      services.transferCard(id, boardId, listId, index),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_CARD_TRANSFER, ({ payload: { boardId, listId, index } }) =>\n      services.transferCurrentCard(boardId, listId, index),\n    ),\n    takeEvery(EntryActionTypes.CARD_DUPLICATE, ({ payload: { id, data } }) =>\n      services.duplicateCard(id, data),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_CARD_DUPLICATE, ({ payload: { data } }) =>\n      services.duplicateCurrentCard(data),\n    ),\n    takeEvery(EntryActionTypes.CARD_COPY, ({ payload: { id } }) => services.copyCard(id)),\n    takeEvery(EntryActionTypes.CARD_CUT, ({ payload: { id } }) => services.cutCard(id)),\n    takeEvery(EntryActionTypes.CARD_PASTE, ({ payload: { listId } }) => services.pasteCard(listId)),\n    takeEvery(EntryActionTypes.CARD_IN_CURRENT_CONTEXT_PASTE, () =>\n      services.pasteCardInCurrentContext(),\n    ),\n    takeEvery(EntryActionTypes.CARD_IN_CURRENT_LIST_PASTE, () => services.pasteCardInCurrentList()),\n    takeEvery(EntryActionTypes.TO_ADJACENT_CARD_GO, ({ payload: { direction } }) =>\n      services.goToAdjacentCard(direction),\n    ),\n    takeEvery(EntryActionTypes.CARD_DELETE, ({ payload: { id } }) => services.deleteCard(id)),\n    takeEvery(EntryActionTypes.CURRENT_CARD_DELETE, () => services.deleteCurrentCard()),\n    takeEvery(EntryActionTypes.CARD_DELETE_HANDLE, ({ payload: { card } }) =>\n      services.handleCardDelete(card),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/comments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* commentsWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.COMMENTS_IN_CURRENT_CARD_FETCH, () =>\n      services.fetchCommentsInCurrentCard(),\n    ),\n    takeEvery(EntryActionTypes.COMMENT_IN_CURRENT_CARD_CREATE, ({ payload: { data } }) =>\n      services.createCommentInCurrentCard(data),\n    ),\n    takeEvery(EntryActionTypes.COMMENT_CREATE_HANDLE, ({ payload: { comment, users } }) =>\n      services.handleCommentCreate(comment, users),\n    ),\n    takeEvery(EntryActionTypes.COMMENT_UPDATE, ({ payload: { id, data } }) =>\n      services.updateComment(id, data),\n    ),\n    takeEvery(EntryActionTypes.COMMENT_UPDATE_HANDLE, ({ payload: { comment } }) =>\n      services.handleCommentUpdate(comment),\n    ),\n    takeEvery(EntryActionTypes.COMMENT_DELETE, ({ payload: { id } }) => services.deleteComment(id)),\n    takeEvery(EntryActionTypes.COMMENT_DELETE_HANDLE, ({ payload: { comment } }) =>\n      services.handleCommentDelete(comment),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/config.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* configWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.CONFIG_UPDATE, ({ payload: { data } }) =>\n      services.updateConfig(data),\n    ),\n    takeEvery(EntryActionTypes.CONFIG_UPDATE_HANDLE, ({ payload: { config } }) =>\n      services.handleConfigUpdate(config),\n    ),\n    takeEvery(EntryActionTypes.SMTP_CONFIG_TEST, () => services.testSmtpConfig()),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/core.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* coreWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.FAVORITES_TOGGLE, ({ payload: { isEnabled } }) =>\n      services.toggleFavorites(isEnabled),\n    ),\n    takeEvery(EntryActionTypes.EDIT_MODE_TOGGLE, ({ payload: { isEnabled } }) =>\n      services.toggleEditMode(isEnabled),\n    ),\n    takeEvery(EntryActionTypes.HOME_VIEW_UPDATE, ({ payload: { value } }) =>\n      services.updateHomeView(value),\n    ),\n    takeEvery(EntryActionTypes.LOGOUT, ({ payload: { revokeAccessToken } }) =>\n      services.logout(revokeAccessToken),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* customFieldGroupsWatchers() {\n  yield all([\n    takeEvery(\n      EntryActionTypes.CUSTOM_FIELD_GROUP_IN_CURRENT_BOARD_CREATE,\n      ({ payload: { data } }) => services.createCustomFieldGroupInCurrentBoard(data),\n    ),\n    takeEvery(EntryActionTypes.CUSTOM_FIELD_GROUP_IN_CURRENT_CARD_CREATE, ({ payload: { data } }) =>\n      services.createCustomFieldGroupInCurrentCard(data),\n    ),\n    takeEvery(\n      EntryActionTypes.CUSTOM_FIELD_GROUP_CREATE_HANDLE,\n      ({ payload: { customFieldGroup } }) =>\n        services.handleCustomFieldGroupCreate(customFieldGroup),\n    ),\n    takeEvery(EntryActionTypes.CUSTOM_FIELD_GROUP_UPDATE, ({ payload: { id, data } }) =>\n      services.updateCustomFieldGroup(id, data),\n    ),\n    takeEvery(\n      EntryActionTypes.CUSTOM_FIELD_GROUP_UPDATE_HANDLE,\n      ({ payload: { customFieldGroup } }) =>\n        services.handleCustomFieldGroupUpdate(customFieldGroup),\n    ),\n    takeEvery(EntryActionTypes.CUSTOM_FIELD_GROUP_MOVE, ({ payload: { id, index } }) =>\n      services.moveCustomFieldGroup(id, index),\n    ),\n    takeEvery(EntryActionTypes.CUSTOM_FIELD_GROUP_DELETE, ({ payload: { id } }) =>\n      services.deleteCustomFieldGroup(id),\n    ),\n    takeEvery(\n      EntryActionTypes.CUSTOM_FIELD_GROUP_DELETE_HANDLE,\n      ({ payload: { customFieldGroup } }) =>\n        services.handleCustomFieldGroupDelete(customFieldGroup),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/custom-field-values.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* customFieldValuessWatchers() {\n  yield all([\n    takeEvery(\n      EntryActionTypes.CUSTOM_FIELD_VALUE_UPDATE,\n      ({ payload: { cardId, customFieldGroupId, customFieldId, data } }) =>\n        services.updateCustomFieldValue(cardId, customFieldGroupId, customFieldId, data),\n    ),\n    takeEvery(\n      EntryActionTypes.CUSTOM_FIELD_VALUE_UPDATE_HANDLE,\n      ({ payload: { customFieldValue } }) =>\n        services.handleCustomFieldValueUpdate(customFieldValue),\n    ),\n    takeEvery(\n      EntryActionTypes.CUSTOM_FIELD_VALUE_DELETE,\n      ({ payload: { cardId, customFieldGroupId, customFieldId } }) =>\n        services.deleteCustomFieldValue(cardId, customFieldGroupId, customFieldId),\n    ),\n    takeEvery(\n      EntryActionTypes.CUSTOM_FIELD_VALUE_DELETE_HANDLE,\n      ({ payload: { customFieldValue } }) =>\n        services.handleCustomFieldValueDelete(customFieldValue),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/custom-fields.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* customFieldsWatchers() {\n  yield all([\n    takeEvery(\n      EntryActionTypes.CUSTOM_FIELD_IN_BASE_GROUP_CREATE,\n      ({ payload: { baseCustomFieldGroupId, data } }) =>\n        services.createCustomFieldInBaseGroup(baseCustomFieldGroupId, data),\n    ),\n    takeEvery(\n      EntryActionTypes.CUSTOM_FIELD_IN_GROUP_CREATE,\n      ({ payload: { customFieldGroupId, data } }) =>\n        services.createCustomFieldInGroup(customFieldGroupId, data),\n    ),\n    takeEvery(EntryActionTypes.CUSTOM_FIELD_CREATE_HANDLE, ({ payload: { customField } }) =>\n      services.handleCustomFieldCreate(customField),\n    ),\n    takeEvery(EntryActionTypes.CUSTOM_FIELD_UPDATE, ({ payload: { id, data } }) =>\n      services.updateCustomField(id, data),\n    ),\n    takeEvery(EntryActionTypes.CUSTOM_FIELD_UPDATE_HANDLE, ({ payload: { customField } }) =>\n      services.handleCustomFieldUpdate(customField),\n    ),\n    takeEvery(EntryActionTypes.CUSTOM_FIELD_MOVE, ({ payload: { id, index } }) =>\n      services.moveCustomField(id, index),\n    ),\n    takeEvery(EntryActionTypes.CUSTOM_FIELD_DELETE, ({ payload: { id } }) =>\n      services.deleteCustomField(id),\n    ),\n    takeEvery(EntryActionTypes.CUSTOM_FIELD_DELETE_HANDLE, ({ payload: { customField } }) =>\n      services.handleCustomFieldDelete(customField),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport router from './router';\nimport socket from './socket';\nimport bootstrap from './bootstrap';\nimport core from './core';\nimport modals from './modals';\nimport config from './config';\nimport webhooks from './webhooks';\nimport users from './users';\nimport projects from './projects';\nimport projectManagers from './project-managers';\nimport backgroundImages from './background-images';\nimport baseCustomFieldGroups from './base-custom-field-groups';\nimport boards from './boards';\nimport boardMemberships from './board-memberships';\nimport labels from './labels';\nimport lists from './lists';\nimport cards from './cards';\nimport taskLists from './task-lists';\nimport tasks from './tasks';\nimport attachments from './attachments';\nimport customFieldGroups from './custom-field-groups';\nimport customFields from './custom-fields';\nimport customFieldValues from './custom-field-values';\nimport comments from './comments';\nimport activities from './activities';\nimport notifications from './notifications';\nimport notificationServices from './notification-services';\n\nexport default [\n  router,\n  socket,\n  bootstrap,\n  core,\n  modals,\n  config,\n  webhooks,\n  users,\n  projects,\n  projectManagers,\n  backgroundImages,\n  baseCustomFieldGroups,\n  boards,\n  boardMemberships,\n  labels,\n  lists,\n  cards,\n  taskLists,\n  tasks,\n  attachments,\n  customFieldGroups,\n  customFields,\n  customFieldValues,\n  comments,\n  activities,\n  notifications,\n  notificationServices,\n];\n"
  },
  {
    "path": "client/src/sagas/core/watchers/labels.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* labelsWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.LABEL_IN_CURRENT_BOARD_CREATE, ({ payload: { data } }) =>\n      services.createLabelInCurrentBoard(data),\n    ),\n    takeEvery(EntryActionTypes.LABEL_FROM_CARD_CREATE, ({ payload: { cardId, data } }) =>\n      services.createLabelFromCard(cardId, data),\n    ),\n    takeEvery(EntryActionTypes.LABEL_CREATE_HANDLE, ({ payload: { label } }) =>\n      services.handleLabelCreate(label),\n    ),\n    takeEvery(EntryActionTypes.LABEL_UPDATE, ({ payload: { id, data } }) =>\n      services.updateLabel(id, data),\n    ),\n    takeEvery(EntryActionTypes.LABEL_UPDATE_HANDLE, ({ payload: { label } }) =>\n      services.handleLabelUpdate(label),\n    ),\n    takeEvery(EntryActionTypes.LABEL_MOVE, ({ payload: { id, index } }) =>\n      services.moveLabel(id, index),\n    ),\n    takeEvery(EntryActionTypes.LABEL_DELETE, ({ payload: { id } }) => services.deleteLabel(id)),\n    takeEvery(EntryActionTypes.LABEL_DELETE_HANDLE, ({ payload: { label } }) =>\n      services.handleLabelDelete(label),\n    ),\n    takeEvery(EntryActionTypes.LABEL_TO_CARD_ADD, ({ payload: { id, cardId } }) =>\n      services.addLabelToCard(id, cardId),\n    ),\n    takeEvery(EntryActionTypes.LABEL_TO_CURRENT_CARD_ADD, ({ payload: { id } }) =>\n      services.addLabelToCurrentCard(id),\n    ),\n    takeEvery(EntryActionTypes.LABEL_TO_CARD_ADD_HANDLE, ({ payload: { cardLabel } }) =>\n      services.handleLabelToCardAdd(cardLabel),\n    ),\n    takeEvery(EntryActionTypes.LABEL_FROM_CARD_REMOVE, ({ payload: { id, cardId } }) =>\n      services.removeLabelFromCard(id, cardId),\n    ),\n    takeEvery(EntryActionTypes.LABEL_FROM_CURRENT_CARD_REMOVE, ({ payload: { id } }) =>\n      services.removeLabelFromCurrentCard(id),\n    ),\n    takeEvery(EntryActionTypes.LABEL_FROM_CARD_REMOVE_HANDLE, ({ payload: { cardLabel } }) =>\n      services.handleLabelFromCardRemove(cardLabel),\n    ),\n    takeEvery(EntryActionTypes.LABEL_TO_FILTER_IN_CURRENT_BOARD_ADD, ({ payload: { id } }) =>\n      services.addLabelToFilterInCurrentBoard(id),\n    ),\n    takeEvery(EntryActionTypes.LABEL_FROM_FILTER_IN_CURRENT_BOARD_REMOVE, ({ payload: { id } }) =>\n      services.removeLabelFromFilterInCurrentBoard(id),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* listsWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.LIST_IN_CURRENT_BOARD_CREATE, ({ payload: { data } }) =>\n      services.createListInCurrentBoard(data),\n    ),\n    takeEvery(EntryActionTypes.LIST_CREATE_HANDLE, ({ payload: { list } }) =>\n      services.handleListCreate(list),\n    ),\n    takeEvery(EntryActionTypes.LIST_UPDATE, ({ payload: { id, data } }) =>\n      services.updateList(id, data),\n    ),\n    takeEvery(EntryActionTypes.LIST_UPDATE_HANDLE, ({ payload: { list } }) =>\n      services.handleListUpdate(list),\n    ),\n    takeEvery(EntryActionTypes.LIST_MOVE, ({ payload: { id, index } }) =>\n      services.moveList(id, index),\n    ),\n    takeEvery(EntryActionTypes.LIST_TRANSFER, ({ payload: { id, boardId, index } }) =>\n      services.transferList(id, boardId, index),\n    ),\n    takeEvery(EntryActionTypes.LIST_SORT, ({ payload: { id, data } }) =>\n      services.sortList(id, data),\n    ),\n    takeEvery(EntryActionTypes.LIST_CARDS_TO_ARCHIVE_LIST_MOVE, ({ payload: { id } }) =>\n      services.moveListCardsToArchiveList(id),\n    ),\n    takeEvery(EntryActionTypes.TRASH_LIST_IN_CURRENT_BOARD_CLEAR, () =>\n      services.clearTrashListInCurrentBoard(),\n    ),\n    takeEvery(EntryActionTypes.LIST_CLEAR_HANDLE, ({ payload: { list } }) =>\n      services.handleListClear(list),\n    ),\n    takeEvery(EntryActionTypes.LIST_DELETE, ({ payload: { id } }) => services.deleteList(id)),\n    takeEvery(EntryActionTypes.LIST_DELETE_HANDLE, ({ payload: { list, cards } }) =>\n      services.handleListDelete(list, cards),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/modals.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* modalsWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.MODAL_OPEN, ({ payload: { type, params } }) =>\n      services.openModal(type, params),\n    ),\n    takeEvery(EntryActionTypes.MODAL_CLOSE, () => services.closeModal()),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/notification-services.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* notificationServicesWatchers() {\n  yield all([\n    takeEvery(\n      EntryActionTypes.NOTIFICATION_SERVICE_IN_CURRENT_USER_CREATE,\n      ({ payload: { data } }) => services.createNotificationServiceInCurrentUser(data),\n    ),\n    takeEvery(\n      EntryActionTypes.NOTIFICATION_SERVICE_IN_BOARD_CREATE,\n      ({ payload: { boardId, data } }) => services.createNotificationServiceInBoard(boardId, data),\n    ),\n    takeEvery(\n      EntryActionTypes.NOTIFICATION_SERVICE_CREATE_HANDLE,\n      ({ payload: { notificationService } }) =>\n        services.handleNotificationServiceCreate(notificationService),\n    ),\n    takeEvery(EntryActionTypes.NOTIFICATION_SERVICE_UPDATE, ({ payload: { id, data } }) =>\n      services.updateNotificationService(id, data),\n    ),\n    takeEvery(\n      EntryActionTypes.NOTIFICATION_SERVICE_UPDATE_HANDLE,\n      ({ payload: { notificationService } }) =>\n        services.handleNotificationServiceUpdate(notificationService),\n    ),\n    takeEvery(EntryActionTypes.NOTIFICATION_SERVICE_TEST, ({ payload: { id } }) =>\n      services.testNotificationService(id),\n    ),\n    takeEvery(EntryActionTypes.NOTIFICATION_SERVICE_DELETE, ({ payload: { id } }) =>\n      services.deleteNotificationService(id),\n    ),\n    takeEvery(\n      EntryActionTypes.NOTIFICATION_SERVICE_DELETE_HANDLE,\n      ({ payload: { notificationService } }) =>\n        services.handleNotificationServiceDelete(notificationService),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/notifications.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* notificationsWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.ALL_NOTIFICATIONS_DELETE, () => services.deleteAllNotifications()),\n    takeEvery(EntryActionTypes.NOTIFICATION_CREATE_HANDLE, ({ payload: { notification, users } }) =>\n      services.handleNotificationCreate(notification, users),\n    ),\n    takeEvery(EntryActionTypes.NOTIFICATION_DELETE, ({ payload: { id } }) =>\n      services.deleteNotification(id),\n    ),\n    takeEvery(EntryActionTypes.NOTIFICATION_DELETE_HANDLE, ({ payload: { notification } }) =>\n      services.handleNotificationDelete(notification),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/project-managers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* projectManagersWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.MANAGER_IN_CURRENT_PROJECT_CREATE, ({ payload: { data } }) =>\n      services.createManagerInCurrentProject(data),\n    ),\n    takeEvery(\n      EntryActionTypes.PROJECT_MANAGER_CREATE_HANDLE,\n      ({ payload: { projectManager, users } }) =>\n        services.handleProjectManagerCreate(projectManager, users),\n    ),\n    takeEvery(EntryActionTypes.PROJECT_MANAGER_DELETE, ({ payload: { id } }) =>\n      services.deleteProjectManager(id),\n    ),\n    takeEvery(EntryActionTypes.PROJECT_MANAGER_DELETE_HANDLE, ({ payload: { projectManager } }) =>\n      services.handleProjectManagerDelete(projectManager),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/projects.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* projectsWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.PROJECTS_SEARCH, ({ payload: { value } }) =>\n      services.searchProjects(value),\n    ),\n    takeEvery(EntryActionTypes.PROJECTS_ORDER_UPDATE, ({ payload: { value } }) =>\n      services.updateProjectsOrder(value),\n    ),\n    takeEvery(EntryActionTypes.HIDDEN_PROJECTS_TOGGLE, ({ payload: { isVisible } }) =>\n      services.toggleHiddenProjects(isVisible),\n    ),\n    takeEvery(EntryActionTypes.PROJECT_CREATE, ({ payload: { data } }) =>\n      services.createProject(data),\n    ),\n    takeEvery(EntryActionTypes.PROJECT_CREATE_HANDLE, ({ payload: { project } }) =>\n      services.handleProjectCreate(project),\n    ),\n    takeEvery(EntryActionTypes.PROJECT_UPDATE, ({ payload: { id, data } }) =>\n      services.updateProject(id, data),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_PROJECT_UPDATE, ({ payload: { data } }) =>\n      services.updateCurrentProject(data),\n    ),\n    takeEvery(EntryActionTypes.PROJECT_UPDATE_HANDLE, ({ payload: { project } }) =>\n      services.handleProjectUpdate(project),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_PROJECT_DELETE, () => services.deleteCurrentProject()),\n    takeEvery(EntryActionTypes.PROJECT_DELETE_HANDLE, ({ payload: { project } }) =>\n      services.handleProjectDelete(project),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/router.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { takeEvery } from 'redux-saga/effects';\nimport { LOCATION_CHANGE_HANDLE } from '../../../lib/redux-router';\n\nimport services from '../services';\n\nexport default function* routerWatchers() {\n  yield takeEvery(LOCATION_CHANGE_HANDLE, () => services.handleLocationChange());\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/socket.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { eventChannel } from 'redux-saga';\nimport { all, call, cancelled, put, take, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport entryActions from '../../../entry-actions';\nimport api, { socket } from '../../../api';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nconst createSocketEventsChannel = () =>\n  eventChannel((emit) => {\n    const handleDisconnect = () => {\n      emit(entryActions.handleSocketDisconnect());\n    };\n\n    const handleReconnect = () => {\n      emit(entryActions.handleSocketReconnect());\n    };\n\n    const handleLogout = () => {\n      emit(entryActions.logout(false));\n    };\n\n    const handleBootstrapUpdate = ({ item }) => {\n      emit(entryActions.handleBootstrapUpdate(item));\n    };\n\n    const handleConfigUpdate = ({ item }) => {\n      emit(entryActions.handleConfigUpdate(item));\n    };\n\n    const handleWebhookCreate = ({ item }) => {\n      emit(entryActions.handleWebhookCreate(item));\n    };\n\n    const handleWebhookUpdate = ({ item }) => {\n      emit(entryActions.handleWebhookUpdate(item));\n    };\n\n    const handleWebhookDelete = ({ item }) => {\n      emit(entryActions.handleWebhookDelete(item));\n    };\n\n    const handleUsersReset = () => {\n      emit(entryActions.handleUsersReset());\n    };\n\n    const handleUserCreate = ({ item }) => {\n      emit(entryActions.handleUserCreate(item));\n    };\n\n    const handleUserUpdate = ({ item }) => {\n      emit(entryActions.handleUserUpdate(item));\n    };\n\n    const handleUserDelete = ({ item }) => {\n      emit(entryActions.handleUserDelete(item));\n    };\n\n    const handleProjectCreate = ({ item }) => {\n      emit(entryActions.handleProjectCreate(item));\n    };\n\n    const handleProjectUpdate = ({ item }) => {\n      emit(entryActions.handleProjectUpdate(item));\n    };\n\n    const handleProjectDelete = ({ item }) => {\n      emit(entryActions.handleProjectDelete(item));\n    };\n\n    const handleProjectManagerCreate = ({ item, included: { users } }) => {\n      emit(entryActions.handleProjectManagerCreate(item, users));\n    };\n\n    const handleProjectManagerDelete = ({ item }) => {\n      emit(entryActions.handleProjectManagerDelete(item));\n    };\n\n    const handleBackgroundImageCreate = ({ item, requestId }) => {\n      emit(entryActions.handleBackgroundImageCreate(item, requestId));\n    };\n\n    const handleBackgroundImageDelete = ({ item }) => {\n      emit(entryActions.handleBackgroundImageDelete(item));\n    };\n\n    const handleBaseCustomFieldGroupCreate = ({ item }) => {\n      emit(entryActions.handleBaseCustomFieldGroupCreate(item));\n    };\n\n    const handleBaseCustomFieldGroupUpdate = ({ item }) => {\n      emit(entryActions.handleBaseCustomFieldGroupUpdate(item));\n    };\n\n    const handleBaseCustomFieldGroupDelete = ({ item }) => {\n      emit(entryActions.handleBaseCustomFieldGroupDelete(item));\n    };\n\n    const handleBoardCreate = ({ item, included: { boardMemberships }, requestId }) => {\n      emit(entryActions.handleBoardCreate(item, boardMemberships, requestId));\n    };\n\n    const handleBoardUpdate = ({ item }) => {\n      emit(entryActions.handleBoardUpdate(item));\n    };\n\n    const handleBoardDelete = ({ item }) => {\n      emit(entryActions.handleBoardDelete(item));\n    };\n\n    const handleBoardMembershipCreate = ({ item, included: { users } = {} }) => {\n      emit(entryActions.handleBoardMembershipCreate(item, users));\n    };\n\n    const handleBoardMembershipUpdate = ({ item }) => {\n      emit(entryActions.handleBoardMembershipUpdate(item));\n    };\n\n    const handleBoardMembershipDelete = ({ item }) => {\n      emit(entryActions.handleBoardMembershipDelete(item));\n    };\n\n    const handleListCreate = ({ item }) => {\n      emit(entryActions.handleListCreate(item));\n    };\n\n    const handleListUpdate = ({ item }) => {\n      emit(entryActions.handleListUpdate(item));\n    };\n\n    const handleListClear = ({ item }) => {\n      emit(entryActions.handleListClear(item));\n    };\n\n    const handleListDelete = api.makeHandleListDelete(({ item, included: { cards } }) => {\n      emit(entryActions.handleListDelete(item, cards));\n    });\n\n    const handleLabelCreate = ({ item }) => {\n      emit(entryActions.handleLabelCreate(item));\n    };\n\n    const handleLabelUpdate = ({ item }) => {\n      emit(entryActions.handleLabelUpdate(item));\n    };\n\n    const handleLabelDelete = ({ item }) => {\n      emit(entryActions.handleLabelDelete(item));\n    };\n\n    const handleCardsUpdate = api.makeHandleCardsUpdate(\n      ({ items, included: { activities } = {} }) => {\n        emit(entryActions.handleCardsUpdate(items, activities));\n      },\n    );\n\n    const handleCardCreate = api.makeHandleCardCreate(({ item }) => {\n      emit(entryActions.handleCardCreate(item));\n    });\n\n    const handleCardUpdate = api.makeHandleCardUpdate(({ item }) => {\n      emit(entryActions.handleCardUpdate(item));\n    });\n\n    const handleCardDelete = api.makeHandleCardDelete(({ item }) => {\n      emit(entryActions.handleCardDelete(item));\n    });\n\n    const handleUserToCardAdd = ({ item }) => {\n      emit(entryActions.handleUserToCardAdd(item));\n    };\n\n    const handleUserFromCardRemove = ({ item }) => {\n      emit(entryActions.handleUserFromCardRemove(item));\n    };\n\n    const handleLabelToCardAdd = ({ item }) => {\n      emit(entryActions.handleLabelToCardAdd(item));\n    };\n\n    const handleLabelFromCardRemove = ({ item }) => {\n      emit(entryActions.handleLabelFromCardRemove(item));\n    };\n\n    const handleTaskListCreate = ({ item }) => {\n      emit(entryActions.handleTaskListCreate(item));\n    };\n\n    const handleTaskListUpdate = ({ item }) => {\n      emit(entryActions.handleTaskListUpdate(item));\n    };\n\n    const handleTaskListDelete = ({ item }) => {\n      emit(entryActions.handleTaskListDelete(item));\n    };\n\n    const handleTaskCreate = ({ item }) => {\n      emit(entryActions.handleTaskCreate(item));\n    };\n\n    const handleTaskUpdate = ({ item }) => {\n      emit(entryActions.handleTaskUpdate(item));\n    };\n\n    const handleTaskDelete = ({ item }) => {\n      emit(entryActions.handleTaskDelete(item));\n    };\n\n    const handleAttachmentCreate = api.makeHandleAttachmentCreate(({ item, requestId }) => {\n      emit(entryActions.handleAttachmentCreate(item, requestId));\n    });\n\n    const handleAttachmentUpdate = api.makeHandleAttachmentUpdate(({ item }) => {\n      emit(entryActions.handleAttachmentUpdate(item));\n    });\n\n    const handleAttachmentDelete = api.makeHandleAttachmentDelete(({ item }) => {\n      emit(entryActions.handleAttachmentDelete(item));\n    });\n\n    const handleCustomFieldGroupCreate = ({ item }) => {\n      emit(entryActions.handleCustomFieldGroupCreate(item));\n    };\n\n    const handleCustomFieldGroupUpdate = ({ item }) => {\n      emit(entryActions.handleCustomFieldGroupUpdate(item));\n    };\n\n    const handleCustomFieldGroupDelete = ({ item }) => {\n      emit(entryActions.handleCustomFieldGroupDelete(item));\n    };\n\n    const handleCustomFieldCreate = ({ item }) => {\n      emit(entryActions.handleCustomFieldCreate(item));\n    };\n\n    const handleCustomFieldUpdate = ({ item }) => {\n      emit(entryActions.handleCustomFieldUpdate(item));\n    };\n\n    const handleCustomFieldDelete = ({ item }) => {\n      emit(entryActions.handleCustomFieldDelete(item));\n    };\n\n    const handleCustomFieldValueUpdate = ({ item }) => {\n      emit(entryActions.handleCustomFieldValueUpdate(item));\n    };\n\n    const handleCustomFieldValueDelete = ({ item }) => {\n      emit(entryActions.handleCustomFieldValueDelete(item));\n    };\n\n    const handleCommentCreate = api.makeHandleCommentCreate(({ item, included: { users } }) => {\n      emit(entryActions.handleCommentCreate(item, users));\n    });\n\n    const handleCommentUpdate = api.makeHandleCommentUpdate(({ item }) => {\n      emit(entryActions.handleCommentUpdate(item));\n    });\n\n    const handleCommentDelete = api.makeHandleCommentDelete(({ item }) => {\n      emit(entryActions.handleCommentDelete(item));\n    });\n\n    const handleActivityCreate = api.makeHandleActivityCreate(({ item }) => {\n      emit(entryActions.handleActivityCreate(item));\n    });\n\n    const handleNotificationCreate = api.makeHandleNotificationCreate(\n      ({ item, included: { users } }) => {\n        emit(entryActions.handleNotificationCreate(item, users));\n      },\n    );\n\n    const handleNotificationUpdate = api.makeHandleNotificationUpdate(({ item }) => {\n      emit(entryActions.handleNotificationDelete(item));\n    });\n\n    const handleNotificationServiceCreate = ({ item }) => {\n      emit(entryActions.handleNotificationServiceCreate(item));\n    };\n\n    const handleNotificationServiceUpdate = ({ item }) => {\n      emit(entryActions.handleNotificationServiceUpdate(item));\n    };\n\n    const handleNotificationServiceDelete = ({ item }) => {\n      emit(entryActions.handleNotificationServiceDelete(item));\n    };\n\n    socket.on('disconnect', handleDisconnect);\n    socket.on('reconnect', handleReconnect);\n\n    socket.on('logout', handleLogout);\n\n    socket.on('bootstrapUpdate', handleBootstrapUpdate);\n\n    socket.on('configUpdate', handleConfigUpdate);\n\n    socket.on('webhookCreate', handleWebhookCreate);\n    socket.on('webhookUpdate', handleWebhookUpdate);\n    socket.on('webhookDelete', handleWebhookDelete);\n\n    socket.on('usersReset', handleUsersReset);\n    socket.on('userCreate', handleUserCreate);\n    socket.on('userUpdate', handleUserUpdate);\n    socket.on('userDelete', handleUserDelete);\n\n    socket.on('projectCreate', handleProjectCreate);\n    socket.on('projectUpdate', handleProjectUpdate);\n    socket.on('projectDelete', handleProjectDelete);\n\n    socket.on('projectManagerCreate', handleProjectManagerCreate);\n    socket.on('projectManagerDelete', handleProjectManagerDelete);\n\n    socket.on('backgroundImageCreate', handleBackgroundImageCreate);\n    socket.on('backgroundImageDelete', handleBackgroundImageDelete);\n\n    socket.on('baseCustomFieldGroupCreate', handleBaseCustomFieldGroupCreate);\n    socket.on('baseCustomFieldGroupUpdate', handleBaseCustomFieldGroupUpdate);\n    socket.on('baseCustomFieldGroupDelete', handleBaseCustomFieldGroupDelete);\n\n    socket.on('boardCreate', handleBoardCreate);\n    socket.on('boardUpdate', handleBoardUpdate);\n    socket.on('boardDelete', handleBoardDelete);\n\n    socket.on('boardMembershipCreate', handleBoardMembershipCreate);\n    socket.on('boardMembershipUpdate', handleBoardMembershipUpdate);\n    socket.on('boardMembershipDelete', handleBoardMembershipDelete);\n\n    socket.on('listCreate', handleListCreate);\n    socket.on('listUpdate', handleListUpdate);\n    socket.on('listClear', handleListClear);\n    socket.on('listDelete', handleListDelete);\n\n    socket.on('labelCreate', handleLabelCreate);\n    socket.on('labelUpdate', handleLabelUpdate);\n    socket.on('labelDelete', handleLabelDelete);\n\n    socket.on('cardsUpdate', handleCardsUpdate);\n    socket.on('cardCreate', handleCardCreate);\n    socket.on('cardUpdate', handleCardUpdate);\n    socket.on('cardDelete', handleCardDelete);\n\n    socket.on('cardMembershipCreate', handleUserToCardAdd);\n    socket.on('cardMembershipDelete', handleUserFromCardRemove);\n\n    socket.on('cardLabelCreate', handleLabelToCardAdd);\n    socket.on('cardLabelDelete', handleLabelFromCardRemove);\n\n    socket.on('taskListCreate', handleTaskListCreate);\n    socket.on('taskListUpdate', handleTaskListUpdate);\n    socket.on('taskListDelete', handleTaskListDelete);\n\n    socket.on('taskCreate', handleTaskCreate);\n    socket.on('taskUpdate', handleTaskUpdate);\n    socket.on('taskDelete', handleTaskDelete);\n\n    socket.on('attachmentCreate', handleAttachmentCreate);\n    socket.on('attachmentUpdate', handleAttachmentUpdate);\n    socket.on('attachmentDelete', handleAttachmentDelete);\n\n    socket.on('customFieldGroupCreate', handleCustomFieldGroupCreate);\n    socket.on('customFieldGroupUpdate', handleCustomFieldGroupUpdate);\n    socket.on('customFieldGroupDelete', handleCustomFieldGroupDelete);\n\n    socket.on('customFieldCreate', handleCustomFieldCreate);\n    socket.on('customFieldUpdate', handleCustomFieldUpdate);\n    socket.on('customFieldDelete', handleCustomFieldDelete);\n\n    socket.on('customFieldValueUpdate', handleCustomFieldValueUpdate);\n    socket.on('customFieldValueDelete', handleCustomFieldValueDelete);\n\n    socket.on('commentCreate', handleCommentCreate);\n    socket.on('commentUpdate', handleCommentUpdate);\n    socket.on('commentDelete', handleCommentDelete);\n\n    socket.on('actionCreate', handleActivityCreate);\n\n    socket.on('notificationCreate', handleNotificationCreate);\n    socket.on('notificationUpdate', handleNotificationUpdate);\n\n    socket.on('notificationServiceCreate', handleNotificationServiceCreate);\n    socket.on('notificationServiceUpdate', handleNotificationServiceUpdate);\n    socket.on('notificationServiceDelete', handleNotificationServiceDelete);\n\n    return () => {\n      socket.off('disconnect', handleDisconnect);\n      socket.off('reconnect', handleReconnect);\n\n      socket.off('logout', handleLogout);\n\n      socket.off('bootstrapUpdate', handleBootstrapUpdate);\n\n      socket.off('configUpdate', handleConfigUpdate);\n\n      socket.off('webhookCreate', handleWebhookCreate);\n      socket.off('webhookUpdate', handleWebhookUpdate);\n      socket.off('webhookDelete', handleWebhookDelete);\n\n      socket.off('usersReset', handleUsersReset);\n      socket.off('userCreate', handleUserCreate);\n      socket.off('userUpdate', handleUserUpdate);\n      socket.off('userDelete', handleUserDelete);\n\n      socket.off('projectCreate', handleProjectCreate);\n      socket.off('projectUpdate', handleProjectUpdate);\n      socket.off('projectDelete', handleProjectDelete);\n\n      socket.off('projectManagerCreate', handleProjectManagerCreate);\n      socket.off('projectManagerDelete', handleProjectManagerDelete);\n\n      socket.off('backgroundImageCreate', handleBackgroundImageCreate);\n      socket.off('backgroundImageDelete', handleBackgroundImageDelete);\n\n      socket.off('baseCustomFieldGroupCreate', handleBaseCustomFieldGroupCreate);\n      socket.off('baseCustomFieldGroupUpdate', handleBaseCustomFieldGroupUpdate);\n      socket.off('baseCustomFieldGroupDelete', handleBaseCustomFieldGroupDelete);\n\n      socket.off('boardCreate', handleBoardCreate);\n      socket.off('boardUpdate', handleBoardUpdate);\n      socket.off('boardDelete', handleBoardDelete);\n\n      socket.off('boardMembershipCreate', handleBoardMembershipCreate);\n      socket.off('boardMembershipUpdate', handleBoardMembershipUpdate);\n      socket.off('boardMembershipDelete', handleBoardMembershipDelete);\n\n      socket.off('listCreate', handleListCreate);\n      socket.off('listUpdate', handleListUpdate);\n      socket.off('listClear', handleListClear);\n      socket.off('listDelete', handleListDelete);\n\n      socket.off('labelCreate', handleLabelCreate);\n      socket.off('labelUpdate', handleLabelUpdate);\n      socket.off('labelDelete', handleLabelDelete);\n\n      socket.off('cardsUpdate', handleCardsUpdate);\n      socket.off('cardCreate', handleCardCreate);\n      socket.off('cardUpdate', handleCardUpdate);\n      socket.off('cardDelete', handleCardDelete);\n\n      socket.off('cardMembershipCreate', handleUserToCardAdd);\n      socket.off('cardMembershipDelete', handleUserFromCardRemove);\n\n      socket.off('cardLabelCreate', handleLabelToCardAdd);\n      socket.off('cardLabelDelete', handleLabelFromCardRemove);\n\n      socket.off('taskListCreate', handleTaskListCreate);\n      socket.off('taskListUpdate', handleTaskListUpdate);\n      socket.off('taskListDelete', handleTaskListDelete);\n\n      socket.off('taskCreate', handleTaskCreate);\n      socket.off('taskUpdate', handleTaskUpdate);\n      socket.off('taskDelete', handleTaskDelete);\n\n      socket.off('attachmentCreate', handleAttachmentCreate);\n      socket.off('attachmentUpdate', handleAttachmentUpdate);\n      socket.off('attachmentDelete', handleAttachmentDelete);\n\n      socket.off('customFieldGroupCreate', handleCustomFieldGroupCreate);\n      socket.off('customFieldGroupUpdate', handleCustomFieldGroupUpdate);\n      socket.off('customFieldGroupDelete', handleCustomFieldGroupDelete);\n\n      socket.off('customFieldCreate', handleCustomFieldCreate);\n      socket.off('customFieldUpdate', handleCustomFieldUpdate);\n      socket.off('customFieldDelete', handleCustomFieldDelete);\n\n      socket.off('customFieldValueUpdate', handleCustomFieldValueUpdate);\n      socket.off('customFieldValueDelete', handleCustomFieldValueDelete);\n\n      socket.off('commentCreate', handleCommentCreate);\n      socket.off('commentUpdate', handleCommentUpdate);\n      socket.off('commentDelete', handleCommentDelete);\n\n      socket.off('actionCreate', handleActivityCreate);\n\n      socket.off('notificationCreate', handleNotificationCreate);\n      socket.off('notificationUpdate', handleNotificationUpdate);\n\n      socket.off('notificationServiceCreate', handleNotificationServiceCreate);\n      socket.off('notificationServiceUpdate', handleNotificationServiceUpdate);\n      socket.off('notificationServiceDelete', handleNotificationServiceDelete);\n    };\n  });\n\nexport default function* socketWatchers() {\n  yield all([\n    yield takeEvery(EntryActionTypes.SOCKET_DISCONNECT_HANDLE, () =>\n      services.handleSocketDisconnect(),\n    ),\n    yield takeEvery(EntryActionTypes.SOCKET_RECONNECT_HANDLE, () =>\n      services.handleSocketReconnect(),\n    ),\n  ]);\n\n  const socketEventsChannel = yield call(createSocketEventsChannel);\n\n  try {\n    while (true) {\n      const action = yield take(socketEventsChannel);\n\n      yield put(action);\n    }\n  } finally {\n    if (yield cancelled()) {\n      socketEventsChannel.close();\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/task-lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* taskListsWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.TASK_LIST_IN_CURRENT_CARD_CREATE, ({ payload: { data } }) =>\n      services.createTaskListInCurrentCard(data),\n    ),\n    takeEvery(EntryActionTypes.TASK_LIST_CREATE_HANDLE, ({ payload: { taskList } }) =>\n      services.handleTaskListCreate(taskList),\n    ),\n    takeEvery(EntryActionTypes.TASK_LIST_UPDATE, ({ payload: { id, data } }) =>\n      services.updateTaskList(id, data),\n    ),\n    takeEvery(EntryActionTypes.TASK_LIST_UPDATE_HANDLE, ({ payload: { taskList } }) =>\n      services.handleTaskListUpdate(taskList),\n    ),\n    takeEvery(EntryActionTypes.TASK_LIST_MOVE, ({ payload: { id, index } }) =>\n      services.moveTaskList(id, index),\n    ),\n    takeEvery(EntryActionTypes.TASK_LIST_DELETE, ({ payload: { id } }) =>\n      services.deleteTaskList(id),\n    ),\n    takeEvery(EntryActionTypes.TASK_LIST_DELETE_HANDLE, ({ payload: { taskList } }) =>\n      services.handleTaskListDelete(taskList),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/tasks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* tasksWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.TASK_CREATE, ({ payload: { taskListId, data } }) =>\n      services.createTask(taskListId, data),\n    ),\n    takeEvery(EntryActionTypes.TASK_CREATE_HANDLE, ({ payload: { task } }) =>\n      services.handleTaskCreate(task),\n    ),\n    takeEvery(EntryActionTypes.TASK_UPDATE, ({ payload: { id, data } }) =>\n      services.updateTask(id, data),\n    ),\n    takeEvery(EntryActionTypes.TASK_UPDATE_HANDLE, ({ payload: { task } }) =>\n      services.handleTaskUpdate(task),\n    ),\n    takeEvery(EntryActionTypes.TASK_MOVE, ({ payload: { id, taskListId, index } }) =>\n      services.moveTask(id, taskListId, index),\n    ),\n    takeEvery(EntryActionTypes.TASK_DELETE, ({ payload: { id } }) => services.deleteTask(id)),\n    takeEvery(EntryActionTypes.TASK_DELETE_HANDLE, ({ payload: { task } }) =>\n      services.handleTaskDelete(task),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/users.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* usersWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.USERS_RESET_HANDLE, () => services.handleUsersReset()),\n    takeEvery(EntryActionTypes.USER_CREATE, ({ payload: { data } }) => services.createUser(data)),\n    takeEvery(EntryActionTypes.USER_CREATE_HANDLE, ({ payload: { user } }) =>\n      services.handleUserCreate(user),\n    ),\n    takeEvery(EntryActionTypes.USER_CREATE_ERROR_CLEAR, () => services.clearUserCreateError()),\n    takeEvery(EntryActionTypes.USER_UPDATE, ({ payload: { id, data } }) =>\n      services.updateUser(id, data),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_USER_UPDATE, ({ payload: { data } }) =>\n      services.updateCurrentUser(data),\n    ),\n    takeEvery(EntryActionTypes.USER_UPDATE_HANDLE, ({ payload: { user } }) =>\n      services.handleUserUpdate(user),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_USER_LANGUAGE_UPDATE, ({ payload: { language } }) =>\n      services.updateCurrentUserLanguage(language),\n    ),\n    takeEvery(EntryActionTypes.USER_EMAIL_UPDATE, ({ payload: { id, data } }) =>\n      services.updateUserEmail(id, data),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_USER_EMAIL_UPDATE, ({ payload: { data } }) =>\n      services.updateCurrentUserEmail(data),\n    ),\n    takeEvery(EntryActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR, ({ payload: { id } }) =>\n      services.clearUserEmailUpdateError(id),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_USER_EMAIL_UPDATE_ERROR_CLEAR, () =>\n      services.clearCurrentUserEmailUpdateError(),\n    ),\n    takeEvery(EntryActionTypes.USER_PASSWORD_UPDATE, ({ payload: { id, data } }) =>\n      services.updateUserPassword(id, data),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE, ({ payload: { data } }) =>\n      services.updateCurrentUserPassword(data),\n    ),\n    takeEvery(EntryActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR, ({ payload: { id } }) =>\n      services.clearUserPasswordUpdateError(id),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE_ERROR_CLEAR, () =>\n      services.clearCurrentUserPasswordUpdateError(),\n    ),\n    takeEvery(EntryActionTypes.USER_USERNAME_UPDATE, ({ payload: { id, data } }) =>\n      services.updateUserUsername(id, data),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_USER_USERNAME_UPDATE, ({ payload: { data } }) =>\n      services.updateCurrentUserUsername(data),\n    ),\n    takeEvery(EntryActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR, ({ payload: { id } }) =>\n      services.clearUserUsernameUpdateError(id),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_USER_USERNAME_UPDATE_ERROR_CLEAR, () =>\n      services.clearCurrentUserUsernameUpdateError(),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_USER_AVATAR_UPDATE, ({ payload: { data } }) =>\n      services.updateCurrentUserAvatar(data),\n    ),\n    takeEvery(EntryActionTypes.USER_API_KEY_CREATE, ({ payload: { id } }) =>\n      services.createUserApiKey(id),\n    ),\n    takeEvery(EntryActionTypes.USER_API_KEY_DELETE, ({ payload: { id } }) =>\n      services.deleteUserApiKey(id),\n    ),\n    takeEvery(EntryActionTypes.USER_API_KEY_VALUE_CLEAR, ({ payload: { id } }) =>\n      services.clearUserApiKeyValue(id),\n    ),\n    takeEvery(EntryActionTypes.USER_DELETE, ({ payload: { id } }) => services.deleteUser(id)),\n    takeEvery(EntryActionTypes.USER_DELETE_HANDLE, ({ payload: { user } }) =>\n      services.handleUserDelete(user),\n    ),\n    takeEvery(EntryActionTypes.USER_TO_CARD_ADD, ({ payload: { id, cardId } }) =>\n      services.addUserToCard(id, cardId),\n    ),\n    takeEvery(EntryActionTypes.USER_TO_CURRENT_CARD_ADD, ({ payload: { id } }) =>\n      services.addUserToCurrentCard(id),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_USER_TO_CURRENT_CARD_ADD, () =>\n      services.addCurrentUserToCurrentCard(),\n    ),\n    takeEvery(EntryActionTypes.USER_TO_CARD_ADD_HANDLE, ({ payload: { cardMembership } }) =>\n      services.handleUserToCardAdd(cardMembership),\n    ),\n    takeEvery(EntryActionTypes.USER_FROM_CARD_REMOVE, ({ payload: { id, cardId } }) =>\n      services.removeUserFromCard(id, cardId),\n    ),\n    takeEvery(EntryActionTypes.USER_FROM_CURRENT_CARD_REMOVE, ({ payload: { id } }) =>\n      services.removeUserFromCurrentCard(id),\n    ),\n    takeEvery(EntryActionTypes.CURRENT_USER_FROM_CURRENT_CARD_REMOVE, () =>\n      services.removeCurrentUserFromCurrentCard(),\n    ),\n    takeEvery(EntryActionTypes.USER_FROM_CARD_REMOVE_HANDLE, ({ payload: { cardMembership } }) =>\n      services.handleUserFromCardRemove(cardMembership),\n    ),\n    takeEvery(\n      EntryActionTypes.USER_TO_FILTER_IN_CURRENT_BOARD_ADD,\n      ({ payload: { id, replace } }) => services.addUserToFilterInCurrentBoard(id, replace),\n    ),\n    takeEvery(EntryActionTypes.USER_FROM_FILTER_IN_CURRENT_BOARD_REMOVE, ({ payload: { id } }) =>\n      services.removeUserFromFilterInCurrentBoard(id),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/core/watchers/webhooks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* webhooksWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.WEBHOOK_CREATE, ({ payload: { data } }) =>\n      services.createWebhook(data),\n    ),\n    takeEvery(EntryActionTypes.WEBHOOK_CREATE_HANDLE, ({ payload: { webhook } }) =>\n      services.handleWebhookCreate(webhook),\n    ),\n    takeEvery(EntryActionTypes.WEBHOOK_UPDATE, ({ payload: { id, data } }) =>\n      services.updateWebhook(id, data),\n    ),\n    takeEvery(EntryActionTypes.WEBHOOK_UPDATE_HANDLE, ({ payload: { webhook } }) =>\n      services.handleWebhookUpdate(webhook),\n    ),\n    takeEvery(EntryActionTypes.WEBHOOK_DELETE, ({ payload: { id } }) => services.deleteWebhook(id)),\n    takeEvery(EntryActionTypes.WEBHOOK_DELETE_HANDLE, ({ payload: { webhook } }) =>\n      services.handleWebhookDelete(webhook),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, select } from 'redux-saga/effects';\n\nimport loginSaga from './login';\nimport coreSaga from './core';\nimport selectors from '../selectors';\n\nexport default function* rootSaga() {\n  const accessToken = yield select(selectors.selectAccessToken);\n\n  if (!accessToken) {\n    yield call(loginSaga);\n  }\n\n  yield call(coreSaga);\n}\n"
  },
  {
    "path": "client/src/sagas/login/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, cancel, fork, take } from 'redux-saga/effects';\n\nimport watchers from './watchers';\nimport services from './services';\nimport runWatchers from '../run-watchers';\nimport ActionTypes from '../../constants/ActionTypes';\n\nexport default function* loginSaga() {\n  const watcherTasks = yield runWatchers(watchers);\n\n  yield fork(services.initializeLogin);\n\n  yield take([\n    ActionTypes.AUTHENTICATE__SUCCESS,\n    ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS,\n    ActionTypes.TERMS_ACCEPT__SUCCESS,\n  ]);\n\n  yield cancel(watcherTasks);\n  yield call(services.goToRoot);\n}\n"
  },
  {
    "path": "client/src/sagas/login/services/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport router from './router';\nimport login from './login';\n\nexport default {\n  ...router,\n  ...login,\n};\n"
  },
  {
    "path": "client/src/sagas/login/services/login.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { nanoid } from 'nanoid';\nimport { call, put, select } from 'redux-saga/effects';\nimport { replace } from '../../../lib/redux-router';\n\nimport selectors from '../../../selectors';\nimport actions from '../../../actions';\nimport api from '../../../api';\nimport i18n from '../../../i18n';\nimport { setAccessToken } from '../../../utils/access-token-storage';\nimport Paths from '../../../constants/Paths';\nimport AccessTokenSteps from '../../../constants/AccessTokenSteps';\n\nexport function* initializeLogin() {\n  const { item: bootstrap } = yield call(api.getBootstrap); // TODO: handle error\n\n  yield put(actions.initializeLogin(bootstrap));\n}\n\nexport function* authenticate(data) {\n  yield put(actions.authenticate(data));\n\n  let accessToken;\n  try {\n    ({ item: accessToken } = yield call(api.createAccessToken, data));\n  } catch (error) {\n    let terms;\n    if (error.step === AccessTokenSteps.ACCEPT_TERMS) {\n      ({ item: terms } = yield call(api.getTerms, i18n.resolvedLanguage));\n    }\n\n    yield put(actions.authenticate.failure(error, terms));\n    return;\n  }\n\n  yield call(setAccessToken, accessToken);\n  yield put(actions.authenticate.success(accessToken));\n}\n\nexport function* authenticateWithOidc() {\n  const oidcBootstrap = yield select(selectors.selectOidcBootstrap);\n\n  const state = nanoid();\n  window.localStorage.setItem('oidc-state', state);\n\n  const nonce = nanoid();\n  window.localStorage.setItem('oidc-nonce', nonce);\n\n  let redirectUrl = `${oidcBootstrap.authorizationUrl}`;\n  redirectUrl += `&state=${encodeURIComponent(state)}`;\n  redirectUrl += `&nonce=${encodeURIComponent(nonce)}`;\n\n  window.location.href = redirectUrl;\n}\n\nexport function* authenticateWithOidcCallback() {\n  // https://github.com/plankanban/planka/issues/511#issuecomment-1771385639\n  const params = new URLSearchParams(window.location.hash.substring(1) || window.location.search);\n\n  const state = window.localStorage.getItem('oidc-state');\n  window.localStorage.removeItem('oidc-state');\n\n  const nonce = window.localStorage.getItem('oidc-nonce');\n  window.localStorage.removeItem('oidc-nonce');\n\n  yield put(replace(Paths.LOGIN));\n\n  if (params.get('error') !== null) {\n    yield put(\n      actions.authenticateWithOidc.failure(\n        new Error(\n          `OIDC Authorization error: ${params.get('error')}: ${params.get('error_description')}`,\n        ),\n      ),\n    );\n    return;\n  }\n\n  const code = params.get('code');\n  if (code === null) {\n    yield put(\n      actions.authenticateWithOidc.failure(new Error('Invalid OIDC response: no code parameter')),\n    );\n    return;\n  }\n\n  if (params.get('state') !== state) {\n    yield put(\n      actions.authenticateWithOidc.failure(\n        new Error('Unable to process OIDC response: state mismatch'),\n      ),\n    );\n    return;\n  }\n\n  if (nonce === null) {\n    yield put(\n      actions.authenticateWithOidc.failure(\n        new Error('Unable to process OIDC response: no nonce issued'),\n      ),\n    );\n    return;\n  }\n\n  const oidcBootstrap = yield select(selectors.selectOidcBootstrap);\n\n  if (oidcBootstrap?.debug) {\n    const {\n      included: { logs },\n    } = yield call(api.debugOidc, {\n      code,\n      nonce,\n    });\n\n    yield put(actions.authenticateWithOidc.debug(logs));\n    return;\n  }\n\n  let accessToken;\n  try {\n    ({ item: accessToken } = yield call(api.exchangeForAccessTokenWithOidc, {\n      code,\n      nonce,\n    }));\n  } catch (error) {\n    let terms;\n    if (error.step === AccessTokenSteps.ACCEPT_TERMS) {\n      ({ item: terms } = yield call(api.getTerms, i18n.resolvedLanguage));\n    }\n\n    yield put(actions.authenticateWithOidc.failure(error, terms));\n    return;\n  }\n\n  yield call(setAccessToken, accessToken);\n  yield put(actions.authenticateWithOidc.success(accessToken));\n}\n\nexport function* clearAuthenticateError() {\n  yield put(actions.clearAuthenticateError());\n}\n\nexport function* acceptTerms(signature) {\n  yield put(actions.acceptTerms(signature));\n\n  const { pendingToken } = yield select(selectors.selectAuthenticateForm);\n\n  let accessToken;\n  try {\n    ({ item: accessToken } = yield call(api.acceptTerms, {\n      pendingToken,\n      signature,\n      initialLanguage: i18n.resolvedLanguage,\n    }));\n  } catch (error) {\n    yield put(actions.acceptTerms.failure(error));\n    return;\n  }\n\n  yield call(setAccessToken, accessToken);\n  yield put(actions.acceptTerms.success(accessToken));\n}\n\nexport function* cancelTerms() {\n  const { pendingToken } = yield select(selectors.selectAuthenticateForm);\n\n  yield put(actions.cancelTerms());\n\n  try {\n    yield call(api.revokePendingToken, {\n      pendingToken,\n    });\n  } catch (error) {\n    yield put(actions.cancelTerms.failure(error));\n    return;\n  }\n\n  yield put(actions.cancelTerms.success(pendingToken));\n}\n\nexport function* updateTermsLanguage(value) {\n  yield put(actions.updateTermsLanguage(value));\n\n  let terms;\n  try {\n    ({ item: terms } = yield call(api.getTerms, value));\n  } catch (error) {\n    yield put(actions.updateTermsLanguage.failure(error));\n    return;\n  }\n\n  yield put(actions.updateTermsLanguage.success(terms));\n}\n\nexport default {\n  initializeLogin,\n  authenticate,\n  authenticateWithOidc,\n  authenticateWithOidcCallback,\n  clearAuthenticateError,\n  acceptTerms,\n  cancelTerms,\n  updateTermsLanguage,\n};\n"
  },
  {
    "path": "client/src/sagas/login/services/router.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { call, put, select, take } from 'redux-saga/effects';\nimport { push } from '../../../lib/redux-router';\n\nimport { authenticateWithOidc, authenticateWithOidcCallback } from './login';\nimport selectors from '../../../selectors';\nimport ActionTypes from '../../../constants/ActionTypes';\nimport Paths from '../../../constants/Paths';\n\nexport function* goTo(pathname) {\n  yield put(push(pathname));\n}\n\nexport function* goToLogin() {\n  yield call(goTo, Paths.LOGIN);\n}\n\nexport function* goToRoot() {\n  yield call(goTo, Paths.ROOT);\n}\n\nexport function* handleLocationChange() {\n  const pathsMatch = yield select(selectors.selectPathsMatch);\n\n  if (!pathsMatch) {\n    return;\n  }\n\n  switch (pathsMatch.pattern.path) {\n    case Paths.ROOT:\n    case Paths.PROJECTS:\n    case Paths.BOARDS:\n    case Paths.CARDS:\n      yield call(goToLogin);\n\n      break;\n    default:\n  }\n\n  const isInitializing = yield select(selectors.selectIsInitializing);\n\n  if (isInitializing) {\n    yield take(ActionTypes.LOGIN_INITIALIZE);\n  }\n\n  switch (pathsMatch.pattern.path) {\n    case Paths.LOGIN: {\n      const oidcBootstrap = yield select(selectors.selectOidcBootstrap);\n\n      if (oidcBootstrap) {\n        const params = new URLSearchParams(window.location.search);\n\n        if (params.has('authenticateWithOidc')) {\n          yield call(authenticateWithOidc);\n        }\n      }\n\n      break;\n    }\n    case Paths.OIDC_CALLBACK:\n      yield call(authenticateWithOidcCallback);\n\n      break;\n    default:\n  }\n}\n\nexport default {\n  goTo,\n  goToLogin,\n  goToRoot,\n  handleLocationChange,\n};\n"
  },
  {
    "path": "client/src/sagas/login/watchers/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport router from './router';\nimport login from './login';\n\nexport default [router, login];\n"
  },
  {
    "path": "client/src/sagas/login/watchers/login.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, takeEvery } from 'redux-saga/effects';\n\nimport services from '../services';\nimport EntryActionTypes from '../../../constants/EntryActionTypes';\n\nexport default function* loginWatchers() {\n  yield all([\n    takeEvery(EntryActionTypes.AUTHENTICATE, ({ payload: { data } }) =>\n      services.authenticate(data),\n    ),\n    takeEvery(EntryActionTypes.WITH_OIDC_AUTHENTICATE, () => services.authenticateWithOidc()),\n    takeEvery(EntryActionTypes.AUTHENTICATE_ERROR_CLEAR, () => services.clearAuthenticateError()),\n    takeEvery(EntryActionTypes.TERMS_ACCEPT, ({ payload: { signature } }) =>\n      services.acceptTerms(signature),\n    ),\n    takeEvery(EntryActionTypes.TERMS_CANCEL, () => services.cancelTerms()),\n    takeEvery(EntryActionTypes.TERMS_LANGUAGE_UPDATE, ({ payload: { value } }) =>\n      services.updateTermsLanguage(value),\n    ),\n  ]);\n}\n"
  },
  {
    "path": "client/src/sagas/login/watchers/router.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { takeEvery } from 'redux-saga/effects';\nimport { LOCATION_CHANGE_HANDLE } from '../../../lib/redux-router';\n\nimport services from '../services';\n\nexport default function* routerWatchers() {\n  yield takeEvery(LOCATION_CHANGE_HANDLE, () => services.handleLocationChange());\n}\n"
  },
  {
    "path": "client/src/sagas/run-watchers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { all, call, spawn } from 'redux-saga/effects';\n\nfunction* safeWatch(watcher) {\n  while (true) {\n    try {\n      yield call(watcher);\n      break;\n    } catch (error) {\n      console.error(error); // eslint-disable-line no-console\n    }\n  }\n}\n\nexport default function* runWatchers(watchers) {\n  return yield all(watchers.map((watcher) => spawn(safeWatch, watcher)));\n}\n"
  },
  {
    "path": "client/src/selectors/activities.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectActivityById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Activity }, id) => {\n      const activityModel = Activity.withId(id);\n\n      if (!activityModel) {\n        return activityModel;\n      }\n\n      return {\n        ...activityModel.ref,\n        isPersisted: !isLocalId(activityModel.id),\n      };\n    },\n  );\n\nexport const selectActivityById = makeSelectActivityById();\n\nexport default {\n  makeSelectActivityById,\n  selectActivityById,\n};\n"
  },
  {
    "path": "client/src/selectors/attachments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectAttachmentById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Attachment }, id) => {\n      const attachmentModel = Attachment.withId(id);\n\n      if (!attachmentModel) {\n        return attachmentModel;\n      }\n\n      return {\n        ...attachmentModel.ref,\n        isPersisted: !isLocalId(attachmentModel.id),\n      };\n    },\n  );\n\nexport const selectAttachmentById = makeSelectAttachmentById();\n\nexport const selectIsAttachmentWithIdExists = createSelector(\n  orm,\n  (_, id) => id,\n  ({ Attachment }, id) => Attachment.idExists(id),\n);\n\nexport default {\n  makeSelectAttachmentById,\n  selectAttachmentById,\n  selectIsAttachmentWithIdExists,\n};\n"
  },
  {
    "path": "client/src/selectors/background-images.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectBackgroundImageById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ BackgroundImage }, id) => {\n      const backgroundImageModel = BackgroundImage.withId(id);\n\n      if (!backgroundImageModel) {\n        return backgroundImageModel;\n      }\n\n      return {\n        ...backgroundImageModel.ref,\n        isPersisted: !isLocalId(backgroundImageModel.id),\n      };\n    },\n  );\n\nexport const selectBackgroundImageById = makeSelectBackgroundImageById();\n\nexport const selectIsBackgroundImageWithIdExists = createSelector(\n  orm,\n  (_, id) => id,\n  ({ BackgroundImage }, id) => BackgroundImage.idExists(id),\n);\n\nexport default {\n  makeSelectBackgroundImageById,\n  selectBackgroundImageById,\n  selectIsBackgroundImageWithIdExists,\n};\n"
  },
  {
    "path": "client/src/selectors/base-custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectBaseCustomFieldGroupById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ BaseCustomFieldGroup }, id) => {\n      const baseCustomFieldGroupModel = BaseCustomFieldGroup.withId(id);\n\n      if (!baseCustomFieldGroupModel) {\n        return baseCustomFieldGroupModel;\n      }\n\n      return {\n        ...baseCustomFieldGroupModel.ref,\n        isPersisted: !isLocalId(baseCustomFieldGroupModel.id),\n      };\n    },\n  );\n\nexport const selectBaseCustomFieldGroupById = makeSelectBaseCustomFieldGroupById();\n\nexport const makeSelectCustomFieldsByBaseGroupId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ BaseCustomFieldGroup }, id) => {\n      if (!id) {\n        return id;\n      }\n\n      const baseCustomFieldGroupModel = BaseCustomFieldGroup.withId(id);\n\n      if (!baseCustomFieldGroupModel) {\n        return baseCustomFieldGroupModel;\n      }\n\n      return baseCustomFieldGroupModel.getCustomFieldsQuerySet().toRefArray();\n    },\n  );\n\nexport const selectCustomFieldsByBaseGroupId = makeSelectCustomFieldsByBaseGroupId();\n\nexport default {\n  makeSelectBaseCustomFieldGroupById,\n  selectBaseCustomFieldGroupById,\n  makeSelectCustomFieldsByBaseGroupId,\n  selectCustomFieldsByBaseGroupId,\n};\n"
  },
  {
    "path": "client/src/selectors/board-memberships.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectBoardMembershipById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ BoardMembership }, id) => {\n      const boardMembershipModel = BoardMembership.withId(id);\n\n      if (!boardMembershipModel) {\n        return boardMembershipModel;\n      }\n\n      return {\n        ...boardMembershipModel.ref,\n        isPersisted: !isLocalId(boardMembershipModel.id),\n      };\n    },\n  );\n\nexport const selectBoardMembershipById = makeSelectBoardMembershipById();\n\nexport default {\n  makeSelectBoardMembershipById,\n  selectBoardMembershipById,\n};\n"
  },
  {
    "path": "client/src/selectors/boards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { selectPath } from './router';\nimport { selectCurrentUserId } from './users';\nimport { isLocalId } from '../utils/local-id';\nimport { isListArchiveOrTrash } from '../utils/record-helpers';\nimport { ListTypes } from '../constants/Enums';\n\nexport const makeSelectBoardById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    (state) => selectPath(state).boardId,\n    ({ Board }, id) => {\n      const boardModel = Board.withId(id);\n\n      if (!boardModel) {\n        return boardModel;\n      }\n\n      return {\n        ...boardModel.ref,\n        isPersisted: !isLocalId(boardModel.id),\n      };\n    },\n  );\n\nexport const selectBoardById = makeSelectBoardById();\n\nexport const makeSelectCurrentUserMembershipByBoardId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    (state) => selectCurrentUserId(state),\n    ({ Board }, id, currentUserId) => {\n      if (!id) {\n        return id;\n      }\n\n      const boardModel = Board.withId(id);\n\n      if (!boardModel) {\n        return boardModel;\n      }\n\n      const boardMembershipModel = boardModel.getMembershipModelByUserId(currentUserId);\n\n      if (!boardMembershipModel) {\n        return boardMembershipModel;\n      }\n\n      return boardMembershipModel.ref;\n    },\n  );\n\nconst selectCurrentUserMembershipByBoardId = makeSelectCurrentUserMembershipByBoardId();\n\nexport const makeSelectNotificationsTotalByBoardId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    (state) => selectCurrentUserId(state),\n    ({ Board }, id) => {\n      const boardModel = Board.withId(id);\n\n      if (!boardModel) {\n        return boardModel;\n      }\n\n      return boardModel.getUnreadNotificationsQuerySet().count();\n    },\n  );\n\nexport const selectNotificationsTotalByBoardId = makeSelectNotificationsTotalByBoardId();\n\nexport const makeSelectNotificationServiceIdsByBoardId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    (state) => selectCurrentUserId(state),\n    ({ Board }, id) => {\n      const boardModel = Board.withId(id);\n\n      if (!boardModel) {\n        return boardModel;\n      }\n\n      return boardModel\n        .getNotificationServicesQuerySet()\n        .toRefArray()\n        .map((notificationService) => notificationService.id);\n    },\n  );\n\nexport const selectNotificationServiceIdsByBoardId = makeSelectNotificationServiceIdsByBoardId();\n\nexport const selectIsBoardWithIdAvailableForCurrentUser = createSelector(\n  orm,\n  (_, id) => id,\n  (state) => selectCurrentUserId(state),\n  ({ Board, User }, id, currentUserId) => {\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return false;\n    }\n\n    const currentUserModel = User.withId(currentUserId);\n    return boardModel.isAvailableForUser(currentUserModel);\n  },\n);\n\nexport const selectCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel.ref;\n  },\n);\n\nexport const selectMembershipsForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel\n      .getMembershipsQuerySet()\n      .toModelArray()\n      .map((boardMembershipModel) => ({\n        ...boardMembershipModel.ref,\n        isPersisted: !isLocalId(boardMembershipModel.id),\n        user: boardMembershipModel.user.ref,\n      }));\n  },\n);\n\nexport const selectMemberUserIdsForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel\n      .getMembershipsQuerySet()\n      .toModelArray()\n      .map((boardMembershipModel) => boardMembershipModel.user.id);\n  },\n);\n\nexport const selectCurrentUserMembershipForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  (state) => selectCurrentUserId(state),\n  ({ Board }, id, currentUserId) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    const boardMembershipModel = boardModel.getMembershipModelByUserId(currentUserId);\n\n    if (!boardMembershipModel) {\n      return boardMembershipModel;\n    }\n\n    return boardMembershipModel.ref;\n  },\n);\n\nexport const selectLabelsForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel.getLabelsQuerySet().toRefArray();\n  },\n);\n\nexport const selectArchiveListIdForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    const listModel = boardModel.lists\n      .filter({\n        type: ListTypes.ARCHIVE,\n      })\n      .first();\n\n    return listModel.id;\n  },\n);\n\nexport const selectTrashListIdForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    const listModel = boardModel.lists\n      .filter({\n        type: ListTypes.TRASH,\n      })\n      .first();\n\n    return listModel.id;\n  },\n);\n\nexport const selectKanbanListIdsForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel\n      .getKanbanListsQuerySet()\n      .toRefArray()\n      .map((list) => list.id);\n  },\n);\n\n// TODO: rename?\nexport const selectAvailableListsForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel\n      .getListsQuerySet()\n      .toRefArray()\n      .filter((list) => !isListArchiveOrTrash(list));\n  },\n);\n\nexport const selectCardsExceptCurrentForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  (state) => selectPath(state).cardId,\n  ({ Board }, id, cardId) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel\n      .getCardsModelArray()\n      .filter((cardModel) => cardModel.id !== cardId)\n      .map((cardModel) => cardModel.ref);\n  },\n);\n\nexport const selectFilteredCardIdsForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel.getFilteredCardsModelArray().map((cardModel) => cardModel.id);\n  },\n);\n\nexport const selectCustomFieldGroupIdsForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel\n      .getCustomFieldGroupsQuerySet()\n      .toRefArray()\n      .map((customFieldGroup) => customFieldGroup.id);\n  },\n);\n\nexport const selectCustomFieldGroupsForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel\n      .getCustomFieldGroupsQuerySet()\n      .toModelArray()\n      .map((customFieldGroupModel) => {\n        if (!customFieldGroupModel.name) {\n          return {\n            ...customFieldGroupModel.ref,\n            name: customFieldGroupModel.baseCustomFieldGroup.name,\n          };\n        }\n\n        return customFieldGroupModel.ref;\n      });\n  },\n);\n\nexport const selectActivityIdsForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel.getActivitiesModelArray().map((activity) => activity.id);\n  },\n);\n\nexport const selectFilterUserIdsForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel.filterUsers.toRefArray().map((user) => user.id);\n  },\n);\n\nexport const selectFilterLabelIdsForCurrentBoard = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return boardModel.filterLabels.toRefArray().map((label) => label.id);\n  },\n);\n\nexport const selectIsBoardWithIdExists = createSelector(\n  orm,\n  (_, id) => id,\n  ({ Board }, id) => Board.idExists(id),\n);\n\nexport default {\n  makeSelectBoardById,\n  selectBoardById,\n  makeSelectCurrentUserMembershipByBoardId,\n  selectCurrentUserMembershipByBoardId,\n  makeSelectNotificationsTotalByBoardId,\n  selectNotificationsTotalByBoardId,\n  makeSelectNotificationServiceIdsByBoardId,\n  selectNotificationServiceIdsByBoardId,\n  selectIsBoardWithIdAvailableForCurrentUser,\n  selectCurrentBoard,\n  selectMembershipsForCurrentBoard,\n  selectMemberUserIdsForCurrentBoard,\n  selectCurrentUserMembershipForCurrentBoard,\n  selectLabelsForCurrentBoard,\n  selectArchiveListIdForCurrentBoard,\n  selectTrashListIdForCurrentBoard,\n  selectKanbanListIdsForCurrentBoard,\n  selectAvailableListsForCurrentBoard,\n  selectCardsExceptCurrentForCurrentBoard,\n  selectFilteredCardIdsForCurrentBoard,\n  selectCustomFieldGroupIdsForCurrentBoard,\n  selectCustomFieldGroupsForCurrentBoard,\n  selectActivityIdsForCurrentBoard,\n  selectFilterUserIdsForCurrentBoard,\n  selectFilterLabelIdsForCurrentBoard,\n  selectIsBoardWithIdExists,\n};\n"
  },
  {
    "path": "client/src/selectors/cards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { selectRecentCardId } from './core';\nimport { selectPath } from './router';\nimport { selectCurrentUserId } from './users';\nimport { buildCustomFieldValueId } from '../models/CustomFieldValue';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectCardById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Card }, id) => {\n      const cardModel = Card.withId(id);\n\n      if (!cardModel) {\n        return cardModel;\n      }\n\n      return {\n        ...cardModel.ref,\n        isPersisted: !isLocalId(id),\n      };\n    },\n  );\n\nexport const selectCardById = makeSelectCardById();\n\nexport const makeSelectCardIndexById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Card }, id) => {\n      const cardModel = Card.withId(id);\n\n      if (!cardModel) {\n        return cardModel;\n      }\n\n      return cardModel.list\n        .getCardsModelArray()\n        .findIndex((cardModelItem) => cardModelItem.id === cardModel.id);\n    },\n  );\n\nexport const selectCardIndexById = makeSelectCardIndexById();\n\nexport const makeSelectUserIdsByCardId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Card }, id) => {\n      const cardModel = Card.withId(id);\n\n      if (!cardModel) {\n        return cardModel;\n      }\n\n      return cardModel.users.toRefArray().map((user) => user.id);\n    },\n  );\n\nexport const selectUserIdsByCardId = makeSelectUserIdsByCardId();\n\nexport const makeSelectLabelIdsByCardId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Card }, id) => {\n      const cardModel = Card.withId(id);\n\n      if (!cardModel) {\n        return cardModel;\n      }\n\n      return cardModel.labels.toRefArray().map((label) => label.id);\n    },\n  );\n\nexport const selectLabelIdsByCardId = makeSelectLabelIdsByCardId();\n\nexport const makeSelectShownOnFrontOfCardTaskListIdsByCardId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Card }, id) => {\n      const cardModel = Card.withId(id);\n\n      if (!cardModel) {\n        return cardModel;\n      }\n\n      return cardModel.getShownOnFrontOfCardTaskListsModelArray().map((taskList) => taskList.id);\n    },\n  );\n\nexport const selectShownOnFrontOfCardTaskListIdsByCardId =\n  makeSelectShownOnFrontOfCardTaskListIdsByCardId();\n\nexport const makeSelectAttachmentsTotalByCardId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Card }, id) => {\n      const cardModel = Card.withId(id);\n\n      if (!cardModel) {\n        return cardModel;\n      }\n\n      return cardModel.attachments.count();\n    },\n  );\n\nexport const selectAttachmentsTotalByCardId = makeSelectAttachmentsTotalByCardId();\n\nexport const makeSelectShownOnFrontOfCardCustomFieldValueIdsByCardId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Card, CustomFieldValue }, id) => {\n      if (!id) {\n        return id;\n      }\n\n      const cardModel = Card.withId(id);\n\n      if (!cardModel) {\n        return cardModel;\n      }\n\n      return [\n        ...cardModel.board\n          .getCustomFieldGroupsQuerySet()\n          .toModelArray()\n          .flatMap((customFieldGroupModel) =>\n            customFieldGroupModel\n              .getShownOnFrontOfCardCustomFieldsModelArray()\n              .flatMap((customFieldModel) => {\n                const customFieldValue = CustomFieldValue.withId(\n                  buildCustomFieldValueId({\n                    cardId: id,\n                    customFieldGroupId: customFieldGroupModel.id,\n                    customFieldId: customFieldModel.id,\n                  }),\n                );\n\n                return customFieldValue ? customFieldValue.id : [];\n              }),\n          ),\n        ...cardModel\n          .getCustomFieldGroupsQuerySet()\n          .toModelArray()\n          .flatMap((customFieldGroupModel) =>\n            customFieldGroupModel\n              .getShownOnFrontOfCardCustomFieldsModelArray()\n              .flatMap((customFieldModel) => {\n                const customFieldValue = CustomFieldValue.withId(\n                  buildCustomFieldValueId({\n                    cardId: id,\n                    customFieldGroupId: customFieldGroupModel.id,\n                    customFieldId: customFieldModel.id,\n                  }),\n                );\n\n                return customFieldValue ? customFieldValue.id : [];\n              }),\n          ),\n      ];\n    },\n  );\n\nexport const selectShownOnFrontOfCardCustomFieldValueIdsByCardId =\n  makeSelectShownOnFrontOfCardCustomFieldValueIdsByCardId();\n\nexport const makeSelectNotificationsByCardId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Card }, id) => {\n      const cardModel = Card.withId(id);\n\n      if (!cardModel) {\n        return cardModel;\n      }\n\n      return cardModel.getUnreadNotificationsQuerySet().toRefArray();\n    },\n  );\n\nexport const selectNotificationsByCardId = makeSelectNotificationsByCardId();\n\nexport const makeSelectNotificationsTotalByCardId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Card }, id) => {\n      const cardModel = Card.withId(id);\n\n      if (!cardModel) {\n        return cardModel;\n      }\n\n      return cardModel.getUnreadNotificationsQuerySet().count();\n    },\n  );\n\nexport const selectNotificationsTotalByCardId = makeSelectNotificationsTotalByCardId();\n\nexport const makeSelectIsCardWithIdRecent = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    (state) => selectRecentCardId(state),\n    ({ Card }, id, recentCardId) => {\n      const cardModel = Card.withId(id);\n\n      if (!cardModel) {\n        return false;\n      }\n\n      return cardModel.id === recentCardId;\n    },\n  );\n\nexport const selectIsCardWithIdRecent = makeSelectIsCardWithIdRecent();\n\nexport const selectIsCardWithIdAvailableForCurrentUser = createSelector(\n  orm,\n  (_, id) => id,\n  (state) => selectCurrentUserId(state),\n  ({ Card, User }, id, currentUserId) => {\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return false;\n    }\n\n    const currentUserModel = User.withId(currentUserId);\n    return cardModel.isAvailableForUser(currentUserModel);\n  },\n);\n\nexport const selectCurrentCard = createSelector(\n  orm,\n  (state) => selectPath(state).cardId,\n  ({ Card }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return cardModel.ref;\n  },\n);\n\nexport const selectUserIdsForCurrentCard = createSelector(\n  orm,\n  (state) => selectPath(state).cardId,\n  ({ Card }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return cardModel.users.toRefArray().map((user) => user.id);\n  },\n);\n\nexport const selectLabelIdsForCurrentCard = createSelector(\n  orm,\n  (state) => selectPath(state).cardId,\n  ({ Card }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return cardModel.labels.toRefArray().map((label) => label.id);\n  },\n);\n\nexport const selectTaskListIdsForCurrentCard = createSelector(\n  orm,\n  (state) => selectPath(state).cardId,\n  ({ Card }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return cardModel\n      .getTaskListsQuerySet()\n      .toRefArray()\n      .map((taskList) => taskList.id);\n  },\n);\n\nexport const selectAttachmentIdsForCurrentCard = createSelector(\n  orm,\n  (state) => selectPath(state).cardId,\n  ({ Card }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return cardModel\n      .getAttachmentsQuerySet()\n      .toRefArray()\n      .map((attachment) => attachment.id);\n  },\n);\n\nexport const selectImageAttachmentIdsExceptCoverForCurrentCard = createSelector(\n  orm,\n  (state) => selectPath(state).cardId,\n  ({ Card }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return cardModel\n      .getAttachmentsQuerySet()\n      .toModelArray()\n      .filter(\n        (attachmentModel) =>\n          attachmentModel.data && attachmentModel.data.image && !attachmentModel.coveredCard,\n      )\n      .map((attachmentModel) => attachmentModel.id);\n  },\n);\n\nexport const selectAttachmentsForCurrentCard = createSelector(\n  orm,\n  (state) => selectPath(state).cardId,\n  ({ Card }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return cardModel.getAttachmentsQuerySet().toRefArray();\n  },\n);\n\nexport const selectCustomFieldGroupIdsForCurrentCard = createSelector(\n  orm,\n  (state) => selectPath(state).cardId,\n  ({ Card }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return cardModel\n      .getCustomFieldGroupsQuerySet()\n      .toRefArray()\n      .map((customFieldGroup) => customFieldGroup.id);\n  },\n);\n\nexport const selectCommentIdsForCurrentCard = createSelector(\n  orm,\n  (state) => selectPath(state).cardId,\n  ({ Card }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return cardModel.getCommentsModelArray().map((commentModel) => commentModel.id);\n  },\n);\n\nexport const selectActivityIdsForCurrentCard = createSelector(\n  orm,\n  (state) => selectPath(state).cardId,\n  ({ Card }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return cardModel.getActivitiesModelArray().map((activity) => activity.id);\n  },\n);\n\nexport const selectIsCurrentUserInCurrentCard = createSelector(\n  orm,\n  (state) => selectPath(state).cardId,\n  (state) => selectCurrentUserId(state),\n  ({ Card }, id, currentUserId) => {\n    if (!id) {\n      return false;\n    }\n\n    const cardModel = Card.withId(id);\n\n    if (!cardModel) {\n      return false;\n    }\n\n    return cardModel.hasUserWithId(currentUserId);\n  },\n);\n\nexport default {\n  makeSelectCardById,\n  selectCardById,\n  makeSelectCardIndexById,\n  selectCardIndexById,\n  makeSelectUserIdsByCardId,\n  selectUserIdsByCardId,\n  makeSelectLabelIdsByCardId,\n  selectLabelIdsByCardId,\n  makeSelectShownOnFrontOfCardTaskListIdsByCardId,\n  selectShownOnFrontOfCardTaskListIdsByCardId,\n  makeSelectAttachmentsTotalByCardId,\n  makeSelectShownOnFrontOfCardCustomFieldValueIdsByCardId,\n  selectShownOnFrontOfCardCustomFieldValueIdsByCardId,\n  selectAttachmentsTotalByCardId,\n  makeSelectNotificationsByCardId,\n  selectNotificationsByCardId,\n  makeSelectNotificationsTotalByCardId,\n  selectNotificationsTotalByCardId,\n  makeSelectIsCardWithIdRecent,\n  selectIsCardWithIdRecent,\n  selectIsCardWithIdAvailableForCurrentUser,\n  selectCurrentCard,\n  selectUserIdsForCurrentCard,\n  selectLabelIdsForCurrentCard,\n  selectTaskListIdsForCurrentCard,\n  selectAttachmentIdsForCurrentCard,\n  selectImageAttachmentIdsExceptCoverForCurrentCard,\n  selectAttachmentsForCurrentCard,\n  selectCustomFieldGroupIdsForCurrentCard,\n  selectCommentIdsForCurrentCard,\n  selectActivityIdsForCurrentCard,\n  selectIsCurrentUserInCurrentCard,\n};\n"
  },
  {
    "path": "client/src/selectors/comments.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectCommentById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Comment }, id) => {\n      const commentModel = Comment.withId(id);\n\n      if (!commentModel) {\n        return commentModel;\n      }\n\n      return {\n        ...commentModel.ref,\n        isPersisted: !isLocalId(commentModel.id),\n      };\n    },\n  );\n\nexport const selectCommentById = makeSelectCommentById();\n\nexport default {\n  makeSelectCommentById,\n  selectCommentById,\n};\n"
  },
  {
    "path": "client/src/selectors/common.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport const selectIsSocketDisconnected = ({ socket: { isDisconnected } }) => isDisconnected;\n\nexport const selectIsInitializing = ({ common: { isInitializing } }) => isInitializing;\n\nexport const selectBootstrap = ({ common: { bootstrap } }) => bootstrap;\n\nexport const selectOidcBootstrap = (state) => selectBootstrap(state).oidc;\n\nexport const selectActiveUsersLimit = (state) => selectBootstrap(state).activeUsersLimit;\n\nexport const selectAccessToken = ({ auth: { accessToken } }) => accessToken;\n\nexport const selectAuthenticateForm = ({ ui: { authenticateForm } }) => authenticateForm;\n\nexport const selectUserCreateForm = ({ ui: { userCreateForm } }) => userCreateForm;\n\nexport const selectProjectCreateForm = ({ ui: { projectCreateForm } }) => projectCreateForm;\n\nexport const selectSmtpTestState = ({ ui: { smtpTestState } }) => smtpTestState;\n\nexport default {\n  selectIsSocketDisconnected,\n  selectIsInitializing,\n  selectBootstrap,\n  selectOidcBootstrap,\n  selectActiveUsersLimit,\n  selectAccessToken,\n  selectAuthenticateForm,\n  selectUserCreateForm,\n  selectProjectCreateForm,\n  selectSmtpTestState,\n};\n"
  },
  {
    "path": "client/src/selectors/core.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport const selectIsContentFetching = ({ core: { isContentFetching } }) => isContentFetching;\n\nexport const selectIsLogouting = ({ core: { isLogouting } }) => isLogouting;\n\nexport const selectIsFavoritesEnabled = ({ core: { isFavoritesEnabled } }) => isFavoritesEnabled;\n\nexport const selectIsEditModeEnabled = ({ core: { isEditModeEnabled } }) => isEditModeEnabled;\n\nexport const selectClipboard = ({ core: { clipboard } }) => clipboard;\n\nexport const selectConfig = ({ core: { config } }) => config;\n\nexport const selectRecentCardId = ({ core: { recentCardId } }) => recentCardId;\n\nexport const selectPrevCardId = ({ core: { prevCardIds } }) => prevCardIds.at(-1);\n\nexport const selectHomeView = ({ core: { homeView } }) => homeView;\n\nexport const selectProjectsSearch = ({ core: { projectsSearch } }) => projectsSearch;\n\nexport const selectProjectsOrder = ({ core: { projectsOrder } }) => projectsOrder;\n\nexport const selectIsHiddenProjectsVisible = ({ core: { isHiddenProjectsVisible } }) =>\n  isHiddenProjectsVisible;\n\nexport default {\n  selectIsContentFetching,\n  selectIsLogouting,\n  selectIsFavoritesEnabled,\n  selectIsEditModeEnabled,\n  selectClipboard,\n  selectConfig,\n  selectRecentCardId,\n  selectPrevCardId,\n  selectHomeView,\n  selectProjectsSearch,\n  selectProjectsOrder,\n  selectIsHiddenProjectsVisible,\n};\n"
  },
  {
    "path": "client/src/selectors/custom-field-groups.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectCustomFieldGroupById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ CustomFieldGroup }, id) => {\n      const customFieldGroupModel = CustomFieldGroup.withId(id);\n\n      if (!customFieldGroupModel) {\n        return customFieldGroupModel;\n      }\n\n      return {\n        ...customFieldGroupModel.ref,\n        name: customFieldGroupModel.name || customFieldGroupModel.baseCustomFieldGroup.name,\n        isPersisted: !isLocalId(customFieldGroupModel.id),\n      };\n    },\n  );\n\nexport const selectCustomFieldGroupById = makeSelectCustomFieldGroupById();\n\nexport const makeSelectCustomFieldIdsByGroupId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ CustomFieldGroup }, id) => {\n      if (!id) {\n        return id;\n      }\n\n      const customFieldGroupModel = CustomFieldGroup.withId(id);\n\n      if (!customFieldGroupModel) {\n        return customFieldGroupModel;\n      }\n\n      return customFieldGroupModel\n        .getCustomFieldsModelArray()\n        .map((customFieldModel) => customFieldModel.id);\n    },\n  );\n\nexport const selectCustomFieldIdsByGroupId = makeSelectCustomFieldIdsByGroupId();\n\nexport const makeSelectCustomFieldsByGroupId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ CustomFieldGroup }, id) => {\n      if (!id) {\n        return id;\n      }\n\n      const customFieldGroupModel = CustomFieldGroup.withId(id);\n\n      if (!customFieldGroupModel) {\n        return customFieldGroupModel;\n      }\n\n      return customFieldGroupModel\n        .getCustomFieldsModelArray()\n        .map((customFieldModel) => customFieldModel.ref);\n    },\n  );\n\nexport const selectCustomFieldsByGroupId = makeSelectCustomFieldsByGroupId();\n\nexport default {\n  makeSelectCustomFieldGroupById,\n  selectCustomFieldGroupById,\n  makeSelectCustomFieldIdsByGroupId,\n  selectCustomFieldIdsByGroupId,\n  makeSelectCustomFieldsByGroupId,\n  selectCustomFieldsByGroupId,\n};\n"
  },
  {
    "path": "client/src/selectors/custom-field-values.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\n\nexport const makeSelectCustomFieldValueById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ CustomFieldValue }, id) => {\n      const customFieldValueModel = CustomFieldValue.withId(id);\n\n      if (!customFieldValueModel) {\n        return customFieldValueModel;\n      }\n\n      return customFieldValueModel.ref;\n    },\n  );\n\nexport const selectCustomFieldValueById = makeSelectCustomFieldValueById();\n\nexport default {\n  makeSelectCustomFieldValueById,\n  selectCustomFieldValueById,\n};\n"
  },
  {
    "path": "client/src/selectors/custom-fields.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectCustomFieldById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ CustomField }, id) => {\n      const customFieldModel = CustomField.withId(id);\n\n      if (!customFieldModel) {\n        return customFieldModel;\n      }\n\n      return {\n        ...customFieldModel.ref,\n        isPersisted: !isLocalId(customFieldModel.id),\n      };\n    },\n  );\n\nexport const selectCustomFieldById = makeSelectCustomFieldById();\n\nexport default {\n  makeSelectCustomFieldById,\n  selectCustomFieldById,\n};\n"
  },
  {
    "path": "client/src/selectors/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport router from './router';\nimport common from './common';\nimport core from './core';\nimport modals from './modals';\nimport positioning from './positioning';\nimport webhooks from './webhooks';\nimport users from './users';\nimport projects from './projects';\nimport projectManagers from './project-managers';\nimport backgroundImages from './background-images';\nimport baseCustomFieldGroups from './base-custom-field-groups';\nimport boards from './boards';\nimport boardMemberships from './board-memberships';\nimport labels from './labels';\nimport lists from './lists';\nimport cards from './cards';\nimport taskLists from './task-lists';\nimport tasks from './tasks';\nimport attachments from './attachments';\nimport customFieldGroups from './custom-field-groups';\nimport customFields from './custom-fields';\nimport customFieldValues from './custom-field-values';\nimport comments from './comments';\nimport activities from './activities';\nimport notifications from './notifications';\nimport notificationServices from './notification-services';\n\nexport default {\n  ...router,\n  ...common,\n  ...core,\n  ...modals,\n  ...positioning,\n  ...webhooks,\n  ...users,\n  ...projects,\n  ...projectManagers,\n  ...backgroundImages,\n  ...baseCustomFieldGroups,\n  ...boards,\n  ...boardMemberships,\n  ...labels,\n  ...lists,\n  ...cards,\n  ...taskLists,\n  ...tasks,\n  ...attachments,\n  ...customFieldGroups,\n  ...customFields,\n  ...customFieldValues,\n  ...comments,\n  ...activities,\n  ...notifications,\n  ...notificationServices,\n};\n"
  },
  {
    "path": "client/src/selectors/labels.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectLabelById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Label }, id) => {\n      const labelModel = Label.withId(id);\n\n      if (!labelModel) {\n        return labelModel;\n      }\n\n      return {\n        ...labelModel.ref,\n        isPersisted: !isLocalId(labelModel.id),\n      };\n    },\n  );\n\nexport const selectLabelById = makeSelectLabelById();\n\nexport default {\n  makeSelectLabelById,\n  selectLabelById,\n};\n"
  },
  {
    "path": "client/src/selectors/lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { selectPath } from './router';\nimport { selectCurrentUserId } from './users';\nimport { isLocalId } from '../utils/local-id';\nimport { BoardContexts, ListTypes } from '../constants/Enums';\n\nexport const makeSelectListById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ List }, id) => {\n      const listModel = List.withId(id);\n\n      if (!listModel) {\n        return listModel;\n      }\n\n      return {\n        ...listModel.ref,\n        isPersisted: !isLocalId(id),\n      };\n    },\n  );\n\nexport const selectListById = makeSelectListById();\n\nexport const makeSelectCardIdsByListId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ List }, id) => {\n      const listModel = List.withId(id);\n\n      if (!listModel) {\n        return listModel;\n      }\n\n      return listModel.getCardsModelArray().map((cardModel) => cardModel.id);\n    },\n  );\n\nexport const selectCardIdsByListId = makeSelectCardIdsByListId();\n\nexport const makeSelectFilteredCardIdsByListId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ List }, id) => {\n      const listModel = List.withId(id);\n\n      if (!listModel) {\n        return listModel;\n      }\n\n      return listModel.getFilteredCardsModelArray().map((cardModel) => cardModel.id);\n    },\n  );\n\nexport const selectFilteredCardIdsByListId = makeSelectFilteredCardIdsByListId();\n\nexport const selectIsListWithIdAvailableForCurrentUser = createSelector(\n  orm,\n  (_, id) => id,\n  (state) => selectCurrentUserId(state),\n  ({ List, User }, id, currentUserId) => {\n    const listModel = List.withId(id);\n\n    if (!listModel) {\n      return false;\n    }\n\n    const currentUserModel = User.withId(currentUserId);\n    return listModel.isAvailableForUser(currentUserModel);\n  },\n);\n\nexport const selectCurrentListId = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    if (boardModel.context === BoardContexts.BOARD) {\n      return null;\n    }\n\n    const listModel = boardModel.lists\n      .filter({\n        type: boardModel.context || ListTypes.ACTIVE, // TODO: hack?\n      })\n      .first();\n\n    return listModel && listModel.id;\n  },\n);\n\nexport const selectCurrentList = createSelector(\n  orm,\n  (state) => selectCurrentListId(state),\n  ({ List }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const listModel = List.withId(id);\n\n    if (!listModel) {\n      return listModel;\n    }\n\n    return listModel.ref;\n  },\n);\n\nexport const selectFirstKanbanListId = createSelector(\n  orm,\n  (state) => selectPath(state).boardId,\n  ({ Board }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const boardModel = Board.withId(id);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    const listModel = boardModel.getKanbanListsQuerySet().first();\n    return listModel && listModel.id;\n  },\n);\n\nexport const selectFilteredCardIdsForCurrentList = createSelector(\n  orm,\n  (state) => selectCurrentListId(state),\n  ({ List }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const listModel = List.withId(id);\n\n    if (!listModel) {\n      return listModel;\n    }\n\n    return listModel.getFilteredCardsModelArray().map((cardModel) => cardModel.id);\n  },\n);\n\nexport default {\n  makeSelectListById,\n  selectListById,\n  makeSelectCardIdsByListId,\n  selectCardIdsByListId,\n  makeSelectFilteredCardIdsByListId,\n  selectFilteredCardIdsByListId,\n  selectIsListWithIdAvailableForCurrentUser,\n  selectCurrentListId,\n  selectCurrentList,\n  selectFirstKanbanListId,\n  selectFilteredCardIdsForCurrentList,\n};\n"
  },
  {
    "path": "client/src/selectors/modals.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { selectPath } from './router';\nimport { selectCurrentUserId } from './users';\nimport { isUserAdminOrProjectOwner } from '../utils/record-helpers';\nimport ModalTypes from '../constants/ModalTypes';\nimport { UserRoles } from '../constants/Enums';\n\nexport const selectCurrentModal = ({ core: { modal } }) => modal;\n\nexport const isCurrentModalAvailableForCurrentUser = createSelector(\n  orm,\n  selectCurrentModal,\n  (state) => selectCurrentUserId(state),\n  (state) => selectPath(state).projectId,\n  ({ User, Project, Board }, currentModal, currentUserId, currentProjectId) => {\n    if (currentModal) {\n      const currentUserModel = User.withId(currentUserId);\n\n      switch (currentModal.type) {\n        case ModalTypes.ADMINISTRATION:\n          return currentUserModel.role === UserRoles.ADMIN;\n        case ModalTypes.ADD_PROJECT:\n          return isUserAdminOrProjectOwner(currentUserModel);\n        case ModalTypes.PROJECT_SETTINGS: {\n          const projectModel = Project.withId(currentProjectId);\n          return !!projectModel && projectModel.isExternalAccessibleForUser(currentUserModel);\n        }\n        case ModalTypes.BOARD_SETTINGS: {\n          const boardModel = Board.withId(currentModal.params.id);\n\n          if (!boardModel || !boardModel.isAvailableForUser(currentUserModel)) {\n            return false;\n          }\n\n          return boardModel.project.hasManagerWithUserId(currentUserId);\n        }\n        case ModalTypes.BOARD_ACTIVITIES: {\n          const boardModel = Board.withId(currentModal.params.id);\n          return !!boardModel && boardModel.isAvailableForUser(currentUserModel);\n        }\n        default:\n          return true;\n      }\n    }\n\n    return true;\n  },\n);\n\nexport default {\n  selectCurrentModal,\n  isCurrentModalAvailableForCurrentUser,\n};\n"
  },
  {
    "path": "client/src/selectors/notification-services.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectNotificationServiceById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ NotificationService }, id) => {\n      const notificationServiceModel = NotificationService.withId(id);\n\n      if (!notificationServiceModel) {\n        return notificationServiceModel;\n      }\n\n      return {\n        ...notificationServiceModel.ref,\n        isPersisted: !isLocalId(notificationServiceModel.id),\n      };\n    },\n  );\n\nexport const selectNotificationServiceById = makeSelectNotificationServiceById();\n\nexport default {\n  makeSelectNotificationServiceById,\n  selectNotificationServiceById,\n};\n"
  },
  {
    "path": "client/src/selectors/notifications.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\n\nexport const makeSelectNotificationById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Notification }, id) => {\n      const notificationModel = Notification.withId(id);\n\n      if (!notificationModel) {\n        return notificationModel;\n      }\n\n      return notificationModel.ref;\n    },\n  );\n\nexport const selectNotificationById = makeSelectNotificationById();\n\nexport const makeSelectNotificationIdsByCardId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Notification }, id) =>\n      Notification.filter({\n        cardId: id,\n      })\n        .toRefArray()\n        .map((notification) => notification.id),\n  );\n\nexport const selectNotificationIdsByCardId = makeSelectNotificationIdsByCardId();\n\nexport default {\n  makeSelectNotificationById,\n  selectNotificationById,\n  makeSelectNotificationIdsByCardId,\n  selectNotificationIdsByCardId,\n};\n"
  },
  {
    "path": "client/src/selectors/positioning.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport isUndefined from 'lodash/isUndefined';\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport Config from '../constants/Config';\n\nconst nextPosition = (items, index, excludedId) => {\n  const filteredItems = isUndefined(excludedId)\n    ? items\n    : items.filter((item) => item.id !== excludedId);\n\n  if (isUndefined(index)) {\n    const lastItem = filteredItems[filteredItems.length - 1];\n    return (lastItem ? lastItem.position : 0) + Config.POSITION_GAP;\n  }\n\n  const prevItem = filteredItems[index - 1];\n  const nextItem = filteredItems[index];\n\n  const prevPosition = prevItem ? prevItem.position : 0;\n\n  if (!nextItem) {\n    return prevPosition + Config.POSITION_GAP;\n  }\n\n  return prevPosition + (nextItem.position - prevPosition) / 2;\n};\n\nexport const selectNextBoardPosition = createSelector(\n  orm,\n  (_, projectId) => projectId,\n  (_, __, index) => index,\n  (_, __, ___, excludedId) => excludedId,\n  ({ Project }, projectId, index, excludedId) => {\n    const projectModel = Project.withId(projectId);\n\n    if (!projectModel) {\n      return projectModel;\n    }\n\n    return nextPosition(projectModel.getBoardsQuerySet().toRefArray(), index, excludedId);\n  },\n);\n\nexport const selectNextLabelPosition = createSelector(\n  orm,\n  (_, boardId) => boardId,\n  (_, __, index) => index,\n  (_, __, ___, excludedId) => excludedId,\n  ({ Board }, boardId, index, excludedId) => {\n    const boardModel = Board.withId(boardId);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return nextPosition(boardModel.getLabelsQuerySet().toRefArray(), index, excludedId);\n  },\n);\n\nexport const selectNextListPosition = createSelector(\n  orm,\n  (_, boardId) => boardId,\n  (_, __, index) => index,\n  (_, __, ___, excludedId) => excludedId,\n  ({ Board }, boardId, index, excludedId) => {\n    const boardModel = Board.withId(boardId);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return nextPosition(boardModel.getKanbanListsQuerySet().toRefArray(), index, excludedId);\n  },\n);\n\nexport const selectNextCardPosition = createSelector(\n  orm,\n  (_, listId) => listId,\n  (_, __, index) => index,\n  (_, __, ___, excludedId) => excludedId,\n  ({ List }, listId, index, excludedId) => {\n    const listModel = List.withId(listId);\n\n    if (!listModel) {\n      return listModel;\n    }\n\n    return nextPosition(listModel.getFilteredCardsModelArray(), index, excludedId);\n  },\n);\n\nexport const selectNextTaskListPosition = createSelector(\n  orm,\n  (_, cardId) => cardId,\n  (_, __, index) => index,\n  (_, __, ___, excludedId) => excludedId,\n  ({ Card }, cardId, index, excludedId) => {\n    const cardModel = Card.withId(cardId);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return nextPosition(cardModel.getTaskListsQuerySet().toRefArray(), index, excludedId);\n  },\n);\n\nexport const selectNextTaskPosition = createSelector(\n  orm,\n  (_, taskListId) => taskListId,\n  (_, __, index) => index,\n  (_, __, ___, excludedId) => excludedId,\n  ({ TaskList }, taskListId, index, excludedId) => {\n    const taskListModel = TaskList.withId(taskListId);\n\n    if (!taskListModel) {\n      return taskListModel;\n    }\n\n    return nextPosition(taskListModel.getTasksQuerySet().toRefArray(), index, excludedId);\n  },\n);\n\nexport const selectNextCustomFieldGroupPositionInBoard = createSelector(\n  orm,\n  (_, cardId) => cardId,\n  (_, __, index) => index,\n  (_, __, ___, excludedId) => excludedId,\n  ({ Board }, boardId, index, excludedId) => {\n    const boardModel = Board.withId(boardId);\n\n    if (!boardModel) {\n      return boardModel;\n    }\n\n    return nextPosition(boardModel.getCustomFieldGroupsQuerySet().toRefArray(), index, excludedId);\n  },\n);\n\nexport const selectNextCustomFieldGroupPositionInCard = createSelector(\n  orm,\n  (_, cardId) => cardId,\n  (_, __, index) => index,\n  (_, __, ___, excludedId) => excludedId,\n  ({ Card }, cardId, index, excludedId) => {\n    const cardModel = Card.withId(cardId);\n\n    if (!cardModel) {\n      return cardModel;\n    }\n\n    return nextPosition(cardModel.getCustomFieldGroupsQuerySet().toRefArray(), index, excludedId);\n  },\n);\n\nexport const selectNextCustomFieldPositionInBaseGroup = createSelector(\n  orm,\n  (_, baseCustomFieldGroupId) => baseCustomFieldGroupId,\n  (_, __, index) => index,\n  (_, __, ___, excludedId) => excludedId,\n  ({ BaseCustomFieldGroup }, baseCustomFieldGroupId, index, excludedId) => {\n    const baseCustomFieldGroupModel = BaseCustomFieldGroup.withId(baseCustomFieldGroupId);\n\n    if (!baseCustomFieldGroupModel) {\n      return baseCustomFieldGroupModel;\n    }\n\n    return nextPosition(\n      baseCustomFieldGroupModel.getCustomFieldsQuerySet().toRefArray(),\n      index,\n      excludedId,\n    );\n  },\n);\n\nexport const selectNextCustomFieldPositionInGroup = createSelector(\n  orm,\n  (_, customFieldGroupId) => customFieldGroupId,\n  (_, __, index) => index,\n  (_, __, ___, excludedId) => excludedId,\n  ({ CustomFieldGroup }, customFieldGroupId, index, excludedId) => {\n    const customFieldGroupModel = CustomFieldGroup.withId(customFieldGroupId);\n\n    if (!customFieldGroupModel) {\n      return customFieldGroupModel;\n    }\n\n    return nextPosition(\n      customFieldGroupModel.getCustomFieldsQuerySet().toRefArray(),\n      index,\n      excludedId,\n    );\n  },\n);\n\nexport default {\n  selectNextBoardPosition,\n  selectNextLabelPosition,\n  selectNextListPosition,\n  selectNextCardPosition,\n  selectNextTaskListPosition,\n  selectNextTaskPosition,\n  selectNextCustomFieldGroupPositionInBoard,\n  selectNextCustomFieldGroupPositionInCard,\n  selectNextCustomFieldPositionInBaseGroup,\n  selectNextCustomFieldPositionInGroup,\n};\n"
  },
  {
    "path": "client/src/selectors/project-managers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\n\nexport const makeSelectProjectManagerById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ ProjectManager }, id) => {\n      const projectManagerModel = ProjectManager.withId(id);\n\n      if (!projectManagerModel) {\n        return projectManagerModel;\n      }\n\n      return projectManagerModel.ref;\n    },\n  );\n\nexport const selectProjectManagerById = makeSelectProjectManagerById();\n\nexport default {\n  makeSelectProjectManagerById,\n  selectProjectManagerById,\n};\n"
  },
  {
    "path": "client/src/selectors/projects.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { selectPath } from './router';\nimport { selectCurrentUserId } from './users';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectProjectById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Project }, id) => {\n      const projectModel = Project.withId(id);\n\n      if (!projectModel) {\n        return projectModel;\n      }\n\n      return projectModel.ref;\n    },\n  );\n\nexport const selectProjectById = makeSelectProjectById();\n\nexport const makeSelectBoardIdsByProjectId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    (state) => selectCurrentUserId(state),\n    ({ Project, User }, id, currentUserId) => {\n      if (!id) {\n        return id;\n      }\n\n      const projectModel = Project.withId(id);\n\n      if (!projectModel) {\n        return projectModel;\n      }\n\n      const currentUserModel = User.withId(currentUserId);\n\n      return projectModel\n        .getBoardsModelArrayAvailableForUser(currentUserModel)\n        .map((boardModel) => boardModel.id);\n    },\n  );\n\nexport const selectBoardIdsByProjectId = makeSelectBoardIdsByProjectId();\n\nexport const makeSelectFirstBoardIdByProjectId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    (state) => selectCurrentUserId(state),\n    ({ Project, User }, id, currentUserId) => {\n      const projectModel = Project.withId(id);\n\n      if (!projectModel) {\n        return projectModel;\n      }\n\n      const currentUserModel = User.withId(currentUserId);\n      const boardsModels = projectModel.getBoardsModelArrayAvailableForUser(currentUserModel);\n\n      return boardsModels[0] && boardsModels[0].id;\n    },\n  );\n\nexport const selectFirstBoardIdByProjectId = makeSelectFirstBoardIdByProjectId();\n\nexport const makeSelectNotificationsTotalByProjectId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    (state) => selectCurrentUserId(state),\n    ({ Project, User }, id, currentUserId) => {\n      const projectModel = Project.withId(id);\n\n      if (!projectModel) {\n        return projectModel;\n      }\n\n      const currentUserModel = User.withId(currentUserId);\n      const boardsModels = projectModel.getBoardsModelArrayAvailableForUser(currentUserModel);\n\n      return boardsModels.reduce(\n        (result, boardModel) => result + boardModel.getUnreadNotificationsQuerySet().count(),\n        0,\n      );\n    },\n  );\n\nexport const selectNotificationsTotalByProjectId = makeSelectNotificationsTotalByProjectId();\n\nexport const makeSelectIsProjectWithIdAvailableForCurrentUser = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    (state) => selectCurrentUserId(state),\n    ({ Project, User }, id, currentUserId) => {\n      const projectModel = Project.withId(id);\n\n      if (!projectModel) {\n        return false;\n      }\n\n      const currentUserModel = User.withId(currentUserId);\n      return projectModel.isAvailableForUser(currentUserModel);\n    },\n  );\n\nexport const selectIsProjectWithIdAvailableForCurrentUser =\n  makeSelectIsProjectWithIdAvailableForCurrentUser();\n\nexport const makeSelectIsProjectWithIdExternalAccessibleForCurrentUser = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    (state) => selectCurrentUserId(state),\n    ({ Project, User }, id, currentUserId) => {\n      const projectModel = Project.withId(id);\n\n      if (!projectModel) {\n        return false;\n      }\n\n      const currentUserModel = User.withId(currentUserId);\n      return projectModel.isExternalAccessibleForUser(currentUserModel);\n    },\n  );\n\nexport const selectIsProjectWithIdExternalAccessibleForCurrentUser =\n  makeSelectIsProjectWithIdExternalAccessibleForCurrentUser();\n\nexport const selectCurrentProject = createSelector(\n  orm,\n  (state) => selectPath(state).projectId,\n  ({ Project }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const projectModel = Project.withId(id);\n\n    if (!projectModel) {\n      return projectModel;\n    }\n\n    return projectModel.ref;\n  },\n);\n\nexport const selectManagersForCurrentProject = createSelector(\n  orm,\n  (state) => selectPath(state).projectId,\n  ({ Project }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const projectModel = Project.withId(id);\n\n    if (!projectModel) {\n      return projectModel;\n    }\n\n    return projectModel\n      .getManagersQuerySet()\n      .toModelArray()\n      .map((projectManagerModel) => ({\n        ...projectManagerModel.ref,\n        isPersisted: !isLocalId(projectManagerModel.id),\n        user: projectManagerModel.user.ref,\n      }));\n  },\n);\n\nexport const selectManagerUserIdsForCurrentProject = createSelector(\n  orm,\n  (state) => selectPath(state).projectId,\n  ({ Project }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const projectModel = Project.withId(id);\n\n    if (!projectModel) {\n      return projectModel;\n    }\n\n    return projectModel\n      .getManagersQuerySet()\n      .toRefArray()\n      .map((projectManager) => projectManager.userId);\n  },\n);\n\nexport const selectBackgroundImageIdsForCurrentProject = createSelector(\n  orm,\n  (state) => selectPath(state).projectId,\n  ({ Project }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const projectModel = Project.withId(id);\n\n    if (!projectModel) {\n      return projectModel;\n    }\n\n    return projectModel\n      .getBackgroundImagesQuerySet()\n      .toRefArray()\n      .map((backgroundImage) => backgroundImage.id);\n  },\n);\n\nexport const selectBaseCustomFieldGroupIdsForCurrentProject = createSelector(\n  orm,\n  (state) => selectPath(state).projectId,\n  ({ Project }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const projectModel = Project.withId(id);\n\n    if (!projectModel) {\n      return projectModel;\n    }\n\n    return projectModel\n      .getBaseCustomFieldGroupsQuerySet()\n      .toRefArray()\n      .map((baseCustomFieldGroup) => baseCustomFieldGroup.id);\n  },\n);\n\nexport const selectBaseCustomFieldGroupsForCurrentProject = createSelector(\n  orm,\n  (state) => selectPath(state).projectId,\n  ({ Project }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const projectModel = Project.withId(id);\n\n    if (!projectModel) {\n      return projectModel;\n    }\n\n    return projectModel\n      .getBaseCustomFieldGroupsQuerySet()\n      .toRefArray()\n      .map((baseCustomFieldGroup) => ({\n        ...baseCustomFieldGroup,\n        isPersisted: !isLocalId(baseCustomFieldGroup.id),\n      }));\n  },\n);\n\nexport const selectBoardIdsForCurrentProject = createSelector(\n  orm,\n  (state) => selectPath(state).projectId,\n  (state) => selectCurrentUserId(state),\n  ({ Project, User }, id, currentUserId) => {\n    if (!id) {\n      return id;\n    }\n\n    const projectModel = Project.withId(id);\n\n    if (!projectModel) {\n      return projectModel;\n    }\n\n    const currentUserModel = User.withId(currentUserId);\n\n    return projectModel\n      .getBoardsModelArrayAvailableForUser(currentUserModel)\n      .map((boardModel) => boardModel.id);\n  },\n);\n\nexport const selectIsCurrentUserManagerForCurrentProject = createSelector(\n  orm,\n  (state) => selectPath(state).projectId,\n  (state) => selectCurrentUserId(state),\n  ({ Project }, id, currentUserId) => {\n    if (!id) {\n      return false;\n    }\n\n    const projectModel = Project.withId(id);\n\n    if (!projectModel) {\n      return false;\n    }\n\n    return projectModel.hasManagerWithUserId(currentUserId);\n  },\n);\n\nexport default {\n  makeSelectProjectById,\n  selectProjectById,\n  makeSelectBoardIdsByProjectId,\n  selectBoardIdsByProjectId,\n  makeSelectFirstBoardIdByProjectId,\n  selectFirstBoardIdByProjectId,\n  makeSelectNotificationsTotalByProjectId,\n  selectNotificationsTotalByProjectId,\n  makeSelectIsProjectWithIdAvailableForCurrentUser,\n  selectIsProjectWithIdAvailableForCurrentUser,\n  makeSelectIsProjectWithIdExternalAccessibleForCurrentUser,\n  selectIsProjectWithIdExternalAccessibleForCurrentUser,\n  selectCurrentProject,\n  selectManagersForCurrentProject,\n  selectManagerUserIdsForCurrentProject,\n  selectBackgroundImageIdsForCurrentProject,\n  selectBaseCustomFieldGroupIdsForCurrentProject,\n  selectBaseCustomFieldGroupsForCurrentProject,\n  selectBoardIdsForCurrentProject,\n  selectIsCurrentUserManagerForCurrentProject,\n};\n"
  },
  {
    "path": "client/src/selectors/router.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector as createReselectSelector } from 'reselect';\nimport { createSelector as createReduxOrmSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { selectCurrentUserId } from './users';\nimport matchPaths from '../utils/match-paths';\nimport Paths from '../constants/Paths';\n\nexport const selectPathname = ({\n  router: {\n    location: { pathname },\n  },\n}) => pathname;\n\nexport const selectPathsMatch = createReselectSelector(selectPathname, (pathname) =>\n  matchPaths(pathname, Object.values(Paths)),\n);\n\nexport const selectPath = createReduxOrmSelector(\n  orm,\n  selectPathsMatch,\n  (state) => selectCurrentUserId(state),\n  ({ User, Project, Board, Card }, pathsMatch, currentUserId) => {\n    if (pathsMatch) {\n      const currentUserModel = User.withId(currentUserId);\n\n      switch (pathsMatch.pattern.path) {\n        case Paths.PROJECTS: {\n          const projectModel = Project.withId(pathsMatch.params.id);\n\n          if (!projectModel || !projectModel.isAvailableForUser(currentUserModel)) {\n            return {\n              projectId: null,\n            };\n          }\n\n          return {\n            projectId: projectModel.id,\n          };\n        }\n        case Paths.BOARDS: {\n          const boardModel = Board.withId(pathsMatch.params.id);\n\n          if (!boardModel || !boardModel.isAvailableForUser(currentUserModel)) {\n            return {\n              boardId: null,\n              projectId: null,\n            };\n          }\n\n          return {\n            boardId: boardModel.id,\n            projectId: boardModel.projectId,\n          };\n        }\n        case Paths.CARDS: {\n          const cardModel = Card.withId(pathsMatch.params.id);\n\n          if (!cardModel || !cardModel.isAvailableForUser(currentUserModel)) {\n            return {\n              cardId: null,\n              boardId: null,\n              projectId: null,\n            };\n          }\n\n          return {\n            cardId: cardModel.id,\n            boardId: cardModel.boardId,\n            projectId: cardModel.board.projectId,\n          };\n        }\n        default:\n      }\n    }\n\n    return {};\n  },\n);\n\nexport default {\n  selectPathname,\n  selectPathsMatch,\n  selectPath,\n};\n"
  },
  {
    "path": "client/src/selectors/task-lists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectTaskListById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ TaskList }, id) => {\n      const taskListModel = TaskList.withId(id);\n\n      if (!taskListModel) {\n        return taskListModel;\n      }\n\n      return {\n        ...taskListModel.ref,\n        isPersisted: !isLocalId(taskListModel.id),\n      };\n    },\n  );\n\nexport const selectTaskListById = makeSelectTaskListById();\n\nexport const makeSelectTasksByTaskListId = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ TaskList }, id) => {\n      const taskListModel = TaskList.withId(id);\n\n      if (!taskListModel) {\n        return taskListModel;\n      }\n\n      return taskListModel.getTasksQuerySet().toRefArray();\n    },\n  );\n\nexport const selectTasksByTaskListId = makeSelectTasksByTaskListId();\n\nexport default {\n  makeSelectTaskListById,\n  selectTaskListById,\n  makeSelectTasksByTaskListId,\n  selectTasksByTaskListId,\n};\n"
  },
  {
    "path": "client/src/selectors/tasks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectTaskById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Task }, id) => {\n      const taskModel = Task.withId(id);\n\n      if (!taskModel) {\n        return taskModel;\n      }\n\n      return {\n        ...taskModel.ref,\n        isPersisted: !isLocalId(taskModel.id),\n      };\n    },\n  );\n\nexport const selectTaskById = makeSelectTaskById();\n\nexport default {\n  makeSelectTaskById,\n  selectTaskById,\n};\n"
  },
  {
    "path": "client/src/selectors/users.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport {\n  selectIsFavoritesEnabled,\n  selectIsHiddenProjectsVisible,\n  selectProjectsOrder,\n  selectProjectsSearch,\n} from './core';\nimport { isLocalId } from '../utils/local-id';\nimport { isUserAdminOrProjectOwner } from '../utils/record-helpers';\nimport { STATIC_USER_BY_ID } from '../constants/StaticUsers';\nimport { BoardMembershipRoles, ProjectGroups, ProjectOrders } from '../constants/Enums';\n\nconst ORDER_BY_ARGS_BY_PROJECTS_ORDER = {\n  [ProjectOrders.ALPHABETICALLY]: [['name', 'id.length', 'id']],\n  [ProjectOrders.BY_CREATION_TIME]: [['id', 'id.length']],\n};\n\nexport const selectCurrentUserId = ({ auth: { userId } }) => userId;\n\nexport const makeSelectUserById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ User }, id) => {\n      if (STATIC_USER_BY_ID[id]) {\n        return STATIC_USER_BY_ID[id];\n      }\n\n      const userModel = User.withId(id);\n\n      if (!userModel) {\n        return userModel;\n      }\n\n      return userModel.ref;\n    },\n  );\n\nexport const selectUserById = makeSelectUserById();\n\nexport const selectUsers = createSelector(orm, ({ User }) => User.getAllQuerySet().toRefArray());\n\nexport const selectActiveUsers = createSelector(orm, ({ User }) =>\n  User.getActiveQuerySet().toRefArray(),\n);\n\nexport const selectActiveUsersTotal = createSelector(orm, ({ User }) =>\n  User.getActiveQuerySet().count(),\n);\n\nexport const selectActiveAdminOrProjectOwnerUsers = createSelector(orm, ({ User }) =>\n  User.getActiveQuerySet()\n    .filter((user) => isUserAdminOrProjectOwner(user))\n    .toRefArray(),\n);\n\nexport const selectCurrentUser = createSelector(\n  orm,\n  (state) => selectCurrentUserId(state),\n  ({ User }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const userModel = User.withId(id);\n\n    if (!userModel) {\n      return userModel;\n    }\n\n    return userModel.ref;\n  },\n);\n\nexport const selectProjectIdsForCurrentUser = createSelector(\n  orm,\n  (state) => selectCurrentUserId(state),\n  ({ User }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const userModel = User.withId(id);\n\n    if (!userModel) {\n      return userModel;\n    }\n\n    return userModel.getProjectsModelArray().map((projectModel) => projectModel.id);\n  },\n);\n\nexport const selectFilteredProjectIdsForCurrentUser = createSelector(\n  orm,\n  (state) => selectCurrentUserId(state),\n  (state) => selectProjectsSearch(state),\n  (state) => selectIsHiddenProjectsVisible(state),\n  (state) => selectProjectsOrder(state),\n  ({ User }, id, projectsSearch, isHiddenProjectsVisible, projectsOrder) => {\n    if (!id) {\n      return id;\n    }\n\n    const userModel = User.withId(id);\n\n    if (!userModel) {\n      return userModel;\n    }\n\n    return userModel\n      .getFilteredProjectsModelArray(\n        projectsSearch,\n        isHiddenProjectsVisible,\n        ORDER_BY_ARGS_BY_PROJECTS_ORDER[projectsOrder],\n      )\n      .map((projectModel) => projectModel.id);\n  },\n);\n\nexport const selectFilteredProjctIdsByGroupForCurrentUser = createSelector(\n  orm,\n  (state) => selectCurrentUserId(state),\n  (state) => selectProjectsSearch(state),\n  (state) => selectIsHiddenProjectsVisible(state),\n  (state) => selectProjectsOrder(state),\n  ({ User }, id, projectsSearch, isHiddenProjectsVisible, projectsOrder) => {\n    if (!id) {\n      return id;\n    }\n\n    const userModel = User.withId(id);\n\n    if (!userModel) {\n      return userModel;\n    }\n\n    const { managerProjectModels, membershipProjectModels, adminProjectModels } =\n      userModel.getFilteredSeparatedProjectsModelArray(\n        projectsSearch,\n        isHiddenProjectsVisible,\n        ORDER_BY_ARGS_BY_PROJECTS_ORDER[projectsOrder],\n      );\n\n    return managerProjectModels.reduce(\n      (result, projectModel) => {\n        const group = projectModel.ownerProjectManager ? ProjectGroups.MY_OWN : ProjectGroups.TEAM;\n\n        result[group].push(projectModel.id);\n        return result;\n      },\n      {\n        [ProjectGroups.MY_OWN]: [],\n        [ProjectGroups.TEAM]: [],\n        [ProjectGroups.SHARED_WITH_ME]: membershipProjectModels.map(\n          (projectModel) => projectModel.id,\n        ),\n        [ProjectGroups.OTHERS]: adminProjectModels.map((projectModel) => projectModel.id),\n      },\n    );\n  },\n);\n\nexport const selectFavoriteProjectIdsForCurrentUser = createSelector(\n  orm,\n  (state) => selectCurrentUserId(state),\n  (state) => selectProjectsOrder(state),\n  ({ User }, id, projectsOrder) => {\n    if (!id) {\n      return id;\n    }\n\n    const userModel = User.withId(id);\n\n    if (!userModel) {\n      return userModel;\n    }\n\n    return userModel\n      .getFavoriteProjectsModelArray(ORDER_BY_ARGS_BY_PROJECTS_ORDER[projectsOrder])\n      .map((projectModel) => projectModel.id);\n  },\n);\n\nexport const selectProjectsToBoardsWithEditorRightsForCurrentUser = createSelector(\n  orm,\n  (state) => selectCurrentUserId(state),\n  ({ User }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const userModel = User.withId(id);\n\n    if (!userModel) {\n      return userModel;\n    }\n\n    return userModel.getMembershipProjectsModelArray().map((projectModel) => ({\n      ...projectModel.ref,\n      boards: projectModel.getBoardsModelArrayForUserWithId(id).flatMap((boardModel) => {\n        const boardMembersipModel = boardModel.getMembershipModelByUserId(id);\n\n        if (boardMembersipModel.role !== BoardMembershipRoles.EDITOR) {\n          return [];\n        }\n\n        return boardModel.ref;\n      }),\n    }));\n  },\n);\n\nexport const selectProjectsToListsWithEditorRightsForCurrentUser = createSelector(\n  orm,\n  (state) => selectCurrentUserId(state),\n  ({ User }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const userModel = User.withId(id);\n\n    if (!userModel) {\n      return userModel;\n    }\n\n    return userModel.getMembershipProjectsModelArray().map((projectModel) => ({\n      ...projectModel.ref,\n      boards: projectModel.getBoardsModelArrayForUserWithId(id).flatMap((boardModel) => {\n        const boardMembersipModel = boardModel.getMembershipModelByUserId(id);\n\n        if (boardMembersipModel.role !== BoardMembershipRoles.EDITOR) {\n          return [];\n        }\n\n        return {\n          ...boardModel.ref,\n          lists: boardModel\n            .getListsQuerySet()\n            .toRefArray()\n            .map((list) => ({\n              ...list,\n              isPersisted: !isLocalId(list.id),\n            })),\n        };\n      }),\n    }));\n  },\n);\n\nexport const selectBoardIdsForCurrentUser = createSelector(\n  orm,\n  (state) => selectCurrentUserId(state),\n  ({ User }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const userModel = User.withId(id);\n\n    if (!userModel) {\n      return userModel;\n    }\n\n    return userModel\n      .getProjectsModelArray()\n      .flatMap((projectModel) =>\n        projectModel\n          .getBoardsModelArrayAvailableForUser(userModel)\n          .map((boardModel) => boardModel.id),\n      );\n  },\n);\n\nexport const selectNotificationIdsForCurrentUser = createSelector(\n  orm,\n  (state) => selectCurrentUserId(state),\n  ({ User }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const userModel = User.withId(id);\n\n    if (!userModel) {\n      return userModel;\n    }\n\n    return userModel\n      .getUnreadNotificationsQuerySet()\n      .toRefArray()\n      .map((notification) => notification.id);\n  },\n);\n\nexport const selectNotificationServiceIdsForCurrentUser = createSelector(\n  orm,\n  (state) => selectCurrentUserId(state),\n  ({ User }, id) => {\n    if (!id) {\n      return id;\n    }\n\n    const userModel = User.withId(id);\n\n    if (!userModel) {\n      return userModel;\n    }\n\n    return userModel\n      .getNotificationServicesQuerySet()\n      .toRefArray()\n      .map((notificationService) => notificationService.id);\n  },\n);\n\nexport const selectIsFavoritesActiveForCurrentUser = createSelector(\n  orm,\n  (state) => selectCurrentUserId(state),\n  (state) => selectIsFavoritesEnabled(state),\n  ({ User }, id, isFavoritesEnabled) => {\n    if (!id) {\n      return false;\n    }\n\n    const userModel = User.withId(id);\n\n    if (!userModel) {\n      return false;\n    }\n\n    // TODO: use selectFavoriteProjectIdsForCurrentUser instead\n    return isFavoritesEnabled && userModel.getFavoriteProjectsModelArray().length > 0;\n  },\n);\n\nexport default {\n  makeSelectUserById,\n  selectUserById,\n  selectCurrentUserId,\n  selectUsers,\n  selectActiveUsers,\n  selectActiveUsersTotal,\n  selectActiveAdminOrProjectOwnerUsers,\n  selectCurrentUser,\n  selectProjectIdsForCurrentUser,\n  selectFilteredProjectIdsForCurrentUser,\n  selectFilteredProjctIdsByGroupForCurrentUser,\n  selectFavoriteProjectIdsForCurrentUser,\n  selectProjectsToBoardsWithEditorRightsForCurrentUser,\n  selectProjectsToListsWithEditorRightsForCurrentUser,\n  selectBoardIdsForCurrentUser,\n  selectNotificationIdsForCurrentUser,\n  selectNotificationServiceIdsForCurrentUser,\n  selectIsFavoritesActiveForCurrentUser,\n};\n"
  },
  {
    "path": "client/src/selectors/webhooks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { createSelector } from 'redux-orm';\n\nimport orm from '../orm';\nimport { isLocalId } from '../utils/local-id';\n\nexport const makeSelectWebhookById = () =>\n  createSelector(\n    orm,\n    (_, id) => id,\n    ({ Webhook }, id) => {\n      const webhookModel = Webhook.withId(id);\n\n      if (!webhookModel) {\n        return webhookModel;\n      }\n\n      return {\n        ...webhookModel.ref,\n        isPersisted: !isLocalId(webhookModel.id),\n      };\n    },\n  );\n\nexport const selectWebhookById = makeSelectWebhookById();\n\nexport const selectWebhookIds = createSelector(orm, ({ Webhook }) =>\n  Webhook.getAllQuerySet()\n    .toRefArray()\n    .map((webhook) => webhook.id),\n);\n\nexport default {\n  makeSelectWebhookById,\n  selectWebhookById,\n  selectWebhookIds,\n};\n"
  },
  {
    "path": "client/src/store.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { applyMiddleware, legacy_createStore as createStore, compose as reduxCompose } from 'redux';\nimport createSagaMiddleware from 'redux-saga';\nimport { createRouterMiddleware } from './lib/redux-router';\n\nimport rootReducer from './reducers';\nimport rootSaga from './sagas';\nimport history from './history';\n\nconst sagaMiddleware = createSagaMiddleware();\nconst middlewares = [sagaMiddleware, createRouterMiddleware(history)];\n\nlet compose = reduxCompose;\nif (import.meta.env.DEV) {\n  middlewares.push(require('redux-logger')); // eslint-disable-line global-require\n\n  // Enable Redux Devtools in development\n  // https://github.com/zalmoxisus/redux-devtools-extension\n  if (typeof window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ !== 'undefined') {\n    compose = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;\n  }\n}\n\nexport default createStore(rootReducer, compose(applyMiddleware(...middlewares)));\n\nsagaMiddleware.run(rootSaga);\n"
  },
  {
    "path": "client/src/styles.module.scss",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n:global {\n  #app {\n    background: #22252a;\n    overflow-x: auto;\n\n    &::-webkit-scrollbar {\n      height: auto;\n    }\n\n    .mentions-input {\n      &__highlighter {\n        line-height: 1.4;\n        padding: 8px 12px;\n      }\n\n      &__suggestions {\n        border: 1px solid #d4d4d5;\n        border-radius: 3px;\n        border-width: 0;\n        box-shadow: 0 8px 16px -4px rgba(9, 45, 66, 0.25),\n          0 0 0 1px rgba(9, 45, 66, 0.08);\n        max-height: 200px;\n        overflow-y: auto;\n\n        &__item {\n          padding: 8px 12px;\n\n          &--focused {\n            background-color: rgba(0, 0, 0, 0.05);\n            color: rgba(0, 0, 0, 0.95);\n          }\n        }\n      }\n    }\n\n    .react-datepicker {\n      border: 0;\n      color: #444444;\n      font-size: 14px;\n      font-family: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n      padding-bottom: 8px;\n    }\n\n    .react-datepicker__current-month,\n    .react-datepicker-time__header {\n      color: #444;\n      font-weight: normal;\n      font-size: 14px;\n      line-height: 32px;\n      padding-bottom: 4px;\n      text-transform: capitalize;\n    }\n\n    .react-datepicker__header {\n      background: #fff;\n      border: 0;\n      padding: 0;\n    }\n\n    .react-datepicker__day-name,\n    .react-datepicker__day,\n    .react-datepicker__time-name {\n      color: #444;\n      width: 40px;\n      margin: 0;\n      border-style: solid;\n      border-width: 0 0 1px 1px;\n      border-color: rgba(34, 36, 38, 0.15);\n    }\n\n    .react-datepicker__day--selected,\n    .react-datepicker__day--in-selecting-range,\n    .react-datepicker__day--in-range {\n      border-radius: 0;\n      color: #fff;\n    }\n\n    .react-datepicker__day-names,\n    .react-datepicker__week {\n      border-right: 1px solid #c2ccd1;\n    }\n\n    .react-datepicker__day-names {\n      background: #ebeef0;\n      border-top: 1px solid #c2ccd1;\n    }\n\n    .react-datepicker__day--outside-month {\n      color: rgba(68, 68, 68, 0.3);\n    }\n\n    .react-datepicker__month {\n      margin: 8px 0 0;\n    }\n\n    .react-datepicker__navigation--previous,\n    .react-datepicker__navigation--next {\n      outline: none;\n    }\n\n    .react-datepicker__navigation--previous {\n      left: 0;\n    }\n\n    .react-datepicker__navigation--next {\n      right: 0;\n    }\n\n    .react-datepicker__navigation {\n      top: 0;\n    }\n\n    .react-datepicker__day {\n      outline: none;\n\n      &:hover {\n        border-radius: 0;\n      }\n    }\n\n    .pswp .pswp__img--placeholder {\n      display: none !important;\n    }\n\n    .ui.input.error > input {\n      background-color: #fff6f6 !important;\n      border-color: #e0b4b4 !important;\n      color: #9f3a38 !important;\n      -webkit-box-shadow: none !important;\n      box-shadow: none !important;\n    }\n\n    .ui.page.dimmer {\n      -webkit-perspective: none;\n      perspective: none;\n    }\n  }\n\n  #root {\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n  }\n\n  .g-root {\n    --g-font-family-monospace: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace !important;\n    --g-font-family-sans: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif !important;\n\n    .g-md-settings-content__separator,\n    .g-md-settings-content__toolbar,\n    .g-md-settings-content__version {\n      display: none;\n    }\n  }\n\n  .yfm {\n    --yfm-color-accent: #2185d0 !important;\n    --yfm-color-link: #1e70bf !important;\n    --yfm-color-text: #17394d !important;\n    --yfm-font-family-monospace: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace !important;\n    --yfm-font-family-sans: \"Nunitoga\", \"Helvetica Neue\", Arial, Helvetica, sans-serif !important;\n\n    font-size: 1em !important;\n\n    a:hover {\n      color: var(--yfm-color-link) !important;\n      text-decoration: underline;\n    }\n\n    h1,\n    h2,\n    h3,\n    h4,\n    h5,\n    h6 {\n      line-height: 1.25 !important;\n    }\n\n    h1 {\n      font-size: 2em !important;\n    }\n\n    h2 {\n      font-size: 1.5em !important;\n    }\n\n    h3 {\n      font-size: 1.25em !important;\n    }\n\n    h4 {\n      font-size: 1em !important;\n    }\n\n    h5 {\n      font-size: .875em !important;\n    }\n\n    h6 {\n      font-size: .85em !important;\n    }\n\n    .mention {\n      color: #0366d6;\n      background-color: #f1f8ff;\n      border-radius: 3px;\n      padding: 0 2px;\n    }\n\n    .yfm-clipboard:hover {\n      .yfm-clipboard-button {\n        min-height: auto;\n        min-width: auto;\n      }\n    }\n\n    .yfm-note {\n      min-height: 60px;\n    }\n  }\n\n  .hljs {\n    display: block;\n    overflow-x: auto;\n    padding: .5em;\n    color: #333;\n    background: #f8f8f8\n  }\n\n  .hljs-comment,\n  .hljs-quote {\n    color: #998;\n    font-style: italic\n  }\n\n  .hljs-keyword,\n  .hljs-selector-tag,\n  .hljs-subst {\n    color: #333;\n    font-weight: bold;\n  }\n\n  .hljs-literal,\n  .hljs-number,\n  .hljs-tag .hljs-attr,\n  .hljs-template-variable,\n  .hljs-variable {\n    color: teal\n  }\n\n  .hljs-doctag,\n  .hljs-string {\n    color: #d14\n  }\n\n  .hljs-section,\n  .hljs-selector-id,\n  .hljs-title {\n    color: #900;\n    font-weight: bold;\n  }\n\n  .hljs-subst {\n    font-weight: normal\n  }\n\n  .hljs-class .hljs-title,\n  .hljs-type {\n    color: #458;\n    font-weight: bold;\n  }\n\n  .hljs-attribute,\n  .hljs-name,\n  .hljs-tag {\n    color: navy;\n    font-weight: normal\n  }\n\n  .hljs-link,\n  .hljs-regexp {\n    color: #009926\n  }\n\n  .hljs-bullet,\n  .hljs-symbol {\n    color: #990073\n  }\n\n  .hljs-built_in,\n  .hljs-builtin-name {\n    color: #0086b3\n  }\n\n  .hljs-meta {\n    color: #999;\n    font-weight: bold;\n  }\n\n  .hljs-deletion {\n    background: #fdd\n  }\n\n  .hljs-addition {\n    background: #dfd\n  }\n\n  .hljs-emphasis {\n    font-style: italic\n  }\n\n  .hljs-strong {\n    font-weight: bold;\n  }\n\n  ::-webkit-scrollbar {\n    height: 10px;\n    width: 10px;\n    -webkit-appearance: none;\n  }\n\n  ::-webkit-scrollbar-track {\n    background: rgba(0, 0, 0, 0.1);\n    border-radius: 0px;\n  }\n\n  ::-webkit-scrollbar-thumb {\n    background: rgba(0, 0, 0, 0.25);\n    border-radius: 5px;\n    cursor: pointer;\n    transition: color 0.2s ease;\n    -webkit-transition: color 0.2s ease;\n  }\n\n  ::-webkit-scrollbar-thumb:window-inactive {\n    background: rgba(0, 0, 0, 0.15);\n  }\n\n  ::-webkit-scrollbar-thumb:hover {\n    background: rgba(128, 135, 139, 0.8);\n  }\n}\n\n:global(#app) {\n  &.dragging > * {\n    pointer-events: none;\n  }\n\n  &.dragScrolling > * {\n    pointer-events: none;\n    user-select: none;\n  }\n\n  /* Backgrounds */\n\n  .backgroundMuddyGrey {\n    background: #69655a !important;\n  }\n\n  .backgroundAutumnLeafs {\n    background: #c9b037 !important;\n  }\n\n  .backgroundMorningSky {\n    background: #52b9d5 !important;\n  }\n\n  .backgroundAntiqueBlue {\n    background: #6c99bb !important;\n  }\n\n  .backgroundAntiqueBlueSoft {\n    background: #c7dcee !important;\n  }\n\n  .backgroundEggYellow {\n    background: #f9c423 !important;\n  }\n\n  .backgroundDesertSand {\n    background: #fad371 !important;\n  }\n\n  .backgroundDarkGranite {\n    background: #8b8680 !important;\n  }\n\n  .backgroundDarkGraniteSoft {\n    background: #d8d6d3 !important;\n  }\n\n  .backgroundFreshSalad {\n    background: #ced85e !important;\n  }\n\n  .backgroundLagoonBlue {\n    background: #109dc0 !important;\n  }\n\n  .backgroundLagoonBlueSoft {\n    background: #bfe3ee !important;\n  }\n\n  .backgroundMidnightBlue {\n    background: #0a63a0 !important;\n  }\n\n  .backgroundLightOrange {\n    background: #fdae5f !important;\n  }\n\n  .backgroundPumpkinOrange {\n    background: #ed9223 !important;\n  }\n\n  .backgroundPumpkinOrangeSoft {\n    background: #f6d2a8 !important;\n  }\n\n  .backgroundLightConcrete {\n    background: #afb0a4 !important;\n  }\n\n  .backgroundSunnyGrass {\n    background: #beca02 !important;\n  }\n\n  .backgroundNavyBlue {\n    background: #1d7299 !important;\n  }\n\n  .backgroundLilacEyes {\n    background: #406cbd !important;\n  }\n\n  .backgroundApricotRed {\n    background: #fc736c !important;\n  }\n\n  .backgroundOrangePeel {\n    background: #de692f !important;\n  }\n\n  .backgroundOrangePeelSoft {\n    background: #f2c6ae !important;\n  }\n\n  .backgroundSilverGlint {\n    background: linear-gradient(160deg, #adadad, #92908d, #e2e2e2, #928e8e, #726f6e) !important;\n  }\n\n  .backgroundBrightMoss {\n    background: #96b352 !important;\n  }\n\n  .backgroundBrightMossSoft {\n    background: #d7e4b6 !important;\n  }\n\n  .backgroundDeepOcean {\n    background: #004c70 !important;\n  }\n\n  .backgroundSummerSky {\n    background: #5d9cec !important;\n  }\n\n  .backgroundBerryRed {\n    background: #e83855 !important;\n  }\n\n  .backgroundBerryRedSoft {\n    background: #f6c6cf !important;\n  }\n\n  .backgroundLightCocoa {\n    background: #a85540 !important;\n  }\n\n  .backgroundGreyStone {\n    background: #aab2bd !important;\n  }\n\n  .backgroundTankGreen {\n    background: #8aa177 !important;\n  }\n\n  .backgroundCoralGreen {\n    background: #2b6a6c !important;\n  }\n\n  .backgroundSugarPlum {\n    background: #7e86c7 !important;\n  }\n\n  .backgroundPinkTulip {\n    background: #e34f7c !important;\n  }\n\n  .backgroundPinkTulipSoft {\n    background: #f2c1d3 !important;\n  }\n\n  .backgroundShadyRust {\n    background: #87564a !important;\n  }\n\n  .backgroundWetRock {\n    background: #83949b !important;\n  }\n\n  .backgroundWetMoss {\n    background: #4a8753 !important;\n  }\n\n  .backgroundTurquoiseSea {\n    background: #00858a !important;\n  }\n\n  .backgroundTurquoiseSeaSoft {\n    background: #b7e1e3 !important;\n  }\n\n  .backgroundLavenderFields {\n    background: #b287bd !important;\n  }\n\n  .backgroundPiggyRed {\n    background: #f97394 !important;\n  }\n\n  .backgroundLightMud {\n    background: #c7a57a !important;\n  }\n\n  .backgroundLightMudSoft {\n    background: #e6d4bc !important;\n  }\n\n  .backgroundGunMetal {\n    background: #4f6573 !important;\n  }\n\n  .backgroundModernGreen {\n    background: #77ce87 !important;\n  }\n\n  .backgroundFrenchCoast {\n    background: #00b4b1 !important;\n  }\n\n  .backgroundSweetLilac {\n    background: #975298 !important;\n  }\n\n  .backgroundRedBurgundy {\n    background: #ad5f7d !important;\n  }\n\n  .backgroundPirateGold {\n    background: linear-gradient(160deg, #a54e07, #b47e11, #fef1a2, #bc881b, #a54e07) !important;\n  }\n\n  .backgroundOceanDive {\n    background: linear-gradient(to top, #062e53, #1ad0e0) !important;\n  }\n\n  .backgroundOldLime {\n    background: linear-gradient(to bottom, #7b920a, #add100) !important;\n  }\n\n  .backgroundTzepeschStyle {\n    background: linear-gradient(to bottom, #190a05, #870000) !important;\n  }\n\n  .backgroundJungleMesh {\n    background: linear-gradient(to bottom, #727a17, #414d0b) !important;\n  }\n\n  .backgroundBlueDanube {\n    background: radial-gradient(circle, rgba(9, 9, 121, 1) 0%, rgba(2, 0, 36, 1) 0%, rgba(2, 29, 66, 1) 0%, rgba(2, 41, 78, 1) 0%, rgba(2, 57, 95, 1) 0%, rgba(1, 105, 144, 1) 100%, rgba(1, 151, 192, 1) 100%, rgba(0, 212, 255, 1) 100%) !important;\n  }\n\n  .backgroundSundownStripe {\n    background: linear-gradient(22deg, rgba(31, 30, 30, 1) 0%, rgba(255, 128, 0, 1) 10%, rgba(255, 128, 0, 1) 41%, rgba(0, 0, 0, 1) 41%, rgba(0, 102, 204, 1) 89%) !important;\n  }\n\n  .backgroundMagicalDawn {\n    background: radial-gradient(circle, rgba(0, 107, 141, 1) 0%, rgba(0, 69, 91, 1) 90%) !important;\n  }\n\n  .backgroundStrawberryDust {\n    background: linear-gradient(180deg, rgba(172, 79, 115, 1) 0%, rgba(254, 158, 150, 1) 66%) !important;\n  }\n\n  .backgroundPurpleRose {\n    background: linear-gradient(128deg, rgba(116, 43, 62, 1) 19%, rgba(192, 71, 103, 1) 90%) !important;\n  }\n\n  .backgroundSunScream {\n    background: linear-gradient(112deg, rgba(251, 221, 19, 1) 19%, rgba(255, 153, 1, 1) 62%) !important;\n  }\n\n  .backgroundWarmRust {\n    background: linear-gradient(141deg, rgba(255, 90, 8, 1) 0%, rgba(88, 0, 0, 1) 96%) !important;\n  }\n\n  .backgroundSkyChange {\n    background: linear-gradient(135deg, rgba(0, 52, 89, 1) 0%, rgba(0, 168, 232, 1) 90%) !important;\n  }\n\n  .backgroundGreenEyes {\n    background: linear-gradient(138deg, rgba(19, 170, 82, 1) 0%, rgba(0, 102, 43, 1) 90%) !important;\n  }\n\n  .backgroundBlueXchange {\n    background: radial-gradient(circle, #294f83, #162c4a) !important;\n  }\n\n  .backgroundBloodOrange {\n    background: linear-gradient(360deg, #d64759 10%, #da7352 360%) !important;\n  }\n\n  .backgroundSourPeel {\n    background: linear-gradient(360deg, #fd6f46 10%, #fb9832 360%) !important;\n  }\n\n  .backgroundGreenNinja {\n    background: linear-gradient(360deg, #224e4d 10%, #083023 360%) !important;\n  }\n\n  .backgroundIceBlue {\n    background: linear-gradient(360deg, #38aecc 10%, #347fb9 360%) !important;\n  }\n\n  .backgroundEpicGreen {\n    background: linear-gradient(360deg, #01a99c 10%, #0698b1 360%) !important;\n  }\n\n  .backgroundAlgaeGreen {\n    background: radial-gradient(circle farthest-corner at 10% 20%, rgba(0, 95, 104, 1) 0%, rgba(15, 156, 168, 1) 90%) !important;\n  }\n\n  .backgroundCoralReef {\n    background: linear-gradient(110.3deg, rgba(238, 179, 123, 1) 8.7%, rgba(216, 103, 77, 1) 47.5%, rgba(114, 43, 54, 1) 89.1%) !important;\n  }\n\n  .backgroundSteelGrey {\n    background: radial-gradient(circle farthest-corner at -4% -12.9%, rgba(74, 98, 110, 1) 0.3%, rgba(30, 33, 48, 1) 90.2%) !important;\n  }\n\n  .backgroundHeatWaves {\n    background: linear-gradient(to right, #12c2e9, #c471ed, #f64f59) !important;\n  }\n\n  .backgroundWowBlue {\n    background: linear-gradient(111.8deg, rgba(0, 104, 155, 1) 19.8%, rgba(0, 173, 239, 1) 92.1%) !important;\n  }\n\n  .backgroundVelvetLounge {\n    background: radial-gradient(circle farthest-corner at 10% 20%, rgba(151, 10, 130, 1) 0%, rgba(33, 33, 33, 1) 100.2%) !important;\n  }\n\n  .backgroundLagoon {\n    background: radial-gradient(circle farthest-corner at 10% 20%, rgba(0, 107, 141, 1) 0%, rgba(0, 69, 91, 1) 90%) !important;\n  }\n\n  .backgroundPurpleRain {\n    background: linear-gradient(91.7deg, rgba(50, 25, 79, 1) -4.3%, rgba(122, 101, 149, 1) 101.8%) !important;\n  }\n\n  .backgroundBlueSteel {\n    background: linear-gradient(to top, #09203f 0%, #537895 100%) !important;\n  }\n\n  .backgroundBlueishCurve {\n    background: linear-gradient(171.8deg, rgba(5, 111, 146, 1) 13.5%, rgba(6, 57, 84, 1) 78.6%) !important;\n  }\n\n  .backgroundPrismLight {\n    background: linear-gradient(111.7deg, rgba(251, 198, 6, 1) 2.4%, rgba(224, 82, 95, 1) 28.3%, rgba(194, 78, 154, 1) 46.2%, rgba(32, 173, 190, 1) 79.4%, rgba(22, 158, 95, 1) 100.2%) !important;\n  }\n\n  .backgroundTheBow {\n    background: radial-gradient(circle farthest-corner at -8.9% 51.2%, rgba(255, 124, 0, 1) 0%, rgba(255, 124, 0, 1) 15.9%, rgba(255, 163, 77, 1) 15.9%, rgba(255, 163, 77, 1) 24.4%, rgba(19, 30, 37, 1) 24.5%, rgba(19, 30, 37, 1) 66%) !important;\n  }\n\n  .backgroundGreenMist {\n    background: linear-gradient(180.5deg, rgba(0, 128, 128, 1) 8.5%, rgba(174, 206, 100, 1) 118.2%) !important;\n  }\n\n  .backgroundRedCurtain {\n    background: radial-gradient(circle at 2.9% 14.3%, rgba(255, 0, 102, 1) 0%, rgba(80, 5, 35, 1) 100.7%) !important;\n  }\n\n  /* Colors */\n\n  .colorBerryRed {\n    color: #e83855 !important;\n  }\n\n  .colorPumpkinOrange {\n    color: #ed9223 !important;\n  }\n\n  .colorLagoonBlue {\n    color: #109dc0 !important;\n  }\n\n  .colorPinkTulip {\n    color: #e34f7c !important;\n  }\n\n  .colorLightMud {\n    color: #c7a57a !important;\n  }\n\n  .colorOrangePeel {\n    color: #de692f !important;\n  }\n\n  .colorBrightMoss {\n    color: #96b352 !important;\n  }\n\n  .colorAntiqueBlue {\n    color: #6c99bb !important;\n  }\n\n  .colorDarkGranite {\n    color: #8b8680 !important;\n  }\n\n  .colorTurquoiseSea {\n    color: #00858a !important;\n  }\n}\n"
  },
  {
    "path": "client/src/utils/access-token-storage.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Cookies from 'js-cookie';\nimport { jwtDecode } from 'jwt-decode';\n\nimport Config from '../constants/Config';\n\nconst PATH = Config.BASE_PATH || '/';\n\nexport const setAccessToken = (accessToken) => {\n  const { exp } = jwtDecode(accessToken);\n  const expires = new Date(exp * 1000);\n\n  Cookies.set(Config.ACCESS_TOKEN_KEY, accessToken, {\n    expires,\n    path: PATH,\n    secure: window.location.protocol === 'https:',\n    sameSite: 'strict',\n  });\n\n  Cookies.set(Config.ACCESS_TOKEN_VERSION_KEY, Config.ACCESS_TOKEN_VERSION, {\n    expires,\n    path: PATH,\n  });\n};\n\nexport const removeAccessToken = () => {\n  Cookies.remove(Config.ACCESS_TOKEN_KEY, { path: PATH });\n  Cookies.remove(Config.ACCESS_TOKEN_VERSION_KEY, { path: PATH });\n};\n\nexport const getAccessToken = () => {\n  let accessToken = Cookies.get(Config.ACCESS_TOKEN_KEY);\n  const accessTokenVersion = Cookies.get(Config.ACCESS_TOKEN_VERSION_KEY);\n\n  if (accessToken && accessTokenVersion !== Config.ACCESS_TOKEN_VERSION) {\n    removeAccessToken();\n    accessToken = undefined;\n  }\n\n  return accessToken;\n};\n"
  },
  {
    "path": "client/src/utils/build-search-parts.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst SEARCH_PARTS_REGEX = /[ ,;]+/; // TODO: move to utils\n\nexport default (search) =>\n  search.split(SEARCH_PARTS_REGEX).flatMap((searchPart) => {\n    if (!searchPart) {\n      return [];\n    }\n\n    return searchPart.toLowerCase();\n  });\n"
  },
  {
    "path": "client/src/utils/element-helpers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport const focusEnd = (element) => {\n  element.focus();\n  element.setSelectionRange(element.value.length + 1, element.value.length + 1);\n};\n\nexport const isActiveTextElement = (element) =>\n  ['input', 'textarea'].includes(element.tagName.toLowerCase()) &&\n  element === document.activeElement;\n\nexport const isUsableMarkdownElement = (element) =>\n  !!element.closest('.yfm a, .yfm-clipboard-button, .yfm-cut-title');\n"
  },
  {
    "path": "client/src/utils/event-helpers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport Config from '../constants/Config';\n\n// eslint-disable-next-line import/prefer-default-export\nexport const isModifierKeyPressed = (event) => (Config.IS_MAC ? event.metaKey : event.ctrlKey);\n"
  },
  {
    "path": "client/src/utils/get-date-format.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport default (date, longDateFormat = 'longDateTime', fullDateFormat = 'fullDateTime') => {\n  const year = date.getFullYear();\n  const currentYear = new Date().getFullYear();\n\n  return year === currentYear ? longDateFormat : fullDateFormat;\n};\n"
  },
  {
    "path": "client/src/utils/get-filename-and-extension.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport default (url) => {\n  const filename = url.split('/').pop().toLowerCase();\n\n  let extension = filename.slice((Math.max(0, filename.lastIndexOf('.')) || Infinity) + 1);\n  extension = extension ? extension.toLowerCase() : null;\n\n  return {\n    filename,\n    extension,\n  };\n};\n"
  },
  {
    "path": "client/src/utils/local-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nlet lastTimestamp = 0;\nlet sequence = 0;\n\nexport const createLocalId = () => {\n  const timestamp = Date.now();\n\n  sequence = timestamp <= lastTimestamp ? sequence + 1 : 0;\n  lastTimestamp = timestamp;\n\n  return `local:${timestamp}-${String(sequence).padStart(4, '0')}`;\n};\n\nexport const isLocalId = (id) => id.startsWith('local:');\n"
  },
  {
    "path": "client/src/utils/local-id.test.js",
    "content": "import { isLocalId } from './local-id';\n\ndescribe('isLocalId', () => {\n  test('is valid', () => {\n    expect(isLocalId('local:1234567890-0000')).toBeTruthy();\n  });\n\n  test('is invalid', () => {\n    expect(isLocalId('1234567890-0000')).toBeFalsy();\n  });\n});\n"
  },
  {
    "path": "client/src/utils/markdown-to-text.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport transform from '@diplodoc/transform';\n\nimport plugins from '../configs/markdown-plugins';\n\nexport default (markdown) => {\n  let tokens;\n  try {\n    tokens = transform(markdown, {\n      plugins,\n      tokens: true,\n    });\n  } catch (error) {\n    return error.toString();\n  }\n\n  return tokens\n    .flatMap((token) => {\n      if (!token.children) {\n        return [];\n      }\n\n      return token.children\n        .flatMap((childrenToken) => (childrenToken.type === 'text' ? childrenToken.content : []))\n        .join('');\n    })\n    .join('\\n');\n};\n"
  },
  {
    "path": "client/src/utils/match-paths.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { matchPath } from 'react-router';\n\nexport default (pathname, paths) => {\n  for (let i = 0; i < paths.length; i += 1) {\n    const match = matchPath(\n      {\n        path: paths[i],\n        end: true,\n      },\n      pathname,\n    );\n\n    if (match) {\n      return match;\n    }\n  }\n\n  return null;\n};\n"
  },
  {
    "path": "client/src/utils/mentions.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst USERNAME_CHAR_CLASS = 'a-zA-Z0-9._';\nconst USERNAME_CHAR_REGEX = new RegExp(`^[${USERNAME_CHAR_CLASS}]$`);\n\nconst MENTION_TEXT_REGEX = new RegExp(\n  `(^|[^${USERNAME_CHAR_CLASS}])@([${USERNAME_CHAR_CLASS}]+)`,\n  'gi',\n);\n\nconst MENTION_MARKUP_REGEX = /@\\[(.*?)\\]\\((.*?)\\)/g;\n\nexport const mentionTextToMarkup = (text, userByUsername) =>\n  text.replace(MENTION_TEXT_REGEX, (match, before, username) => {\n    const user = userByUsername[username.toLowerCase()];\n    return user ? `${before}@[${user.username}](${user.id})` : match;\n  });\n\nexport const mentionMarkupToText = (markup) =>\n  markup.replace(MENTION_MARKUP_REGEX, (_, username) => `@${username}`);\n\nexport const isUsernameChar = (char) => USERNAME_CHAR_REGEX.test(char);\n"
  },
  {
    "path": "client/src/utils/merge-records.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst mergeRecords = (target, ...sources) => {\n  if (sources.length === 0) {\n    return target;\n  }\n\n  const source = sources.shift();\n\n  if (!target || !source) {\n    return mergeRecords(target || source, ...sources);\n  }\n\n  const nextTarget = [...target];\n\n  source.forEach((sourceRecord) => {\n    const index = nextTarget.findIndex((targetRecord) => targetRecord.id === sourceRecord.id);\n\n    if (index >= 0) {\n      Object.assign(nextTarget[index], sourceRecord);\n    } else {\n      nextTarget.push(sourceRecord);\n    }\n  });\n\n  return mergeRecords(nextTarget, ...sources);\n};\n\nexport default mergeRecords;\n"
  },
  {
    "path": "client/src/utils/parse-dnd-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexport default (dndId) => dndId.split(':')[1];\n"
  },
  {
    "path": "client/src/utils/parse-time.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport parseDate from 'date-fns/parse';\n\nconst TIME_REGEX =\n  /^((\\d{1,2})((:|\\.)?(\\d{1,2}))?)(a|p|(am|a\\.m\\.|midnight|mi|pm|p\\.m\\.|noon|n))?$/;\n\nconst ALTERNATIVE_AM_MERIDIEMS_SET = new Set(['am', 'a.m.', 'midnight', 'mi']);\nconst ALTERNATIVE_PM_MERIDIEMS_SET = new Set(['pm', 'p.m.', 'noon', 'n']);\n\nconst TimeFormats = {\n  TWENTY_FOUR_HOUR: 'twentyFourHour',\n  TWELVE_HOUR: 'twelveHour',\n};\n\nconst PATTERNS_GROUPS_BY_TIME_FORMAT = {\n  [TimeFormats.TWENTY_FOUR_HOUR]: {\n    byNumbersTotal: {\n      1: ['H'],\n      2: ['HH'],\n      3: ['Hmm'],\n      4: ['HHmm'],\n    },\n    withDelimiter: ['H:m', 'H:mm', 'HH:m', 'HH:mm'],\n  },\n  [TimeFormats.TWELVE_HOUR]: {\n    byNumbersTotal: {\n      1: ['haaaaa'],\n      2: ['hhaaaaa'],\n      3: ['hmmaaaaa'],\n      4: ['hhmmaaaaa'],\n    },\n    withDelimiter: ['h:maaaaa', 'h:mmaaaaa', 'hh:maaaaa', 'hh:mmaaaaa'],\n  },\n};\n\nconst INVALID_DATE = new Date('invalid-date');\n\nconst normalizeDelimeter = (delimeter) => (delimeter === '.' ? ':' : delimeter);\n\nconst normalizeMeridiem = (meridiem, alternativeMeridiem) => {\n  if (meridiem && alternativeMeridiem) {\n    if (ALTERNATIVE_AM_MERIDIEMS_SET.has(alternativeMeridiem)) {\n      return 'a';\n    }\n\n    if (ALTERNATIVE_PM_MERIDIEMS_SET.has(alternativeMeridiem)) {\n      return 'p';\n    }\n  }\n\n  return meridiem;\n};\n\nconst makeTimeString = (hours, minutes, delimeter, meridiem) => {\n  let timeString = hours;\n  if (delimeter) {\n    timeString += delimeter;\n  }\n  if (minutes) {\n    timeString += minutes;\n  }\n  if (meridiem) {\n    timeString += meridiem;\n  }\n\n  return timeString;\n};\n\nexport default (string, referenceDate) => {\n  const match = string.replace(/\\s/g, '').toLowerCase().match(TIME_REGEX);\n\n  if (!match) {\n    return INVALID_DATE;\n  }\n\n  const [, hoursAndMinutes, hours, , delimeter, minutes, meridiem, alternativeMeridiem] = match;\n\n  const normalizedDelimeter = normalizeDelimeter(delimeter);\n  const normalizedMeridiem = normalizeMeridiem(meridiem, alternativeMeridiem);\n\n  const timeString = makeTimeString(hours, minutes, normalizedDelimeter, normalizedMeridiem);\n\n  const timeFormat = meridiem ? TimeFormats.TWELVE_HOUR : TimeFormats.TWENTY_FOUR_HOUR;\n  const patternsGroups = PATTERNS_GROUPS_BY_TIME_FORMAT[timeFormat];\n\n  const patterns = delimeter\n    ? patternsGroups.withDelimiter\n    : patternsGroups.byNumbersTotal[hoursAndMinutes.length];\n\n  if (!referenceDate) {\n    referenceDate = new Date(); // eslint-disable-line no-param-reassign\n  }\n\n  for (let i = 0; i < patterns.length; i += 1) {\n    const parsedDate = parseDate(timeString, patterns[i], referenceDate);\n\n    if (!Number.isNaN(parsedDate.getTime())) {\n      return parsedDate;\n    }\n  }\n\n  return INVALID_DATE;\n};\n"
  },
  {
    "path": "client/src/utils/record-helpers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport { StaticUserIds } from '../constants/StaticUsers';\nimport { ListTypes, UserRoles } from '../constants/Enums';\n\nexport const isUserStatic = (user) => [StaticUserIds.DELETED].includes(user.id);\n\nexport const isUserAdminOrProjectOwner = (user) =>\n  [UserRoles.ADMIN, UserRoles.PROJECT_OWNER].includes(user.role);\n\nexport const isListArchiveOrTrash = (list) =>\n  [ListTypes.ARCHIVE, ListTypes.TRASH].includes(list.type);\n\nexport const isListFinite = (list) => [ListTypes.ACTIVE, ListTypes.CLOSED].includes(list.type);\n\nexport const isListKanban = (list) => [ListTypes.ACTIVE, ListTypes.CLOSED].includes(list.type);\n"
  },
  {
    "path": "client/src/utils/stopwatch.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst getFullSeconds = ({ startedAt, total }) => {\n  if (startedAt) {\n    return Math.floor((new Date() - startedAt) / 1000) + total;\n  }\n\n  return total;\n};\n\nexport const createStopwatch = ({ hours, minutes, seconds }) => ({\n  startedAt: null,\n  total: hours * 60 * 60 + minutes * 60 + seconds,\n});\n\nexport const updateStopwatch = ({ startedAt }, parts) => ({\n  ...createStopwatch(parts),\n  startedAt: startedAt && new Date(),\n});\n\nexport const startStopwatch = (stopwatch) => ({\n  startedAt: new Date(),\n  total: stopwatch ? stopwatch.total : 0,\n});\n\nexport const stopStopwatch = (stopwatch) => ({\n  startedAt: null,\n  total: getFullSeconds(stopwatch),\n});\n\nexport const getStopwatchParts = (stopwatch) => {\n  const fullSeconds = getFullSeconds(stopwatch);\n\n  const hours = Math.floor(fullSeconds / 3600);\n  const minutes = Math.floor((fullSeconds - hours * 3600) / 60);\n  const seconds = fullSeconds - hours * 3600 - minutes * 60;\n\n  return {\n    hours,\n    minutes,\n    seconds,\n  };\n};\n\nexport const formatStopwatch = (stopwatch) => {\n  const { hours, minutes, seconds } = getStopwatchParts(stopwatch);\n  return [hours, ...[minutes, seconds].map((part) => (part < 10 ? `0${part}` : part))].join(':');\n};\n"
  },
  {
    "path": "client/src/utils/validator.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nimport isURL from 'validator/lib/isURL';\nimport zxcvbn from 'zxcvbn';\n\nconst USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\\.)?[a-zA-Z0-9])*$/;\n\nexport const isUrl = (string) =>\n  isURL(string, {\n    protocols: ['http', 'https'],\n    require_tld: false,\n    require_protocol: true,\n    max_allowed_length: 2048,\n  });\n\nexport const isPassword = (string) => zxcvbn(string).score >= 2; // TODO: move to config\n\nexport const isUsername = (string) =>\n  string.length >= 3 && string.length <= 32 && USERNAME_REGEX.test(string);\n"
  },
  {
    "path": "client/src/version.js",
    "content": "export default '2.1.0';\n"
  },
  {
    "path": "client/tests/acceptance/Config.js",
    "content": "const BASE_URL = process.env.BASE_URL || 'http://localhost:1337';\n\nconst TIMEOUT = parseInt(process.env.TIMEOUT, 10) || 6000;\n\nconst PLAYWRIGHT = {\n  headless: process.env.PLAYWRIGHT_HEADLESS !== 'false',\n  slowMo: parseInt(process.env.PLAYWRIGHT_SLOW_MO, 10) || 1000,\n};\n\nexport default {\n  BASE_URL,\n  TIMEOUT,\n  PLAYWRIGHT,\n};\n"
  },
  {
    "path": "client/tests/acceptance/cucumber.conf.js",
    "content": "import { After, AfterAll, Before, BeforeAll, setDefaultTimeout } from '@cucumber/cucumber';\nimport { chromium } from 'playwright';\n\nimport Config from './Config.js';\n\nsetDefaultTimeout(Config.TIMEOUT);\n\nBeforeAll(async () => {\n  global.browser = await chromium.launch(Config.PLAYWRIGHT);\n});\n\nBefore(async () => {\n  global.context = await global.browser.newContext();\n  global.page = await global.context.newPage();\n});\n\nAfter(async () => {\n  await global.page.close();\n  await global.context.close();\n});\n\nAfterAll(async () => {\n  await global.browser.close();\n});\n"
  },
  {
    "path": "client/tests/acceptance/features/login.feature",
    "content": "Feature: Login\n  Background:\n    Given the user navigates to the login page\n\n  Scenario: User logs in with valid credentials\n    When the user logs in with email or username \"demo\" and password \"demo\" via the web UI\n    Then the user should be redirected to the home page\n\n  Scenario Outline: User logs in with invalid credentials\n    When the user logs in with email or username \"<emailOrUsername>\" and password \"<password>\" via the web UI\n    Then the user should see the message \"<message>\"\n\n    Examples:\n      | emailOrUsername | password  | message             |\n      | spiderman       | spider123 | Invalid credentials |\n      | ironman         | iron123   | Invalid credentials |\n      | aquaman         | aqua123   | Invalid credentials |\n\n  Scenario: User logs out\n    Given the user is logged in with email or username \"demo\" and password \"demo\"\n    When the user logs out via the web UI\n    Then the user should be redirected to the login page\n"
  },
  {
    "path": "client/tests/acceptance/pages/HomePage.js",
    "content": "import Config from '../Config.js';\n\nexport default class HomePage {\n  constructor() {\n    this.url = Config.BASE_URL;\n\n    this.userActionSelector = 'div.menu > a:last-child';\n    this.logOutSelector = 'a:has-text(\"Log Out\")';\n  }\n\n  async navigate() {\n    await page.goto(this.url);\n  }\n\n  async logout() {\n    await page.click(this.userActionSelector);\n    await page.click(this.logOutSelector);\n  }\n}\n"
  },
  {
    "path": "client/tests/acceptance/pages/LoginPage.js",
    "content": "import Config from '../Config.js';\n\nexport default class LoginPage {\n  constructor() {\n    this.url = `${Config.BASE_URL}/login`;\n\n    this.emailOrUsernameInputSelector = 'input[name=\"emailOrUsername\"]';\n    this.passwordInputSelector = 'input[name=\"password\"]';\n    this.logInButtonSelector = 'button.primary';\n    this.messageSelector = 'div.message > div.content > p';\n  }\n\n  async navigate() {\n    await page.goto(this.url);\n  }\n\n  async login(emailOrUsername, password) {\n    await page.fill(this.emailOrUsernameInputSelector, emailOrUsername);\n    await page.fill(this.passwordInputSelector, password);\n    await page.click(this.logInButtonSelector);\n  }\n\n  async getMessage() {\n    return page.innerText(this.messageSelector);\n  }\n}\n"
  },
  {
    "path": "client/tests/acceptance/steps/login.step.js",
    "content": "import assert from 'assert';\nimport { Given, Then, When } from '@cucumber/cucumber';\nimport { expect } from '@playwright/test';\n\nimport LoginPage from '../pages/LoginPage.js';\nimport HomePage from '../pages/HomePage.js';\n\nconst loginPage = new LoginPage();\nconst homePage = new HomePage();\n\n// ---------- GIVEN ----------\n\nGiven('the user navigates to the login page', async () => {\n  await loginPage.navigate();\n\n  await expect(page).toHaveURL(loginPage.url);\n});\n\nGiven(\n  'the user is logged in with email or username {string} and password {string}',\n  async (emailOrUsername, password) => {\n    await loginPage.navigate();\n    await loginPage.login(emailOrUsername, password);\n\n    await expect(page).toHaveURL(homePage.url);\n  },\n);\n\n// ---------- WHEN ----------\n\nWhen(\n  'the user logs in with email or username {string} and password {string} via the web UI',\n  async (emailOrUsername, password) => {\n    await loginPage.login(emailOrUsername, password);\n  },\n);\n\nWhen('the user logs out via the web UI', async () => {\n  await homePage.logout();\n});\n\n// ---------- THEN ----------\n\nThen('the user should be redirected to the home page', async () => {\n  await expect(page).toHaveURL(homePage.url);\n});\n\nThen('the user should be redirected to the login page', async () => {\n  await expect(page).toHaveURL(loginPage.url);\n});\n\nThen('the user should see the message {string}', async (expectedMessage) => {\n  const message = await loginPage.getMessage();\n\n  assert.strictEqual(\n    message,\n    expectedMessage,\n    `Expected message to be \"${expectedMessage}\", but received \"${message}\"`,\n  );\n});\n"
  },
  {
    "path": "client/tests/setup-symlinks.sh",
    "content": "#!/bin/bash\n\n# This script sets up symbolic links between the client dist files and the server directories\n\n# Navigate to the root directory of the git repository\ncd \"$(git rev-parse --show-toplevel)\" || { echo \"Failed to navigate to the git repository root\"; exit 1; }\n\n# Store paths for the client dist, server public, and server views\nCLIENT_PATH=$(pwd)/client/dist\nSERVER_PUBLIC_PATH=$(pwd)/server/public\nSERVER_VIEWS_PATH=$(pwd)/server/views\n\n# Create symbolic links for the necessary client assets in the server's public and views directories\nln -s ${CLIENT_PATH}/favicon.ico ${SERVER_PUBLIC_PATH}/favicon.ico && echo \"Linked favicon.ico successfully\"\nln -s ${CLIENT_PATH}/logo192.png ${SERVER_PUBLIC_PATH}/logo192.png && echo \"Linked logo192.png successfully\"\nln -s ${CLIENT_PATH}/logo512.png ${SERVER_PUBLIC_PATH}/logo512.png && echo \"Linked logo512.png successfully\"\nln -s ${CLIENT_PATH}/manifest.json ${SERVER_PUBLIC_PATH}/manifest.json && echo \"Linked manifest.json successfully\"\nln -s ${CLIENT_PATH}/robots.txt ${SERVER_PUBLIC_PATH}/robots.txt && echo \"Linked robots.txt successfully\"\nln -s ${CLIENT_PATH}/assets ${SERVER_PUBLIC_PATH}/assets && echo \"Linked assets folder successfully\"\nln -s ${CLIENT_PATH}/index.ejs ${SERVER_VIEWS_PATH}/index.ejs && echo \"Linked index.ejs successfully\"\n\necho \"Setup symbolic links completed successfully.\"\n"
  },
  {
    "path": "client/version-template.ejs",
    "content": "export default '<%= pkg.version %>';\n"
  },
  {
    "path": "client/vite.config.js",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { defineConfig } from 'vite';\nimport commonjs from 'vite-plugin-commonjs';\nimport { nodePolyfills } from 'vite-plugin-node-polyfills';\n// eslint-disable-next-line import/no-unresolved\nimport react from '@vitejs/plugin-react';\nimport svgr from 'vite-plugin-svgr';\n// eslint-disable-next-line import/no-unresolved\nimport browserslistToEsbuild from 'browserslist-to-esbuild';\n\n// eslint-disable-next-line no-underscore-dangle\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst createEjsTemplate = () => ({\n  name: 'create-ejs-template',\n  closeBundle() {\n    if (process.env.INDEX_FORMAT !== 'ejs') return;\n\n    const distPath = path.resolve(__dirname, 'dist');\n    const htmlPath = path.join(distPath, 'index.html');\n\n    if (!fs.existsSync(htmlPath)) return;\n\n    const html = fs.readFileSync(htmlPath, 'utf8');\n\n    const ejs = html\n      .replace(/(href|src)=\"\\.\\/([^\"]+)\"/g, '$1=\"<%- basePath %>/$2\"')\n      .replace('</head>', \"  <script>window.BASE_PATH = '<%- basePath %>';</script>\\n  </head>\");\n\n    fs.writeFileSync(path.join(distPath, 'index.ejs'), ejs);\n    fs.unlinkSync(htmlPath);\n  },\n});\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  base: './',\n  plugins: [\n    commonjs(),\n    nodePolyfills({\n      include: ['fs', 'path', 'process', 'url'],\n    }),\n    react(),\n    svgr(),\n    createEjsTemplate(),\n  ],\n  resolve: {\n    alias: {\n      'source-map-js': 'source-map',\n    },\n  },\n  server: {\n    port: 3000,\n    open: true,\n    proxy: {\n      '/api': 'http://localhost:1337',\n      '/socket.io': { target: 'http://localhost:1337', ws: true },\n    },\n  },\n  build: {\n    target: browserslistToEsbuild(['>0.2%', 'not dead', 'not op_mini all']),\n  },\n});\n"
  },
  {
    "path": "docker-backup.sh",
    "content": "#!/bin/bash\n\n# Stop on error\nset -e\n\n# Configure those to match your Docker container names\nDOCKER_CONTAINER_POSTGRES=\"planka-postgres-1\"\nDOCKER_CONTAINER_PLANKA=\"planka-planka-1\"\n\n# Use provided directory or default to current directory\nBACKUP_DIR=\"${1:-$(pwd)}\"\n\nif [ -z \"$1\" ]; then\n    echo \"No backup directory specified, backing up to current directory: $BACKUP_DIR\"\nelse\n    echo \"Backing up to: $BACKUP_DIR\"\nfi\necho\n\nif date --version >/dev/null 2>&1; then\n    # GNU date (Linux)\n    BACKUP_DATETIME=$(date --utc +%FT%H-%M-%SZ)\nelse\n    # BSD date (macOS)\n    BACKUP_DATETIME=$(date -u +%FT%H-%M-%SZ)\nfi\n\nBACKUP_TEMP=\"$BACKUP_DIR/$BACKUP_DATETIME-backup\"\n\n# Create temporary directory\nmkdir -p \"$BACKUP_TEMP\"\n\necho -n \"Exporting postgres database ... \"\ndocker exec -t \"$DOCKER_CONTAINER_POSTGRES\" pg_dumpall -c -U postgres > \"$BACKUP_TEMP/postgres.sql\"\necho \"Success!\"\necho\n\necho -n \"Exporting data volume ... \"\ndocker run --rm --volumes-from \"$DOCKER_CONTAINER_PLANKA\" -v \"$BACKUP_TEMP:/backup\" node:22-alpine cp -r /app/data /backup/data\necho \"Success!\"\necho\n\necho -n \"Creating final tarball $BACKUP_DATETIME-backup.tgz ... \"\ntar -C \"$BACKUP_DIR\" -czf \"$BACKUP_TEMP.tgz\" \"$BACKUP_DATETIME-backup\"\necho \"Success!\"\necho\n\necho -n \"Cleaning up temporary files and directories ... \"\nrm -rf \"$BACKUP_TEMP\"\necho \"Success!\"\necho\n\necho \"Backup Complete!\"\n"
  },
  {
    "path": "docker-compose-dev.yml",
    "content": "services:\n  planka-server:\n    build:\n      context: .\n      dockerfile: Dockerfile.dev\n    image: planka-dev\n    pull_policy: never\n    command: [\"bash\", \"-c\", \"npm install && npm run db:init && npm start\"]\n    restart: on-failure\n    volumes:\n      - ./server:/app\n    ports:\n      - 1337:1337\n    environment:\n      - BASE_URL=http://localhost:1337\n      - DATABASE_URL=postgresql://postgres@postgres/planka\n      - SECRET_KEY=notsecretkey\n\n      # - LOG_LEVEL=warn\n\n      # - TRUST_PROXY=true\n      # - MAX_UPLOAD_FILE_SIZE=\n      # - TOKEN_EXPIRES_IN=365 # In days\n\n      # - STORAGE_LIMIT=\n      # - ACTIVE_USERS_LIMIT=\n\n      # related: https://github.com/knex/knex/issues/2354\n      # As knex does not pass query parameters from the connection string,\n      # we have to use environment variables in order to pass the desired values, e.g.\n      # - PGSSLMODE=<value>\n\n      # Configure knex to accept SSL certificates\n      # - KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE=false\n\n      # The default application language used as a fallback when a user's language is not set.\n      # This language is also used for per-board notifications.\n      # - DEFAULT_LANGUAGE=en-US\n\n      # Do not comment out DEFAULT_ADMIN_EMAIL if you want to prevent this user from being edited/deleted\n      # - DEFAULT_ADMIN_EMAIL=demo@demo.demo\n      # - DEFAULT_ADMIN_PASSWORD=demo\n      # - DEFAULT_ADMIN_NAME=Demo Demo\n      # - DEFAULT_ADMIN_USERNAME=demo\n\n      # Set to true to show more detailed authentication error messages.\n      # It should not be enabled without a rate limiter for security reasons.\n      # - SHOW_DETAILED_AUTH_ERRORS=false\n\n      # All outgoing HTTP requests (SMTP, webhooks, Apprise notifications, favicon fetching, etc.)\n      # will be sent through this proxy if set.\n      # - OUTGOING_PROXY=http://proxy:3128\n\n      # Set to true to expose the Swagger specification at /swagger.json\n      # - SWAGGER_EXPOSED=false\n\n      # - S3_ENDPOINT=\n      # - S3_REGION=\n      # - S3_ACCESS_KEY_ID=\n      # - S3_SECRET_ACCESS_KEY=\n      # - S3_BUCKET=\n      # - S3_FORCE_PATH_STYLE=true\n\n      # - OIDC_ISSUER=\n      # - OIDC_CLIENT_ID=\n      # - OIDC_CLIENT_SECRET=\n      # - OIDC_USE_OAUTH_CALLBACK=true\n      # - OIDC_ID_TOKEN_SIGNED_RESPONSE_ALG=\n      # - OIDC_USERINFO_SIGNED_RESPONSE_ALG=\n      # - OIDC_SCOPES=openid email profile\n      # - OIDC_RESPONSE_MODE=fragment\n      # - OIDC_USE_DEFAULT_RESPONSE_MODE=true\n      # - OIDC_ADMIN_ROLES=admin\n      # - OIDC_PROJECT_OWNER_ROLES=project_owner\n      # - OIDC_BOARD_USER_ROLES=board_user\n      # - OIDC_CLAIMS_SOURCE=userinfo\n      # - OIDC_EMAIL_ATTRIBUTE=email\n      # - OIDC_NAME_ATTRIBUTE=name\n      # - OIDC_USERNAME_ATTRIBUTE=preferred_username\n      # - OIDC_ROLES_ATTRIBUTE=groups\n      # - OIDC_IGNORE_USERNAME=true\n      # - OIDC_IGNORE_ROLES=true\n      # - OIDC_ENFORCED=true\n      # - OIDC_TIMEOUT=3500\n      # - OIDC_DEBUG=true\n\n      # Email Notifications (https://nodemailer.com/smtp/)\n      # These values override and disable configuration in the UI if set.\n      # - SMTP_HOST=\n      # - SMTP_PORT=587\n      # - SMTP_NAME=\n      # - SMTP_SECURE=true\n      # - SMTP_TLS_REJECT_UNAUTHORIZED=false\n      # - SMTP_USER=\n      # - SMTP_PASSWORD=\n      # - SMTP_FROM=\"Demo Demo\" <demo@demo.demo>\n\n      # Using Gravatar directly exposes user IPs and hashed emails to a third party (GDPR risk).\n      # Use a proxy you control for privacy, or leave commented out or empty to disable.\n      # - GRAVATAR_BASE_URL=https://www.gravatar.com/avatar/\n    depends_on:\n      postgres:\n        condition: service_healthy\n\n  planka-client:\n    image: planka-dev\n    pull_policy: never\n    command: [\"bash\", \"-c\", \"npm install && npx vite --host\"]\n    restart: on-failure\n    volumes:\n      - ./client:/app\n    ports:\n      - 3000:3000\n\n  postgres:\n    image: postgres:16-alpine\n    restart: on-failure\n    volumes:\n      - db-data:/var/lib/postgresql/data\n    environment:\n      - POSTGRES_DB=planka\n      - POSTGRES_HOST_AUTH_METHOD=trust\n    healthcheck:\n      test:\n        [\n          \"CMD-SHELL\",\n          \"pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-planka} && psql -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-planka} -c 'SELECT 1'\",\n        ]\n      interval: 3s\n      timeout: 5s\n      retries: 15\n      start_period: 10s\n\nvolumes:\n  db-data:\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  planka:\n    image: ghcr.io/plankanban/planka:latest\n    restart: on-failure\n    volumes:\n      - data:/app/data\n      # - ./terms:/app/terms/custom\n    # Optionally override this to your user/group\n    # user: 1000:1000\n    # tmpfs:\n    #   - /app/.tmp:mode=770,uid=1000,gid=1000\n    ports:\n      - 3000:1337\n    environment:\n      - BASE_URL=http://localhost:3000\n      - DATABASE_URL=postgresql://postgres@postgres/planka\n\n      # Optionally store the database password in secrets:\n      # - DATABASE_URL=postgresql://postgres:$${DATABASE_PASSWORD}@postgres/planka\n      # - DATABASE_PASSWORD__FILE=/run/secrets/database_password\n      # And add the following to the service:\n      # secrets:\n      #   - database_password\n\n      - SECRET_KEY=notsecretkey\n      # Optionally store in secrets - then SECRET_KEY should not be set\n      # - SECRET_KEY__FILE=/run/secrets/secret_key\n\n      # - LOG_LEVEL=warn\n\n      # - TRUST_PROXY=true\n      # - MAX_UPLOAD_FILE_SIZE=\n      # - TOKEN_EXPIRES_IN=365 # In days\n\n      # - STORAGE_LIMIT=\n      # - ACTIVE_USERS_LIMIT=\n\n      # related: https://github.com/knex/knex/issues/2354\n      # As knex does not pass query parameters from the connection string,\n      # we have to use environment variables in order to pass the desired values, e.g.\n      # - PGSSLMODE=<value>\n\n      # Configure knex to accept SSL certificates\n      # - KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE=false\n\n      # The default application language used as a fallback when a user's language is not set.\n      # This language is also used for per-board notifications.\n      # - DEFAULT_LANGUAGE=en-US\n\n      # Do not comment out DEFAULT_ADMIN_EMAIL if you want to prevent this user from being edited/deleted\n      # - DEFAULT_ADMIN_EMAIL=demo@demo.demo\n      # - DEFAULT_ADMIN_PASSWORD=demo\n      # Optionally store in secrets - then DEFAULT_ADMIN_PASSWORD should not be set\n      # - DEFAULT_ADMIN_PASSWORD__FILE=/run/secrets/default_admin_password\n      # - DEFAULT_ADMIN_NAME=Demo Demo\n      # - DEFAULT_ADMIN_USERNAME=demo\n\n      # Set to true to show more detailed authentication error messages.\n      # It should not be enabled without a rate limiter for security reasons.\n      # - SHOW_DETAILED_AUTH_ERRORS=false\n\n      # All outgoing HTTP requests (SMTP, webhooks, Apprise notifications, favicon fetching, etc.)\n      # will be sent through this proxy if set.\n      # If commented out, an internal Squid proxy will be started inside the container,\n      # which you can control via OUTGOING_BLOCKED_* and OUTGOING_ALLOWED_* below.\n      # - OUTGOING_PROXY=http://proxy:3128\n\n      # Set to true to expose the Swagger specification at /swagger.json\n      # - SWAGGER_EXPOSED=false\n\n      # - S3_ENDPOINT=\n      # - S3_REGION=\n      # - S3_ACCESS_KEY_ID=\n      # - S3_SECRET_ACCESS_KEY=\n      # Optionally store in secrets - then S3_SECRET_ACCESS_KEY should not be set\n      # - S3_SECRET_ACCESS_KEY__FILE=/run/secrets/s3_secret_access_key\n      # - S3_BUCKET=\n      # - S3_FORCE_PATH_STYLE=true\n\n      # - OIDC_ISSUER=\n      # - OIDC_CLIENT_ID=\n      # - OIDC_CLIENT_SECRET=\n      # Optionally store in secrets - then OIDC_CLIENT_SECRET should not be set\n      # - OIDC_CLIENT_SECRET__FILE=/run/secrets/oidc_client_secret\n      # - OIDC_USE_OAUTH_CALLBACK=true\n      # - OIDC_ID_TOKEN_SIGNED_RESPONSE_ALG=\n      # - OIDC_USERINFO_SIGNED_RESPONSE_ALG=\n      # - OIDC_SCOPES=openid email profile\n      # - OIDC_RESPONSE_MODE=fragment\n      # - OIDC_USE_DEFAULT_RESPONSE_MODE=true\n      # - OIDC_ADMIN_ROLES=admin\n      # - OIDC_PROJECT_OWNER_ROLES=project_owner\n      # - OIDC_BOARD_USER_ROLES=board_user\n      # - OIDC_CLAIMS_SOURCE=userinfo\n      # - OIDC_EMAIL_ATTRIBUTE=email\n      # - OIDC_NAME_ATTRIBUTE=name\n      # - OIDC_USERNAME_ATTRIBUTE=preferred_username\n      # - OIDC_ROLES_ATTRIBUTE=groups\n      # - OIDC_IGNORE_USERNAME=true\n      # - OIDC_IGNORE_ROLES=true\n      # - OIDC_ENFORCED=true\n      # - OIDC_TIMEOUT=3500\n      # - OIDC_DEBUG=true\n\n      # Email Notifications (https://nodemailer.com/smtp/)\n      # These values override and disable configuration in the UI if set.\n      # - SMTP_HOST=\n      # - SMTP_PORT=587\n      # - SMTP_NAME=\n      # - SMTP_SECURE=true\n      # - SMTP_TLS_REJECT_UNAUTHORIZED=false\n      # - SMTP_USER=\n      # - SMTP_PASSWORD=\n      # Optionally store in secrets - then SMTP_PASSWORD should not be set\n      # - SMTP_PASSWORD__FILE=/run/secrets/smtp_password\n      # - SMTP_FROM=\"Demo Demo\" <demo@demo.demo>\n\n      # Using Gravatar directly exposes user IPs and hashed emails to a third party (GDPR risk).\n      # Use a proxy you control for privacy, or leave commented out or empty to disable.\n      # - GRAVATAR_BASE_URL=https://www.gravatar.com/avatar/\n\n      # --------------------------------------------------------------------\n      # Outgoing traffic control (internal Squid proxy)\n      # --------------------------------------------------------------------\n\n      # These IPs/hostnames will always be blocked (highest priority)\n      # - OUTGOING_BLOCKED_IPS=\n      # - OUTGOING_BLOCKED_HOSTS=localhost,postgres\n\n      # Only these IPs/hostnames will be reachable\n      # - OUTGOING_ALLOWED_IPS=\n      # - OUTGOING_ALLOWED_HOSTS=\n    depends_on:\n      postgres:\n        condition: service_healthy\n\n  postgres:\n    image: postgres:16-alpine\n    restart: on-failure\n    volumes:\n      - db-data:/var/lib/postgresql/data\n    environment:\n      - POSTGRES_DB=planka\n      - POSTGRES_HOST_AUTH_METHOD=trust\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U postgres -d planka\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n\nvolumes:\n  data:\n  db-data:\n"
  },
  {
    "path": "docker-restore.sh",
    "content": "#!/bin/bash\n\n# Stop on error\nset -e\n\n# Configure those to match your Docker container names\nDOCKER_CONTAINER_POSTGRES=\"planka-postgres-1\"\nDOCKER_CONTAINER_PLANKA=\"planka-planka-1\"\n\n# Use provided archive\nBACKUP_ARCHIVE=\"$1\"\n\nif [ -z \"$BACKUP_ARCHIVE\" ]; then\n    echo \"Usage: $0 <backup-archive.tgz>\"\n    exit 1\nfi\n\nBACKUP_DIR=$(dirname \"$BACKUP_ARCHIVE\")\nBACKUP_TEMP=\"$BACKUP_DIR/$(basename \"$BACKUP_ARCHIVE\" .tgz)\"\n\necho -n \"Extracting tarball $BACKUP_ARCHIVE ... \"\ntar -C \"$BACKUP_DIR\" -xzf \"$BACKUP_ARCHIVE\"\necho \"Success!\"\necho\n\necho -n \"Importing postgres database ... \"\ncat \"$BACKUP_TEMP/postgres.sql\" | docker exec -i \"$DOCKER_CONTAINER_POSTGRES\" psql -U postgres\necho \"Success!\"\necho\n\necho -n \"Importing data volume ... \"\ndocker run --rm --user root --volumes-from \"$DOCKER_CONTAINER_PLANKA\" -v \"$BACKUP_TEMP:/backup\" node:22-alpine sh -c \"cp -rf /backup/data/. /app/data && chown -R node:node /app/data/*\"\necho \"Success!\"\necho\n\necho -n \"Cleaning up temporary files and directories ... \"\nrm -r \"$BACKUP_TEMP\"\necho \"Success!\"\necho\n\necho \"Restore complete!\"\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"planka\",\n  \"version\": \"2.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"client:build\": \"npm run build --prefix client\",\n    \"client:lint\": \"npm run lint --prefix client\",\n    \"client:start\": \"npm start --prefix client\",\n    \"client:test\": \"npm test --prefix client\",\n    \"docker:build\": \"docker build -t planka .\",\n    \"gv\": \"npm i --package-lock-only --ignore-scripts && genversion --source . --template server/version-template.ejs server/version.js && genversion --source . --template client/version-template.ejs client/src/version.js\",\n    \"postinstall\": \"npm i --prefix server && npm i --prefix client\",\n    \"lint\": \"npm run server:lint && npm run client:lint\",\n    \"prepare\": \"husky\",\n    \"server:build\": \"npm run build --prefix server\",\n    \"server:console\": \"npm run console --prefix server\",\n    \"server:db:create-admin-user\": \"npm run db:create-admin-user --prefix server\",\n    \"server:db:init\": \"npm run db:init --prefix server\",\n    \"server:db:migrate\": \"npm run db:migrate --prefix server\",\n    \"server:db:seed\": \"npm run db:seed --prefix server\",\n    \"server:db:upgrade\": \"npm run db:upgrade --prefix server\",\n    \"server:lint\": \"npm run lint --prefix server\",\n    \"server:start\": \"npm start --prefix server\",\n    \"server:start:prod\": \"npm run start:prod --prefix server\",\n    \"server:swagger:generate\": \"npm run swagger:generate --prefix server\",\n    \"server:test\": \"npm test --prefix server\",\n    \"start\": \"concurrently -n server,client \\\"npm run server:start\\\" \\\"npm run client:start\\\"\",\n    \"test\": \"npm run server:test && npm run client:test\"\n  },\n  \"lint-staged\": {\n    \"client/src/**/*.{js,jsx}\": [\n      \"npm run client:lint\"\n    ],\n    \"server/**/*.js\": [\n      \"npm run server:lint\"\n    ]\n  },\n  \"dependencies\": {\n    \"concurrently\": \"^9.2.1\",\n    \"genversion\": \"^3.2.0\",\n    \"husky\": \"^9.1.7\",\n    \"lint-staged\": \"^16.4.0\"\n  }\n}\n"
  },
  {
    "path": "server/.buildignore",
    "content": "**/.gitkeep\n**/.DS_Store\n\nbuild.js\ngenerate-swagger.js\nnodemon.json\nswagger.json\nversion-template.ejs\n.buildignore\n.editorconfig\n.env\n.eslintignore\n.gitignore\n.npmrc\n\ndist\nlogs\nnode_modules\ntest\n.tmp\n.venv\n\nviews/index.ejs\n\ndata/*\n\nterms/*\n!terms/_template\n!terms/cloud\n"
  },
  {
    "path": "server/.editorconfig",
    "content": "################################################\n#   ╔═╗╔╦╗╦╔╦╗╔═╗╦═╗┌─┐┌─┐┌┐┌┌─┐┬┌─┐\n#   ║╣  ║║║ ║ ║ ║╠╦╝│  │ ││││├┤ ││ ┬\n#  o╚═╝═╩╝╩ ╩ ╚═╝╩╚═└─┘└─┘┘└┘└  ┴└─┘\n#\n# > Formatting conventions for your Sails app.\n#\n# This file (`.editorconfig`) exists to help\n# maintain consistent formatting throughout the\n# files in your Sails app.\n#\n# For the sake of convention, the Sails team's\n# preferred settings are included here out of the\n# box.  You can also change this file to fit your\n# team's preferences (for example, if all of the\n# developers on your team have a strong preference\n# for tabs over spaces),\n#\n# To review what each of these options mean, see:\n# http://editorconfig.org/\n#\n################################################\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": "server/.eslintignore",
    "content": "assets/dependencies/**/*.js\nviews/**/*.ejs\npublic/**/*.js\n"
  },
  {
    "path": "server/.gitignore",
    "content": "################################################\n#   ┌─┐┬┌┬┐╦╔═╗╔╗╔╔═╗╦═╗╔═╗\n#   │ ┬│ │ ║║ ╦║║║║ ║╠╦╝║╣\n#  o└─┘┴ ┴ ╩╚═╝╝╚╝╚═╝╩╚═╚═╝\n#\n# > Files to exclude from your app's repo.\n#\n# This file (`.gitignore`) is only relevant if\n# you are using git.\n#\n# It exists to signify to git that certain files\n# and/or directories should be ignored for the\n# purposes of version control.\n#\n# This keeps tmp files and sensitive credentials\n# from being uploaded to  your repository.  And\n# it allows you to configure your app for your\n# machine without accidentally committing settings\n# which will smash the local settings of other\n# developers on your team.\n#\n# Some reasonable defaults are included below,\n# but, of course, you should modify/extend/prune\n# to fit your needs!\n#\n################################################\n\n################################################\n# Local Configuration\n#\n# Explicitly ignore files which contain:\n#\n# 1. Sensitive information you'd rather not push to\n#    your git repository.\n#    e.g., your personal API keys or passwords.\n#\n# 2. Developer-specific configuration\n#    Basically, anything that would be annoying\n#    to have to change every time you do a\n#    `git pull` on your laptop.\n#    e.g. your local development database, or\n#    the S3 bucket you're using for file uploads\n#    during development.\n#\n################################################\n\n.env\nconfig/local.js\n\n################################################\n# Dependencies\n#\n#\n# When releasing a production app, you _could_\n# hypothetically include your node_modules folder\n# in your git repo, but during development, it\n# is always best to exclude it, since different\n# developers may be working on different kernels,\n# where dependencies would need to be recompiled\n# anyway.\n#\n# Most of the time, the node_modules folder can\n# be excluded from your code repository, even\n# in production, thanks to features like the\n# package-lock.json file / NPM shrinkwrap.\n#\n# But no matter what, since this is a Sails app,\n# you should always push up the package-lock.json\n# or shrinkwrap file to your repository, to avoid\n# accidentally pulling in upgraded dependencies\n# and breaking your code.\n#\n# That said, if you are having trouble with\n# dependencies, (particularly when using\n# `npm link`) this can be pretty discouraging.\n# But rather than just adding the lockfile to\n# your .gitignore, try this first:\n# ```\n#     rm -rf node_modules\n#     rm package-lock.json\n#     npm install\n# ```\n#\n# [?] For more tips/advice, come by and say hi\n#     over at https://sailsjs.com/support\n#\n################################################\n\nnode_modules\n.venv\n\n################################################\n#\n# > Do you use bower?\n# > re: the bower_components dir, see this:\n# > http://addyosmani.com/blog/checking-in-front-end-dependencies/\n# > (credit Addy Osmani, @addyosmani)\n#\n################################################\n\n################################################\n# Temporary files generated by Sails/Waterline.\n################################################\n\n.tmp\n\n################################################\n# Miscellaneous\n#\n# Common files generated by text editors,\n# operating systems, file systems, dbs, etc.\n################################################\n\n*~\n*#\n.DS_STORE\n.netbeans\nnbproject\n.idea\n*.iml\n.vscode\n.node_history\ndump.rdb\n\nnpm-debug.log\nlib-cov\n*.seed\n*.log\n*.out\n*.pid\n\nswagger.json\n\ndist\nlogs\n\nviews/index.ejs\n\ndata/*\n!data/.gitkeep\n\nterms/*\n!terms/_template\n!terms/cloud\n"
  },
  {
    "path": "server/.npmrc",
    "content": "######################\n#   ╔╗╔╔═╗╔╦╗┬─┐┌─┐  #\n#   ║║║╠═╝║║║├┬┘│    #\n#  o╝╚╝╩  ╩ ╩┴└─└─┘  #\n######################\n\n# Hide NPM log output unless it is related to an error of some kind:\nloglevel=error\n\n# Make \"npm audit\" an opt-in thing for subsequent installs within this app:\naudit=false\n"
  },
  {
    "path": "server/.sailsrc",
    "content": "{\n  \"generators\": {\n    \"modules\": {}\n  },\n  \"hooks\": {\n    \"blueprints\": false,\n    \"grunt\": false,\n    \"session\": false\n  },\n  \"paths\": {\n    \"public\": \"public\"\n  },\n  \"_generatedWith\": {\n    \"sails\": \"1.1.0\",\n    \"sails-generate\": \"1.16.4\"\n  }\n}\n"
  },
  {
    "path": "server/api/controllers/.gitkeep",
    "content": ""
  },
  {
    "path": "server/api/controllers/_internal/update-config.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    storageLimit: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 256,\n      allowNull: true,\n    },\n    activeUsersLimit: {\n      type: 'number',\n      min: 0,\n      allowNull: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n  },\n\n  async fn(inputs) {\n    // eslint-disable-next-line no-restricted-syntax\n    for (const fieldName of Object.keys(inputs)) {\n      if (!_.isNil(sails.config.custom[fieldName])) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    }\n\n    const values = _.pick(inputs, ['storageLimit', 'activeUsersLimit']);\n\n    const internalConfig = await sails.helpers.internalConfig.updateMain.with({\n      values,\n    });\n\n    return {\n      item: internalConfig,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/access-tokens/accept-terms.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /access-tokens/accept-terms:\n *   post:\n *     summary: Accept terms and conditions\n *     description: Accept terms during the authentication flow. Converts the pending token to an access token.\n *     tags:\n *       - Access Tokens\n *     operationId: acceptTerms\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - pendingToken\n *               - signature\n *             properties:\n *               pendingToken:\n *                 type: string\n *                 maxLength: 1024\n *                 description: Pending token received from the authentication flow\n *                 example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ4...\n *               signature:\n *                 type: string\n *                 minLength: 64\n *                 maxLength: 64\n *                 description: Terms signature hash\n *                 example: 940226c4c41f51afe3980ceb63704e752636526f4c52a4ea579e85b247493d94\n *               initialLanguage:\n *                 type: string\n *                 enum: [ar-YE, bg-BG, ca-ES, cs-CZ, da-DK, de-DE, el-GR, en-GB, en-US, es-ES, et-EE, fa-IR, fi-FI, fr-FR, hu-HU, id-ID, it-IT, ja-JP, ko-KR, nl-NL, pl-PL, pt-BR, pt-PT, ro-RO, ru-RU, sk-SK, sr-Cyrl-RS, sr-Latn-RS, sv-SE, tr-TR, uk-UA, uz-UZ, vi-VN, zh-CN, zh-TW]\n *                 nullable: true\n *                 description: Preferred language for user interface and notifications (used only if user language is not set)\n *                 example: en-US\n *     responses:\n *       200:\n *         description: Terms accepted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   type: string\n *                   description: Access token for API authentication\n *                   example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ5...\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         description: Invalid pending token\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_UNAUTHORIZED\n *                 message:\n *                   type: string\n *                   description: Error message\n *                   example: Invalid pending token\n *       403:\n *         description: Authentication restriction\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_FORBIDDEN\n *                 message:\n *                   type: string\n *                   enum:\n *                     - Invalid signature\n *                     - Admin login required to initialize instance\n *                   description: Specific error message\n *                   example: Invalid signature\n *     security: []\n */\n\nconst { getRemoteAddress } = require('../../../utils/remote-address');\n\nconst { AccessTokenSteps } = require('../../../constants');\n\nconst Errors = {\n  INVALID_PENDING_TOKEN: {\n    invalidPendingToken: 'Invalid pending token',\n  },\n  INVALID_SIGNATURE: {\n    invalidSignature: 'Invalid signature',\n  },\n  ADMIN_LOGIN_REQUIRED_TO_INITIALIZE_INSTANCE: {\n    adminLoginRequiredToInitializeInstance: 'Admin login required to initialize instance',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    pendingToken: {\n      type: 'string',\n      maxLength: 1024,\n      required: true,\n    },\n    signature: {\n      type: 'string',\n      minLength: 64,\n      maxLength: 64,\n      required: true,\n    },\n    initialLanguage: {\n      type: 'string',\n      isIn: User.LANGUAGES,\n      allowNull: true,\n    },\n  },\n\n  exits: {\n    invalidPendingToken: {\n      responseType: 'unauthorized',\n    },\n    invalidSignature: {\n      responseType: 'forbidden',\n    },\n    adminLoginRequiredToInitializeInstance: {\n      responseType: 'forbidden',\n    },\n  },\n\n  async fn(inputs) {\n    const remoteAddress = getRemoteAddress(this.req);\n    const { httpOnlyToken } = this.req.cookies;\n\n    try {\n      payload = sails.helpers.utils.verifyJwtToken(inputs.pendingToken);\n    } catch (error) {\n      if (error.raw.name === 'TokenExpiredError') {\n        throw Errors.INVALID_PENDING_TOKEN;\n      }\n\n      sails.log.warn(`Invalid pending token! (IP: ${remoteAddress})`);\n      throw Errors.INVALID_PENDING_TOKEN;\n    }\n\n    if (payload.subject !== AccessTokenSteps.ACCEPT_TERMS) {\n      throw Errors.INVALID_PENDING_TOKEN;\n    }\n\n    let session = await Session.qm.getOneUndeletedByPendingToken(inputs.pendingToken);\n\n    if (!session) {\n      sails.log.warn(`Invalid pending token! (IP: ${remoteAddress})`);\n      throw Errors.INVALID_PENDING_TOKEN;\n    }\n\n    if (session.httpOnlyToken && httpOnlyToken !== session.httpOnlyToken) {\n      throw Errors.INVALID_PENDING_TOKEN;\n    }\n\n    if (!sails.hooks.terms.isSignatureValid(inputs.signature)) {\n      throw Errors.INVALID_SIGNATURE;\n    }\n\n    let user = await User.qm.getOneById(session.userId, {\n      withDeactivated: false,\n    });\n\n    if (!user) {\n      throw Errors.INVALID_PENDING_TOKEN; // TODO: introduce separate error?\n    }\n\n    const values = {\n      termsSignature: inputs.signature,\n      termsAcceptedAt: new Date().toISOString(),\n    };\n\n    if (!user.language && inputs.initialLanguage) {\n      values.language = inputs.initialLanguage;\n    }\n\n    ({ user } = await User.qm.updateOne(user.id, values));\n\n    const internalConfig = await InternalConfig.qm.getOneMain();\n\n    if (!internalConfig.isInitialized) {\n      if (user.role === User.Roles.ADMIN) {\n        await InternalConfig.qm.updateOneMain({\n          isInitialized: true,\n        });\n      } else {\n        throw Errors.ADMIN_LOGIN_REQUIRED_TO_INITIALIZE_INSTANCE;\n      }\n    }\n\n    const { token: accessToken, payload: accessTokenPayload } = sails.helpers.utils.createJwtToken(\n      user.id,\n    );\n\n    session = await Session.qm.updateOne(session.id, {\n      accessToken,\n      pendingToken: null,\n    });\n\n    if (session.httpOnlyToken && !this.req.isSocket) {\n      sails.helpers.utils.setHttpOnlyTokenCookie(\n        session.httpOnlyToken,\n        accessTokenPayload,\n        this.res,\n      );\n    }\n\n    return {\n      item: accessToken,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/access-tokens/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /access-tokens:\n *   post:\n *     summary: User login\n *     description: Authenticates a user using email/username and password. Returns an access token for API authentication.\n *     tags:\n *       - Access Tokens\n *     operationId: createAccessToken\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - emailOrUsername\n *               - password\n *             properties:\n *               emailOrUsername:\n *                 type: string\n *                 maxLength: 256\n *                 description: Email address or username of the user\n *                 example: john.doe@example.com\n *               password:\n *                 type: string\n *                 maxLength: 256\n *                 description: Password of the user\n *                 example: SecurePassword123!\n *               withHttpOnlyToken:\n *                 type: boolean\n *                 description: Whether to include an HTTP-only authentication cookie\n *                 example: true\n *     responses:\n *       200:\n *         description: Login successful\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   type: string\n *                   description: Access token for API authentication\n *                   example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ4...\n *         headers:\n *           Set-Cookie:\n *             description: HTTP-only authentication cookie (if `withHttpOnlyToken` is true)\n *             schema:\n *               type: string\n *               example: httpOnlyToken=29aa3e38-8d24-4029-9743-9cbcf0a0dd5c; HttpOnly; Secure; SameSite=Strict\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         description: Invalid credentials\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_UNAUTHORIZED\n *                 message:\n *                   type: string\n *                   enum:\n *                     - Invalid credentials\n *                     - Invalid email or username\n *                     - Invalid password\n *                   description: Specific error message\n *                   example: Invalid credentials\n *       403:\n *         description: Authentication restriction\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_FORBIDDEN\n *                 message:\n *                   type: string\n *                   enum:\n *                     - Use single sign-on\n *                     - Terms acceptance required\n *                     - Admin login required to initialize instance\n *                   description: Specific error message\n *                   example: Use single sign-on\n *     security: []\n */\n\nconst bcrypt = require('bcrypt');\n\nconst { isEmailOrUsername } = require('../../../utils/validators');\nconst { getRemoteAddress } = require('../../../utils/remote-address');\n\nconst Errors = {\n  INVALID_CREDENTIALS: {\n    invalidCredentials: 'Invalid credentials',\n  },\n  INVALID_EMAIL_OR_USERNAME: {\n    invalidEmailOrUsername: 'Invalid email or username',\n  },\n  INVALID_PASSWORD: {\n    invalidPassword: 'Invalid password',\n  },\n  USE_SINGLE_SIGN_ON: {\n    useSingleSignOn: 'Use single sign-on',\n  },\n  TERMS_ACCEPTANCE_REQUIRED: {\n    termsAcceptanceRequired: 'Terms acceptance required',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    emailOrUsername: {\n      type: 'string',\n      maxLength: 256,\n      custom: isEmailOrUsername,\n      required: true,\n    },\n    password: {\n      type: 'string',\n      maxLength: 256,\n      required: true,\n    },\n    withHttpOnlyToken: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    invalidCredentials: {\n      responseType: 'unauthorized',\n    },\n    invalidEmailOrUsername: {\n      responseType: 'unauthorized',\n    },\n    invalidPassword: {\n      responseType: 'unauthorized',\n    },\n    useSingleSignOn: {\n      responseType: 'forbidden',\n    },\n    termsAcceptanceRequired: {\n      responseType: 'forbidden',\n    },\n    adminLoginRequiredToInitializeInstance: {\n      responseType: 'forbidden',\n    },\n  },\n\n  async fn(inputs) {\n    if (sails.config.custom.oidcEnforced) {\n      throw Errors.USE_SINGLE_SIGN_ON;\n    }\n\n    const remoteAddress = getRemoteAddress(this.req);\n    const user = await User.qm.getOneActiveByEmailOrUsername(inputs.emailOrUsername);\n\n    if (!user) {\n      sails.log.warn(\n        `Invalid email or username: \"${inputs.emailOrUsername}\"! (IP: ${remoteAddress})`,\n      );\n\n      throw sails.config.custom.showDetailedAuthErrors\n        ? Errors.INVALID_EMAIL_OR_USERNAME\n        : Errors.INVALID_CREDENTIALS;\n    }\n\n    if (user.isSsoUser) {\n      throw Errors.USE_SINGLE_SIGN_ON;\n    }\n\n    const isPasswordValid = await bcrypt.compare(inputs.password, user.password);\n\n    if (!isPasswordValid) {\n      sails.log.warn(`Invalid password! (IP: ${remoteAddress})`);\n\n      throw sails.config.custom.showDetailedAuthErrors\n        ? Errors.INVALID_PASSWORD\n        : Errors.INVALID_CREDENTIALS;\n    }\n\n    return sails.helpers.accessTokens.handleSteps\n      .with({\n        user,\n        remoteAddress,\n        request: this.req,\n        response: this.res,\n        withHttpOnlyToken: inputs.withHttpOnlyToken,\n      })\n      .intercept('adminLoginRequiredToInitializeInstance', (error) => ({\n        adminLoginRequiredToInitializeInstance: error.raw,\n      }))\n      .intercept('termsAcceptanceRequired', (error) => ({\n        termsAcceptanceRequired: error.raw,\n      }));\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/access-tokens/debug-oidc.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    code: {\n      type: 'string',\n      maxLength: 2048,\n      required: true,\n    },\n    nonce: {\n      type: 'string',\n      maxLength: 1024,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n  },\n\n  async fn(inputs) {\n    if (!sails.config.custom.oidcDebug) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const logs = ['🔐 Starting OIDC debug flow...', ''];\n    const client = await sails.hooks.oidc.getClient();\n\n    if (!client) {\n      logs.push('❌ OIDC client is not initialized.');\n      logs.push('💡 Hint: Check your OIDC issuer and client configuration.');\n\n      return {\n        item: null,\n        included: {\n          logs,\n        },\n      };\n    }\n\n    let tokenSet;\n    try {\n      logs.push('🔄 Exchanging authorization code...');\n\n      if (sails.config.custom.oidcUseOauthCallback) {\n        tokenSet = await client.oauthCallback(\n          sails.config.custom.oidcRedirectUri,\n          {\n            iss: sails.config.custom.oidcIssuer,\n            code: inputs.code,\n          },\n          { nonce: inputs.nonce },\n        );\n      } else {\n        tokenSet = await client.callback(\n          sails.config.custom.oidcRedirectUri,\n          {\n            iss: sails.config.custom.oidcIssuer,\n            code: inputs.code,\n          },\n          { nonce: inputs.nonce },\n        );\n      }\n\n      logs.push('✅ Authorization code exchanged successfully.', '');\n    } catch (error) {\n      logs.push('❌ Failed to exchange authorization code.');\n      logs.push(`💬 Reason: ${error.message || error.toString()}`);\n      logs.push('💡 Hint: Check redirect URI, client secret, and nonce handling.');\n\n      return {\n        item: null,\n        included: {\n          logs,\n        },\n      };\n    }\n\n    if (sails.config.custom.oidcClaimsSource === 'id_token') {\n      logs.push('📥 Extracting claims from ID token...');\n\n      try {\n        claims = tokenSet.claims();\n        logs.push('✅ Claims extracted successfully.', '');\n      } catch (error) {\n        logs.push('❌ Failed to extract user claims.');\n        logs.push(`💬 Reason: ${error.message || error.toString()}`);\n\n        return {\n          item: null,\n          included: {\n            logs,\n          },\n        };\n      }\n    } else {\n      logs.push('📥 Fetching claims from userinfo endpoint...');\n\n      try {\n        claims = await client.userinfo(tokenSet);\n        logs.push('✅ Claims fetched successfully.', '');\n      } catch (error) {\n        logs.push('❌ Failed to fetch user claims.');\n\n        if (error instanceof SyntaxError && error.message.includes('Unexpected token e in JSON')) {\n          logs.push('💬 Reason: Userinfo response is signed or not JSON.');\n          logs.push(\n            '💡 Hint: Try configuring userinfo signed response algorithm or switch to ID token claims.',\n          );\n        } else {\n          logs.push(`💬 Reason: ${error.message || error.toString()}`);\n        }\n\n        return {\n          item: null,\n          included: {\n            logs,\n          },\n        };\n      }\n    }\n\n    logs.push('📦 Raw claims received:', JSON.stringify(claims, null, 2), '');\n    logs.push('🧩 Evaluating claim mappings...', '');\n\n    const mappings = {\n      email: {\n        attribute: sails.config.custom.oidcEmailAttribute,\n        value: _.get(claims, sails.config.custom.oidcEmailAttribute),\n      },\n      name: {\n        attribute: sails.config.custom.oidcNameAttribute,\n        value: _.get(claims, sails.config.custom.oidcNameAttribute),\n      },\n      username: sails.config.custom.oidcIgnoreUsername\n        ? undefined\n        : {\n            attribute: sails.config.custom.oidcUsernameAttribute,\n            value: _.get(claims, sails.config.custom.oidcUsernameAttribute),\n          },\n      roles: sails.config.custom.oidcIgnoreRoles\n        ? undefined\n        : {\n            attribute: sails.config.custom.oidcRolesAttribute,\n            value: _.get(claims, sails.config.custom.oidcRolesAttribute),\n          },\n    };\n\n    logs.push('📋 Mapping result:', JSON.stringify(mappings, null, 2), '');\n\n    if (!mappings.email.value) {\n      logs.push('❌ Email not resolved.');\n      logs.push('💡 Hint: Check email attribute mapping.', '');\n    }\n\n    if (!mappings.name.value) {\n      logs.push('❌ Name not resolved.');\n      logs.push('💡 Hint: Check name attribute mapping.', '');\n    }\n\n    if (!sails.config.custom.oidcIgnoreUsername) {\n      if (!mappings.username.value) {\n        logs.push('⚠️ Username not resolved.');\n        logs.push('💡 Hint: Check username attribute mapping.', '');\n      }\n    }\n\n    if (!sails.config.custom.oidcIgnoreRoles) {\n      if (!Array.isArray(mappings.roles.value) || mappings.roles.value.length === 0) {\n        logs.push('⚠️ Roles not resolved or empty.');\n        logs.push('💡 Hint: Check roles attribute mapping or IdP role configuration.', '');\n      } else {\n        logs.push('🎭 Resolving user role from OIDC roles...');\n\n        // Use a Set here to avoid quadratic time complexity\n        const claimsRolesSet = new Set(mappings.roles.value);\n\n        const foundRole = [User.Roles.ADMIN, User.Roles.PROJECT_OWNER, User.Roles.BOARD_USER].find(\n          (roleItem) => {\n            const configRoles = sails.config.custom[`oidc${_.upperFirst(roleItem)}Roles`];\n\n            if (configRoles.includes('*')) {\n              return true;\n            }\n\n            return configRoles.some((configRole) => claimsRolesSet.has(configRole));\n          },\n        );\n\n        if (foundRole) {\n          logs.push(`✅ Matched user role → ${_.lowerCase(foundRole)}`, '');\n        } else {\n          logs.push('⚠️ No user role matched configured OIDC roles.');\n          logs.push('💡 Hint: Check role matching settings.', '');\n        }\n      }\n    }\n\n    if (mappings.email.value && mappings.name.value) {\n      logs.push('🎉 OIDC debug completed successfully.');\n    } else {\n      logs.push('🛑 OIDC debug detected missing required attributes.');\n    }\n\n    return {\n      item: null,\n      included: {\n        logs,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/access-tokens/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /access-tokens/me:\n *   delete:\n *     summary: User logout\n *     description: Logs out the current user by deleting the session and access token. Clears HTTP-only cookies if present.\n *     tags:\n *       - Access Tokens\n *     operationId: deleteAccessToken\n *     responses:\n *       200:\n *         description: Logout successful\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   type: string\n *                   description: Revoked access token\n *                   example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ4...\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *     security:\n *       - bearerAuth: []\n */\n\nmodule.exports = {\n  async fn() {\n    const { currentSession } = this.req;\n\n    await Session.qm.deleteOneById(currentSession.id);\n\n    sails.sockets.leaveAll(`@accessToken:${currentSession.accessToken}`);\n\n    if (currentSession.httpOnlyToken && !this.req.isSocket) {\n      sails.helpers.utils.clearHttpOnlyTokenCookie(this.res);\n    }\n\n    return {\n      item: currentSession.accessToken,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/access-tokens/exchange-with-oidc.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /access-tokens/exchange-with-oidc:\n *   post:\n *     summary: Exchange OIDC code for access token\n *     description: Exchanges an OIDC authorization code for an access token. Creates a user if they do not exist.\n *     tags:\n *       - Access Tokens\n *     operationId: exchangeForAccessTokenWithOidc\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - code\n *               - nonce\n *             properties:\n *               code:\n *                 type: string\n *                 maxLength: 2048\n *                 description: Authorization code from OIDC provider\n *                 example: abc123def456ghi789\n *               nonce:\n *                 type: string\n *                 maxLength: 1024\n *                 description: Nonce value for OIDC security\n *                 example: random-nonce-123456\n *               withHttpOnlyToken:\n *                 type: boolean\n *                 description: Whether to include HTTP-only authentication cookie\n *                 example: true\n *     responses:\n *       200:\n *         description: OIDC exchange successful\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   type: string\n *                   description: Access token for API authentication\n *                   example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ4...\n *         headers:\n *           Set-Cookie:\n *             description: HTTP-only authentication cookie (if `withHttpOnlyToken` is true)\n *             schema:\n *               type: string\n *               example: httpOnlyToken=29aa3e38-8d24-4029-9743-9cbcf0a0dd5c; HttpOnly; Secure; SameSite=Strict\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         description: OIDC authentication error\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_UNAUTHORIZED\n *                 message:\n *                   type: string\n *                   enum:\n *                     - Invalid code or nonce\n *                     - Invalid userinfo configuration\n *                   description: Specific error message\n *                   example: Invalid code or nonce\n *       403:\n *         description: Authentication restriction\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_FORBIDDEN\n *                 message:\n *                   type: string\n *                   enum:\n *                     - Terms acceptance required\n *                     - Admin login required to initialize instance\n *                   description: Specific error message\n *                   example: Terms acceptance required\n *       409:\n *         description: Conflict error\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_CONFLICT\n *                 message:\n *                   type: string\n *                   enum:\n *                     - Email already in use\n *                     - Username already in use\n *                     - Active users limit reached\n *                   description: Specific error message\n *                   example: Email already in use\n *       422:\n *         description: Missing required values\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_UNPROCESSABLE_ENTITY\n *                 message:\n *                   type: string\n *                   description: Error message\n *                   example: Unable to retrieve required values (email, name)\n *       500:\n *         description: OIDC configuration error\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_INTERNAL_SERVER_ERROR\n *                 message:\n *                   type: string\n *                   description: Error message\n *                   example: Invalid OIDC configuration\n *     security: []\n */\n\nconst { getRemoteAddress } = require('../../../utils/remote-address');\n\nconst Errors = {\n  INVALID_OIDC_CONFIGURATION: {\n    invalidOidcConfiguration: 'Invalid OIDC configuration',\n  },\n  INVALID_CODE_OR_NONCE: {\n    invalidCodeOrNonce: 'Invalid code or nonce',\n  },\n  INVALID_USERINFO_CONFIGURATION: {\n    invalidUserinfoConfiguration: 'Invalid userinfo configuration',\n  },\n  TERMS_ACCEPTANCE_REQUIRED: {\n    termsAcceptanceRequired: 'Terms acceptance required',\n  },\n  EMAIL_ALREADY_IN_USE: {\n    emailAlreadyInUse: 'Email already in use',\n  },\n  USERNAME_ALREADY_IN_USE: {\n    usernameAlreadyInUse: 'Username already in use',\n  },\n  ACTIVE_USERS_LIMIT_REACHED: {\n    activeUsersLimitReached: 'Active users limit reached',\n  },\n  MISSING_VALUES: {\n    missingValues: 'Unable to retrieve required values (email, name)',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    code: {\n      type: 'string',\n      maxLength: 2048,\n      required: true,\n    },\n    nonce: {\n      type: 'string',\n      maxLength: 1024,\n      required: true,\n    },\n    withHttpOnlyToken: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    invalidOidcConfiguration: {\n      responseType: 'serverError',\n    },\n    invalidCodeOrNonce: {\n      responseType: 'unauthorized',\n    },\n    invalidUserinfoConfiguration: {\n      responseType: 'unauthorized',\n    },\n    termsAcceptanceRequired: {\n      responseType: 'forbidden',\n    },\n    adminLoginRequiredToInitializeInstance: {\n      responseType: 'forbidden',\n    },\n    emailAlreadyInUse: {\n      responseType: 'conflict',\n    },\n    usernameAlreadyInUse: {\n      responseType: 'conflict',\n    },\n    activeUsersLimitReached: {\n      responseType: 'conflict',\n    },\n    missingValues: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const remoteAddress = getRemoteAddress(this.req);\n\n    const user = await sails.helpers.users\n      .getOrCreateOneWithOidc(inputs.code, inputs.nonce)\n      .intercept('invalidOidcConfiguration', () => Errors.INVALID_OIDC_CONFIGURATION)\n      .intercept('invalidCodeOrNonce', () => {\n        sails.log.warn(`Invalid code or nonce! (IP: ${remoteAddress})`);\n        return Errors.INVALID_CODE_OR_NONCE;\n      })\n      .intercept('invalidUserinfoConfiguration', () => Errors.INVALID_USERINFO_CONFIGURATION)\n      .intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE)\n      .intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE)\n      .intercept('activeLimitReached', () => Errors.ACTIVE_USERS_LIMIT_REACHED)\n      .intercept('missingValues', () => Errors.MISSING_VALUES);\n\n    return sails.helpers.accessTokens.handleSteps\n      .with({\n        user,\n        remoteAddress,\n        request: this.req,\n        response: this.res,\n        withHttpOnlyToken: inputs.withHttpOnlyToken,\n      })\n      .intercept('adminLoginRequiredToInitializeInstance', (error) => ({\n        adminLoginRequiredToInitializeInstance: error.raw,\n      }))\n      .intercept('termsAcceptanceRequired', (error) => ({\n        termsAcceptanceRequired: error.raw,\n      }));\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/access-tokens/revoke-pending-token.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /access-tokens/revoke-pending-token:\n *   post:\n *     summary: Revoke pending token\n *     description: Revokes a pending authentication token and cancels the authentication flow.\n *     tags:\n *       - Access Tokens\n *     operationId: revokePendingToken\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - pendingToken\n *             properties:\n *               pendingToken:\n *                 type: string\n *                 maxLength: 1024\n *                 description: Pending token to revoke\n *                 example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ4...\n *     responses:\n *       200:\n *         description: Pending token revoked successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               properties:\n *                 item:\n *                   type: object\n *                   nullable: true\n *                   description: No data returned\n *                   example: null\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *     security: []\n */\n\nconst Errors = {\n  PENDING_TOKEN_NOT_FOUND: {\n    pendingTokenNotFound: 'Pending token not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    pendingToken: {\n      type: 'string',\n      maxLength: 1024,\n      required: true,\n    },\n  },\n\n  exits: {\n    pendingTokenNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { httpOnlyToken } = this.req.cookies;\n    let session = await Session.qm.getOneUndeletedByPendingToken(inputs.pendingToken);\n\n    if (!session) {\n      throw Errors.PENDING_TOKEN_NOT_FOUND;\n    }\n\n    if (session.httpOnlyToken && httpOnlyToken !== session.httpOnlyToken) {\n      throw Errors.PENDING_TOKEN_NOT_FOUND; // Forbidden\n    }\n\n    session = await Session.qm.deleteOneById(session.id);\n\n    if (session.httpOnlyToken && !this.req.isSocket) {\n      sails.helpers.utils.clearHttpOnlyTokenCookie(this.res);\n    }\n\n    return {\n      item: null,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/actions/index-in-board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /boards/{boardId}/actions:\n *   get:\n *     summary: Get board actions\n *     description: Retrieves a list of actions (activity history) for a specific board, with pagination support.\n *     tags:\n *       - Actions\n *     operationId: getBoardActions\n *     parameters:\n *       - name: boardId\n *         in: path\n *         required: true\n *         description: ID of the board to get actions for\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *       - name: beforeId\n *         in: query\n *         required: false\n *         description: ID to get actions before (for pagination)\n *         schema:\n *           type: string\n *           example: \"1357158568008091265\"\n *     responses:\n *       200:\n *         description: Board actions retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - items\n *                 - included\n *               properties:\n *                 items:\n *                   type: array\n *                   items:\n *                     $ref: '#/components/schemas/Action'\n *                 included:\n *                   type: object\n *                   required:\n *                     - users\n *                   properties:\n *                     users:\n *                       type: array\n *                       description: Related users\n *                       items:\n *                         $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    boardId: {\n      ...idInput,\n      required: true,\n    },\n    beforeId: idInput,\n  },\n\n  exits: {\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { board, project } = await sails.helpers.boards\n      .getPathToProjectById(inputs.boardId)\n      .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n        const isProjectManager = await sails.helpers.users.isProjectManager(\n          currentUser.id,\n          project.id,\n        );\n\n        if (!isProjectManager) {\n          throw Errors.BOARD_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    const actions = await Action.qm.getByBoardId(board.id, {\n      beforeId: inputs.beforeId,\n    });\n\n    const userIds = sails.helpers.utils.mapRecords(actions, 'userId', true, true);\n    const users = await User.qm.getByIds(userIds);\n\n    return {\n      items: actions,\n      included: {\n        users: sails.helpers.users.presentMany(users, currentUser),\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/actions/index-in-card.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/actions:\n *   get:\n *     summary: Get card actions\n *     description: Retrieves a list of actions (activity history) for a specific card, with pagination support.\n *     tags:\n *       - Actions\n *     operationId: getCardActions\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to get actions for\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *       - name: beforeId\n *         in: query\n *         required: false\n *         description: ID to get actions before (for pagination)\n *         schema:\n *           type: string\n *           example: \"1357158568008091265\"\n *     responses:\n *       200:\n *         description: Card actions retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - items\n *                 - included\n *               properties:\n *                 items:\n *                   type: array\n *                   items:\n *                     $ref: '#/components/schemas/Action'\n *                 included:\n *                   type: object\n *                   required:\n *                     - users\n *                   properties:\n *                     users:\n *                       type: array\n *                       description: Related users\n *                       items:\n *                         $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    beforeId: idInput,\n  },\n\n  exits: {\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      card.boardId,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n        const isProjectManager = await sails.helpers.users.isProjectManager(\n          currentUser.id,\n          project.id,\n        );\n\n        if (!isProjectManager) {\n          throw Errors.CARD_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    const actions = await Action.qm.getByCardId(card.id, {\n      beforeId: inputs.beforeId,\n    });\n\n    const userIds = sails.helpers.utils.mapRecords(actions, 'userId', true, true);\n    const users = await User.qm.getByIds(userIds);\n\n    return {\n      items: actions,\n      included: {\n        users: sails.helpers.users.presentMany(users, currentUser),\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/attachments/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/attachments:\n *   post:\n *     summary: Create attachment\n *     description: Creates an attachment on a card. Requires board editor permissions.\n *     tags:\n *       - Attachments\n *     operationId: createAttachment\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to create the attachment on\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         multipart/form-data:\n *           schema:\n *             type: object\n *             required:\n *               - type\n *               - name\n *             properties:\n *               type:\n *                 type: string\n *                 enum: [file, link]\n *                 description: Type of the attachment\n *                 example: link\n *               file:\n *                 type: string\n *                 format: binary\n *                 description: File to upload\n *               url:\n *                 type: string\n *                 format: url\n *                 maxLength: 2048\n *                 description: URL for the link attachment\n *                 example: https://google.com/search?q=planka\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the attachment\n *                 example: Important Attachment\n *               requestId:\n *                 type: string\n *                 maxLength: 128\n *                 description: Request ID for tracking\n *                 example: req_123456\n *     responses:\n *       200:\n *         description: Attachment created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Attachment'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         description: Upload or validation error\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_UNPROCESSABLE_ENTITY\n *                 message:\n *                   type: string\n *                   enum:\n *                     - No file was uploaded\n *                     - Url must be present\n *                   description: Specific error message\n *                   example: No file was uploaded\n */\n\nconst { isUrl } = require('../../../utils/validators');\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n  NO_FILE_WAS_UPLOADED: {\n    noFileWasUploaded: 'No file was uploaded',\n  },\n  URL_MUST_BE_PRESENT: {\n    urlMustBePresent: 'Url must be present',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    type: {\n      type: 'string',\n      isIn: Object.values(Attachment.Types),\n      required: true,\n    },\n    url: {\n      type: 'string',\n      maxLength: 2048,\n      custom: isUrl,\n    },\n    name: {\n      type: 'string',\n      maxLength: 128,\n      required: true,\n    },\n    requestId: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n    noFileWasUploaded: {\n      responseType: 'unprocessableEntity',\n    },\n    uploadError: {\n      responseType: 'unprocessableEntity',\n    },\n    urlMustBePresent: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs, exits) {\n    const { currentUser } = this.req;\n\n    const { card, list, board, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    let data;\n    if (inputs.type === Attachment.Types.FILE) {\n      let files;\n      try {\n        files = await sails.helpers.utils.receiveFile(this.req.file('file'));\n      } catch (error) {\n        return exits.uploadError(error.message); // TODO: add error\n      }\n\n      if (files.length === 0) {\n        throw Errors.NO_FILE_WAS_UPLOADED;\n      }\n\n      const file = _.last(files);\n      data = await sails.helpers.attachments.processUploadedFile(file);\n    } else if (inputs.type === Attachment.Types.LINK) {\n      if (!inputs.url) {\n        throw Errors.URL_MUST_BE_PRESENT;\n      }\n\n      data = await sails.helpers.attachments.processLink(inputs.url);\n    }\n\n    const values = {\n      ..._.pick(inputs, ['type', 'name']),\n      data,\n    };\n\n    const attachment = await sails.helpers.attachments.createOne.with({\n      project,\n      board,\n      list,\n      values: {\n        ...values,\n        card,\n        creatorUser: currentUser,\n      },\n      requestId: inputs.requestId,\n      request: this.req,\n    });\n\n    return exits.success({\n      item: sails.helpers.attachments.presentOne(attachment),\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/attachments/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /attachments/{id}:\n *   delete:\n *     summary: Delete attachment\n *     description: Deletes an attachment. Requires board editor permissions.\n *     tags:\n *       - Attachments\n *     operationId: deleteAttachment\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the attachment to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Attachment deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Attachment'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  ATTACHMENT_NOT_FOUND: {\n    attachmentNotFound: 'Attachment not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    attachmentNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.attachments\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND);\n\n    let { attachment } = pathToProject;\n    const { card, list, board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.ATTACHMENT_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    attachment = await sails.helpers.attachments.deleteOne.with({\n      project,\n      board,\n      list,\n      card,\n      record: attachment,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!attachment) {\n      throw Errors.ATTACHMENT_NOT_FOUND;\n    }\n\n    return {\n      item: sails.helpers.attachments.presentOne(attachment),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/attachments/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /attachments/{id}:\n *   patch:\n *     summary: Update attachment\n *     description: Updates an attachment. Requires board editor permissions.\n *     tags:\n *       - Attachments\n *     operationId: updateAttachment\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the attachment to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the attachment\n *                 example: Important Attachment\n *     responses:\n *       200:\n *         description: Attachment updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Attachment'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  ATTACHMENT_NOT_FOUND: {\n    attachmentNotFound: 'Attachment not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    attachmentNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.attachments\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND);\n\n    let { attachment } = pathToProject;\n    const { card, list, board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.ATTACHMENT_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const values = _.pick(inputs, ['name']);\n\n    attachment = await sails.helpers.attachments.updateOne.with({\n      values,\n      project,\n      board,\n      list,\n      card,\n      record: attachment,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!attachment) {\n      throw Errors.ATTACHMENT_NOT_FOUND;\n    }\n\n    return {\n      item: sails.helpers.attachments.presentOne(attachment),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/background-images/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /projects/{projectId}/background-images:\n *   post:\n *     summary: Upload background image\n *     description: Uploads a background image for a project. Requires project manager permissions.\n *     tags:\n *       - Background Images\n *     operationId: createBackgroundImage\n *     parameters:\n *       - name: projectId\n *         in: path\n *         required: true\n *         description: ID of the project to upload background image for\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         multipart/form-data:\n *           schema:\n *             type: object\n *             required:\n *               - file\n *             properties:\n *               file:\n *                 type: string\n *                 format: binary\n *                 description: Background image file (must be an image format)\n *               requestId:\n *                 type: string\n *                 maxLength: 128\n *                 description: Request ID for tracking\n *                 example: req_123456\n *     responses:\n *       200:\n *         description: Background image uploaded successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/BackgroundImage'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         description: File upload error\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_UNPROCESSABLE_ENTITY\n *                 message:\n *                   type: string\n *                   enum:\n *                     - No file was uploaded\n *                     - File is not image\n *                   description: Specific error message\n *                   example: No file was uploaded\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  PROJECT_NOT_FOUND: {\n    projectNotFound: 'Project not found',\n  },\n  NO_FILE_WAS_UPLOADED: {\n    noFileWasUploaded: 'No file was uploaded',\n  },\n  FILE_IS_NOT_IMAGE: {\n    fileIsNotImage: 'File is not image',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    projectId: {\n      ...idInput,\n      required: true,\n    },\n    requestId: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n  },\n\n  exits: {\n    projectNotFound: {\n      responseType: 'notFound',\n    },\n    noFileWasUploaded: {\n      responseType: 'unprocessableEntity',\n    },\n    fileIsNotImage: {\n      responseType: 'unprocessableEntity',\n    },\n    uploadError: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs, exits) {\n    const { currentUser } = this.req;\n\n    const project = await Project.qm.getOneById(inputs.projectId);\n\n    if (!project) {\n      throw Errors.PROJECT_NOT_FOUND;\n    }\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.PROJECT_NOT_FOUND; // Forbidden\n    }\n\n    let files;\n    try {\n      files = await sails.helpers.utils.receiveFile(this.req.file('file'));\n    } catch (error) {\n      return exits.uploadError(error.message); // TODO: add error\n    }\n\n    if (files.length === 0) {\n      throw Errors.NO_FILE_WAS_UPLOADED;\n    }\n\n    const file = _.last(files);\n\n    const values = await sails.helpers.backgroundImages\n      .processUploadedFile(file)\n      .intercept('fileIsNotImage', () => Errors.FILE_IS_NOT_IMAGE);\n\n    const backgroundImage = await sails.helpers.backgroundImages.createOne.with({\n      values: {\n        ...values,\n        project,\n      },\n      actorUser: currentUser,\n      requestId: inputs.requestId,\n      request: this.req,\n    });\n\n    return exits.success({\n      item: sails.helpers.backgroundImages.presentOne(backgroundImage),\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/background-images/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /background-images/{id}:\n *   delete:\n *     summary: Delete background image\n *     description: Deletes a background image. Requires project manager permissions.\n *     tags:\n *       - Background Images\n *     operationId: deleteBackgroundImage\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the background image to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Background image deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/BackgroundImage'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  BACKGROUND_IMAGE_NOT_FOUND: {\n    backgroundImageNotFound: 'Background image not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    backgroundImageNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.backgroundImages\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.BACKGROUND_IMAGE_NOT_FOUND);\n\n    let { backgroundImage } = pathToProject;\n    const { project } = pathToProject;\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.BACKGROUND_IMAGE_NOT_FOUND; // Forbidden\n    }\n\n    backgroundImage = await sails.helpers.backgroundImages.deleteOne.with({\n      project,\n      record: backgroundImage,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!backgroundImage) {\n      throw Errors.BACKGROUND_IMAGE_NOT_FOUND;\n    }\n\n    return {\n      item: sails.helpers.backgroundImages.presentOne(backgroundImage),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/base-custom-field-groups/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /projects/{projectId}/base-custom-field-groups:\n *   post:\n *     summary: Create base custom field group\n *     description: Creates a base custom field group within a project. Requires project manager permissions.\n *     tags:\n *       - Base Custom Field Groups\n *     operationId: createBaseCustomFieldGroup\n *     parameters:\n *       - name: projectId\n *         in: path\n *         required: true\n *         description: ID of the project to create the base custom field group in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - name\n *             properties:\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the base custom field group\n *                 example: Base Properties\n *     responses:\n *       200:\n *         description: Base custom field group created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/BaseCustomFieldGroup'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  PROJECT_NOT_FOUND: {\n    projectNotFound: 'Project not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    projectId: {\n      ...idInput,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      maxLength: 128,\n      required: true,\n    },\n  },\n\n  exits: {\n    projectNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const project = await Project.qm.getOneById(inputs.projectId);\n\n    if (!project) {\n      throw Errors.PROJECT_NOT_FOUND;\n    }\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.PROJECT_NOT_FOUND; // Forbidden\n    }\n\n    const values = _.pick(inputs, ['name']);\n\n    const baseCustomFieldGroup = await sails.helpers.baseCustomFieldGroups.createOne.with({\n      values: {\n        ...values,\n        project,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: baseCustomFieldGroup,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/base-custom-field-groups/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /base-custom-field-groups/{id}:\n *   delete:\n *     summary: Delete base custom field group\n *     description: Deletes a base custom field group. Requires project manager permissions.\n *     tags:\n *       - Base Custom Field Groups\n *     operationId: deleteBaseCustomFieldGroup\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the base custom field group to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Base custom field group deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/BaseCustomFieldGroup'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  BASE_CUSTOM_FIELD_GROUP_NOT_FOUND: {\n    baseCustomFieldGroupNotFound: 'Base custom field group not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    baseCustomFieldGroupNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.baseCustomFieldGroups\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.BASE_CUSTOM_FIELD_GROUP_NOT_FOUND);\n\n    let { baseCustomFieldGroup } = pathToProject;\n    const { project } = pathToProject;\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.BASE_CUSTOM_FIELD_GROUP_NOT_FOUND; // Forbidden\n    }\n\n    baseCustomFieldGroup = await sails.helpers.baseCustomFieldGroups.deleteOne.with({\n      project,\n      record: baseCustomFieldGroup,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!baseCustomFieldGroup) {\n      throw Errors.BASE_CUSTOM_FIELD_GROUP_NOT_FOUND;\n    }\n\n    return {\n      item: baseCustomFieldGroup,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/base-custom-field-groups/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /base-custom-field-groups/{id}:\n *   patch:\n *     summary: Update base custom field group\n *     description: Updates a base custom field group. Requires project manager permissions.\n *     tags:\n *       - Base Custom Field Groups\n *     operationId: updateBaseCustomFieldGroup\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the base custom field group to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the base custom field group\n *                 example: Base Properties\n *     responses:\n *       200:\n *         description: Base custom field group updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/BaseCustomFieldGroup'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  BASE_CUSTOM_FIELD_GROUP_NOT_FOUND: {\n    baseCustomFieldGroupNotFound: 'Base custom field group not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n  },\n\n  exits: {\n    baseCustomFieldGroupNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.baseCustomFieldGroups\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.BASE_CUSTOM_FIELD_GROUP_NOT_FOUND);\n\n    let { baseCustomFieldGroup } = pathToProject;\n    const { project } = pathToProject;\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.BASE_CUSTOM_FIELD_GROUP_NOT_FOUND; // Forbidden\n    }\n\n    const values = _.pick(inputs, ['name']);\n\n    baseCustomFieldGroup = await sails.helpers.baseCustomFieldGroups.updateOne.with({\n      values,\n      project,\n      record: baseCustomFieldGroup,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!baseCustomFieldGroup) {\n      throw Errors.BASE_CUSTOM_FIELD_GROUP_NOT_FOUND;\n    }\n\n    return {\n      item: baseCustomFieldGroup,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/board-memberships/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /boards/{boardId}/board-memberships:\n *   post:\n *     summary: Create board membership\n *     description: Creates a board membership within a board. Requires project manager permissions.\n *     tags:\n *       - Board Memberships\n *     operationId: createBoardMembership\n *     parameters:\n *       - name: boardId\n *         in: path\n *         required: true\n *         description: ID of the board to create the board membership in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - userId\n *               - role\n *             properties:\n *               userId:\n *                 type: string\n *                 description: ID of the user who is a member of the board\n *                 example: \"1357158568008091265\"\n *               role:\n *                 type: string\n *                 enum: [editor, viewer]\n *                 description: Role of the user in the board\n *                 example: editor\n *               canComment:\n *                 type: boolean\n *                 nullable: true\n *                 description: Whether the user can comment on cards (applies only to viewers)\n *                 example: true\n *     responses:\n *       200:\n *         description: Board membership created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/BoardMembership'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n  USER_ALREADY_BOARD_MEMBER: {\n    userAlreadyBoardMember: 'User already board member',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    boardId: {\n      ...idInput,\n      required: true,\n    },\n    userId: {\n      ...idInput,\n      required: true,\n    },\n    role: {\n      type: 'string',\n      isIn: Object.values(BoardMembership.Roles),\n      required: true,\n    },\n    canComment: {\n      type: 'boolean',\n      allowNull: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n    userNotFound: {\n      responseType: 'notFound',\n    },\n    userAlreadyBoardMember: {\n      responseType: 'conflict',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { board, project } = await sails.helpers.boards\n      .getPathToProjectById(inputs.boardId)\n      .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.BOARD_NOT_FOUND; // Forbidden\n    }\n\n    if (!sails.helpers.users.isAdminOrProjectOwner(currentUser)) {\n      if (inputs.userId !== currentUser.id) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    }\n\n    const user = await User.qm.getOneById(inputs.userId, {\n      withDeactivated: false,\n    });\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    const values = _.pick(inputs, ['role', 'canComment']);\n\n    const boardMembership = await sails.helpers.boardMemberships.createOne\n      .with({\n        project,\n        values: {\n          ...values,\n          board,\n          user,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('userAlreadyBoardMember', () => Errors.USER_ALREADY_BOARD_MEMBER);\n\n    return {\n      item: boardMembership,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/board-memberships/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /board-memberships/{id}:\n *   delete:\n *     summary: Delete board membership\n *     description: Deletes a board membership. Users can remove their own membership, project managers can remove any membership.\n *     tags:\n *       - Board Memberships\n *     operationId: deleteBoardMembership\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the board membership to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Board membership deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/BoardMembership'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  BOARD_MEMBERSHIP_NOT_FOUND: {\n    boardMembershipNotFound: 'Board membership not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    boardMembershipNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.boardMemberships\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.BOARD_MEMBERSHIP_NOT_FOUND);\n\n    let { boardMembership } = pathToProject;\n    const { board, project } = pathToProject;\n\n    if (boardMembership.userId !== currentUser.id) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        throw Errors.BOARD_MEMBERSHIP_NOT_FOUND; // Forbidden\n      }\n    }\n\n    const user = await User.qm.getOneById(boardMembership.userId);\n\n    boardMembership = await sails.helpers.boardMemberships.deleteOne.with({\n      user,\n      project,\n      board,\n      record: boardMembership,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!boardMembership) {\n      throw Errors.BOARD_MEMBERSHIP_NOT_FOUND;\n    }\n\n    return {\n      item: boardMembership,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/board-memberships/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /board-memberships/{id}:\n *   patch:\n *     summary: Update board membership\n *     description: Updates a board membership. Requires project manager permissions.\n *     tags:\n *       - Board Memberships\n *     operationId: updateBoardMembership\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the board membership to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               role:\n *                 type: string\n *                 enum: [editor, viewer]\n *                 description: Role of the user in the board\n *                 example: editor\n *               canComment:\n *                 type: boolean\n *                 nullable: true\n *                 description: Whether the user can comment on cards (applies only to viewers)\n *                 example: true\n *     responses:\n *       200:\n *         description: Board membership updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/BoardMembership'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  BOARD_MEMBERSHIP_NOT_FOUND: {\n    boardMembershipNotFound: 'Board membership not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    role: {\n      type: 'string',\n      isIn: Object.values(BoardMembership.Roles),\n    },\n    canComment: {\n      type: 'boolean',\n      allowNull: true,\n    },\n  },\n\n  exits: {\n    boardMembershipNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.boardMemberships\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.BOARD_MEMBERSHIP_NOT_FOUND);\n\n    let { boardMembership } = pathToProject;\n    const { board, project } = pathToProject;\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.BOARD_MEMBERSHIP_NOT_FOUND; // Forbidden\n    }\n\n    const values = _.pick(inputs, ['role', 'canComment']);\n\n    boardMembership = await sails.helpers.boardMemberships.updateOne.with({\n      values,\n      project,\n      board,\n      record: boardMembership,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!boardMembership) {\n      throw Errors.BOARD_MEMBERSHIP_NOT_FOUND;\n    }\n\n    return {\n      item: boardMembership,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/boards/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /projects/{projectId}/boards:\n *   post:\n *     summary: Create board\n *     description: Creates a board within a project. Supports importing from Trello. Requires project manager permissions.\n *     tags:\n *       - Boards\n *     operationId: createBoard\n *     parameters:\n *       - name: projectId\n *         in: path\n *         required: true\n *         description: ID of the project to create the board in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         multipart/form-data:\n *           schema:\n *             type: object\n *             required:\n *               - position\n *               - name\n *             properties:\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the board within the project\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the board\n *                 example: Development Board\n *               importType:\n *                 type: string\n *                 enum: [trello]\n *                 description: Type of import\n *                 example: trello\n *               importFile:\n *                 type: string\n *                 format: binary\n *                 description: Import file\n *               requestId:\n *                 type: string\n *                 maxLength: 128\n *                 description: Request ID for tracking\n *                 example: req_123456\n *     responses:\n *       200:\n *         description: Board created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Board'\n *                 included:\n *                   type: object\n *                   required:\n *                     - boardMemberships\n *                   properties:\n *                     boardMemberships:\n *                       type: array\n *                       items:\n *                         $ref: '#/components/schemas/BoardMembership'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         description: Import file upload error\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - code\n *                 - message\n *               properties:\n *                 code:\n *                   type: string\n *                   description: Error code\n *                   example: E_UNPROCESSABLE_ENTITY\n *                 message:\n *                   type: string\n *                   enum:\n *                     - No import file was uploaded\n *                     - Invalid import file\n *                   description: Specific error message\n *                   example: No import file was uploaded\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  PROJECT_NOT_FOUND: {\n    projectNotFound: 'Project not found',\n  },\n  NO_IMPORT_FILE_WAS_UPLOADED: {\n    noImportFileWasUploaded: 'No import file was uploaded',\n  },\n  INVALID_IMPORT_FILE: {\n    invalidImportFile: 'Invalid import file',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    projectId: {\n      ...idInput,\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      maxLength: 128,\n      required: true,\n    },\n    importType: {\n      type: 'string',\n      isIn: Object.values(Board.ImportTypes),\n    },\n    requestId: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n  },\n\n  exits: {\n    projectNotFound: {\n      responseType: 'notFound',\n    },\n    noImportFileWasUploaded: {\n      responseType: 'unprocessableEntity',\n    },\n    invalidImportFile: {\n      responseType: 'unprocessableEntity',\n    },\n    uploadError: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs, exits) {\n    const { currentUser } = this.req;\n\n    const project = await Project.qm.getOneById(inputs.projectId);\n\n    if (!project) {\n      throw Errors.PROJECT_NOT_FOUND;\n    }\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.PROJECT_NOT_FOUND; // Forbidden\n    }\n\n    let boardImport;\n    if (inputs.importType) {\n      let files;\n      try {\n        files = await sails.helpers.utils.receiveFile(this.req.file('importFile'), false);\n      } catch (error) {\n        return exits.uploadError(error.message); // TODO: add error\n      }\n\n      if (files.length === 0) {\n        throw Errors.NO_IMPORT_FILE_WAS_UPLOADED;\n      }\n\n      const file = _.last(files);\n\n      if (inputs.importType === Board.ImportTypes.TRELLO) {\n        const trelloBoard = await sails.helpers.boards\n          .processUploadedTrelloImportFile(file)\n          .intercept('invalidFile', () => Errors.INVALID_IMPORT_FILE);\n\n        boardImport = {\n          type: inputs.importType,\n          board: trelloBoard,\n        };\n      }\n    }\n\n    const values = _.pick(inputs, ['position', 'name']);\n\n    const { board, boardMembership } = await sails.helpers.boards.createOne.with({\n      values: {\n        ...values,\n        project,\n      },\n      import: boardImport,\n      actorUser: currentUser,\n      requestId: inputs.requestId,\n      request: this.req,\n    });\n\n    return exits.success({\n      item: board,\n      included: {\n        boardMemberships: [boardMembership],\n      },\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/boards/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /boards/{id}:\n *   delete:\n *     summary: Delete board\n *     description: Deletes a board and all its contents (lists, cards, etc.). Requires project manager permissions.\n *     tags:\n *       - Boards\n *     operationId: deleteBoard\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the board to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Board deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Board'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.boards\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);\n\n    let { board } = pathToProject;\n    const { project } = pathToProject;\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.BOARD_NOT_FOUND; // Forbidden\n    }\n\n    board = await sails.helpers.boards.deleteOne.with({\n      project,\n      record: board,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!board) {\n      throw Errors.BOARD_NOT_FOUND;\n    }\n\n    return {\n      item: board,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/boards/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /boards/{id}:\n *   get:\n *     summary: Get board details\n *     description: Retrieves comprehensive board information, including lists, cards, and other related data.\n *     tags:\n *       - Boards\n *     operationId: getBoard\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the board to retrieve\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *       - name: subscribe\n *         in: query\n *         required: false\n *         description: Whether to subscribe to real-time updates for this board (only for socket connections)\n *         schema:\n *           type: boolean\n *           example: true\n *     responses:\n *       200:\n *         description: Board details retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   allOf:\n *                     - $ref: '#/components/schemas/Board'\n *                     - type: object\n *                       properties:\n *                         isSubscribed:\n *                           type: boolean\n *                           description: Whether the current user is subscribed to the board\n *                           example: true\n *                 included:\n *                   type: object\n *                   required:\n *                     - users\n *                     - projects\n *                     - boardMemberships\n *                     - labels\n *                     - lists\n *                     - cards\n *                     - cardMemberships\n *                     - cardLabels\n *                     - taskLists\n *                     - tasks\n *                     - attachments\n *                     - customFieldGroups\n *                     - customFields\n *                     - customFieldValues\n *                   properties:\n *                     users:\n *                       type: array\n *                       description: Related users\n *                       items:\n *                         $ref: '#/components/schemas/User'\n *                     projects:\n *                       type: array\n *                       description: Parent project\n *                       items:\n *                         $ref: '#/components/schemas/Project'\n *                     boardMemberships:\n *                       type: array\n *                       description: Related board memberships\n *                       items:\n *                         $ref: '#/components/schemas/BoardMembership'\n *                     labels:\n *                       type: array\n *                       description: Related labels\n *                       items:\n *                         $ref: '#/components/schemas/Label'\n *                     lists:\n *                       type: array\n *                       description: Related lists\n *                       items:\n *                         $ref: '#/components/schemas/List'\n *                     cards:\n *                       type: array\n *                       description: Related cards\n *                       items:\n *                         allOf:\n *                           - $ref: '#/components/schemas/Card'\n *                           - type: object\n *                             properties:\n *                               isSubscribed:\n *                                 type: boolean\n *                                 description: Whether the current user is subscribed to the card\n *                                 example: true\n *                     cardMemberships:\n *                       type: array\n *                       description: Related card-membership associations\n *                       items:\n *                         $ref: '#/components/schemas/CardMembership'\n *                     cardLabels:\n *                       type: array\n *                       description: Related card-label associations\n *                       items:\n *                         $ref: '#/components/schemas/CardLabel'\n *                     taskLists:\n *                       type: array\n *                       description: Related task lists\n *                       items:\n *                         $ref: '#/components/schemas/TaskList'\n *                     tasks:\n *                       type: array\n *                       description: Related tasks\n *                       items:\n *                         $ref: '#/components/schemas/Task'\n *                     attachments:\n *                       type: array\n *                       description: Related attachments\n *                       items:\n *                         $ref: '#/components/schemas/Attachment'\n *                     customFieldGroups:\n *                       type: array\n *                       description: Related custom field groups\n *                       items:\n *                         $ref: '#/components/schemas/CustomFieldGroup'\n *                     customFields:\n *                       type: array\n *                       description: Related custom fields\n *                       items:\n *                         $ref: '#/components/schemas/CustomField'\n *                     customFieldValues:\n *                       type: array\n *                       description: Related custom field values\n *                       items:\n *                         $ref: '#/components/schemas/CustomFieldValue'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    subscribe: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { board, project } = await sails.helpers.boards\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);\n\n    if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n          board.id,\n          currentUser.id,\n        );\n\n        if (!boardMembership) {\n          throw Errors.BOARD_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    board.isSubscribed = await sails.helpers.users.isBoardSubscriber(currentUser.id, board.id);\n\n    const boardMemberships = await BoardMembership.qm.getByBoardId(board.id);\n    const labels = await Label.qm.getByBoardId(board.id);\n    const lists = await List.qm.getByBoardId(board.id);\n\n    const finiteLists = lists.filter((list) => sails.helpers.lists.isFinite(list));\n    const finiteListIds = sails.helpers.utils.mapRecords(finiteLists);\n\n    const cards = await Card.qm.getByListIds(finiteListIds);\n    const cardIds = sails.helpers.utils.mapRecords(cards);\n\n    const userIds = _.union(\n      sails.helpers.utils.mapRecords(boardMemberships, 'userId'),\n      sails.helpers.utils.mapRecords(cards, 'creatorUserId', true, true),\n    );\n\n    const users = await User.qm.getByIds(userIds);\n    const cardMemberships = await CardMembership.qm.getByCardIds(cardIds);\n    const cardLabels = await CardLabel.qm.getByCardIds(cardIds);\n\n    const taskLists = await TaskList.qm.getByCardIds(cardIds);\n    const taskListIds = sails.helpers.utils.mapRecords(taskLists);\n\n    const tasks = await Task.qm.getByTaskListIds(taskListIds);\n    const attachments = await Attachment.qm.getByCardIds(cardIds);\n\n    const boardCustomFieldGroups = await CustomFieldGroup.qm.getByBoardId(board.id);\n    const cardCustomFieldGroups = await CustomFieldGroup.qm.getByCardIds(cardIds);\n\n    const customFieldGroups = [...boardCustomFieldGroups, ...cardCustomFieldGroups];\n    const customFieldGroupIds = sails.helpers.utils.mapRecords(customFieldGroups);\n\n    const customFields = await CustomField.qm.getByCustomFieldGroupIds(customFieldGroupIds);\n    const customFieldValues = await CustomFieldValue.qm.getByCardIds(cardIds);\n\n    const cardSubscriptions = await CardSubscription.qm.getByCardIdsAndUserId(\n      cardIds,\n      currentUser.id,\n    );\n\n    const isSubscribedByCardId = cardSubscriptions.reduce(\n      (result, cardSubscription) => ({\n        ...result,\n        [cardSubscription.cardId]: true,\n      }),\n      {},\n    );\n\n    cards.forEach((card) => {\n      // eslint-disable-next-line no-param-reassign\n      card.isSubscribed = isSubscribedByCardId[card.id] || false;\n    });\n\n    if (inputs.subscribe && this.req.isSocket) {\n      sails.sockets.join(this.req, `board:${board.id}`);\n    }\n\n    return {\n      item: board,\n      included: {\n        boardMemberships,\n        labels,\n        lists,\n        cards,\n        cardMemberships,\n        cardLabels,\n        taskLists,\n        tasks,\n        customFieldGroups,\n        customFields,\n        customFieldValues,\n        users: sails.helpers.users.presentMany(users, currentUser),\n        projects: [project],\n        attachments: sails.helpers.attachments.presentMany(attachments),\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/boards/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /boards/{id}:\n *   patch:\n *     summary: Update board\n *     description: Updates a board. Project managers can update all fields, board members can only subscribe/unsubscribe.\n *     tags:\n *       - Boards\n *     operationId: updateBoard\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the board to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the board within the project\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the board\n *                 example: Development Board\n *               defaultView:\n *                 type: string\n *                 enum: [kanban, grid, list]\n *                 description: Default view for the board\n *                 example: kanban\n *               defaultCardType:\n *                 type: string\n *                 enum: [project, story]\n *                 description: Default card type for new cards\n *                 example: project\n *               limitCardTypesToDefaultOne:\n *                 type: boolean\n *                 description: Whether to limit card types to default one\n *                 example: false\n *               alwaysDisplayCardCreator:\n *                 type: boolean\n *                 description: Whether to always display card creators\n *                 example: false\n *               displayCardAges:\n *                 type: boolean\n *                 description: Whether to display card ages\n *                 example: false\n *               expandTaskListsByDefault:\n *                 type: boolean\n *                 description: Whether to expand task lists by default\n *                 example: false\n *               isSubscribed:\n *                 type: boolean\n *                 description: Whether the current user is subscribed to the board\n *                 example: true\n *     responses:\n *       200:\n *         description: Board updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Board'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n    defaultView: {\n      type: 'string',\n      isIn: Object.values(Board.Views),\n    },\n    defaultCardType: {\n      type: 'string',\n      isIn: Object.values(Card.Types),\n    },\n    limitCardTypesToDefaultOne: {\n      type: 'boolean',\n    },\n    alwaysDisplayCardCreator: {\n      type: 'boolean',\n    },\n    displayCardAges: {\n      type: 'boolean',\n    },\n    expandTaskListsByDefault: {\n      type: 'boolean',\n    },\n    isSubscribed: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.boards\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);\n\n    let { board } = pathToProject;\n    const { project } = pathToProject;\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n    const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, board.id);\n\n    if (!isProjectManager && !isBoardMember) {\n      throw Errors.BOARD_NOT_FOUND; // Forbidden\n    }\n\n    const availableInputKeys = ['id'];\n    if (isProjectManager) {\n      availableInputKeys.push(\n        'position',\n        'name',\n        'defaultView',\n        'defaultCardType',\n        'limitCardTypesToDefaultOne',\n        'alwaysDisplayCardCreator',\n        'displayCardAges',\n        'expandTaskListsByDefault',\n      );\n    }\n    if (isBoardMember) {\n      availableInputKeys.push('isSubscribed');\n    }\n\n    if (_.difference(Object.keys(inputs), availableInputKeys).length > 0) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const values = _.pick(inputs, [\n      'position',\n      'name',\n      'defaultView',\n      'defaultCardType',\n      'limitCardTypesToDefaultOne',\n      'alwaysDisplayCardCreator',\n      'displayCardAges',\n      'expandTaskListsByDefault',\n      'isSubscribed',\n    ]);\n\n    board = await sails.helpers.boards.updateOne.with({\n      values,\n      project,\n      record: board,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!board) {\n      throw Errors.BOARD_NOT_FOUND;\n    }\n\n    return {\n      item: board,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/bootstrap/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /bootstrap:\n *   get:\n *     summary: Get application bootstrap\n *     description: Retrieves the application bootstrap.\n *     tags:\n *       - Bootstrap\n *     operationId: getBootstrap\n *     responses:\n *       200:\n *         description: Bootstrap retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - oidc\n *                 - version\n *               properties:\n *                 oidc:\n *                   type: object\n *                   required:\n *                     - authorizationUrl\n *                     - endSessionUrl\n *                     - isEnforced\n *                   nullable: true\n *                   description: OpenID Connect configuration (null if not configured)\n *                   properties:\n *                     authorizationUrl:\n *                       type: string\n *                       format: uri\n *                       description: OIDC authorization URL for initiating authentication\n *                       example: https://oidc.example.com/auth\n *                     endSessionUrl:\n *                       type: string\n *                       format: uri\n *                       nullable: true\n *                       description: OIDC end session URL for logout (null if not supported by provider)\n *                       example: https://oidc.example.com/logout\n *                     isEnforced:\n *                       type: boolean\n *                       description: Whether OIDC authentication is enforced (users must use OIDC to login)\n *                       example: false\n *                 activeUsersLimit:\n *                   type: number\n *                   nullable: true\n *                   description: Maximum number of active users allowed (conditionally added for admins if configured)\n *                   example: 100\n *                 customerPanelUrl:\n *                   type: string\n *                   format: uri\n *                   description: URL to the customer management panel (conditionally added for admins if configured)\n *                   example: https://panel.example.com\n *                 termsLanguages:\n *                   type: array\n *                   description: List of available language codes for terms localization\n *                   items:\n *                     type: string\n *                   example: [de-DE, en-US]\n *                 version:\n *                   type: string\n *                   description: Current version of the PLANKA application\n *                   example: 2.0.0\n *     security: []\n */\n\nmodule.exports = {\n  async fn() {\n    const { currentUser } = this.req;\n\n    const internalConfig = await InternalConfig.qm.getOneMain();\n    const oidc = await sails.hooks.oidc.getBootstrap();\n\n    return {\n      item: sails.helpers.bootstrap.presentOne(internalConfig, oidc, currentUser),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/card-labels/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/card-labels:\n *   post:\n *     summary: Add label to card\n *     description: Adds a label to a card. Requires board editor permissions.\n *     tags:\n *       - Card Labels\n *     operationId: createCardLabel\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to add the label to\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - labelId\n *             properties:\n *               labelId:\n *                 type: string\n *                 description: ID of the label to add to the card\n *                 example: \"1357158568008091265\"\n *     responses:\n *       200:\n *         description: Label added to card successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CardLabel'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n  LABEL_NOT_FOUND: {\n    labelNotFound: 'Label not found',\n  },\n  LABEL_ALREADY_IN_CARD: {\n    labelAlreadyInCard: 'Label already in card',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    labelId: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n    labelNotFound: {\n      responseType: 'notFound',\n    },\n    labelAlreadyInCard: {\n      responseType: 'conflict',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, list, board, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const label = await Label.qm.getOneById(inputs.labelId, {\n      boardId: board.id,\n    });\n\n    if (!label) {\n      throw Errors.LABEL_NOT_FOUND;\n    }\n\n    const cardLabel = await sails.helpers.cardLabels.createOne\n      .with({\n        project,\n        board,\n        list,\n        values: {\n          card,\n          label,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('labelAlreadyInCard', () => Errors.LABEL_ALREADY_IN_CARD);\n\n    return {\n      item: cardLabel,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/card-labels/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/card-labels/labelId:{labelId}:\n *   delete:\n *     summary: Remove label from card\n *     description: Removes a label from a card. Requires board editor permissions.\n *     tags:\n *       - Card Labels\n *     operationId: deleteCardLabel\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to remove the label from\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *       - name: labelId\n *         in: path\n *         required: true\n *         description: ID of the label to remove from the card\n *         schema:\n *           type: string\n *           example: \"1357158568008091265\"\n *     responses:\n *       200:\n *         description: Label removed from card successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CardLabel'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n  LABEL_NOT_IN_CARD: {\n    labelNotInCard: 'Label not in card',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    labelId: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n    labelNotInCard: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, list, board, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    let cardLabel = await CardLabel.qm.getOneByCardIdAndLabelId(card.id, inputs.labelId);\n\n    if (!cardLabel) {\n      throw Errors.LABEL_NOT_IN_CARD;\n    }\n\n    cardLabel = await sails.helpers.cardLabels.deleteOne.with({\n      project,\n      board,\n      list,\n      card,\n      record: cardLabel,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!cardLabel) {\n      throw Errors.LABEL_NOT_IN_CARD;\n    }\n\n    return {\n      item: cardLabel,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/card-memberships/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/card-memberships:\n *   post:\n *     summary: Add user to card\n *     description: Adds a user to a card. Requires board editor permissions.\n *     tags:\n *       - Card Memberships\n *     operationId: createCardMembership\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to add the user to\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - userId\n *             properties:\n *               userId:\n *                 type: string\n *                 description: ID of the card to add the user to\n *                 example: \"1357158568008091265\"\n *     responses:\n *       200:\n *         description: User added to card successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CardMembership'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n  USER_ALREADY_CARD_MEMBER: {\n    userAlreadyCardMember: 'User already card member',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    userId: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n    userNotFound: {\n      responseType: 'notFound',\n    },\n    userAlreadyCardMember: {\n      responseType: 'conflict',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, list, board, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const user = await User.qm.getOneById(inputs.userId);\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    const isBoardMember = await sails.helpers.users.isBoardMember(user.id, board.id);\n\n    if (!isBoardMember) {\n      throw Errors.USER_NOT_FOUND; // Forbidden\n    }\n\n    const cardMembership = await sails.helpers.cardMemberships.createOne\n      .with({\n        project,\n        board,\n        list,\n        values: {\n          card,\n          user,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('userAlreadyCardMember', () => Errors.USER_ALREADY_CARD_MEMBER);\n\n    return {\n      item: cardMembership,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/card-memberships/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/card-memberships/userId:{userId}:\n *   delete:\n *     summary: Remove user from card\n *     description: Removes a user from a card. Requires board editor permissions.\n *     tags:\n *       - Card Memberships\n *     operationId: deleteCardMembership\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to remove the user from\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *       - name: userId\n *         in: path\n *         required: true\n *         description: ID of the user to remove from the card\n *         schema:\n *           type: string\n *           example: \"1357158568008091265\"\n *     responses:\n *       200:\n *         description: User removed from card successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CardMembership'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n  USER_NOT_CARD_MEMBER: {\n    userNotCardMember: 'User not card member',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    userId: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n    userNotCardMember: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, list, board, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    let cardMembership = await CardMembership.qm.getOneByCardIdAndUserId(\n      inputs.cardId,\n      inputs.userId,\n    );\n\n    if (!cardMembership) {\n      throw Errors.USER_NOT_CARD_MEMBER;\n    }\n\n    const user = await User.qm.getOneById(cardMembership.userId);\n\n    cardMembership = await sails.helpers.cardMemberships.deleteOne.with({\n      user,\n      project,\n      board,\n      list,\n      card,\n      record: cardMembership,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!cardMembership) {\n      throw Errors.USER_NOT_CARD_MEMBER;\n    }\n\n    return {\n      item: cardMembership,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/cards/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /lists/{listId}/cards:\n *   post:\n *     summary: Create card\n *     description: Creates a card within a list. Requires board editor permissions.\n *     tags:\n *       - Cards\n *     operationId: createCard\n *     parameters:\n *       - name: listId\n *         in: path\n *         required: true\n *         description: ID of the list to create the card in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - type\n *               - name\n *             properties:\n *               type:\n *                 type: string\n *                 enum: [project, story]\n *                 description: Type of the card\n *                 example: project\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 nullable: true\n *                 description: Position of the card within the list\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 1024\n *                 description: Name/title of the card\n *                 example: Implement user authentication\n *               description:\n *                 type: string\n *                 maxLength: 1048576\n *                 nullable: true\n *                 description: Detailed description of the card\n *                 example: Add JWT-based authentication system...\n *               dueDate:\n *                 type: string\n *                 format: date-time\n *                 description: Due date for the card\n *                 example: 2024-01-01T00:00:00.000Z\n *               isDueCompleted:\n *                 type: boolean\n *                 nullable: true\n *                 description: Whether the due date is completed\n *                 example: false\n *               stopwatch:\n *                 type: object\n *                 required:\n *                   - startedAt\n *                   - total\n *                 nullable: true\n *                 description: Stopwatch data for time tracking\n *                 properties:\n *                   startedAt:\n *                     type: string\n *                     format: date-time\n *                     description: When the stopwatch was started\n *                     example: 2024-01-01T00:00:00.000Z\n *                   total:\n *                     type: number\n *                     description: Total time in seconds\n *                     example: 3600\n *     responses:\n *       200:\n *         description: Card created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Card'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { isDueDate, isStopwatch } = require('../../../utils/validators');\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  LIST_NOT_FOUND: {\n    listNotFound: 'List not found',\n  },\n  POSITION_MUST_BE_PRESENT: {\n    positionMustBePresent: 'Position must be present',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    listId: {\n      ...idInput,\n      required: true,\n    },\n    type: {\n      type: 'string',\n      isIn: Object.values(Card.Types),\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n      allowNull: true,\n    },\n    name: {\n      type: 'string',\n      maxLength: 1024,\n      required: true,\n    },\n    description: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 1048576,\n      allowNull: true,\n    },\n    dueDate: {\n      type: 'string',\n      custom: isDueDate,\n    },\n    isDueCompleted: {\n      type: 'boolean',\n      allowNull: true,\n    },\n    stopwatch: {\n      type: 'json',\n      custom: isStopwatch,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    listNotFound: {\n      responseType: 'notFound',\n    },\n    positionMustBePresent: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { list, board, project } = await sails.helpers.lists\n      .getPathToProjectById(inputs.listId)\n      .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.LIST_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const values = _.pick(inputs, [\n      'type',\n      'position',\n      'name',\n      'description',\n      'dueDate',\n      'isDueCompleted',\n      'stopwatch',\n    ]);\n\n    const card = await sails.helpers.cards.createOne\n      .with({\n        project,\n        values: {\n          ...values,\n          board,\n          list,\n          creatorUser: currentUser,\n        },\n        request: this.req,\n      })\n      .intercept('positionMustBeInValues', () => Errors.POSITION_MUST_BE_PRESENT);\n\n    return {\n      item: card,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/cards/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{id}:\n *   delete:\n *     summary: Delete card\n *     description: Deletes a card and all its contents (tasks, attachments, etc.). Requires board editor permissions.\n *     tags:\n *       - Cards\n *     operationId: deleteCard\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the card to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Card deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Card'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.cards\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    let { card } = pathToProject;\n    const { list, board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    card = await sails.helpers.cards.deleteOne.with({\n      project,\n      board,\n      list,\n      record: card,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!card) {\n      throw Errors.CARD_NOT_FOUND;\n    }\n\n    return {\n      item: card,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/cards/duplicate.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{id}/duplicate:\n *   post:\n *     summary: Duplicate card\n *     description: Creates a duplicate of a card with all its contents (tasks, attachments, etc.). Requires board editor permissions.\n *     tags:\n *       - Cards\n *     operationId: duplicateCard\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the card to duplicate\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               boardId:\n *                 type: string\n *                 description: ID of the board to duplicate the card to\n *                 example: \"1357158568008091265\"\n *               listId:\n *                 type: string\n *                 description: ID of the list to duplicate the card to\n *                 example: \"1357158568008091266\"\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 nullable: true\n *                 description: Position for the duplicated card within the list\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 1024\n *                 nullable: true\n *                 description: Name/title for the duplicated card\n *                 example: Implement user authentication (copy)\n *     responses:\n *       200:\n *         description: Card duplicated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Card'\n *                 included:\n *                   type: object\n *                   required:\n *                     - cardMemberships\n *                     - cardLabels\n *                     - taskLists\n *                     - tasks\n *                     - attachments\n *                     - customFieldGroups\n *                     - customFields\n *                     - customFieldValues\n *                   properties:\n *                     cardMemberships:\n *                       type: array\n *                       description: Related card-membership associations\n *                       items:\n *                         $ref: '#/components/schemas/CardMembership'\n *                     cardLabels:\n *                       type: array\n *                       description: Related card-label associations\n *                       items:\n *                         $ref: '#/components/schemas/CardLabel'\n *                     taskLists:\n *                       type: array\n *                       description: Related task lists\n *                       items:\n *                         $ref: '#/components/schemas/TaskList'\n *                     tasks:\n *                       type: array\n *                       description: Related tasks\n *                       items:\n *                         $ref: '#/components/schemas/Task'\n *                     attachments:\n *                       type: array\n *                       description: Related attachments\n *                       items:\n *                         $ref: '#/components/schemas/Attachment'\n *                     customFieldGroups:\n *                       type: array\n *                       description: Related custom field groups\n *                       items:\n *                         $ref: '#/components/schemas/CustomFieldGroup'\n *                     customFields:\n *                       type: array\n *                       description: Related custom fields\n *                       items:\n *                         $ref: '#/components/schemas/CustomField'\n *                     customFieldValues:\n *                       type: array\n *                       description: Related custom field values\n *                       items:\n *                         $ref: '#/components/schemas/CustomFieldValue'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n  LIST_NOT_FOUND: {\n    listNotFound: 'List not found',\n  },\n  LIST_MUST_BE_PRESENT: {\n    listMustBePresent: 'List must be present',\n  },\n  POSITION_MUST_BE_PRESENT: {\n    positionMustBePresent: 'Position must be present',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    boardId: idInput,\n    listId: idInput,\n    position: {\n      type: 'number',\n      min: 0,\n      allowNull: true,\n    },\n    name: {\n      type: 'string',\n      maxLength: 1024,\n      allowNull: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n    listNotFound: {\n      responseType: 'notFound',\n    },\n    listMustBePresent: {\n      responseType: 'unprocessableEntity',\n    },\n    positionMustBePresent: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, list, board, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    let boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!isProjectManager) {\n      if (!boardMembership) {\n        throw Errors.CARD_NOT_FOUND; // Forbidden\n      }\n\n      if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    }\n\n    let nextProject;\n    let nextBoard;\n\n    if (!_.isUndefined(inputs.boardId)) {\n      ({ board: nextBoard, project: nextProject } = await sails.helpers.boards\n        .getPathToProjectById(inputs.boardId)\n        .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND));\n\n      boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n        nextBoard.id,\n        currentUser.id,\n      );\n\n      if (!boardMembership) {\n        throw Errors.BOARD_NOT_FOUND; // Forbidden\n      }\n    }\n\n    if (!boardMembership) {\n      throw Errors.LIST_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    let nextList;\n    if (!_.isUndefined(inputs.listId)) {\n      nextList = await List.qm.getOneById(inputs.listId, {\n        boardId: (nextBoard || board).id,\n      });\n\n      if (!nextList) {\n        throw Errors.LIST_NOT_FOUND;\n      }\n    }\n\n    const values = _.pick(inputs, ['position', 'name']);\n\n    const {\n      card: nextCard,\n      cardMemberships,\n      cardLabels,\n      taskLists,\n      tasks,\n      attachments,\n      customFieldGroups,\n      customFields,\n      customFieldValues,\n    } = await sails.helpers.cards.duplicateOne\n      .with({\n        project,\n        board,\n        list,\n        record: card,\n        values: {\n          ...values,\n          project: nextProject,\n          board: nextBoard,\n          list: nextList,\n          creatorUser: currentUser,\n        },\n        request: this.req,\n      })\n      .intercept('positionMustBeInValues', () => Errors.POSITION_MUST_BE_PRESENT)\n      .intercept('listMustBeInValues', () => Errors.LIST_MUST_BE_PRESENT);\n\n    return {\n      item: nextCard,\n      included: {\n        cardMemberships,\n        cardLabels,\n        taskLists,\n        tasks,\n        customFieldGroups,\n        customFields,\n        customFieldValues,\n        attachments: sails.helpers.attachments.presentMany(attachments),\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/cards/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /lists/{listId}/cards:\n *   get:\n *     summary: Get cards in list\n *     description: Retrieves cards from an endless list with filtering, search, and pagination support.\n *     tags:\n *       - Cards\n *     operationId: getCards\n *     parameters:\n *       - name: listId\n *         in: path\n *         required: true\n *         description: ID of the list to get cards from (must be an endless list)\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *       - name: before[listChangedAt]\n *         in: query\n *         required: false\n *         description: Pagination cursor field `listChangedAt` (use together with `before[id]`)\n *         schema:\n *           type: string\n *           format: date-time\n *           example: 2024-01-01T00:00:00.000Z\n *       - name: before[id]\n *         in: query\n *         required: false\n *         description: Pagination cursor field `id` (use together with `before[listChangedAt]`)\n *         schema:\n *           type: string\n *           example: \"1357158568008091265\"\n *       - name: search\n *         in: query\n *         required: false\n *         description: Search term to filter cards\n *         schema:\n *           type: string\n *           maxLength: 128\n *           example: bug fix\n *       - name: userIds\n *         in: query\n *         required: false\n *         description: Comma-separated user IDs to filter by members or task assignees\n *         schema:\n *           type: string\n *           example: 1357158568008091266,1357158568008091267\n *       - name: labelIds\n *         in: query\n *         required: false\n *         description: Comma-separated label IDs to filter by labels\n *         schema:\n *           type: string\n *           example: 1357158568008091268,1357158568008091269\n *     responses:\n *       200:\n *         description: Cards retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - items\n *                 - included\n *               properties:\n *                 items:\n *                   type: array\n *                   items:\n *                     allOf:\n *                       - $ref: '#/components/schemas/Card'\n *                       - type: object\n *                         properties:\n *                           isSubscribed:\n *                             type: boolean\n *                             description: Whether the current user is subscribed to the card\n *                             example: true\n *                 included:\n *                   type: object\n *                   required:\n *                     - users\n *                     - cardMemberships\n *                     - cardLabels\n *                     - taskLists\n *                     - tasks\n *                     - attachments\n *                     - customFieldGroups\n *                     - customFields\n *                     - customFieldValues\n *                   properties:\n *                     users:\n *                       type: array\n *                       description: Related users\n *                       items:\n *                         $ref: '#/components/schemas/User'\n *                     cardMemberships:\n *                       type: array\n *                       description: Related card-membership associations\n *                       items:\n *                         $ref: '#/components/schemas/CardMembership'\n *                     cardLabels:\n *                       type: array\n *                       description: Related card-label associations\n *                       items:\n *                         $ref: '#/components/schemas/CardLabel'\n *                     taskLists:\n *                       type: array\n *                       description: Realted Task lists\n *                       items:\n *                         $ref: '#/components/schemas/TaskList'\n *                     tasks:\n *                       type: array\n *                       description: Related tasks\n *                       items:\n *                         $ref: '#/components/schemas/Task'\n *                     attachments:\n *                       type: array\n *                       description: Related attachments\n *                       items:\n *                         $ref: '#/components/schemas/Attachment'\n *                     customFieldGroups:\n *                       type: array\n *                       description: Related custom field groups\n *                       items:\n *                         $ref: '#/components/schemas/CustomFieldGroup'\n *                     customFields:\n *                       type: array\n *                       description: Related custom fields\n *                       items:\n *                         $ref: '#/components/schemas/CustomField'\n *                     customFieldValues:\n *                       type: array\n *                       description: Related custom field values\n *                       items:\n *                         $ref: '#/components/schemas/CustomFieldValue'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst moment = require('moment');\n\nconst { isId } = require('../../../utils/validators');\nconst { idInput, idsInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  LIST_NOT_FOUND: {\n    listNotFound: 'List not found',\n  },\n};\n\nconst isBefore = (value) => {\n  if (!_.isPlainObject(value) || _.size(value) !== 2) {\n    return false;\n  }\n\n  if (!moment(value.listChangedAt, moment.ISO_8601, true).isValid()) {\n    return false;\n  }\n\n  if (!isId(value.id)) {\n    return false;\n  }\n\n  return true;\n};\n\nmodule.exports = {\n  inputs: {\n    listId: {\n      ...idInput,\n      required: true,\n    },\n    before: {\n      type: 'json',\n      custom: isBefore,\n    },\n    search: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n    userIds: idsInput,\n    labelIds: idsInput,\n  },\n\n  exits: {\n    listNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { list, project } = await sails.helpers.lists\n      .getPathToProjectById(inputs.listId)\n      .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);\n\n    if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n          list.boardId,\n          currentUser.id,\n        );\n\n        if (!boardMembership) {\n          throw Errors.LIST_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    let filterUserIds;\n    if (inputs.userIds) {\n      const boardMemberships = await BoardMembership.qm.getByBoardId(list.boardId);\n\n      const availableUserIdsSet = new Set(\n        sails.helpers.utils.mapRecords(boardMemberships, 'userId'),\n      );\n\n      filterUserIds = _.uniq(inputs.userIds.split(','));\n      filterUserIds = filterUserIds.filter((userId) => availableUserIdsSet.has(userId));\n    }\n\n    let filterLabelIds;\n    if (inputs.labelIds) {\n      const labels = await Label.qm.getByBoardId(list.boardId);\n      const availableLabelIdsSet = new Set(sails.helpers.utils.mapRecords(labels));\n\n      filterLabelIds = _.uniq(inputs.labelIds.split(','));\n      filterLabelIds = filterLabelIds.filter((labelId) => availableLabelIdsSet.has(labelId));\n    }\n\n    const cards = await Card.qm.getByEndlessListId(list.id, {\n      before: inputs.before,\n      search: inputs.search,\n      userIds: filterUserIds,\n      labelIds: filterLabelIds,\n    });\n\n    const cardIds = sails.helpers.utils.mapRecords(cards);\n\n    const userIds = sails.helpers.utils.mapRecords(cards, 'creatorUserId', true, true);\n    const users = await User.qm.getByIds(userIds);\n\n    const cardSubscriptions = await CardSubscription.qm.getByCardIdsAndUserId(\n      cardIds,\n      currentUser.id,\n    );\n\n    const cardMemberships = await CardMembership.qm.getByCardIds(cardIds);\n    const cardLabels = await CardLabel.qm.getByCardIds(cardIds);\n\n    const taskLists = await TaskList.qm.getByCardIds(cardIds);\n    const taskListIds = sails.helpers.utils.mapRecords(taskLists);\n\n    const tasks = await Task.qm.getByTaskListIds(taskListIds);\n    const attachments = await Attachment.qm.getByCardIds(cardIds);\n\n    const customFieldGroups = await CustomFieldGroup.qm.getByCardIds(cardIds);\n    const customFieldGroupIds = sails.helpers.utils.mapRecords(customFieldGroups);\n\n    const customFields = await CustomField.qm.getByCustomFieldGroupIds(customFieldGroupIds);\n    const customFieldValues = await CustomFieldValue.qm.getByCardIds(cardIds);\n\n    const isSubscribedByCardId = cardSubscriptions.reduce(\n      (result, cardSubscription) => ({\n        ...result,\n        [cardSubscription.cardId]: true,\n      }),\n      {},\n    );\n\n    cards.forEach((card) => {\n      // eslint-disable-next-line no-param-reassign\n      card.isSubscribed = isSubscribedByCardId[card.id] || false;\n    });\n\n    return {\n      items: cards,\n      included: {\n        cardMemberships,\n        cardLabels,\n        taskLists,\n        tasks,\n        customFieldGroups,\n        customFields,\n        customFieldValues,\n        users: sails.helpers.users.presentMany(users, currentUser),\n        attachments: sails.helpers.attachments.presentMany(attachments),\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/cards/read-notifications.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{id}/read-notifications:\n *   post:\n *     summary: Mark card notifications as read\n *     description: Marks all notifications for a specific card as read for the current user. Requires access to the card.\n *     tags:\n *       - Cards\n *     operationId: readCardNotifications\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the card to mark notifications as read for\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Notifications marked as read successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Card'\n *                 included:\n *                   type: object\n *                   required:\n *                     - notifications\n *                   properties:\n *                     notifications:\n *                       type: array\n *                       description: Related notifications\n *                       items:\n *                         $ref: '#/components/schemas/Notification'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n          card.boardId,\n          currentUser.id,\n        );\n\n        if (!boardMembership) {\n          throw Errors.CARD_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    const notifications = await sails.helpers.cards.readNotificationsForUser.with({\n      record: card,\n      user: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: card,\n      included: {\n        notifications,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/cards/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{id}:\n *   get:\n *     summary: Get card details\n *     description: Retrieves comprehensive card information, including tasks, attachments, and other related data.\n *     tags:\n *       - Cards\n *     operationId: getCard\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the card to retrieve\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Card details retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   allOf:\n *                     - $ref: '#/components/schemas/Card'\n *                     - type: object\n *                       properties:\n *                         isSubscribed:\n *                           type: boolean\n *                           description: Whether the current user is subscribed to the card\n *                           example: true\n *                 included:\n *                   type: object\n *                   required:\n *                     - users\n *                     - cardMemberships\n *                     - cardLabels\n *                     - taskLists\n *                     - tasks\n *                     - attachments\n *                     - customFieldGroups\n *                     - customFields\n *                     - customFieldValues\n *                   properties:\n *                     users:\n *                       type: array\n *                       description: Related users\n *                       items:\n *                         $ref: '#/components/schemas/User'\n *                     cardMemberships:\n *                       type: array\n *                       description: Related card-membership associations\n *                       items:\n *                         $ref: '#/components/schemas/CardMembership'\n *                     cardLabels:\n *                       type: array\n *                       description: Related card-label associations\n *                       items:\n *                         $ref: '#/components/schemas/CardLabel'\n *                     taskLists:\n *                       type: array\n *                       description: Related task lists\n *                       items:\n *                         $ref: '#/components/schemas/TaskList'\n *                     tasks:\n *                       type: array\n *                       description: Related tasks\n *                       items:\n *                         $ref: '#/components/schemas/Task'\n *                     attachments:\n *                       type: array\n *                       description: Related attachments\n *                       items:\n *                         $ref: '#/components/schemas/Attachment'\n *                     customFieldGroups:\n *                       type: array\n *                       description: Related custom field groups\n *                       items:\n *                         $ref: '#/components/schemas/CustomFieldGroup'\n *                     customFields:\n *                       type: array\n *                       description: Related custom fields\n *                       items:\n *                         $ref: '#/components/schemas/CustomField'\n *                     customFieldValues:\n *                       type: array\n *                       description: Related custom field values\n *                       items:\n *                         $ref: '#/components/schemas/CustomFieldValue'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n          card.boardId,\n          currentUser.id,\n        );\n\n        if (!boardMembership) {\n          throw Errors.CARD_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    card.isSubscribed = await sails.helpers.users.isCardSubscriber(currentUser.id, card.id);\n\n    const users = card.creatorUserId ? await User.qm.getByIds([card.creatorUserId]) : [];\n    const cardMemberships = await CardMembership.qm.getByCardId(card.id);\n    const cardLabels = await CardLabel.qm.getByCardId(card.id);\n\n    const taskLists = await TaskList.qm.getByCardId(card.id);\n    const taskListIds = sails.helpers.utils.mapRecords(taskLists);\n\n    const tasks = await Task.qm.getByTaskListIds(taskListIds);\n    const attachments = await Attachment.qm.getByCardId(card.id);\n\n    const customFieldGroups = await CustomFieldGroup.qm.getByCardId(card.id);\n    const customFieldGroupIds = sails.helpers.utils.mapRecords(customFieldGroups);\n\n    const customFields = await CustomField.qm.getByCustomFieldGroupIds(customFieldGroupIds);\n    const customFieldValues = await CustomFieldValue.qm.getByCardId(card.id);\n\n    return {\n      item: card,\n      included: {\n        cardMemberships,\n        cardLabels,\n        taskLists,\n        tasks,\n        customFieldGroups,\n        customFields,\n        customFieldValues,\n        users: sails.helpers.users.presentMany(users, currentUser),\n        attachments: sails.helpers.attachments.presentMany(attachments),\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/cards/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{id}:\n *   patch:\n *     summary: Update card\n *     description: Updates a card. Board editors can update all fields, viewers can only subscribe/unsubscribe.\n *     tags:\n *       - Cards\n *     operationId: updateCard\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the card to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               boardId:\n *                 type: string\n *                 description: ID of the board to move the card to\n *                 example: \"1357158568008091265\"\n *               listId:\n *                 type: string\n *                 description: ID of the list to move the card to\n *                 example: \"1357158568008091266\"\n *               coverAttachmentId:\n *                 type: string\n *                 nullable: true\n *                 description: ID of the attachment used as cover\n *                 example: \"1357158568008091267\"\n *               type:\n *                 type: string\n *                 enum: [project, story]\n *                 description: Type of the card\n *                 example: project\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 nullable: true\n *                 description: Position of the card within the list (required when moving card to new list)\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 1024\n *                 description: Name/title of the card\n *                 example: Implement user authentication\n *               description:\n *                 type: string\n *                 maxLength: 1048576\n *                 nullable: true\n *                 description: Detailed description of the card\n *                 example: Add JWT-based authentication system...\n *               dueDate:\n *                 type: string\n *                 format: date-time\n *                 nullable: true\n *                 description: Due date for the card\n *                 example: 2024-01-01T00:00:00.000Z\n *               isDueCompleted:\n *                 type: boolean\n *                 nullable: true\n *                 description: Whether the due date is completed\n *                 example: false\n *               stopwatch:\n *                 type: object\n *                 required:\n *                   - startedAt\n *                   - total\n *                 nullable: true\n *                 description: Stopwatch data for time tracking\n *                 properties:\n *                   startedAt:\n *                     type: string\n *                     format: date-time\n *                     description: When the stopwatch was started\n *                     example: 2024-01-01T00:00:00.000Z\n *                   total:\n *                     type: number\n *                     description: Total time in seconds\n *                     example: 3600\n *               isSubscribed:\n *                 type: boolean\n *                 description: Whether the current user is subscribed to the card\n *     responses:\n *       200:\n *         description: Card updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Card'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { isDueDate, isStopwatch } = require('../../../utils/validators');\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n  LIST_NOT_FOUND: {\n    listNotFound: 'List not found',\n  },\n  COVER_ATTACHMENT_NOT_FOUND: {\n    coverAttachmentNotFound: 'Cover attachment not found',\n  },\n  LIST_MUST_BE_PRESENT: {\n    listMustBePresent: 'List must be present',\n  },\n  COVER_ATTACHMENT_MUST_CONTAIN_IMAGE: {\n    coverAttachmentMustContainImage: 'Cover attachment must contain image',\n  },\n  POSITION_MUST_BE_PRESENT: {\n    positionMustBePresent: 'Position must be present',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    boardId: idInput,\n    listId: idInput,\n    coverAttachmentId: {\n      ...idInput,\n      allowNull: true,\n    },\n    type: {\n      type: 'string',\n      isIn: Object.values(Card.Types),\n    },\n    position: {\n      type: 'number',\n      min: 0,\n      allowNull: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 1024,\n    },\n    description: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 1048576,\n      allowNull: true,\n    },\n    dueDate: {\n      type: 'string',\n      custom: isDueDate,\n      allowNull: true,\n    },\n    isDueCompleted: {\n      type: 'boolean',\n      allowNull: true,\n    },\n    stopwatch: {\n      type: 'json',\n      custom: isStopwatch,\n    },\n    isSubscribed: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n    listNotFound: {\n      responseType: 'notFound',\n    },\n    coverAttachmentNotFound: {\n      responseType: 'notFound',\n    },\n    listMustBePresent: {\n      responseType: 'unprocessableEntity',\n    },\n    coverAttachmentMustContainImage: {\n      responseType: 'unprocessableEntity',\n    },\n    positionMustBePresent: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.cards\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    let { card } = pathToProject;\n    const { list, board, project } = pathToProject;\n\n    let boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    const availableInputKeys = ['id', 'isSubscribed'];\n    if (boardMembership.role === BoardMembership.Roles.EDITOR) {\n      availableInputKeys.push(\n        'boardId',\n        'listId',\n        'coverAttachmentId',\n        'type',\n        'position',\n        'name',\n        'description',\n        'dueDate',\n        'isDueCompleted',\n        'stopwatch',\n      );\n    }\n\n    if (_.difference(Object.keys(inputs), availableInputKeys).length > 0) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    let nextProject;\n    let nextBoard;\n\n    if (!_.isUndefined(inputs.boardId)) {\n      ({ board: nextBoard, project: nextProject } = await sails.helpers.boards\n        .getPathToProjectById(inputs.boardId)\n        .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND));\n\n      boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n        nextBoard.id,\n        currentUser.id,\n      );\n\n      if (!boardMembership) {\n        throw Errors.BOARD_NOT_FOUND; // Forbidden\n      }\n\n      if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    }\n\n    let nextList;\n    if (!_.isUndefined(inputs.listId)) {\n      nextList = await List.qm.getOneById(inputs.listId, {\n        boardId: (nextBoard || board).id,\n      });\n\n      if (!nextList) {\n        throw Errors.LIST_NOT_FOUND;\n      }\n    }\n\n    let nextCoverAttachment;\n    if (inputs.coverAttachmentId) {\n      nextCoverAttachment = await Attachment.qm.getOneById(inputs.coverAttachmentId, {\n        cardId: card.id,\n      });\n\n      if (!nextCoverAttachment || nextCoverAttachment.type !== Attachment.Types.FILE) {\n        throw Errors.COVER_ATTACHMENT_NOT_FOUND;\n      }\n    }\n\n    const values = _.pick(inputs, [\n      'coverAttachmentId',\n      'type',\n      'position',\n      'name',\n      'description',\n      'dueDate',\n      'isDueCompleted',\n      'stopwatch',\n      'isSubscribed',\n    ]);\n\n    card = await sails.helpers.cards.updateOne\n      .with({\n        project,\n        board,\n        list,\n        record: card,\n        values: {\n          ...values,\n          project: nextProject,\n          board: nextBoard,\n          list: nextList,\n          coverAttachment: nextCoverAttachment,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('positionMustBeInValues', () => Errors.POSITION_MUST_BE_PRESENT)\n      .intercept('listMustBeInValues', () => Errors.LIST_MUST_BE_PRESENT)\n      .intercept(\n        'coverAttachmentInValuesMustContainImage',\n        () => Errors.COVER_ATTACHMENT_MUST_CONTAIN_IMAGE,\n      );\n\n    if (!card) {\n      throw Errors.CARD_NOT_FOUND;\n    }\n\n    return {\n      item: card,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/comments/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/comments:\n *   post:\n *     summary: Create comment\n *     description: Creates a new comment on a card. Requires board editor permissions or comment permissions.\n *     tags:\n *       - Comments\n *     operationId: createComment\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to create the comment on\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - text\n *             properties:\n *               text:\n *                 type: string\n *                 maxLength: 1048576\n *                 description: Content of the comment\n *                 example: This task is almost complete...\n *     responses:\n *       200:\n *         description: Comment created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Comment'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    text: {\n      type: 'string',\n      maxLength: 1048576,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, list, board, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      if (!boardMembership.canComment) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    }\n\n    const values = _.pick(inputs, ['text']);\n\n    const comment = await sails.helpers.comments.createOne.with({\n      project,\n      board,\n      list,\n      values: {\n        ...values,\n        card,\n        user: currentUser,\n      },\n      request: this.req,\n    });\n\n    return {\n      item: comment,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/comments/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /comments/{id}:\n *   delete:\n *     summary: Delete comment\n *     description: Deletes a comment. Can be deleted by the comment author (with comment permissions) or project manager.\n *     tags:\n *       - Comments\n *     operationId: deleteComment\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the comment to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Comment deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Comment'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  COMMENT_NOT_FOUND: {\n    commentNotFound: 'Comment not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    commentNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.comments\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.COMMENT_NOT_FOUND);\n\n    let { comment } = pathToProject;\n    const { card, list, board, project } = pathToProject;\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      if (comment.userId !== currentUser.id) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n\n      const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n        board.id,\n        currentUser.id,\n      );\n\n      if (!boardMembership) {\n        throw Errors.COMMENT_NOT_FOUND; // Forbidden\n      }\n\n      if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n        if (!boardMembership.canComment) {\n          throw Errors.NOT_ENOUGH_RIGHTS;\n        }\n      }\n    }\n\n    comment = await sails.helpers.comments.deleteOne.with({\n      project,\n      board,\n      list,\n      card,\n      record: comment,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!comment) {\n      throw Errors.COMMENT_NOT_FOUND;\n    }\n\n    return {\n      item: comment,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/comments/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/comments:\n *   get:\n *     summary: Get card comments\n *     description: Retrieves comments for a card with pagination support. Requires access to the card.\n *     tags:\n *       - Comments\n *     operationId: getComments\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to retrieve comments for\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *       - name: beforeId\n *         in: query\n *         required: false\n *         description: ID to get comments before (for pagination)\n *         schema:\n *           type: string\n *           example: \"1357158568008091265\"\n *     responses:\n *       200:\n *         description: Comments retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - items\n *                 - included\n *               properties:\n *                 items:\n *                   type: array\n *                   items:\n *                     $ref: '#/components/schemas/Comment'\n *                 included:\n *                   type: object\n *                   required:\n *                     - users\n *                   properties:\n *                     users:\n *                       type: array\n *                       description: Related users\n *                       items:\n *                         $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    beforeId: idInput,\n  },\n\n  exits: {\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n          card.boardId,\n          currentUser.id,\n        );\n\n        if (!boardMembership) {\n          throw Errors.CARD_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    const comments = await Comment.qm.getByCardId(card.id, {\n      beforeId: inputs.beforeId,\n    });\n\n    const userIds = sails.helpers.utils.mapRecords(comments, 'userId', true, true);\n    const users = await User.qm.getByIds(userIds);\n\n    return {\n      items: comments,\n      included: {\n        users: sails.helpers.users.presentMany(users, currentUser),\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/comments/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /comments/{id}:\n *   patch:\n *     summary: Update comment\n *     description: Updates a comment. Only the author of the comment can update it.\n *     tags:\n *       - Comments\n *     operationId: updateComments\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the comment to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               text:\n *                 type: string\n *                 maxLength: 1048576\n *                 description: Content of the comment\n *                 example: This task is almost complete...\n *     responses:\n *       200:\n *         description: Comment updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Comment'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  COMMENT_NOT_FOUND: {\n    commentNotFound: 'Comment not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    text: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 1048576,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    commentNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.comments\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.COMMENT_NOT_FOUND);\n\n    let { comment } = pathToProject;\n    const { card, list, board, project } = pathToProject;\n\n    if (comment.userId !== currentUser.id) {\n      throw Errors.COMMENT_NOT_FOUND; // Forbidden\n    }\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.COMMENT_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      if (!boardMembership.canComment) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    }\n\n    const values = _.pick(inputs, ['text']);\n\n    comment = await sails.helpers.comments.updateOne.with({\n      values,\n      project,\n      board,\n      list,\n      card,\n      record: comment,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!comment) {\n      throw Errors.COMMENT_NOT_FOUND;\n    }\n\n    return {\n      item: comment,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/config/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /config:\n *   get:\n *     summary: Get application configuration\n *     description: Retrieves the application configuration. Requires admin privileges.\n *     tags:\n *       - Config\n *     operationId: getConfig\n *     responses:\n *       200:\n *         description: Configuration retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Config'\n */\n\nmodule.exports = {\n  async fn() {\n    const config = await Config.qm.getOneMain();\n\n    return {\n      item: sails.helpers.config.presentOne(config),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/config/test-smtp.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /config/test-smtp:\n *   post:\n *     summary: Test SMTP configuration\n *     description: Sends a test email to verify the SMTP is configured correctly. Only available when SMTP is configured via the UI.\n *     tags:\n *       - Config\n *     operationId: testSmtpConfig\n *     responses:\n *       200:\n *         description: Test email sent successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Config'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n */\n\nconst Errors = {\n  NOT_AVAILABLE: {\n    notAvailable: 'Not available',\n  },\n};\n\nmodule.exports = {\n  exits: {\n    notAvailable: {\n      responseType: 'forbidden',\n    },\n  },\n\n  async fn() {\n    const { currentUser } = this.req;\n\n    if (sails.config.custom.smtpHost) {\n      return Errors.NOT_AVAILABLE;\n    }\n\n    const { transporter, config } = await sails.helpers.utils.makeSmtpTransporter({\n      connectionTimeout: 5000,\n      greetingTimeout: 5000,\n      socketTimeout: 10000,\n      dnsTimeout: 3000,\n    });\n\n    if (!transporter) {\n      return Errors.NOT_AVAILABLE;\n    }\n\n    const logs = ['📧 Sending test email...'];\n    try {\n      /* eslint-disable no-underscore-dangle */\n      const info = await transporter.sendMail({\n        to: currentUser.email,\n        subject: this.req.i18n.__('Test Title'),\n        text: this.req.i18n.__('This is a test text message!'),\n        html: this.req.i18n.__('This is a <i>test</i> <b>html</b> <code>message</code>!'),\n      });\n      /* eslint-enable no-underscore-dangle */\n      logs.push('✅ Email sent successfully.', '');\n\n      logs.push(`📬 Message ID: ${info.messageId}`);\n      if (info.response) {\n        logs.push(`📤 Server response: ${info.response.trim()}`);\n      }\n\n      logs.push('', '🎉 Your configuration is working correctly.');\n    } catch (error) {\n      logs.push('❌ Failed to send email.', '');\n\n      if (error.code) {\n        logs.push(`⚠️ Error code: ${error.code}`);\n      }\n      logs.push(`💬 Reason: ${error.message.trim()}`);\n\n      if (error.code === 'EDNS') {\n        logs.push('', '💡 Hint: Check your host setting.');\n      } else if (error.code === 'ETIMEDOUT') {\n        logs.push('', '💡 Hint: Check your host and port settings.');\n      } else if (error.code === 'EAUTH') {\n        logs.push('', '💡 Hint: Check your username and password.');\n      } else if (error.code === 'ESOCKET') {\n        if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) {\n          logs.push('', '💡 Hint: Check your host and port settings.');\n        } else if (error.message.includes('wrong version number')) {\n          logs.push('', '💡 Hint: Try toggling \"Use secure connection\".');\n        } else if (error.message.includes('certificate')) {\n          logs.push('', '💡 Hint: Try toggling \"Reject unauthorized TLS certificates\".');\n        }\n      }\n    } finally {\n      transporter.close();\n    }\n\n    return {\n      item: sails.helpers.config.presentOne(config),\n      included: {\n        logs,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/config/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /config:\n *   patch:\n *     summary: Update application configuration\n *     description: Updates the application configuration. Requires admin privileges.\n *     tags:\n *       - Config\n *     operationId: updateConfig\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               smtpHost:\n *                 type: string\n *                 maxLength: 256\n *                 nullable: true\n *                 description: Hostname or IP address of the SMTP server\n *                 example: smtp.example.com\n *               smtpPort:\n *                 type: number\n *                 minimum: 0\n *                 maximum: 65535\n *                 nullable: true\n *                 description: Port number of the SMTP server\n *                 example: 587\n *               smtpName:\n *                 type: string\n *                 maxLength: 256\n *                 nullable: true\n *                 description: Client hostname used in the EHLO command for SMTP\n *                 example: localhost\n *               smtpSecure:\n *                 type: boolean\n *                 description: Whether to use a secure connection for SMTP\n *                 example: false\n *               smtpTlsRejectUnauthorized:\n *                 type: boolean\n *                 description: Whether to reject unauthorized or self-signed TLS certificates for SMTP connections\n *                 example: true\n *               smtpUser:\n *                 type: string\n *                 maxLength: 256\n *                 nullable: true\n *                 description: Username for authenticating with the SMTP server\n *                 example: no-reply@example.com\n *               smtpPassword:\n *                 type: string\n *                 maxLength: 256\n *                 nullable: true\n *                 description: Password for authenticating with the SMTP server\n *                 example: SecurePassword123!\n *               smtpFrom:\n *                 type: string\n *                 maxLength: 256\n *                 nullable: true\n *                 description: Default \"from\" used for outgoing SMTP emails\n *                 example: no-reply@example.com\n *     responses:\n *       200:\n *         description: Configuration updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Config'\n */\n\nmodule.exports = {\n  inputs: {\n    smtpHost: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 256,\n      allowNull: true,\n    },\n    smtpPort: {\n      type: 'number',\n      min: 0,\n      max: 65535,\n      allowNull: true,\n    },\n    smtpName: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 256,\n      allowNull: true,\n    },\n    smtpSecure: {\n      type: 'boolean',\n    },\n    smtpTlsRejectUnauthorized: {\n      type: 'boolean',\n    },\n    smtpUser: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 256,\n      allowNull: true,\n    },\n    smtpPassword: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 256,\n      allowNull: true,\n    },\n    smtpFrom: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 256,\n      allowNull: true,\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const values = _.pick(inputs, [\n      'smtpHost',\n      'smtpPort',\n      'smtpName',\n      'smtpSecure',\n      'smtpTlsRejectUnauthorized',\n      'smtpUser',\n      'smtpPassword',\n      'smtpFrom',\n    ]);\n\n    const config = await sails.helpers.config.updateMain.with({\n      values,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: sails.helpers.config.presentOne(config),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/custom-field-groups/create-in-board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /boards/{boardId}/custom-field-groups:\n *   post:\n *     summary: Create board custom field group\n *     description: Creates a custom field group within a board. Either `baseCustomFieldGroupId` or `name` must be provided. Requires board editor permissions.\n *     tags:\n *       - Custom Field Groups\n *     operationId: createBoardCustomFieldGroup\n *     parameters:\n *       - name: boardId\n *         in: path\n *         required: true\n *         description: ID of the board to create the custom field group in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - position\n *             properties:\n *               baseCustomFieldGroupId:\n *                 type: string\n *                 description: ID of the base custom field group used as a template\n *                 example: \"1357158568008091265\"\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the custom field group within the board\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 nullable: true\n *                 description: Name/title of the custom field group (required if `baseCustomFieldGroupId` is not provided)\n *                 example: Properties\n *     responses:\n *       200:\n *         description: Custom field group created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CustomFieldGroup'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n  BASE_CUSTOM_FIELD_GROUP_NOT_FOUND: {\n    baseCustomFieldGroupNotFound: 'Base custom field group not found',\n  },\n  BASE_CUSTOM_FIELD_GROUP_OR_NAME_MUST_BE_PRESENT: {\n    baseCustomFieldGroupOrNameMustBePresent: 'Base custom field group or name must be present',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    boardId: {\n      ...idInput,\n      required: true,\n    },\n    baseCustomFieldGroupId: idInput,\n    position: {\n      type: 'number',\n      min: 0,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n      allowNull: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n    baseCustomFieldGroupNotFound: {\n      responseType: 'notFound',\n    },\n    baseCustomFieldGroupOrNameMustBePresent: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { board, project } = await sails.helpers.boards\n      .getPathToProjectById(inputs.boardId)\n      .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.BOARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    let baseCustomFieldGroup;\n    if (inputs.baseCustomFieldGroupId) {\n      baseCustomFieldGroup = await BaseCustomFieldGroup.qm.getOneById(\n        inputs.baseCustomFieldGroupId,\n        {\n          projectId: project.id,\n        },\n      );\n\n      if (!baseCustomFieldGroup) {\n        throw Errors.BASE_CUSTOM_FIELD_GROUP_NOT_FOUND;\n      }\n    }\n\n    const values = _.pick(inputs, ['position', 'name']);\n\n    const customFieldGroup = await sails.helpers.customFieldGroups.createOneInBoard\n      .with({\n        project,\n        values: {\n          ...values,\n          board,\n          baseCustomFieldGroup,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept(\n        'baseCustomFieldGroupOrNameMustBeInValues',\n        () => Errors.BASE_CUSTOM_FIELD_GROUP_OR_NAME_MUST_BE_PRESENT,\n      );\n\n    return {\n      item: customFieldGroup,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/custom-field-groups/create-in-card.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/custom-field-groups:\n *   post:\n *     summary: Create card custom field group\n *     description: Creates a custom field group within a card. Either `baseCustomFieldGroupId` or `name` must be provided. Requires board editor permissions.\n *     tags:\n *       - Custom Field Groups\n *     operationId: createCardCustomFieldGroup\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to create the custom field group in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - position\n *             properties:\n *               baseCustomFieldGroupId:\n *                 type: string\n *                 description: ID of the base custom field group used as a template\n *                 example: \"1357158568008091265\"\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the custom field group within the card\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 nullable: true\n *                 description: Name/title of the custom field group (required if `baseCustomFieldGroupId` is not provided)\n *                 example: Properties\n *     responses:\n *       200:\n *         description: Custom field group created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CustomFieldGroup'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n  BASE_CUSTOM_FIELD_GROUP_NOT_FOUND: {\n    baseCustomFieldGroupNotFound: 'Base custom field group not found',\n  },\n  BASE_CUSTOM_FIELD_GROUP_OR_NAME_MUST_BE_PRESENT: {\n    baseCustomFieldGroupOrNameMustBePresent: 'Base custom field group or name must be present',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    baseCustomFieldGroupId: idInput,\n    position: {\n      type: 'number',\n      min: 0,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n      allowNull: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n    baseCustomFieldGroupNotFound: {\n      responseType: 'notFound',\n    },\n    baseCustomFieldGroupOrNameMustBePresent: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, list, board, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    let baseCustomFieldGroup;\n    if (inputs.baseCustomFieldGroupId) {\n      baseCustomFieldGroup = await BaseCustomFieldGroup.qm.getOneById(\n        inputs.baseCustomFieldGroupId,\n        {\n          projectId: project.id,\n        },\n      );\n\n      if (!baseCustomFieldGroup) {\n        throw Errors.BASE_CUSTOM_FIELD_GROUP_NOT_FOUND;\n      }\n    }\n\n    const values = _.pick(inputs, ['position', 'name']);\n\n    const customFieldGroup = await sails.helpers.customFieldGroups.createOneInCard\n      .with({\n        project,\n        board,\n        list,\n        values: {\n          ...values,\n          card,\n          baseCustomFieldGroup,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept(\n        'baseCustomFieldGroupOrNameMustBeInValues',\n        () => Errors.BASE_CUSTOM_FIELD_GROUP_OR_NAME_MUST_BE_PRESENT,\n      );\n\n    return {\n      item: customFieldGroup,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/custom-field-groups/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /custom-field-groups/{id}:\n *   delete:\n *     summary: Delete custom field group\n *     description: Deletes a custom field group. Requires board editor permissions.\n *     tags:\n *       - Custom Field Groups\n *     operationId: deleteCustomFieldGroup\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the custom field group to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Custom field group deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CustomFieldGroup'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CUSTOM_FIELD_GROUP_NOT_FOUND: {\n    customFieldGroupNotFound: 'Custom field group not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    customFieldGroupNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.customFieldGroups\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.CUSTOM_FIELD_GROUP_NOT_FOUND);\n\n    let { customFieldGroup } = pathToProject;\n    const { card, list, board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CUSTOM_FIELD_GROUP_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    if (customFieldGroup.boardId) {\n      customFieldGroup = await sails.helpers.customFieldGroups.deleteOneInBoard.with({\n        project,\n        board,\n        record: customFieldGroup,\n        actorUser: currentUser,\n        request: this.req,\n      });\n    } else if (customFieldGroup.cardId) {\n      customFieldGroup = await sails.helpers.customFieldGroups.deleteOneInCard.with({\n        project,\n        board,\n        list,\n        card,\n        record: customFieldGroup,\n        actorUser: currentUser,\n        request: this.req,\n      });\n    }\n\n    if (!customFieldGroup) {\n      throw Errors.CUSTOM_FIELD_GROUP_NOT_FOUND;\n    }\n\n    return {\n      item: customFieldGroup,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/custom-field-groups/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /custom-field-groups/{id}:\n *   get:\n *     summary: Get custom field group details\n *     description: Retrieves comprehensive custom field group information, including fields and values. Requires access to the board/card.\n *     tags:\n *       - Custom Field Groups\n *     operationId: getCustomFieldGroup\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the custom field group to retrieve\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Custom field group details retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CustomFieldGroup'\n *                 included:\n *                   type: object\n *                   required:\n *                     - customFields\n *                     - customFieldValues\n *                   properties:\n *                     customFields:\n *                       type: array\n *                       description: Related custom fields\n *                       items:\n *                         $ref: '#/components/schemas/CustomField'\n *                     customFieldValues:\n *                       type: array\n *                       description: Related custom field values (for card-specific groups)\n *                       items:\n *                         $ref: '#/components/schemas/CustomFieldValue'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  CUSTOM_FIELD_GROUP_NOT_FOUND: {\n    customFieldGroupNotFound: 'Custom field group not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    customFieldGroupNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { customFieldGroup, board, project } = await sails.helpers.customFieldGroups\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.CUSTOM_FIELD_GROUP_NOT_FOUND);\n\n    if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n          board.id,\n          currentUser.id,\n        );\n\n        if (!boardMembership) {\n          throw Errors.CUSTOM_FIELD_GROUP_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    const customFields = await CustomField.qm.getByCustomFieldGroupId(customFieldGroup.id);\n\n    const customFieldValues = customFieldGroup.cardId\n      ? await CustomFieldValue.qm.getByCustomFieldGroupId(customFieldGroup.id)\n      : [];\n\n    return {\n      item: customFieldGroup,\n      included: {\n        customFields,\n        customFieldValues,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/custom-field-groups/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /custom-field-groups/{id}:\n *   patch:\n *     summary: Update custom field group\n *     description: Updates a custom field group. Supports both board-wide and card-specific groups. Requires board editor permissions.\n *     tags:\n *       - Custom Field Groups\n *     operationId: updateCustomFieldGroup\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the custom field group to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the custom field group within the board/card\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 nullable: true\n *                 description: Name/title of the custom field group\n *                 example: Properties\n *     responses:\n *       200:\n *         description: Custom field group updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CustomFieldGroup'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CUSTOM_FIELD_GROUP_NOT_FOUND: {\n    customFieldGroupNotFound: 'Custom field group not found',\n  },\n  NAME_MUST_NOT_BE_NULL: {\n    nameMustNotBeNull: 'Name must not be null',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n      allowNull: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    customFieldGroupNotFound: {\n      responseType: 'notFound',\n    },\n    nameMustNotBeNull: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.customFieldGroups\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.CUSTOM_FIELD_GROUP_NOT_FOUND);\n\n    let { customFieldGroup } = pathToProject;\n    const { card, list, board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CUSTOM_FIELD_GROUP_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const values = _.pick(inputs, ['position', 'name']);\n\n    if (customFieldGroup.boardId) {\n      customFieldGroup = await sails.helpers.customFieldGroups.updateOneInBoard\n        .with({\n          values,\n          project,\n          board,\n          record: customFieldGroup,\n          actorUser: currentUser,\n          request: this.req,\n        })\n        .intercept('nameInValuesMustNotBeNull', () => Errors.NAME_MUST_NOT_BE_NULL);\n    } else if (customFieldGroup.cardId) {\n      customFieldGroup = await sails.helpers.customFieldGroups.updateOneInCard\n        .with({\n          values,\n          project,\n          board,\n          list,\n          card,\n          record: customFieldGroup,\n          actorUser: currentUser,\n          request: this.req,\n        })\n        .intercept('nameInValuesMustNotBeNull', () => Errors.NAME_MUST_NOT_BE_NULL);\n    }\n\n    if (!customFieldGroup) {\n      throw Errors.CUSTOM_FIELD_GROUP_NOT_FOUND;\n    }\n\n    return {\n      item: customFieldGroup,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/custom-field-values/create-or-update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/custom-field-values/customFieldGroupId:{customFieldGroupId}:customFieldId:{customFieldId}:\n *   patch:\n *     summary: Create or update custom field value\n *     description: Creates or updates a custom field value for a card. Requires board editor permissions.\n *     tags:\n *       - Custom Field Values\n *     operationId: updateCustomFieldValue\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to set the custom field value for\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *       - name: customFieldGroupId\n *         in: path\n *         required: true\n *         description: ID of the custom field group the value belongs to\n *         schema:\n *           type: string\n *           example: \"1357158568008091265\"\n *       - name: customFieldId\n *         in: path\n *         required: true\n *         description: ID of the custom field the value belongs to\n *         schema:\n *           type: string\n *           example: \"1357158568008091266\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - content\n *             properties:\n *               content:\n *                 type: string\n *                 maxLength: 512\n *                 description: Content/value of the custom field\n *                 example: High Priority\n *     responses:\n *       200:\n *         description: Custom field value created or updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CustomFieldValue'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n  CUSTOM_FIELD_GROUP_NOT_FOUND: {\n    customFieldGroupNotFound: 'Custom field group not found',\n  },\n  CUSTOM_FIELD_NOT_FOUND: {\n    customFieldNotFound: 'Custom field not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    customFieldGroupId: {\n      ...idInput,\n      required: true,\n    },\n    customFieldId: {\n      ...idInput,\n      required: true,\n    },\n    content: {\n      type: 'string',\n      maxLength: 512,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n    customFieldGroupNotFound: {\n      responseType: 'notFound',\n    },\n    customFieldNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, list, board, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const customFieldGroup = await CustomFieldGroup.qm.getOneById(inputs.customFieldGroupId);\n\n    if (!customFieldGroup) {\n      throw Errors.CUSTOM_FIELD_GROUP_NOT_FOUND;\n    }\n\n    if (customFieldGroup.boardId) {\n      if (customFieldGroup.boardId !== board.id) {\n        throw Errors.CUSTOM_FIELD_GROUP_NOT_FOUND;\n      }\n    } else if (customFieldGroup.cardId) {\n      if (customFieldGroup.cardId !== card.id) {\n        throw Errors.CUSTOM_FIELD_GROUP_NOT_FOUND;\n      }\n    }\n\n    const customField = await CustomField.qm.getOneById(inputs.customFieldId);\n\n    if (!customField) {\n      throw Errors.CUSTOM_FIELD_NOT_FOUND;\n    }\n\n    if (customFieldGroup.baseCustomFieldGroupId) {\n      if (customField.baseCustomFieldGroupId !== customFieldGroup.baseCustomFieldGroupId) {\n        throw Errors.CUSTOM_FIELD_NOT_FOUND;\n      }\n    } else if (customField.customFieldGroupId !== customFieldGroup.id) {\n      throw Errors.CUSTOM_FIELD_NOT_FOUND;\n    }\n\n    const values = _.pick(inputs, ['content']);\n\n    const customFieldValue = await sails.helpers.customFieldValues.createOrUpdateOne.with({\n      project,\n      board,\n      list,\n      values: {\n        ...values,\n        card,\n        customFieldGroup,\n        customField,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: customFieldValue,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/custom-field-values/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/custom-field-value/customFieldGroupId:{customFieldGroupId}:customFieldId:{customFieldId}:\n *   delete:\n *     summary: Delete custom field value\n *     description: Deletes a custom field value for a specific card. Requires board editor permissions.\n *     tags:\n *       - Custom Field Values\n *     operationId: deleteCustomFieldValue\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to delete the custom field value from\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *       - name: customFieldGroupId\n *         in: path\n *         required: true\n *         description: ID of the custom field group the value belongs to\n *         schema:\n *           type: string\n *           example: \"1357158568008091265\"\n *       - name: customFieldId\n *         in: path\n *         required: true\n *         description: ID of the custom field the value belongs to\n *         schema:\n *           type: string\n *           example: \"1357158568008091266\"\n *     responses:\n *       200:\n *         description: Custom field value deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CustomFieldValue'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n  CUSTOM_FIELD_GROUP_NOT_FOUND: {\n    customFieldGroupNotFound: 'Custom field group not found',\n  },\n  CUSTOM_FIELD_VALUE_NOT_FOUND: {\n    customFieldValueNotFound: 'Custom field value not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    customFieldGroupId: {\n      ...idInput,\n      required: true,\n    },\n    customFieldId: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n    customFieldGroupNotFound: {\n      responseType: 'notFound',\n    },\n    customFieldValueNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, list, board, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const customFieldGroup = await CustomFieldGroup.qm.getOneById(inputs.customFieldGroupId);\n\n    if (!customFieldGroup) {\n      throw Errors.CUSTOM_FIELD_GROUP_NOT_FOUND;\n    }\n\n    let customFieldValue =\n      await CustomFieldValue.qm.getOneByCardIdAndCustomFieldGroupIdAndCustomFieldId(\n        card.id,\n        customFieldGroup.id,\n        inputs.customFieldId,\n      );\n\n    if (!customFieldValue) {\n      throw Errors.CUSTOM_FIELD_VALUE_NOT_FOUND;\n    }\n\n    customFieldValue = await sails.helpers.customFieldValues.deleteOne.with({\n      project,\n      board,\n      list,\n      card,\n      customFieldGroup,\n      record: customFieldValue,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!customFieldValue) {\n      throw Errors.CUSTOM_FIELD_VALUE_NOT_FOUND;\n    }\n\n    return {\n      item: customFieldValue,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/custom-fields/create-in-base-custom-field-group.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /base-custom-field-groups/{baseCustomFieldGroupId}/custom-fields:\n *   post:\n *     summary: Create custom field in base custom field group\n *     description: Creates a custom field within a base custom field group. Requires project manager permissions.\n *     tags:\n *       - Custom Fields\n *     operationId: createCustomFieldInBaseGroup\n *     parameters:\n *       - name: baseCustomFieldGroupId\n *         in: path\n *         required: true\n *         description: ID of the base custom field group to create the custom field in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - position\n *               - name\n *             properties:\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the custom field within the group\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the custom field\n *                 example: Priority\n *               showOnFrontOfCard:\n *                 type: boolean\n *                 description: Whether to show the field on the front of cards\n *                 example: false\n *     responses:\n *       200:\n *         description: Custom field created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CustomField'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  BASE_CUSTOM_FIELD_GROUP_NOT_FOUND: {\n    baseCustomFieldGroupNotFound: 'Base custom field group not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    baseCustomFieldGroupId: {\n      ...idInput,\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      maxLength: 128,\n      required: true,\n    },\n    showOnFrontOfCard: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    baseCustomFieldGroupNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { baseCustomFieldGroup, project } = await sails.helpers.baseCustomFieldGroups\n      .getPathToProjectById(inputs.baseCustomFieldGroupId)\n      .intercept('pathNotFound', () => Errors.BASE_CUSTOM_FIELD_GROUP_NOT_FOUND);\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.BASE_CUSTOM_FIELD_GROUP_NOT_FOUND; // Forbidden\n    }\n\n    const values = _.pick(inputs, ['position', 'name', 'showOnFrontOfCard']);\n\n    const customField = await sails.helpers.customFields.createOneInBaseCustomFieldGroup.with({\n      project,\n      values: {\n        ...values,\n        baseCustomFieldGroup,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: customField,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/custom-fields/create-in-custom-field-group.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /custom-field-groups/{customFieldGroupId}/custom-fields:\n *   post:\n *     summary: Create custom field in custom field group\n *     description: Creates a custom field within a custom field group. Requires board editor permissions.\n *     tags:\n *       - Custom Fields\n *     operationId: createCustomFieldInGroup\n *     parameters:\n *       - name: customFieldGroupId\n *         in: path\n *         required: true\n *         description: ID of the custom field group to create the custom field in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - position\n *               - name\n *             properties:\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the custom field within the group\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the custom field\n *                 example: Priority\n *               showOnFrontOfCard:\n *                 type: boolean\n *                 description: Whether to show the field on the front of cards\n *                 example: false\n *     responses:\n *       200:\n *         description: Custom field created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CustomField'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CUSTOM_FIELD_GROUP_NOT_FOUND: {\n    customFieldGroupNotFound: 'Custom field group not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    customFieldGroupId: {\n      ...idInput,\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      maxLength: 128,\n      required: true,\n    },\n    showOnFrontOfCard: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    customFieldGroupNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { customFieldGroup, card, list, board, project } = await sails.helpers.customFieldGroups\n      .getPathToProjectById(inputs.customFieldGroupId)\n      .intercept('pathNotFound', () => Errors.CUSTOM_FIELD_GROUP_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CUSTOM_FIELD_GROUP_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const values = _.pick(inputs, ['position', 'name', 'showOnFrontOfCard']);\n\n    const customField = await sails.helpers.customFields.createOneInCustomFieldGroup.with({\n      project,\n      board,\n      list,\n      card,\n      values: {\n        ...values,\n        customFieldGroup,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: customField,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/custom-fields/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /custom-fields/{id}:\n *   delete:\n *     summary: Delete custom field\n *     description: Deletes a custom field. Can delete the in base custom field group (requires project manager permissions) or the custom field group (requires board editor permissions).\n *     tags:\n *       - Custom Fields\n *     operationId: deleteCustomField\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the custom field to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Custom field deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CustomField'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CUSTOM_FIELD_NOT_FOUND: {\n    customFieldNotFound: 'Custom field not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    customFieldNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.customFields\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.CUSTOM_FIELD_NOT_FOUND);\n\n    let { customField } = pathToProject;\n    const { customFieldGroup, card, list, board, baseCustomFieldGroup, project } = pathToProject;\n\n    if (customField.baseCustomFieldGroupId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        throw Errors.CUSTOM_FIELD_NOT_FOUND; // Forbidden\n      }\n\n      customField = await sails.helpers.customFields.deleteOneInBaseCustomFieldGroup.with({\n        project,\n        baseCustomFieldGroup,\n        record: customField,\n        actorUser: currentUser,\n        request: this.req,\n      });\n    } else if (customField.customFieldGroupId) {\n      const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n        board.id,\n        currentUser.id,\n      );\n\n      if (!boardMembership) {\n        throw Errors.CUSTOM_FIELD_NOT_FOUND; // Forbidden\n      }\n\n      if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n\n      customField = await sails.helpers.customFields.deleteOneInCustomFieldGroup.with({\n        project,\n        board,\n        list,\n        card,\n        customFieldGroup,\n        record: customField,\n        actorUser: currentUser,\n        request: this.req,\n      });\n    }\n\n    if (!customField) {\n      throw Errors.CUSTOM_FIELD_NOT_FOUND;\n    }\n\n    return {\n      item: customField,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/custom-fields/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /custom-fields/{id}:\n *   patch:\n *     summary: Update custom field\n *     description: Updates a custom field. Can update in the base custom field group (requires project manager permissions) or the custom field group (requires board editor permissions).\n *     tags:\n *       - Custom Fields\n *     operationId: updateCustomField\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the custom field to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the custom field within the group\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the custom field\n *                 example: Priority\n *               showOnFrontOfCard:\n *                 type: boolean\n *                 description: Whether to show the field on the front of cards\n *                 example: false\n *     responses:\n *       200:\n *         description: Custom field updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/CustomField'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CUSTOM_FIELD_NOT_FOUND: {\n    customFieldNotFound: 'Custom field not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n    showOnFrontOfCard: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    customFieldNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.customFields\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.CUSTOM_FIELD_NOT_FOUND);\n\n    let { customField } = pathToProject;\n    const { customFieldGroup, card, list, board, baseCustomFieldGroup, project } = pathToProject;\n\n    const values = _.pick(inputs, ['position', 'name', 'showOnFrontOfCard']);\n\n    if (customField.baseCustomFieldGroupId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        throw Errors.CUSTOM_FIELD_NOT_FOUND; // Forbidden\n      }\n\n      customField = await sails.helpers.customFields.updateOneInBaseCustomFieldGroup.with({\n        values,\n        project,\n        baseCustomFieldGroup,\n        record: customField,\n        actorUser: currentUser,\n        request: this.req,\n      });\n    } else if (customField.customFieldGroupId) {\n      const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n        board.id,\n        currentUser.id,\n      );\n\n      if (!boardMembership) {\n        throw Errors.CUSTOM_FIELD_NOT_FOUND; // Forbidden\n      }\n\n      if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n\n      customField = await sails.helpers.customFields.updateOneInCustomFieldGroup.with({\n        values,\n        project,\n        board,\n        list,\n        card,\n        customFieldGroup,\n        record: customField,\n        actorUser: currentUser,\n        request: this.req,\n      });\n    }\n\n    if (!customField) {\n      throw Errors.CUSTOM_FIELD_NOT_FOUND;\n    }\n\n    return {\n      item: customField,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/file-attachments/download-thumbnail.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  FILE_ATTACHMENT_NOT_FOUND: {\n    fileAttachmentNotFound: 'File attachment not found',\n  },\n};\n\nconst FILE_NAMES = ['outside-360', 'outside-720'];\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    fileName: {\n      type: 'string',\n      isIn: FILE_NAMES,\n      required: true,\n    },\n    fileExtension: {\n      type: 'string',\n      maxLength: 128, // TODO: unnecessary?\n      required: true,\n    },\n  },\n\n  exits: {\n    fileAttachmentNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs, exits) {\n    const { currentUser } = this.req;\n\n    const { attachment, board, project } = await sails.helpers.attachments\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.FILE_ATTACHMENT_NOT_FOUND);\n\n    if (attachment.type !== Attachment.Types.FILE) {\n      throw Errors.FILE_ATTACHMENT_NOT_FOUND;\n    }\n\n    if (!attachment.data.image) {\n      throw Errors.FILE_ATTACHMENT_NOT_FOUND;\n    }\n\n    if (inputs.fileExtension !== attachment.data.image.thumbnailsExtension) {\n      throw Errors.FILE_ATTACHMENT_NOT_FOUND;\n    }\n\n    if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n          board.id,\n          currentUser.id,\n        );\n\n        if (!boardMembership) {\n          throw Errors.FILE_ATTACHMENT_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    const fileManager = sails.hooks['file-manager'].getInstance();\n\n    let readStream;\n    let headers;\n\n    try {\n      [readStream, headers] = await fileManager.read(\n        `${sails.config.custom.attachmentsPathSegment}/${attachment.data.uploadedFileId}/thumbnails/${inputs.fileName}.${inputs.fileExtension}`,\n        {\n          withHeaders: true,\n        },\n      );\n    } catch (error) {\n      throw Errors.FILE_ATTACHMENT_NOT_FOUND;\n    }\n\n    this.res.set({\n      ...headers,\n      'Content-Type': attachment.data.mimeType,\n      'Cache-Control': 'private, max-age=86400, no-transform', // TODO: move to config\n    });\n\n    readStream.on('error', () => {\n      if (this.res.headersSent) {\n        this.res.destroy();\n      } else {\n        throw Errors.FILE_ATTACHMENT_NOT_FOUND;\n      }\n    });\n\n    return exits.success(readStream);\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/file-attachments/download.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  FILE_ATTACHMENT_NOT_FOUND: {\n    fileAttachmentNotFound: 'File attachment not found',\n  },\n};\n\nconst INLINE_MIME_TYPES_SET = new Set([\n  'application/pdf',\n  'audio/mpeg',\n  'audio/wav',\n  'audio/ogg',\n  'audio/opus',\n  'audio/mp4',\n  'audio/x-aac',\n  'video/mp4',\n  'video/ogg',\n  'video/webm',\n]);\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    fileAttachmentNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs, exits) {\n    const { currentUser } = this.req;\n\n    const { attachment, board, project } = await sails.helpers.attachments\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.FILE_ATTACHMENT_NOT_FOUND);\n\n    if (attachment.type !== Attachment.Types.FILE) {\n      throw Errors.FILE_ATTACHMENT_NOT_FOUND;\n    }\n\n    if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n          board.id,\n          currentUser.id,\n        );\n\n        if (!boardMembership) {\n          throw Errors.FILE_ATTACHMENT_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    const fileManager = sails.hooks['file-manager'].getInstance();\n\n    let readStream;\n    let headers;\n\n    try {\n      [readStream, headers] = await fileManager.read(\n        `${sails.config.custom.attachmentsPathSegment}/${attachment.data.uploadedFileId}/${attachment.data.filename}`,\n        {\n          withHeaders: true,\n        },\n      );\n    } catch (error) {\n      throw Errors.FILE_ATTACHMENT_NOT_FOUND;\n    }\n\n    if (attachment.data.mimeType) {\n      headers['Content-Type'] = attachment.data.mimeType;\n    }\n    if (!INLINE_MIME_TYPES_SET.has(attachment.data.mimeType) && !attachment.data.image) {\n      headers['Content-Disposition'] = 'attachment';\n    }\n\n    this.res.set({\n      ...headers,\n      'Cache-Control': 'private, max-age=86400, no-transform', // TODO: move to config\n    });\n\n    readStream.on('error', () => {\n      if (this.res.headersSent) {\n        this.res.destroy();\n      } else {\n        throw Errors.FILE_ATTACHMENT_NOT_FOUND;\n      }\n    });\n\n    return exits.success(readStream);\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/index.js",
    "content": "/*!\n * Copyright (c) 2025 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  exits: {\n    success: {\n      responseType: 'view',\n      viewTemplatePath: 'index',\n    },\n  },\n\n  fn() {\n    return {\n      basePath: sails.config.custom.baseUrlPath,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/labels/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /boards/{boardId}/labels:\n *   post:\n *     summary: Create label\n *     description: Creates a label within a board. Requires board editor permissions.\n *     tags:\n *       - Labels\n *     operationId: createLabel\n *     parameters:\n *       - name: boardId\n *         in: path\n *         required: true\n *         description: ID of the board to create the label in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - position\n *               - color\n *             properties:\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the label within the board\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 nullable: true\n *                 description: Name/title of the label\n *                 example: Bug\n *               color:\n *                 type: string\n *                 enum: [muddy-grey, autumn-leafs, morning-sky, antique-blue, egg-yellow, desert-sand, dark-granite, fresh-salad, lagoon-blue, midnight-blue, light-orange, pumpkin-orange, light-concrete, sunny-grass, navy-blue, lilac-eyes, apricot-red, orange-peel, silver-glint, bright-moss, deep-ocean, summer-sky, berry-red, light-cocoa, grey-stone, tank-green, coral-green, sugar-plum, pink-tulip, shady-rust, wet-rock, wet-moss, turquoise-sea, lavender-fields, piggy-red, light-mud, gun-metal, modern-green, french-coast, sweet-lilac, red-burgundy, pirate-gold]\n *                 description: Color of the label\n *                 example: berry-red\n *     responses:\n *       200:\n *         description: Label created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Label'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    boardId: {\n      ...idInput,\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n      allowNull: true,\n    },\n    color: {\n      type: 'string',\n      isIn: Label.COLORS,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { board, project } = await sails.helpers.boards\n      .getPathToProjectById(inputs.boardId)\n      .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.BOARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const values = _.pick(inputs, ['position', 'name', 'color']);\n\n    const label = await sails.helpers.labels.createOne.with({\n      project,\n      values: {\n        ...values,\n        board,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: label,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/labels/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /labels/{id}:\n *   delete:\n *     summary: Delete label\n *     description: Deletes a label. Requires board editor permissions.\n *     tags:\n *       - Labels\n *     operationId: deleteLabel\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the label to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Label deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Label'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  LABEL_NOT_FOUND: {\n    labelNotFound: 'Label not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    labelNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.labels\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND);\n\n    let { label } = pathToProject;\n    const { board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.LABEL_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    label = await sails.helpers.labels.deleteOne.with({\n      project,\n      board,\n      record: label,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!label) {\n      throw Errors.LABEL_NOT_FOUND;\n    }\n\n    return {\n      item: label,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/labels/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /labels/{id}:\n *   patch:\n *     summary: Update label\n *     description: Updates a label. Requires board editor permissions.\n *     tags:\n *       - Labels\n *     operationId: updateLabel\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the label to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the label within the board\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 nullable: true\n *                 description: Name/title of the label\n *                 example: Bug\n *               color:\n *                 type: string\n *                 enum: [muddy-grey, autumn-leafs, morning-sky, antique-blue, egg-yellow, desert-sand, dark-granite, fresh-salad, lagoon-blue, midnight-blue, light-orange, pumpkin-orange, light-concrete, sunny-grass, navy-blue, lilac-eyes, apricot-red, orange-peel, silver-glint, bright-moss, deep-ocean, summer-sky, berry-red, light-cocoa, grey-stone, tank-green, coral-green, sugar-plum, pink-tulip, shady-rust, wet-rock, wet-moss, turquoise-sea, lavender-fields, piggy-red, light-mud, gun-metal, modern-green, french-coast, sweet-lilac, red-burgundy, pirate-gold]\n *                 description: Color of the label\n *                 example: berry-red\n *     responses:\n *       200:\n *         description: Label updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Label'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  LABEL_NOT_FOUND: {\n    labelNotFound: 'Label not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n      allowNull: true,\n    },\n    color: {\n      type: 'string',\n      isIn: Label.COLORS,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    labelNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.labels\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND);\n\n    let { label } = pathToProject;\n    const { board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.LABEL_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const values = _.pick(inputs, ['position', 'name', 'color']);\n\n    label = await sails.helpers.labels.updateOne.with({\n      values,\n      project,\n      board,\n      record: label,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!label) {\n      throw Errors.LABEL_NOT_FOUND;\n    }\n\n    return {\n      item: label,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/lists/clear.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /lists/{id}/clear:\n *   post:\n *     summary: Clear list\n *     description: Deletes all cards from a list. Only works with trash-type lists. Requires project manager or board editor permissions.\n *     tags:\n *       - Lists\n *     operationId: clearList\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the list to clear (must be a trash-type list)\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: List cleared successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/List'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  LIST_NOT_FOUND: {\n    listNotFound: 'List not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    listNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { list, board, project } = await sails.helpers.lists\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n        board.id,\n        currentUser.id,\n      );\n\n      if (!boardMembership) {\n        throw Errors.LIST_NOT_FOUND; // Forbidden\n      }\n\n      if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    }\n\n    // TODO: allow for other types?\n    if (list.type !== List.Types.TRASH) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    await sails.helpers.lists.clearOne.with({\n      project,\n      board,\n      record: list,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: list,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/lists/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /boards/{boardId}/lists:\n *   post:\n *     summary: Create list\n *     description: Creates a list within a board. Requires board editor permissions.\n *     tags:\n *       - Lists\n *     operationId: createList\n *     parameters:\n *       - name: boardId\n *         in: path\n *         required: true\n *         description: ID of the board to create the list in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - type\n *               - position\n *               - name\n *             properties:\n *               type:\n *                 type: string\n *                 enum: [active, closed]\n *                 description: Type/status of the list\n *                 example: active\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the list within the board\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the list\n *                 example: To Do\n *     responses:\n *       200:\n *         description: List created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/List'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    boardId: {\n      ...idInput,\n      required: true,\n    },\n    type: {\n      type: 'string',\n      isIn: List.KANBAN_TYPES,\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      maxLength: 128,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { board, project } = await sails.helpers.boards\n      .getPathToProjectById(inputs.boardId)\n      .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.BOARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const values = _.pick(inputs, ['type', 'position', 'name']);\n\n    const list = await sails.helpers.lists.createOne.with({\n      project,\n      values: {\n        ...values,\n        board,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: list,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/lists/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /lists/{id}:\n *   delete:\n *     summary: Delete list\n *     description: Deletes a list and moves its cards to a trash list. Can only delete finite lists. Requires board editor permissions.\n *     tags:\n *       - Lists\n *     operationId: deleteList\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the list to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: List deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/List'\n *                 included:\n *                   type: object\n *                   required:\n *                     - cards\n *                   properties:\n *                     cards:\n *                       type: array\n *                       description: Related cards\n *                       items:\n *                         $ref: '#/components/schemas/Card'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  LIST_NOT_FOUND: {\n    listNotFound: 'List not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    listNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.lists\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);\n\n    let { list } = pathToProject;\n    const { board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.LIST_NOT_FOUND; // Forbidden\n    }\n\n    if (!sails.helpers.lists.isKanban(list)) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const result = await sails.helpers.lists.deleteOne.with({\n      project,\n      board,\n      record: list,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    ({ list } = result);\n    const { cards } = result;\n\n    if (!list) {\n      throw Errors.LIST_NOT_FOUND;\n    }\n\n    return {\n      item: list,\n      included: {\n        cards,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/lists/move-cards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /lists/{id}/move-cards:\n *   post:\n *     summary: Move cards\n *     description: Moves all cards from a closed list to an archive list. Requires board editor permissions.\n *     tags:\n *       - Lists\n *     operationId: moveListCards\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the source list (must be a closed-type list)\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - listId\n *             properties:\n *               listId:\n *                 type: string\n *                 description: ID of the target list (must be an archive-type list)\n *                 example: \"1357158568008091265\"\n *     responses:\n *       200:\n *         description: Cards moved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/List'\n *                 included:\n *                   type: object\n *                   required:\n *                     - cards\n *                     - actions\n *                   properties:\n *                     cards:\n *                       type: array\n *                       description: Related cards\n *                       items:\n *                         $ref: '#/components/schemas/Card'\n *                     actions:\n *                       type: array\n *                       description: Related actions\n *                       items:\n *                         $ref: '#/components/schemas/Action'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  LIST_NOT_FOUND: {\n    listNotFound: 'List not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    listId: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    listNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { list, board, project } = await sails.helpers.lists\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.LIST_NOT_FOUND; // Forbidden\n    }\n\n    // TODO: allow for other types?\n    if (list.type !== List.Types.CLOSED) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const nextList = await List.qm.getOneById(inputs.listId, {\n      boardId: board.id,\n    });\n\n    if (!nextList) {\n      throw Errors.LIST_NOT_FOUND; // Forbidden\n    }\n\n    // TODO: allow for other types?\n    if (nextList.type !== List.Types.ARCHIVE) {\n      throw Errors.LIST_NOT_FOUND;\n    }\n\n    const { cards, actions } = await sails.helpers.lists.moveCards.with({\n      project,\n      board,\n      record: list,\n      values: {\n        list: nextList,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: list,\n      included: {\n        cards,\n        actions,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/lists/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /lists/{id}:\n *   get:\n *     summary: Get list details\n *     description: Retrieves comprehensive list information, including cards, attachments, and other related data. Requires access to the board.\n *     tags:\n *       - Lists\n *     operationId: getList\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the list to retrieve\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: List details retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/List'\n *                 included:\n *                   type: object\n *                   required:\n *                     - users\n *                     - cards\n *                     - cardMemberships\n *                     - cardLabels\n *                     - taskLists\n *                     - tasks\n *                     - attachments\n *                     - customFieldGroups\n *                     - customFields\n *                     - customFieldValues\n *                   properties:\n *                     users:\n *                       type: array\n *                       description: Related users\n *                       items:\n *                         $ref: '#/components/schemas/User'\n *                     cards:\n *                       type: array\n *                       description: Related cards\n *                       items:\n *                         allOf:\n *                           - $ref: '#/components/schemas/Card'\n *                           - type: object\n *                             properties:\n *                               isSubscribed:\n *                                 type: boolean\n *                                 description: Whether the current user is subscribed to the card\n *                                 example: true\n *                     cardMemberships:\n *                       type: array\n *                       description: Related card-membership associations\n *                       items:\n *                         $ref: '#/components/schemas/CardMembership'\n *                     cardLabels:\n *                       type: array\n *                       description: Related card-label associations\n *                       items:\n *                         $ref: '#/components/schemas/CardLabel'\n *                     taskLists:\n *                       type: array\n *                       description: Related task lists\n *                       items:\n *                         $ref: '#/components/schemas/TaskList'\n *                     tasks:\n *                       type: array\n *                       description: Related tasks\n *                       items:\n *                         $ref: '#/components/schemas/Task'\n *                     attachments:\n *                       type: array\n *                       description: Related attachments\n *                       items:\n *                         $ref: '#/components/schemas/Attachment'\n *                     customFieldGroups:\n *                       type: array\n *                       description: Related custom field groups\n *                       items:\n *                         $ref: '#/components/schemas/CustomFieldGroup'\n *                     customFields:\n *                       type: array\n *                       description: Related custom fields\n *                       items:\n *                         $ref: '#/components/schemas/CustomField'\n *                     customFieldValues:\n *                       type: array\n *                       description: Related custom field values\n *                       items:\n *                         $ref: '#/components/schemas/CustomFieldValue'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  LIST_NOT_FOUND: {\n    listNotFound: 'List not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    listNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { list, project } = await sails.helpers.lists\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);\n\n    if (!sails.helpers.lists.isFinite(list)) {\n      throw Errors.LIST_NOT_FOUND;\n    }\n\n    if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n          list.boardId,\n          currentUser.id,\n        );\n\n        if (!boardMembership) {\n          throw Errors.LIST_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    const cards = await Card.qm.getByListId(list.id);\n    const cardIds = sails.helpers.utils.mapRecords(cards);\n\n    const userIds = sails.helpers.utils.mapRecords(cards, 'creatorUserId', true, true);\n    const users = await User.qm.getByIds(userIds);\n\n    const cardMemberships = await CardMembership.qm.getByCardIds(cardIds);\n    const cardLabels = await CardLabel.qm.getByCardIds(cardIds);\n\n    const taskLists = await TaskList.qm.getByCardIds(cardIds);\n    const taskListIds = sails.helpers.utils.mapRecords(taskLists);\n\n    const tasks = await Task.qm.getByTaskListIds(taskListIds);\n    const attachments = await Attachment.qm.getByCardIds(cardIds);\n\n    const customFieldGroups = await CustomFieldGroup.qm.getByCardIds(cardIds);\n    const customFieldGroupIds = sails.helpers.utils.mapRecords(customFieldGroups);\n\n    const customFields = await CustomField.qm.getByCustomFieldGroupIds(customFieldGroupIds);\n    const customFieldValues = await CustomFieldValue.qm.getByCardIds(cardIds);\n\n    const cardSubscriptions = await CardSubscription.qm.getByCardIdsAndUserId(\n      cardIds,\n      currentUser.id,\n    );\n\n    const isSubscribedByCardId = cardSubscriptions.reduce(\n      (result, cardSubscription) => ({\n        ...result,\n        [cardSubscription.cardId]: true,\n      }),\n      {},\n    );\n\n    cards.forEach((card) => {\n      // eslint-disable-next-line no-param-reassign\n      card.isSubscribed = isSubscribedByCardId[card.id] || false;\n    });\n\n    return {\n      item: list,\n      included: {\n        cards,\n        cardMemberships,\n        cardLabels,\n        taskLists,\n        tasks,\n        customFieldGroups,\n        customFields,\n        customFieldValues,\n        users: sails.helpers.users.presentMany(users, currentUser),\n        attachments: sails.helpers.attachments.presentMany(attachments),\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/lists/sort.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /lists/{id}/sort:\n *   post:\n *     summary: Sort cards in list\n *     description: Sorts all cards within a list. Requires board editor permissions.\n *     tags:\n *       - Lists\n *     operationId: sortList\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the list to sort\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - fieldName\n *             properties:\n *               fieldName:\n *                 type: string\n *                 enum: [name, dueDate, createdAt]\n *                 description: Field to sort cards by\n *                 example: name\n *               order:\n *                 type: string\n *                 enum: [asc, desc]\n *                 description: Sorting order\n *                 example: asc\n *     responses:\n *       200:\n *         description: List sorted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/List'\n *                 included:\n *                   type: object\n *                   properties:\n *                     cards:\n *                       type: array\n *                       description: Related cards\n *                       items:\n *                         $ref: '#/components/schemas/Card'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  LIST_NOT_FOUND: {\n    listNotFound: 'List not found',\n  },\n  CANNOT_BE_SORTED_AS_ENDLESS_LIST: {\n    cannotBeSortedAsEndlessList: 'Cannot be sorted as endless list',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    fieldName: {\n      type: 'string',\n      isIn: Object.values(List.SortFieldNames),\n      required: true,\n    },\n    order: {\n      type: 'string',\n      isIn: Object.values(List.SortOrders),\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    listNotFound: {\n      responseType: 'notFound',\n    },\n    cannotBeSortedAsEndlessList: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { list, board, project } = await sails.helpers.lists\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.LIST_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const options = _.pick(inputs, ['fieldName', 'order']);\n\n    const cards = await sails.helpers.lists.sortOne\n      .with({\n        options,\n        project,\n        board,\n        record: list,\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('cannotBeSortedAsEndlessList', () => Errors.CANNOT_BE_SORTED_AS_ENDLESS_LIST);\n\n    return {\n      item: list,\n      included: {\n        cards,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/lists/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /lists/{id}:\n *   patch:\n *     summary: Update list\n *     description: Updates a list. Can move lists between boards. Requires board editor permissions.\n *     tags:\n *       - Lists\n *     operationId: updateList\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the list to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               boardId:\n *                 type: string\n *                 description: ID of the board to move list to\n *                 example: \"1357158568008091265\"\n *               type:\n *                 type: string\n *                 enum: [active, closed]\n *                 description: Type/status of the list\n *                 example: active\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the list within the board\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the list\n *                 example: To Do\n *               color:\n *                 type: string\n *                 enum: [berry-red, pumpkin-orange, lagoon-blue, pink-tulip, light-mud, orange-peel, bright-moss, antique-blue, dark-granite, turquoise-sea]\n *                 nullable: true\n *                 description: Color for the list\n *                 example: lagoon-blue\n *     responses:\n *       200:\n *         description: List updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/List'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  LIST_NOT_FOUND: {\n    listNotFound: 'List not found',\n  },\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    boardId: idInput,\n    type: {\n      type: 'string',\n      isIn: List.KANBAN_TYPES,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n    color: {\n      type: 'string',\n      isIn: List.COLORS,\n      allowNull: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    listNotFound: {\n      responseType: 'notFound',\n    },\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.lists\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);\n\n    let { list } = pathToProject;\n    const { board, project } = pathToProject;\n\n    let boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.LIST_NOT_FOUND; // Forbidden\n    }\n\n    if (!sails.helpers.lists.isKanban(list)) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    let nextProject;\n    let nextBoard;\n\n    if (!_.isUndefined(inputs.boardId)) {\n      ({ board: nextBoard, project: nextProject } = await sails.helpers.boards\n        .getPathToProjectById(inputs.boardId)\n        .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND));\n\n      boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n        nextBoard.id,\n        currentUser.id,\n      );\n\n      if (!boardMembership) {\n        throw Errors.BOARD_NOT_FOUND; // Forbidden\n      }\n\n      if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    }\n\n    const values = _.pick(inputs, ['type', 'position', 'name', 'color']);\n\n    list = await sails.helpers.lists.updateOne.with({\n      project,\n      board,\n      record: list,\n      values: {\n        ...values,\n        project: nextProject,\n        board: nextBoard,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!list) {\n      throw Errors.LIST_NOT_FOUND;\n    }\n\n    return {\n      item: list,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/notification-services/create-in-board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /boards/{boardId}/notification-services:\n *   post:\n *     summary: Create notification service for board\n *     description: Creates a new notification service for a board. Requires project manager permissions.\n *     tags:\n *       - Notification Services\n *     operationId: createBoardNotificationService\n *     parameters:\n *       - name: boardId\n *         in: path\n *         required: true\n *         description: ID of the board to create notification service for\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - url\n *               - format\n *             properties:\n *               url:\n *                 type: string\n *                 maxLength: 512\n *                 description: URL endpoint for notifications\n *                 example: https://service.example.com/planka\n *               format:\n *                 type: string\n *                 enum: [text, markdown, html]\n *                 description: Format for notification messages\n *                 example: text\n *     responses:\n *       200:\n *         description: Notification service created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/NotificationService'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  BOARD_NOT_FOUND: {\n    boardNotFound: 'Board not found',\n  },\n  LIMIT_REACHED: {\n    limitReached: 'Limit reached',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    boardId: {\n      ...idInput,\n      required: true,\n    },\n    url: {\n      type: 'string',\n      maxLength: 512,\n      required: true,\n    },\n    format: {\n      type: 'string',\n      isIn: Object.values(NotificationService.Formats),\n      required: true,\n    },\n  },\n\n  exits: {\n    boardNotFound: {\n      responseType: 'notFound',\n    },\n    limitReached: {\n      responseType: 'conflict',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { board, project } = await sails.helpers.boards\n      .getPathToProjectById(inputs.boardId)\n      .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.BOARD_NOT_FOUND; // Forbidden\n    }\n\n    const values = _.pick(inputs, ['url', 'format']);\n\n    const notificationService = await sails.helpers.notificationServices.createOneInBoard\n      .with({\n        project,\n        values: {\n          ...values,\n          board,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('limitReached', () => Errors.LIMIT_REACHED);\n\n    return {\n      item: notificationService,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/notification-services/create-in-user.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /users/{userId}/notification-services:\n *   post:\n *     summary: Create notification service for user\n *     description: Creates a new notification service for a user. Users can only create services for themselves.\n *     tags:\n *       - Notification Services\n *     operationId: createUserNotificationService\n *     parameters:\n *       - name: userId\n *         in: path\n *         required: true\n *         description: ID of the user to create notification service for (must be the current user)\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - url\n *               - format\n *             properties:\n *               url:\n *                 type: string\n *                 maxLength: 512\n *                 description: URL endpoint for notifications\n *                 example: https://service.example.com/planka\n *               format:\n *                 type: string\n *                 enum: [text, markdown, html]\n *                 description: Format for notification messages\n *                 example: text\n *     responses:\n *       200:\n *         description: Notification service created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/NotificationService'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n  LIMIT_REACHED: {\n    limitReached: 'Limit reached',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    userId: {\n      ...idInput,\n      required: true,\n    },\n    url: {\n      type: 'string',\n      maxLength: 512,\n      required: true,\n    },\n    format: {\n      type: 'string',\n      isIn: Object.values(NotificationService.Formats),\n      required: true,\n    },\n  },\n\n  exits: {\n    userNotFound: {\n      responseType: 'notFound',\n    },\n    limitReached: {\n      responseType: 'conflict',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    if (inputs.userId !== currentUser.id) {\n      throw Errors.USER_NOT_FOUND; // Forbidden\n    }\n\n    const values = _.pick(inputs, ['url', 'format']);\n\n    const notificationService = await sails.helpers.notificationServices.createOneInUser\n      .with({\n        values: {\n          ...values,\n          user: currentUser,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('limitReached', () => Errors.LIMIT_REACHED);\n\n    return {\n      item: notificationService,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/notification-services/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /notification-services/{id}:\n *   delete:\n *     summary: Delete notification service\n *     description: Deletes a notification service. Users can delete their own services, project managers can delete board services.\n *     tags:\n *       - Notification Services\n *     operationId: deleteNotificationService\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the notification service to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Notification service deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/NotificationService'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOTIFICATION_SERVICE_NOT_FOUND: {\n    notificationServiceNotFound: 'Notification service not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notificationServiceNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.notificationServices\n      .getPathToUserById(inputs.id)\n      .intercept('pathNotFound', () => Errors.NOTIFICATION_SERVICE_NOT_FOUND);\n\n    let { notificationService } = pathToProject;\n    const { user, board, project } = pathToProject;\n\n    if (notificationService.userId) {\n      if (user.id !== currentUser.id) {\n        throw Errors.NOTIFICATION_SERVICE_NOT_FOUND; // Forbidden\n      }\n\n      notificationService = await sails.helpers.notificationServices.deleteOneInUser.with({\n        user,\n        record: notificationService,\n        actorUser: currentUser,\n        request: this.req,\n      });\n    } else if (notificationService.boardId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        throw Errors.NOTIFICATION_SERVICE_NOT_FOUND; // Forbidden\n      }\n\n      notificationService = await sails.helpers.notificationServices.deleteOneInBoard.with({\n        project,\n        board,\n        record: notificationService,\n        actorUser: currentUser,\n        request: this.req,\n      });\n    }\n\n    if (!notificationService) {\n      throw Errors.NOTIFICATION_SERVICE_NOT_FOUND;\n    }\n\n    return {\n      item: notificationService,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/notification-services/test.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /notification-services/{id}/test:\n *   post:\n *     summary: Test notification service\n *     description: Sends a test notification to verify the notification service is working. Users can test their own services, project managers can test board services.\n *     tags:\n *       - Notification Services\n *     operationId: testNotificationService\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the notification service to test\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Test notification sent successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/NotificationService'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOTIFICATION_SERVICE_NOT_FOUND: {\n    notificationServiceNotFound: 'Notification service not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notificationServiceNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { notificationService, user, project } = await sails.helpers.notificationServices\n      .getPathToUserById(inputs.id)\n      .intercept('pathNotFound', () => Errors.NOTIFICATION_SERVICE_NOT_FOUND);\n\n    if (notificationService.userId) {\n      if (user.id !== currentUser.id) {\n        throw Errors.NOTIFICATION_SERVICE_NOT_FOUND; // Forbidden\n      }\n    } else if (notificationService.boardId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        throw Errors.NOTIFICATION_SERVICE_NOT_FOUND; // Forbidden\n      }\n    }\n\n    /* eslint-disable no-underscore-dangle */\n    await sails.helpers.utils.sendNotifications.with({\n      services: [_.pick(notificationService, ['url', 'format'])],\n      title: this.req.i18n.__('Test Title'),\n      bodyByFormat: {\n        text: this.req.i18n.__('This is a test text message!'),\n        markdown: this.req.i18n.__('This is a *test* **markdown** `message`!'),\n        html: this.req.i18n.__('This is a <i>test</i> <b>html</b> <code>message</code>!'),\n      },\n    });\n    /* eslint-enable no-underscore-dangle */\n\n    return {\n      item: notificationService,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/notification-services/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /notification-services/{id}:\n *   patch:\n *     summary: Update notification service\n *     description: Updates a notification service. Users can update their own services, project managers can update board services.\n *     tags:\n *       - Notification Services\n *     operationId: updateNotificationService\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the notification service to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               url:\n *                 type: string\n *                 maxLength: 512\n *                 description: URL endpoint for notifications\n *                 example: https://service.example.com/planka\n *               format:\n *                 type: string\n *                 enum: [text, markdown, html]\n *                 description: Format for notification messages\n *                 example: text\n *     responses:\n *       200:\n *         description: Notification service updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/NotificationService'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOTIFICATION_SERVICE_NOT_FOUND: {\n    notificationServiceNotFound: 'Notification service not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    url: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 512,\n    },\n    format: {\n      type: 'string',\n      isIn: Object.values(NotificationService.Formats),\n    },\n  },\n\n  exits: {\n    notificationServiceNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.notificationServices\n      .getPathToUserById(inputs.id)\n      .intercept('pathNotFound', () => Errors.NOTIFICATION_SERVICE_NOT_FOUND);\n\n    let { notificationService } = pathToProject;\n    const { user, board, project } = pathToProject;\n\n    const values = _.pick(inputs, ['url', 'format']);\n\n    if (notificationService.userId) {\n      if (user.id !== currentUser.id) {\n        throw Errors.NOTIFICATION_SERVICE_NOT_FOUND; // Forbidden\n      }\n\n      notificationService = await sails.helpers.notificationServices.updateOneInUser.with({\n        values,\n        user,\n        record: notificationService,\n        actorUser: currentUser,\n        request: this.req,\n      });\n    } else if (notificationService.boardId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        throw Errors.NOTIFICATION_SERVICE_NOT_FOUND; // Forbidden\n      }\n\n      notificationService = await sails.helpers.notificationServices.updateOneInBoard.with({\n        values,\n        project,\n        board,\n        record: notificationService,\n        actorUser: currentUser,\n        request: this.req,\n      });\n    }\n\n    if (!notificationService) {\n      throw Errors.NOTIFICATION_SERVICE_NOT_FOUND;\n    }\n\n    return {\n      item: notificationService,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/notifications/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /notifications:\n *   get:\n *     summary: Get user notifications\n *     description: Retrieves all unread notifications for the current user, including creator users.\n *     tags:\n *       - Notifications\n *     operationId: getNotifications\n *     responses:\n *       200:\n *         description: Notifications retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - items\n *                 - included\n *               properties:\n *                 items:\n *                   type: array\n *                   items:\n *                     $ref: '#/components/schemas/Notification'\n *                 included:\n *                   type: object\n *                   required:\n *                     - users\n *                   properties:\n *                     users:\n *                       type: array\n *                       description: Related users\n *                       items:\n *                         $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n */\n\nmodule.exports = {\n  async fn() {\n    const { currentUser } = this.req;\n\n    const notifications = await Notification.qm.getUnreadByUserId(currentUser.id);\n\n    const userIds = sails.helpers.utils.mapRecords(notifications, 'creatorUserId', true, true);\n    const users = await User.qm.getByIds(userIds);\n\n    return {\n      items: notifications,\n      included: {\n        users: sails.helpers.users.presentMany(users, currentUser),\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/notifications/read-all.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /notifications/read-all:\n *   post:\n *     summary: Mark all notifications as read\n *     description: Marks all notifications for the current user as read.\n *     tags:\n *       - Notifications\n *     operationId: readAllNotifications\n *     responses:\n *       200:\n *         description: Notifications marked as read successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - items\n *               properties:\n *                 items:\n *                   type: array\n *                   items:\n *                     $ref: '#/components/schemas/Notification'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n */\n\nmodule.exports = {\n  async fn() {\n    const { currentUser } = this.req;\n\n    const notifications = await sails.helpers.notifications.readAllForUser.with({\n      user: currentUser,\n      request: this.req,\n    });\n\n    return {\n      items: notifications,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/notifications/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /notifications/{id}:\n *   get:\n *     summary: Get notification details\n *     description: Retrieves notification, including creator users. Users can only access their own notifications.\n *     tags:\n *       - Notifications\n *     operationId: getNotification\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the notification to retrieve\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Notification details retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Notification'\n *                 included:\n *                   type: object\n *                   required:\n *                     - users\n *                   properties:\n *                     users:\n *                       type: array\n *                       description: Related users\n *                       items:\n *                         $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOTIFICATION_NOT_FOUND: {\n    notificationNotFound: 'Notification not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notificationNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const notification = await Notification.qm.getOneById(inputs.id, {\n      userId: currentUser.id,\n    });\n\n    if (!notification) {\n      throw Errors.NOTIFICATION_NOT_FOUND;\n    }\n\n    const users = notification.creatorUserId\n      ? await User.qm.getByIds([notification.creatorUserId])\n      : [];\n\n    return {\n      item: notification,\n      included: {\n        users,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/notifications/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /notifications/{id}:\n *   patch:\n *     summary: Update notification\n *     description: Updates a notification. Users can only update their own notifications.\n *     tags:\n *       - Notifications\n *     operationId: updateNotification\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the notification to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               isRead:\n *                 type: boolean\n *                 description: Whether the notification has been read\n *                 example: true\n *     responses:\n *       200:\n *         description: Notification updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Notification'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOTIFICATION_NOT_FOUND: {\n    notificationNotFound: 'Notification not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    isRead: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    notificationNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    let notification = await Notification.qm.getOneById(inputs.id, {\n      userId: currentUser.id,\n    });\n\n    if (!notification) {\n      throw Errors.NOTIFICATION_NOT_FOUND;\n    }\n\n    const values = _.pick(inputs, ['isRead']);\n\n    notification = await sails.helpers.notifications.updateOne.with({\n      values,\n      record: notification,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!notification) {\n      throw Errors.NOTIFICATION_NOT_FOUND;\n    }\n\n    return {\n      item: notification,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/project-managers/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /projects/{projectId}/project-managers:\n *   post:\n *     summary: Create project manager\n *     description: Creates a project manager within a project. Requires admin privileges for shared projects or existing project manager permissions. The user must be an admin or project owner.\n *     tags:\n *       - Project Managers\n *     operationId: createProjectManager\n *     parameters:\n *       - name: projectId\n *         in: path\n *         required: true\n *         description: ID of the project to create the project manager in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - userId\n *             properties:\n *               userId:\n *                 type: string\n *                 description: ID of the user who is assigned as project manager\n *                 example: \"1357158568008091265\"\n *     responses:\n *       200:\n *         description: Project manager created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/ProjectManager'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  PROJECT_NOT_FOUND: {\n    projectNotFound: 'Project not found',\n  },\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n  USER_ALREADY_PROJECT_MANAGER: {\n    userAlreadyProjectManager: 'User already project manager',\n  },\n  USER_MUST_BE_ADMIN_OR_PROJECT_OWNER: {\n    userMustBeAdminOrProjectOwner: 'User must be admin or project owner',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    projectId: {\n      ...idInput,\n      required: true,\n    },\n    userId: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    projectNotFound: {\n      responseType: 'notFound',\n    },\n    userNotFound: {\n      responseType: 'notFound',\n    },\n    userAlreadyProjectManager: {\n      responseType: 'conflict',\n    },\n    userMustBeAdminOrProjectOwner: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const project = await Project.qm.getOneById(inputs.projectId);\n\n    if (!project) {\n      throw Errors.PROJECT_NOT_FOUND;\n    }\n\n    if (currentUser.role !== User.Roles.ADMIN) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        throw Errors.PROJECT_NOT_FOUND; // Forbidden\n      }\n    }\n\n    if (project.ownerProjectManagerId) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const user = await User.qm.getOneById(inputs.userId, {\n      withDeactivated: false,\n    });\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    const projectManager = await sails.helpers.projectManagers.createOne\n      .with({\n        values: {\n          project,\n          user,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('userAlreadyProjectManager', () => Errors.USER_ALREADY_PROJECT_MANAGER)\n      .intercept(\n        'userInValuesMustBeAdminOrProjectOwner',\n        () => Errors.USER_MUST_BE_ADMIN_OR_PROJECT_OWNER,\n      );\n\n    return {\n      item: projectManager,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/project-managers/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /project-managers/{id}:\n *   delete:\n *     summary: Delete project manager\n *     description: Deletes a project manager. Requires admin privileges for shared projects or existing project manager permissions. Cannot remove the last project manager.\n *     tags:\n *       - Project Managers\n *     operationId: deleteProjectManager\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the project manager to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Project manager deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/ProjectManager'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  PROJECT_MANAGER_NOT_FOUND: {\n    projectManagerNotFound: 'Project manager not found',\n  },\n  MUST_NOT_BE_LAST: {\n    mustNotBeLast: 'Must not be last',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    projectManagerNotFound: {\n      responseType: 'notFound',\n    },\n    mustNotBeLast: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.projectManagers\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.PROJECT_MANAGER_NOT_FOUND);\n\n    let { projectManager } = pathToProject;\n    const { project } = pathToProject;\n\n    if (currentUser.role !== User.Roles.ADMIN) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        throw Errors.PROJECT_MANAGER_NOT_FOUND; // Forbidden\n      }\n    }\n\n    if (project.ownerProjectManagerId) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const user = await User.qm.getOneById(projectManager.userId);\n\n    projectManager = await sails.helpers.projectManagers.deleteOne\n      .with({\n        user,\n        project,\n        record: projectManager,\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('mustNotBeLast', () => Errors.MUST_NOT_BE_LAST);\n\n    if (!projectManager) {\n      throw Errors.PROJECT_MANAGER_NOT_FOUND;\n    }\n\n    return {\n      item: projectManager,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/projects/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /projects:\n *   post:\n *     summary: Create project\n *     description: Creates a project. The current user automatically becomes a project manager.\n *     tags:\n *       - Projects\n *     operationId: createProject\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - type\n *               - name\n *             properties:\n *               type:\n *                 type: string\n *                 enum: [private, shared]\n *                 description: Type of the project\n *                 example: private\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the project\n *                 example: Development Project\n *               description:\n *                 type: string\n *                 maxLength: 1024\n *                 nullable: true\n *                 description: Detailed description of the project\n *                 example: A project for developing new features...\n *     responses:\n *       200:\n *         description: Project created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Project'\n *                 included:\n *                   type: object\n *                   required:\n *                     - projectManagers\n *                   properties:\n *                     projectManagers:\n *                       type: array\n *                       description: Related project managers\n *                       items:\n *                         $ref: '#/components/schemas/ProjectManager'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n */\n\nmodule.exports = {\n  inputs: {\n    type: {\n      type: 'string',\n      isIn: Object.values(Project.Types),\n      required: true,\n    },\n    name: {\n      type: 'string',\n      maxLength: 128,\n      required: true,\n    },\n    description: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 1024,\n      allowNull: true,\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const values = _.pick(inputs, ['type', 'name', 'description']);\n\n    const { project, projectManager } = await sails.helpers.projects.createOne.with({\n      values,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: project,\n      included: {\n        projectManagers: [projectManager],\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/projects/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /projects/{id}:\n *   delete:\n *     summary: Delete project\n *     description: Deletes a project. The project must not have any boards. Requires project manager permissions.\n *     tags:\n *       - Projects\n *     operationId: deleteProject\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the project to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Project deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Project'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  PROJECT_NOT_FOUND: {\n    projectNotFound: 'Project not found',\n  },\n  MUST_NOT_HAVE_BOARDS: {\n    mustNotHaveBoards: 'Must not have boards',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    projectNotFound: {\n      responseType: 'notFound',\n    },\n    mustNotHaveBoards: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    let project = await Project.qm.getOneById(inputs.id);\n\n    if (!project) {\n      throw Errors.PROJECT_NOT_FOUND;\n    }\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    if (!isProjectManager) {\n      throw Errors.PROJECT_NOT_FOUND; // Forbidden\n    }\n\n    project = await sails.helpers.projects.deleteOne\n      .with({\n        record: project,\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('mustNotHaveBoards', () => Errors.MUST_NOT_HAVE_BOARDS);\n\n    if (!project) {\n      throw Errors.PROJECT_NOT_FOUND;\n    }\n\n    return {\n      item: project,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/projects/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /projects:\n *   get:\n *     summary: Get all accessible projects\n *     description: Retrieves all projects the current user has access to, including managed projects, membership projects, and shared projects (for admins).\n *     tags:\n *       - Projects\n *     operationId: getProjects\n *     responses:\n *       200:\n *         description: Projects retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - items\n *                 - included\n *               properties:\n *                 items:\n *                   type: array\n *                   items:\n *                     allOf:\n *                       - $ref: '#/components/schemas/Project'\n *                       - type: object\n *                         properties:\n *                           isFavorite:\n *                             type: boolean\n *                             description: Whether the project is marked as favorite by the current user\n *                             example: true\n *                 included:\n *                   type: object\n *                   required:\n *                     - users\n *                     - projectManagers\n *                     - backgroundImages\n *                     - baseCustomFieldGroups\n *                     - boards\n *                     - boardMemberships\n *                     - customFields\n *                     - notificationServices\n *                   properties:\n *                     users:\n *                       type: array\n *                       description: Related users\n *                       items:\n *                         $ref: '#/components/schemas/User'\n *                     projectManagers:\n *                       type: array\n *                       description: Related project managers\n *                       items:\n *                         $ref: '#/components/schemas/ProjectManager'\n *                     backgroundImages:\n *                       type: array\n *                       description: Related background images\n *                       items:\n *                         $ref: '#/components/schemas/BackgroundImage'\n *                     baseCustomFieldGroups:\n *                       type: array\n *                       description: Related base custom field groups\n *                       items:\n *                         $ref: '#/components/schemas/BaseCustomFieldGroup'\n *                     boards:\n *                       type: array\n *                       description: Related boards\n *                       items:\n *                         $ref: '#/components/schemas/Board'\n *                     boardMemberships:\n *                       type: array\n *                       description: Related board memberships (for current user)\n *                       items:\n *                         $ref: '#/components/schemas/BoardMembership'\n *                     customFields:\n *                       type: array\n *                       description: Related custom fields\n *                       items:\n *                         $ref: '#/components/schemas/CustomField'\n *                     notificationServices:\n *                       type: array\n *                       description: Related notification services (for managed projects)\n *                       items:\n *                         $ref: '#/components/schemas/NotificationService'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n */\n\nmodule.exports = {\n  async fn() {\n    const { currentUser } = this.req;\n\n    let sharedProjects;\n    let sharedProjectIds;\n\n    const managerProjectIds = await sails.helpers.users.getManagerProjectIds(currentUser.id);\n    const fullyVisibleProjectIds = [...managerProjectIds];\n\n    if (currentUser.role === User.Roles.ADMIN) {\n      sharedProjects = await Project.qm.getShared({\n        exceptIdOrIds: managerProjectIds,\n      });\n\n      sharedProjectIds = sails.helpers.utils.mapRecords(sharedProjects);\n      fullyVisibleProjectIds.push(...sharedProjectIds);\n    }\n\n    const boardMemberships = await BoardMembership.qm.getByUserId(currentUser.id);\n    const membershipBoardIds = sails.helpers.utils.mapRecords(boardMemberships, 'boardId');\n\n    const membershipBoards = await Board.qm.getByIds(membershipBoardIds, {\n      exceptProjectIdOrIds: fullyVisibleProjectIds,\n    });\n\n    const membershipProjectIds = sails.helpers.utils.mapRecords(\n      membershipBoards,\n      'projectId',\n      true,\n    );\n\n    const projectIds = [...managerProjectIds, ...membershipProjectIds];\n    const projects = await Project.qm.getByIds(projectIds);\n\n    if (sharedProjectIds) {\n      projectIds.push(...sharedProjectIds);\n      projects.push(...sharedProjects);\n    }\n\n    const fullyVisibleBoards = await Board.qm.getByProjectIds(fullyVisibleProjectIds);\n    const boards = [...fullyVisibleBoards, ...membershipBoards];\n\n    const projectFavorites = await ProjectFavorite.qm.getByProjectIdsAndUserId(\n      projectIds,\n      currentUser.id,\n    );\n\n    const projectManagers = await ProjectManager.qm.getByProjectIds(projectIds);\n\n    const userIds = sails.helpers.utils.mapRecords(projectManagers, 'userId', true);\n    const users = await User.qm.getByIds(userIds);\n\n    const backgroundImages = await BackgroundImage.qm.getByProjectIds(projectIds);\n\n    const baseCustomFieldGroups = await BaseCustomFieldGroup.qm.getByProjectIds(projectIds);\n    const baseCustomFieldGroupsIds = sails.helpers.utils.mapRecords(baseCustomFieldGroups);\n\n    const customFields =\n      await CustomField.qm.getByBaseCustomFieldGroupIds(baseCustomFieldGroupsIds);\n\n    let notificationServices = [];\n    if (managerProjectIds.length > 0) {\n      const managerProjectIdsSet = new Set(managerProjectIds);\n\n      const managerBoardIds = boards.flatMap((board) =>\n        managerProjectIdsSet.has(board.projectId) ? board.id : [],\n      );\n\n      notificationServices = await NotificationService.qm.getByBoardIds(managerBoardIds);\n    }\n\n    const isFavoriteByProjectId = projectFavorites.reduce(\n      (result, projectFavorite) => ({\n        ...result,\n        [projectFavorite.projectId]: true,\n      }),\n      {},\n    );\n\n    projects.forEach((project) => {\n      // eslint-disable-next-line no-param-reassign\n      project.isFavorite = isFavoriteByProjectId[project.id] || false;\n    });\n\n    return {\n      items: projects,\n      included: {\n        projectManagers,\n        baseCustomFieldGroups,\n        boards,\n        boardMemberships,\n        customFields,\n        notificationServices,\n        users: sails.helpers.users.presentMany(users, currentUser),\n        backgroundImages: sails.helpers.backgroundImages.presentMany(backgroundImages),\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/projects/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /projects/{id}:\n *   get:\n *     summary: Get project details\n *     description: Retrieves comprehensive project information, including boards, board memberships, and other related data.\n *     tags:\n *       - Projects\n *     operationId: getProject\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the project to retrieve\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Project details retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   allOf:\n *                     - $ref: '#/components/schemas/Project'\n *                     - type: object\n *                       properties:\n *                         isFavorite:\n *                           type: boolean\n *                           description: Whether the project is marked as favorite by the current user\n *                           example: true\n *                 included:\n *                   type: object\n *                   required:\n *                     - users\n *                     - projectManagers\n *                     - backgroundImages\n *                     - baseCustomFieldGroups\n *                     - boards\n *                     - boardMemberships\n *                     - customFields\n *                     - notificationServices\n *                   properties:\n *                     users:\n *                       type: array\n *                       description: Related users\n *                       items:\n *                         $ref: '#/components/schemas/User'\n *                     projectManagers:\n *                       type: array\n *                       description: Related project managers\n *                       items:\n *                         $ref: '#/components/schemas/ProjectManager'\n *                     backgroundImages:\n *                       type: array\n *                       description: Related background images\n *                       items:\n *                         $ref: '#/components/schemas/BackgroundImage'\n *                     baseCustomFieldGroups:\n *                       type: array\n *                       description: Related base custom field groups\n *                       items:\n *                         $ref: '#/components/schemas/BaseCustomFieldGroup'\n *                     boards:\n *                       type: array\n *                       description: Related boards\n *                       items:\n *                         $ref: '#/components/schemas/Board'\n *                     boardMemberships:\n *                       type: array\n *                       description: Related board memberships (for current user)\n *                       items:\n *                         $ref: '#/components/schemas/BoardMembership'\n *                     customFields:\n *                       type: array\n *                       description: Related custom fields\n *                       items:\n *                         $ref: '#/components/schemas/CustomField'\n *                     notificationServices:\n *                       type: array\n *                       description: Related notification services (for managed projects)\n *                       items:\n *                         $ref: '#/components/schemas/NotificationService'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  PROJECT_NOT_FOUND: {\n    projectNotFound: 'Project not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    projectNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const project = await Project.qm.getOneById(inputs.id);\n\n    if (!project) {\n      throw Errors.PROJECT_NOT_FOUND;\n    }\n\n    const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);\n\n    const boardMemberships = await BoardMembership.qm.getByProjectIdAndUserId(\n      project.id,\n      currentUser.id,\n    );\n\n    let boards;\n    if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n      if (!isProjectManager) {\n        if (boardMemberships.length === 0) {\n          throw Errors.PROJECT_NOT_FOUND; // Forbidden\n        }\n\n        const boardIds = sails.helpers.utils.mapRecords(boardMemberships, 'boardId');\n        boards = await Board.qm.getByIds(boardIds);\n      }\n    }\n\n    if (!boards) {\n      boards = await Board.qm.getByProjectId(project.id);\n    }\n\n    project.isFavorite = await sails.helpers.users.isProjectFavorite(currentUser.id, project.id);\n\n    const projectManagers = await ProjectManager.qm.getByProjectId(project.id);\n\n    const userIds = sails.helpers.utils.mapRecords(projectManagers, 'userId');\n    const users = await User.qm.getByIds(userIds);\n\n    const backgroundImages = await BackgroundImage.qm.getByProjectId(project.id);\n\n    const baseCustomFieldGroups = await BaseCustomFieldGroup.qm.getByProjectId(project.id);\n    const baseCustomFieldGroupsIds = sails.helpers.utils.mapRecords(baseCustomFieldGroups);\n\n    const customFields =\n      await CustomField.qm.getByBaseCustomFieldGroupIds(baseCustomFieldGroupsIds);\n\n    let notificationServices = [];\n    if (isProjectManager) {\n      boardIds = sails.helpers.utils.mapRecords(boards);\n      notificationServices = await NotificationService.qm.getByBoardIds(boardIds);\n    }\n\n    return {\n      item: project,\n      included: {\n        projectManagers,\n        baseCustomFieldGroups,\n        boards,\n        boardMemberships,\n        customFields,\n        notificationServices,\n        users: sails.helpers.users.presentMany(users, currentUser),\n        backgroundImages: sails.helpers.backgroundImages.presentMany(backgroundImages),\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/projects/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /projects/{id}:\n *   patch:\n *     summary: Update project\n *     description: Updates a project. Accessible fields depend on user permissions.\n *     tags:\n *       - Projects\n *     operationId: updateProject\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the project to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               ownerProjectManagerId:\n *                 type: string\n *                 nullable: true\n *                 description: ID of the project manager who owns the project\n *                 example: \"1357158568008091265\"\n *               backgroundImageId:\n *                 type: string\n *                 nullable: true\n *                 description: ID of the background image used as background\n *                 example: \"1357158568008091266\"\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the project\n *                 example: Development Project\n *               description:\n *                 type: string\n *                 maxLength: 1024\n *                 nullable: true\n *                 description: Detailed description of the project\n *                 example: A project for developing new features...\n *               backgroundType:\n *                 type: string\n *                 enum: [gradient, image]\n *                 nullable: true\n *                 description: Type of background for the project\n *                 example: gradient\n *               backgroundGradient:\n *                 type: string\n *                 enum: [old-lime, ocean-dive, tzepesch-style, jungle-mesh, strawberry-dust, purple-rose, sun-scream, warm-rust, sky-change, green-eyes, blue-xchange, blood-orange, sour-peel, green-ninja, algae-green, coral-reef, steel-grey, heat-waves, velvet-lounge, purple-rain, blue-steel, blueish-curve, prism-light, green-mist, red-curtain]\n *                 nullable: true\n *                 description: Gradient background for the project\n *                 example: ocean-dive\n *               isHidden:\n *                 type: boolean\n *                 description: Whether the project is hidden\n *                 example: false\n *               isFavorite:\n *                 type: boolean\n *                 description: Whether the project is marked as favorite by the current user\n *                 example: true\n *     responses:\n *       200:\n *         description: Project updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Project'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  PROJECT_NOT_FOUND: {\n    projectNotFound: 'Project not found',\n  },\n  OWNER_PROJECT_MANAGER_NOT_FOUND: {\n    ownerProjectManagerNotFound: 'Owner project manager not found',\n  },\n  BACKGROUND_IMAGE_NOT_FOUND: {\n    backgroundImageNotFound: 'Background image not found',\n  },\n  PROJECT_ALREADY_HAS_OWNER_PROJECT_MANAGER: {\n    projectAlreadyHasOwnerProjectManager: 'Project already has owner project manager',\n  },\n  OWNER_PROJECT_MANAGER_MUST_BE_LAST_MANAGER: {\n    ownerProjectManagerMustBeLastManager: 'Owner project manager must be last manager',\n  },\n  BACKGROUND_IMAGE_MUST_BE_PRESENT: {\n    backgroundImageMustBePresent: 'Background image must be present',\n  },\n  BACKGROUND_GRADIENT_MUST_BE_PRESENT: {\n    backgroundGradientMustBePresent: 'Background gradient must be present',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    ownerProjectManagerId: {\n      ...idInput,\n      allowNull: true,\n    },\n    backgroundImageId: {\n      ...idInput,\n      allowNull: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n    description: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 1024,\n      allowNull: true,\n    },\n    backgroundType: {\n      type: 'string',\n      isIn: Object.values(Project.BackgroundTypes),\n      allowNull: true,\n    },\n    backgroundGradient: {\n      type: 'string',\n      isIn: Project.BACKGROUND_GRADIENTS,\n      allowNull: true,\n    },\n    isHidden: {\n      type: 'boolean',\n    },\n    isFavorite: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    projectNotFound: {\n      responseType: 'notFound',\n    },\n    ownerProjectManagerNotFound: {\n      responseType: 'notFound',\n    },\n    backgroundImageNotFound: {\n      responseType: 'notFound',\n    },\n    projectAlreadyHasOwnerProjectManager: {\n      responseType: 'conflict',\n    },\n    ownerProjectManagerMustBeLastManager: {\n      responseType: 'unprocessableEntity',\n    },\n    backgroundImageMustBePresent: {\n      responseType: 'unprocessableEntity',\n    },\n    backgroundGradientMustBePresent: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    let project = await Project.qm.getOneById(inputs.id);\n\n    if (!project) {\n      throw Errors.PROJECT_NOT_FOUND;\n    }\n\n    const projectManager = await ProjectManager.qm.getOneByProjectIdAndUserId(\n      project.id,\n      currentUser.id,\n    );\n\n    const availableInputKeys = ['id', 'isFavorite'];\n    if (project.ownerProjectManagerId) {\n      if (projectManager) {\n        if (!_.isNil(inputs.ownerProjectManagerId)) {\n          throw Errors.NOT_ENOUGH_RIGHTS;\n        }\n\n        availableInputKeys.push('ownerProjectManagerId', 'isHidden');\n      }\n    } else if (currentUser.role === User.Roles.ADMIN) {\n      availableInputKeys.push('ownerProjectManagerId', 'isHidden');\n    } else if (projectManager) {\n      availableInputKeys.push('isHidden');\n    }\n\n    if (projectManager) {\n      availableInputKeys.push(\n        'backgroundImageId',\n        'name',\n        'description',\n        'backgroundType',\n        'backgroundGradient',\n      );\n    }\n\n    if (_.difference(Object.keys(inputs), availableInputKeys).length > 0) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    let nextOwnerProjectManager;\n    if (inputs.ownerProjectManagerId) {\n      nextOwnerProjectManager = await ProjectManager.qm.getOneById(inputs.ownerProjectManagerId, {\n        projectId: project.id,\n      });\n\n      if (!nextOwnerProjectManager) {\n        throw Errors.OWNER_PROJECT_MANAGER_NOT_FOUND;\n      }\n\n      delete inputs.ownerProjectManagerId; // eslint-disable-line no-param-reassign\n    }\n\n    let nextBackgroundImage;\n    if (inputs.backgroundImageId) {\n      nextBackgroundImage = await BackgroundImage.qm.getOneById(inputs.backgroundImageId, {\n        projectId: project.id,\n      });\n\n      if (!nextBackgroundImage) {\n        throw Errors.BACKGROUND_IMAGE_NOT_FOUND;\n      }\n\n      delete inputs.backgroundImageId; // eslint-disable-line no-param-reassign\n    }\n\n    if (!_.isUndefined(inputs.isFavorite)) {\n      if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n        if (!projectManager) {\n          const boardMembershipsTotal =\n            await sails.helpers.projects.getBoardMembershipsTotalByIdAndUserId(\n              project.id,\n              currentUser.id,\n            );\n\n          if (boardMembershipsTotal === 0) {\n            throw Errors.PROJECT_NOT_FOUND; // Forbidden\n          }\n        }\n      }\n    }\n\n    const values = _.pick(inputs, [\n      'ownerProjectManagerId',\n      'backgroundImageId',\n      'name',\n      'description',\n      'backgroundType',\n      'backgroundGradient',\n      'isHidden',\n      'isFavorite',\n    ]);\n\n    project = await sails.helpers.projects.updateOne\n      .with({\n        record: project,\n        values: {\n          ...values,\n          ownerProjectManager: nextOwnerProjectManager,\n          backgroundImage: nextBackgroundImage,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept(\n        'ownerProjectManagerInValuesMustBeLastManager',\n        () => Errors.OWNER_PROJECT_MANAGER_MUST_BE_LAST_MANAGER,\n      )\n      .intercept(\n        'backgroundImageInValuesMustBePresent',\n        () => Errors.BACKGROUND_IMAGE_MUST_BE_PRESENT,\n      )\n      .intercept(\n        'backgroundGradientInValuesMustBePresent',\n        () => Errors.BACKGROUND_GRADIENT_MUST_BE_PRESENT,\n      )\n      .intercept(\n        'alreadyHasOwnerProjectManager',\n        () => Errors.PROJECT_ALREADY_HAS_OWNER_PROJECT_MANAGER,\n      );\n\n    if (!project) {\n      throw Errors.PROJECT_NOT_FOUND;\n    }\n\n    return {\n      item: project,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/swagger/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst fs = require('fs');\nconst path = require('path');\n\nconst SWAGGER_PATH = path.join(sails.config.appPath, 'swagger.json');\n\nmodule.exports = {\n  async fn() {\n    if (!sails.config.custom.swaggerExposed) {\n      return this.res.notFound();\n    }\n\n    let specification;\n    try {\n      const content = fs.readFileSync(SWAGGER_PATH, 'utf8');\n      specification = JSON.parse(content);\n    } catch (error) {\n      sails.log.warn('swagger.json not found, run \"npm run swagger:generate\" to create it');\n      return this.res.notFound();\n    }\n\n    return specification;\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/task-lists/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /cards/{cardId}/task-lists:\n *   post:\n *     summary: Create task list\n *     description: Creates a task list within a card. Requires board editor permissions.\n *     tags:\n *       - Task Lists\n *     operationId: createTaskList\n *     parameters:\n *       - name: cardId\n *         in: path\n *         required: true\n *         description: ID of the card to create task list in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - position\n *               - name\n *             properties:\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the task list within the card\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the task list\n *                 example: Development Tasks\n *               showOnFrontOfCard:\n *                 type: boolean\n *                 description: Whether to show the task list on the front of the card\n *                 example: true\n *               hideCompletedTasks:\n *                 type: boolean\n *                 description: Whether to hide completed tasks\n *                 example: false\n *     responses:\n *       200:\n *         description: Task list created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/TaskList'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  CARD_NOT_FOUND: {\n    cardNotFound: 'Card not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    cardId: {\n      ...idInput,\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      maxLength: 128,\n      required: true,\n    },\n    showOnFrontOfCard: {\n      type: 'boolean',\n    },\n    hideCompletedTasks: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    cardNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { card, list, board, project } = await sails.helpers.cards\n      .getPathToProjectById(inputs.cardId)\n      .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.CARD_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const values = _.pick(inputs, ['position', 'name', 'showOnFrontOfCard', 'hideCompletedTasks']);\n\n    const taskList = await sails.helpers.taskLists.createOne.with({\n      project,\n      board,\n      list,\n      values: {\n        ...values,\n        card,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: taskList,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/task-lists/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /task-lists/{id}:\n *   delete:\n *     summary: Delete task list\n *     description: Deletes a task list and all its tasks. Requires board editor permissions.\n *     tags:\n *       - Task Lists\n *     operationId: deleteTaskList\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the task list to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Task list deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/TaskList'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  TASK_LIST_NOT_FOUND: {\n    taskListNotFound: 'Task list not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    taskListNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.taskLists\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.TASK_LIST_NOT_FOUND);\n\n    let { taskList } = pathToProject;\n    const { card, list, board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.TASK_LIST_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    taskList = await sails.helpers.taskLists.deleteOne.with({\n      project,\n      board,\n      list,\n      card,\n      record: taskList,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!taskList) {\n      throw Errors.TASK_LIST_NOT_FOUND;\n    }\n\n    return {\n      item: taskList,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/task-lists/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /task-lists/{id}:\n *   get:\n *     summary: Get task list details\n *     description: Retrieves task list information, including tasks. Requires access to the card.\n *     tags:\n *       - Task Lists\n *     operationId: getTaskList\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the task list to retrieve\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Task list details retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/TaskList'\n *                 included:\n *                   type: object\n *                   required:\n *                     - tasks\n *                   properties:\n *                     tasks:\n *                       type: array\n *                       description: Related tasks\n *                       items:\n *                         $ref: '#/components/schemas/Task'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  TASK_LIST_NOT_FOUND: {\n    taskListNotFound: 'Task list not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    taskListNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { taskList, board, project } = await sails.helpers.taskLists\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.TASK_LIST_NOT_FOUND);\n\n    if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {\n      const isProjectManager = await sails.helpers.users.isProjectManager(\n        currentUser.id,\n        project.id,\n      );\n\n      if (!isProjectManager) {\n        const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n          board.id,\n          currentUser.id,\n        );\n\n        if (!boardMembership) {\n          throw Errors.TASK_LIST_NOT_FOUND; // Forbidden\n        }\n      }\n    }\n\n    const tasks = await Task.qm.getByTaskListId(taskList.id);\n\n    return {\n      item: taskList,\n      included: {\n        tasks,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/task-lists/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /task-lists/{id}:\n *   patch:\n *     summary: Update task list\n *     description: Updates a task list. Requires board editor permissions.\n *     tags:\n *       - Task Lists\n *     operationId: updateTaskList\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the task list to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the task list within the card\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the task list\n *                 example: Development Tasks\n *               showOnFrontOfCard:\n *                 type: boolean\n *                 description: Whether to show the task list on the front of the card\n *                 example: true\n *               hideCompletedTasks:\n *                 type: boolean\n *                 description: Whether to hide completed tasks\n *                 example: false\n *     responses:\n *       200:\n *         description: Task list updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/TaskList'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  TASK_LIST_NOT_FOUND: {\n    taskListNotFound: 'Task list not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n    showOnFrontOfCard: {\n      type: 'boolean',\n    },\n    hideCompletedTasks: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    taskListNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.taskLists\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.TASK_LIST_NOT_FOUND);\n\n    let { taskList } = pathToProject;\n    const { card, list, board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.TASK_LIST_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const values = _.pick(inputs, ['position', 'name', 'showOnFrontOfCard', 'hideCompletedTasks']);\n\n    taskList = await sails.helpers.taskLists.updateOne.with({\n      values,\n      project,\n      board,\n      list,\n      card,\n      record: taskList,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!taskList) {\n      throw Errors.TASK_LIST_NOT_FOUND;\n    }\n\n    return {\n      item: taskList,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/tasks/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /task-lists/{taskListId}/tasks:\n *   post:\n *     summary: Create task\n *     description: Creates a task within a task list. Either `linkedCardId` or `name` must be provided. Requires board editor permissions.\n *     tags:\n *       - Tasks\n *     operationId: createTask\n *     parameters:\n *       - name: taskListId\n *         in: path\n *         required: true\n *         description: ID of the task list to create task in\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - position\n *             properties:\n *               linkedCardId:\n *                 type: string\n *                 description: ID of the card linked to the task\n *                 example: \"1357158568008091265\"\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the task within the task list\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 1024\n *                 nullable: true\n *                 description: Name/title of the task (required if `linkedCardId` is not provided)\n *                 example: Write unit tests\n *               isCompleted:\n *                 type: boolean\n *                 description: Whether the task is completed\n *                 example: false\n *     responses:\n *       200:\n *         description: Task created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Task'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  TASK_LIST_NOT_FOUND: {\n    taskListNotFound: 'Task list not found',\n  },\n  LINKED_CARD_NOT_FOUND: {\n    linkedCardNotFound: 'Linked card not found',\n  },\n  LINKED_CARD_OR_NAME_MUST_BE_PRESENT: {\n    linkedCardOrNameMustBePresent: 'Linked card or name must be present',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    taskListId: {\n      ...idInput,\n      required: true,\n    },\n    linkedCardId: idInput,\n    position: {\n      type: 'number',\n      min: 0,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 1024,\n      allowNull: true,\n    },\n    isCompleted: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    taskListNotFound: {\n      responseType: 'notFound',\n    },\n    linkedCardNotFound: {\n      responseType: 'notFound',\n    },\n    linkedCardOrNameMustBePresent: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const { taskList, card, list, board, project } = await sails.helpers.taskLists\n      .getPathToProjectById(inputs.taskListId)\n      .intercept('pathNotFound', () => Errors.TASK_LIST_NOT_FOUND);\n\n    let boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.TASK_LIST_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    let linkedCard;\n    if (!_.isUndefined(inputs.linkedCardId)) {\n      const path = await sails.helpers.cards\n        .getPathToProjectById(inputs.linkedCardId)\n        .intercept('pathNotFound', () => Errors.LINKED_CARD_NOT_FOUND);\n\n      ({ card: linkedCard } = path);\n\n      if (currentUser.role !== User.Roles.ADMIN || path.project.ownerProjectManagerId) {\n        const isProjectManager = await sails.helpers.users.isProjectManager(\n          currentUser.id,\n          path.project.id,\n        );\n\n        if (!isProjectManager) {\n          boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n            linkedCard.boardId,\n            currentUser.id,\n          );\n\n          if (!boardMembership) {\n            throw Errors.LINKED_CARD_NOT_FOUND; // Forbidden\n          }\n        }\n      }\n    }\n\n    const values = _.pick(inputs, ['position', 'name', 'isCompleted']);\n\n    const task = await sails.helpers.tasks.createOne\n      .with({\n        project,\n        board,\n        list,\n        card,\n        values: {\n          ...values,\n          taskList,\n          linkedCard,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept(\n        'linkedCardOrNameMustBeInValues',\n        () => Errors.LINKED_CARD_OR_NAME_MUST_BE_PRESENT,\n      );\n\n    return {\n      item: task,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/tasks/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /tasks/{id}:\n *   delete:\n *     summary: Delete task\n *     description: Deletes a task. Requires board editor permissions.\n *     tags:\n *       - Tasks\n *     operationId: deleteTask\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the task to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Task deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Task'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  TASK_NOT_FOUND: {\n    taskNotFound: 'Task not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    taskNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.tasks\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.TASK_NOT_FOUND);\n\n    let { task } = pathToProject;\n    const { taskList, card, list, board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.TASK_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    task = await sails.helpers.tasks.deleteOne.with({\n      project,\n      board,\n      list,\n      card,\n      taskList,\n      record: task,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!task) {\n      throw Errors.TASK_NOT_FOUND;\n    }\n\n    return {\n      item: task,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/tasks/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /tasks/{id}:\n *   patch:\n *     summary: Update task\n *     description: Updates a task. Linked card tasks have limited update options. Requires board editor permissions.\n *     tags:\n *       - Tasks\n *     operationId: updateTask\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the task to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               taskListId:\n *                 type: string\n *                 description: ID of the task list to move the task to\n *                 example: \"1357158568008091265\"\n *               assigneeUserId:\n *                 type: string\n *                 nullable: true\n *                 description: ID of the user assigned to the task (null to unassign)\n *                 example: \"1357158568008091266\"\n *               position:\n *                 type: number\n *                 minimum: 0\n *                 description: Position of the task within the task list\n *                 example: 65536\n *               name:\n *                 type: string\n *                 maxLength: 1024\n *                 description: Name/title of the task\n *                 example: Write unit tests\n *               isCompleted:\n *                 type: boolean\n *                 description: Whether the task is completed\n *                 example: true\n *     responses:\n *       200:\n *         description: Task updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Task'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  TASK_NOT_FOUND: {\n    taskNotFound: 'Task not found',\n  },\n  TASK_LIST_NOT_FOUND: {\n    taskListNotFound: 'Task list not found',\n  },\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    taskListId: idInput,\n    assigneeUserId: {\n      ...idInput,\n      allowNull: true,\n    },\n    position: {\n      type: 'number',\n      min: 0,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 1024,\n    },\n    isCompleted: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    taskNotFound: {\n      responseType: 'notFound',\n    },\n    taskListNotFound: {\n      responseType: 'notFound',\n    },\n    userNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const pathToProject = await sails.helpers.tasks\n      .getPathToProjectById(inputs.id)\n      .intercept('pathNotFound', () => Errors.TASK_NOT_FOUND);\n\n    let { task } = pathToProject;\n    const { taskList, card, list, board, project } = pathToProject;\n\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      board.id,\n      currentUser.id,\n    );\n\n    if (!boardMembership) {\n      throw Errors.TASK_NOT_FOUND; // Forbidden\n    }\n\n    if (boardMembership.role !== BoardMembership.Roles.EDITOR) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    if (task.linkedCardId) {\n      const availableInputKeys = ['id', 'taskListId', 'position'];\n\n      if (_.difference(Object.keys(inputs), availableInputKeys).length > 0) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    }\n\n    let nextTaskList;\n    if (!_.isUndefined(inputs.taskListId)) {\n      nextTaskList = await TaskList.qm.getOneById(inputs.taskListId, {\n        cardId: card.id,\n      });\n\n      if (!nextTaskList) {\n        throw Errors.TASK_LIST_NOT_FOUND;\n      }\n    }\n\n    if (inputs.assigneeUserId) {\n      const isBoardMember = await sails.helpers.users.isBoardMember(\n        inputs.assigneeUserId,\n        board.id,\n      );\n\n      if (!isBoardMember) {\n        throw Errors.USER_NOT_FOUND;\n      }\n    }\n\n    const values = _.pick(inputs, ['assigneeUserId', 'position', 'name', 'isCompleted']);\n\n    task = await sails.helpers.tasks.updateOne.with({\n      project,\n      board,\n      list,\n      card,\n      taskList,\n      record: task,\n      values: {\n        ...values,\n        taskList: nextTaskList,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!task) {\n      throw Errors.TASK_NOT_FOUND;\n    }\n\n    return {\n      item: task,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/terms/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /terms:\n *   get:\n *     summary: Get terms and conditions\n *     description: Retrieves terms and conditions in the specified language.\n *     tags:\n *       - Terms\n *     operationId: getTerms\n *     parameters:\n *       - name: language\n *         in: query\n *         required: false\n *         description: Language code for terms localization\n *         schema:\n *           type: string\n *           example: en-US\n *     responses:\n *       200:\n *         description: Terms content retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   type: object\n *                   required:\n *                     - type\n *                     - language\n *                     - content\n *                     - signature\n *                   properties:\n *                     language:\n *                       type: string\n *                       description: Language code used\n *                       example: en-US\n *                     content:\n *                       type: string\n *                       description: Markdown content of the terms\n *                       example: Your privacy is important to us...\n *                     signature:\n *                       type: string\n *                       description: Signature hash of terms\n *                       example: 940226c4c41f51afe3980ceb63704e752636526f4c52a4ea579e85b247493d94\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *     security: []\n */\n\nmodule.exports = {\n  inputs: {\n    language: {\n      type: 'string',\n    },\n  },\n\n  async fn(inputs) {\n    const terms = await sails.hooks.terms.getPayload(inputs.language);\n\n    return {\n      item: terms,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/users/create-api-key.js",
    "content": "/*!\n * Copyright (c) 2025 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /users/{id}/api-key:\n *   post:\n *     summary: Create user API key\n *     description: Generates a user's API key. The full API key is returned only once and cannot be retrieved again.\n *     tags:\n *       - Users\n *     operationId: createUserApiKey\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the user to create API key for\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: API key created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/User'\n *                 included:\n *                   type: object\n *                   required:\n *                     - apiKey\n *                   properties:\n *                     apiKey:\n *                       type: string\n *                       description: API key of the user (returned only once)\n *                       example: D89VszVs_oSS6TdDtYmi0j1LhugOioY40dDVssESO\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    userNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    let user = await User.qm.getOneById(inputs.id);\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    const { key: apiKey, prefix: apiKeyPrefix } = sails.helpers.utils.generateApiKey();\n\n    user = await sails.helpers.users.updateOne.with({\n      record: user,\n      values: {\n        apiKeyPrefix,\n        apiKeyHash: sails.helpers.utils.hash(apiKey),\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    return {\n      item: sails.helpers.users.presentOne(user, currentUser),\n      included: {\n        apiKey,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/users/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /users:\n *   post:\n *     summary: Create user\n *     description: Creates a user account. Requires admin privileges.\n *     tags:\n *       - Users\n *     operationId: createUser\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - email\n *               - password\n *               - role\n *               - name\n *             properties:\n *               email:\n *                 type: string\n *                 format: email\n *                 maxLength: 256\n *                 description: Email address for login and notifications\n *                 example: john.doe@example.com\n *               password:\n *                 type: string\n *                 maxLength: 256\n *                 description: Password for user authentication (must meet password requirements)\n *                 example: SecurePassword123!\n *               role:\n *                 type: string\n *                 enum: [admin, projectOwner, boardUser]\n *                 description: User role defining access permissions\n *                 example: admin\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Full display name of the user\n *                 example: John Doe\n *               username:\n *                 type: string\n *                 minLength: 3\n *                 maxLength: 32\n *                 pattern: \"^[a-zA-Z0-9]+((_{1}|\\\\.|){1}[a-zA-Z0-9])*$\"\n *                 nullable: true\n *                 description: Unique username for user identification\n *                 example: john_doe\n *               phone:\n *                 type: string\n *                 maxLength: 128\n *                 nullable: true\n *                 description: Contact phone number\n *                 example: \"+1234567890\"\n *               organization:\n *                 type: string\n *                 maxLength: 128\n *                 nullable: true\n *                 description: Organization or company name\n *                 example: Acme Corporation\n *               language:\n *                 type: string\n *                 enum: [ar-YE, bg-BG, ca-ES, cs-CZ, da-DK, de-DE, el-GR, en-GB, en-US, es-ES, et-EE, fa-IR, fi-FI, fr-FR, hu-HU, id-ID, it-IT, ja-JP, ko-KR, nl-NL, pl-PL, pt-BR, pt-PT, ro-RO, ru-RU, sk-SK, sr-Cyrl-RS, sr-Latn-RS, sv-SE, tr-TR, uk-UA, uz-UZ, vi-VN, zh-CN, zh-TW]\n *                 nullable: true\n *                 description: Preferred language for user interface and notifications (if null - will be set automatically on the first login)\n *                 example: en-US\n *               subscribeToOwnCards:\n *                 type: boolean\n *                 description: Whether the user subscribes to their own cards\n *                 example: false\n *               subscribeToCardWhenCommenting:\n *                 type: boolean\n *                 description: Whether the user subscribes to cards when commenting\n *                 example: true\n *               turnOffRecentCardHighlighting:\n *                 type: boolean\n *                 description: Whether recent card highlighting is disabled\n *                 example: false\n *     responses:\n *       200:\n *         description: User created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n */\n\nconst { isPassword } = require('../../../utils/validators');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  EMAIL_ALREADY_IN_USE: {\n    emailAlreadyInUse: 'Email already in use',\n  },\n  USERNAME_ALREADY_IN_USE: {\n    usernameAlreadyInUse: 'Username already in use',\n  },\n  ACTIVE_LIMIT_REACHED: {\n    activeLimitReached: 'Active limit reached',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    email: {\n      type: 'string',\n      maxLength: 256,\n      isEmail: true,\n      required: true,\n    },\n    password: {\n      type: 'string',\n      maxLength: 256,\n      custom: isPassword,\n      required: true,\n    },\n    role: {\n      type: 'string',\n      isIn: Object.values(User.Roles),\n      required: true,\n    },\n    name: {\n      type: 'string',\n      maxLength: 128,\n      required: true,\n    },\n    username: {\n      type: 'string',\n      isNotEmptyString: true,\n      minLength: 3,\n      maxLength: 32,\n      regex: /^[a-zA-Z0-9]+((_|\\.)?[a-zA-Z0-9])*$/,\n      allowNull: true,\n    },\n    phone: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n      allowNull: true,\n    },\n    organization: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n      allowNull: true,\n    },\n    language: {\n      type: 'string',\n      isIn: User.LANGUAGES,\n      allowNull: true,\n    },\n    subscribeToOwnCards: {\n      type: 'boolean',\n    },\n    subscribeToCardWhenCommenting: {\n      type: 'boolean',\n    },\n    turnOffRecentCardHighlighting: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    emailAlreadyInUse: {\n      responseType: 'conflict',\n    },\n    usernameAlreadyInUse: {\n      responseType: 'conflict',\n    },\n    activeLimitReached: {\n      responseType: 'conflict',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    if (sails.config.custom.oidcEnforced) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const values = _.pick(inputs, [\n      'email',\n      'password',\n      'role',\n      'name',\n      'username',\n      'phone',\n      'organization',\n      'language',\n      'subscribeToOwnCards',\n      'subscribeToCardWhenCommenting',\n      'turnOffRecentCardHighlighting',\n    ]);\n\n    const user = await sails.helpers.users.createOne\n      .with({\n        values,\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE)\n      .intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE)\n      .intercept('activeLimitReached', () => Errors.ACTIVE_LIMIT_REACHED);\n\n    return {\n      item: sails.helpers.users.presentOne(user, currentUser),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/users/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /users/{id}:\n *   delete:\n *     summary: Delete user\n *     description: Deletes a user account. Cannot delete the default admin user. Requires admin privileges.\n *     tags:\n *       - Users\n *     operationId: deleteUser\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the user to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: User deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    userNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    let user = await User.qm.getOneById(inputs.id);\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    if (user.email === sails.config.custom.defaultAdminEmail) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    user = await sails.helpers.users.deleteOne.with({\n      record: user,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    return {\n      item: sails.helpers.users.presentOne(user, currentUser),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/users/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /users:\n *   get:\n *     summary: Get all users\n *     description: Retrieves a list of all users. Requires admin or project owner privileges.\n *     tags:\n *       - Users\n *     operationId: getUsers\n *     responses:\n *       200:\n *         description: Users retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - items\n *               properties:\n *                 items:\n *                   type: array\n *                   items:\n *                     $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n */\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n};\n\nmodule.exports = {\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n  },\n\n  async fn() {\n    const { currentUser } = this.req;\n\n    if (!sails.helpers.users.isAdminOrProjectOwner(currentUser)) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    const users = await User.qm.getAll();\n\n    return {\n      items: sails.helpers.users.presentMany(users, currentUser),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/users/show.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /users/{id}:\n *   get:\n *     summary: Get user details\n *     description: Retrieves a user. Use 'me' as ID to get the current user.\n *     tags:\n *       - Users\n *     operationId: getUser\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the user or 'me' for current user\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *       - name: subscribe\n *         in: query\n *         required: false\n *         description: Whether to subscribe to real-time updates for this user (only for socket connections)\n *         schema:\n *           type: boolean\n *           example: true\n *     responses:\n *       200:\n *         description: User details retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *                 - included\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/User'\n *                 included:\n *                   type: object\n *                   required:\n *                     - notificationServices\n *                   properties:\n *                     notificationServices:\n *                       type: array\n *                       description: Related notification services (for current user)\n *                       items:\n *                         $ref: '#/components/schemas/NotificationService'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { ID_REGEX, MAX_STRING_ID, isIdInRange } = require('../../../utils/validators');\n\nconst Errors = {\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n};\n\nconst CURRENT_USER_ID = 'me';\n\nconst ID_OR_CURRENT_USER_ID_REGEX = new RegExp(`${ID_REGEX.source}|^${CURRENT_USER_ID}$`);\n\nconst isCurrentUserIdOrIdInRange = (value) => value === CURRENT_USER_ID || isIdInRange(value);\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      maxLength: MAX_STRING_ID.length,\n      regex: ID_OR_CURRENT_USER_ID_REGEX,\n      custom: isCurrentUserIdOrIdInRange,\n      required: true,\n    },\n    subscribe: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    userNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    let user;\n    let notificationServices = [];\n\n    if (inputs.id === CURRENT_USER_ID || inputs.id === currentUser.id) {\n      user = currentUser;\n      notificationServices = await NotificationService.qm.getByUserId(currentUser.id);\n\n      if (inputs.subscribe && this.req.isSocket) {\n        sails.sockets.join(this.req, `user:${user.id}`);\n      }\n    } else {\n      if (!sails.helpers.users.isAdminOrProjectOwner(currentUser)) {\n        throw Errors.USER_NOT_FOUND; // Forbidden\n      }\n\n      user = await User.qm.getOneById(inputs.id);\n\n      if (!user) {\n        throw Errors.USER_NOT_FOUND;\n      }\n    }\n\n    return {\n      item: sails.helpers.users.presentOne(user, currentUser),\n      included: {\n        notificationServices,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/users/update-avatar.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /users/{id}/avatar:\n *   post:\n *     summary: Update user avatar\n *     description: Updates a user's avatar image. Users can update their own avatar, admins can update any user's avatar.\n *     tags:\n *       - Users\n *     operationId: updateUserAvatar\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the user whose avatar to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         multipart/form-data:\n *           schema:\n *             type: object\n *             required:\n *               - file\n *             properties:\n *               file:\n *                 type: string\n *                 format: binary\n *                 description: Avatar image file (must be an image format)\n *     responses:\n *       200:\n *         description: Avatar updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       422:\n *         $ref: '#/components/responses/UnprocessableEntity'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n  NO_FILE_WAS_UPLOADED: {\n    noFileWasUploaded: 'No file was uploaded',\n  },\n  FILE_IS_NOT_IMAGE: {\n    fileIsNotImage: 'File is not image',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    userNotFound: {\n      responseType: 'notFound',\n    },\n    noFileWasUploaded: {\n      responseType: 'unprocessableEntity',\n    },\n    fileIsNotImage: {\n      responseType: 'unprocessableEntity',\n    },\n    uploadError: {\n      responseType: 'unprocessableEntity',\n    },\n  },\n\n  async fn(inputs, exits) {\n    const { currentUser } = this.req;\n\n    let user;\n    if (currentUser.role === User.Roles.ADMIN) {\n      user = await User.qm.getOneById(inputs.id);\n\n      if (!user) {\n        throw Errors.USER_NOT_FOUND;\n      }\n    } else if (inputs.id !== currentUser.id) {\n      throw Errors.USER_NOT_FOUND; // Forbidden\n    } else {\n      user = currentUser;\n    }\n\n    let files;\n    try {\n      files = await sails.helpers.utils.receiveFile(this.req.file('file'));\n    } catch (error) {\n      return exits.uploadError(error.message); // TODO: add error\n    }\n\n    if (files.length === 0) {\n      throw Errors.NO_FILE_WAS_UPLOADED;\n    }\n\n    const file = _.last(files);\n\n    const avatar = await sails.helpers.users\n      .processUploadedAvatarFile(file)\n      .intercept('fileIsNotImage', () => Errors.FILE_IS_NOT_IMAGE);\n\n    user = await sails.helpers.users.updateOne.with({\n      record: user,\n      values: {\n        avatar,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    return exits.success({\n      item: sails.helpers.users.presentOne(user, currentUser),\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/users/update-email.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /users/{id}/email:\n *   patch:\n *     summary: Update user email\n *     description: Updates a user's email address. Users must provide current password when updating their own email. Admins can update any user's email without a password.\n *     tags:\n *       - Users\n *     operationId: updateUserEmail\n *     parameters:\n *       - in: path\n *         name: id\n *         required: true\n *         description: ID of the user whose email to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - email\n *             properties:\n *               email:\n *                 type: string\n *                 format: email\n *                 maxLength: 256\n *                 description: Email address for login and notifications\n *                 example: john.doe@example.com\n *               currentPassword:\n *                 type: string\n *                 maxLength: 256\n *                 description: Current password (required when updating own email)\n *                 example: SecurePassword123!\n *     responses:\n *       200:\n *         description: Email updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n */\n\nconst bcrypt = require('bcrypt');\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  INVALID_CURRENT_PASSWORD: {\n    invalidCurrentPassword: 'Invalid current password',\n  },\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n  EMAIL_ALREADY_IN_USE: {\n    emailAlreadyInUse: 'Email already in use',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    email: {\n      type: 'string',\n      maxLength: 256,\n      isEmail: true,\n      required: true,\n    },\n    currentPassword: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 256,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    invalidCurrentPassword: {\n      responseType: 'forbidden',\n    },\n    userNotFound: {\n      responseType: 'notFound',\n    },\n    emailAlreadyInUse: {\n      responseType: 'conflict',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    if (inputs.id === currentUser.id) {\n      if (!inputs.currentPassword) {\n        throw Errors.INVALID_CURRENT_PASSWORD;\n      }\n    } else if (currentUser.role !== User.Roles.ADMIN) {\n      throw Errors.USER_NOT_FOUND; // Forbidden\n    }\n\n    let user = await User.qm.getOneById(inputs.id);\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    if (\n      user.email === sails.config.custom.defaultAdminEmail ||\n      user.isSsoUser ||\n      sails.config.custom.demoMode\n    ) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    if (inputs.id === currentUser.id) {\n      const isCurrentPasswordValid = await bcrypt.compare(inputs.currentPassword, user.password);\n\n      if (!isCurrentPasswordValid) {\n        throw Errors.INVALID_CURRENT_PASSWORD;\n      }\n    }\n\n    const values = _.pick(inputs, ['email']);\n\n    user = await sails.helpers.users.updateOne\n      .with({\n        values,\n        record: user,\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE);\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    return {\n      item: sails.helpers.users.presentOne(user, currentUser),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/users/update-password.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /users/{id}/password:\n *   patch:\n *     summary: Update user password\n *     description: Updates a user's password. Users must provide a current password when updating their own password. Admins can update any user's password without the current password. Returns a new access token when updating own password.\n *     tags:\n *       - Users\n *     operationId: updateUserPassword\n *     parameters:\n *       - in: path\n *         name: id\n *         required: true\n *         description: ID of the user whose password to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - password\n *             properties:\n *               password:\n *                 type: string\n *                 maxLength: 256\n *                 description: Password (must meet password requirements)\n *                 example: SecurePassword123!\n *               currentPassword:\n *                 type: string\n *                 maxLength: 256\n *                 description: Current password (required when updating own password)\n *                 example: SecurePassword456!\n *     responses:\n *       200:\n *         description: Password updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/User'\n *                 included:\n *                   type: object\n *                   required:\n *                     - accessToken\n *                   properties:\n *                     accessToken:\n *                       type: string\n *                       description: New acces tokens (when updating own password)\n *                       example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ4...\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst bcrypt = require('bcrypt');\n\nconst { isPassword } = require('../../../utils/validators');\nconst { idInput } = require('../../../utils/inputs');\nconst { getRemoteAddress } = require('../../../utils/remote-address');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  INVALID_CURRENT_PASSWORD: {\n    invalidCurrentPassword: 'Invalid current password',\n  },\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    password: {\n      type: 'string',\n      maxLength: 256,\n      custom: isPassword,\n      required: true,\n    },\n    currentPassword: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 256,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    invalidCurrentPassword: {\n      responseType: 'forbidden',\n    },\n    userNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentSession, currentUser } = this.req;\n\n    if (inputs.id === currentUser.id) {\n      if (!inputs.currentPassword) {\n        throw Errors.INVALID_CURRENT_PASSWORD;\n      }\n    } else if (currentUser.role !== User.Roles.ADMIN) {\n      throw Errors.USER_NOT_FOUND; // Forbidden\n    }\n\n    let user = await User.qm.getOneById(inputs.id);\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    if (\n      user.email === sails.config.custom.defaultAdminEmail ||\n      user.isSsoUser ||\n      sails.config.custom.demoMode\n    ) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    if (inputs.id === currentUser.id) {\n      const isCurrentPasswordValid = await bcrypt.compare(inputs.currentPassword, user.password);\n\n      if (!isCurrentPasswordValid) {\n        throw Errors.INVALID_CURRENT_PASSWORD;\n      }\n    }\n\n    const values = _.pick(inputs, ['password']);\n\n    user = await sails.helpers.users.updateOne.with({\n      values,\n      record: user,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    if (currentSession && user.id === currentUser.id) {\n      const { token: accessToken } = sails.helpers.utils.createJwtToken(\n        user.id,\n        user.passwordChangedAt,\n      );\n\n      await Session.qm.createOne({\n        accessToken,\n        httpOnlyToken: currentSession.httpOnlyToken,\n        userId: user.id,\n        remoteAddress: getRemoteAddress(this.req),\n        userAgent: this.req.headers['user-agent'],\n      });\n\n      return {\n        item: sails.helpers.users.presentOne(user, currentUser),\n        included: {\n          accessToken,\n        },\n      };\n    }\n\n    return {\n      item: sails.helpers.users.presentOne(user, currentUser),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/users/update-username.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /users/{id}/username:\n *   patch:\n *     summary: Update user username\n *     description: Updates a user's username. Users must provide a current password when updating their own username (unless they are SSO users with `oidcIgnoreUsername` enabled). Admins can update any user's username without the current password.\n *     tags:\n *       - Users\n *     operationId: updateUserUsername\n *     parameters:\n *       - in: path\n *         name: id\n *         required: true\n *         description: ID of the user whose username to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               username:\n *                 type: string\n *                 minLength: 3\n *                 maxLength: 32\n *                 pattern: '^[a-zA-Z0-9]+((_|\\.)?[a-zA-Z0-9])*$'\n *                 nullable: true\n *                 description: Unique username for user identification\n *                 example: john_doe\n *               currentPassword:\n *                 type: string\n *                 maxLength: 256\n *                 description: Current password (required when updating own username)\n *                 example: SecurePassword123!\n *     responses:\n *       200:\n *         description: Username updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n */\n\nconst bcrypt = require('bcrypt');\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  INVALID_CURRENT_PASSWORD: {\n    invalidCurrentPassword: 'Invalid current password',\n  },\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n  USERNAME_ALREADY_IN_USE: {\n    usernameAlreadyInUse: 'Username already in use',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    username: {\n      type: 'string',\n      isNotEmptyString: true,\n      minLength: 3,\n      maxLength: 32,\n      regex: /^[a-zA-Z0-9]+((_|\\.)?[a-zA-Z0-9])*$/,\n      allowNull: true,\n    },\n    currentPassword: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 256,\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    invalidCurrentPassword: {\n      responseType: 'forbidden',\n    },\n    userNotFound: {\n      responseType: 'notFound',\n    },\n    usernameAlreadyInUse: {\n      responseType: 'conflict',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    if (inputs.id !== currentUser.id && currentUser.role !== User.Roles.ADMIN) {\n      throw Errors.USER_NOT_FOUND; // Forbidden\n    }\n\n    let user = await User.qm.getOneById(inputs.id);\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    if (user.email === sails.config.custom.defaultAdminEmail || sails.config.custom.demoMode) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    if (user.isSsoUser) {\n      if (!sails.config.custom.oidcIgnoreUsername) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    } else if (inputs.id === currentUser.id) {\n      if (!inputs.currentPassword) {\n        throw Errors.INVALID_CURRENT_PASSWORD;\n      }\n\n      const isCurrentPasswordValid = await bcrypt.compare(inputs.currentPassword, user.password);\n\n      if (!isCurrentPasswordValid) {\n        throw Errors.INVALID_CURRENT_PASSWORD;\n      }\n    }\n\n    const values = _.pick(inputs, ['username']);\n\n    user = await sails.helpers.users.updateOne\n      .with({\n        values,\n        record: user,\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE);\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    return {\n      item: sails.helpers.users.presentOne(user, currentUser),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/users/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /users/{id}:\n *   patch:\n *     summary: Update user\n *     description: Updates a user. Users can update their own profile, admins can update any user.\n *     tags:\n *       - Users\n *     operationId: updateUser\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the user to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               role:\n *                 type: string\n *                 enum: [admin, projectOwner, boardUser]\n *                 description: User role defining access permissions\n *                 example: admin\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Full display name of the user\n *                 example: John Doe\n *               avatar:\n *                 type: object\n *                 nullable: true\n *                 description: Avatar of the user (only null value to remove avatar)\n *                 example: null\n *               phone:\n *                 type: string\n *                 maxLength: 128\n *                 nullable: true\n *                 description: Contact phone number\n *                 example: \"+1234567890\"\n *               organization:\n *                 type: string\n *                 maxLength: 128\n *                 nullable: true\n *                 description: Organization or company name\n *                 example: Acme Corporation\n *               language:\n *                 type: string\n *                 enum: [ar-YE, bg-BG, ca-ES, cs-CZ, da-DK, de-DE, el-GR, en-GB, en-US, es-ES, et-EE, fa-IR, fi-FI, fr-FR, hu-HU, id-ID, it-IT, ja-JP, ko-KR, nl-NL, pl-PL, pt-BR, pt-PT, ro-RO, ru-RU, sk-SK, sr-Cyrl-RS, sr-Latn-RS, sv-SE, tr-TR, uk-UA, uz-UZ, vi-VN, zh-CN, zh-TW]\n *                 description: Preferred language for user interface and notifications\n *                 example: en-US\n *               apiKey:\n *                 type: object\n *                 nullable: true\n *                 description: API key of the user (only null value to remove API key)\n *                 example: null\n *               subscribeToOwnCards:\n *                 type: boolean\n *                 description: Whether the user subscribes to their own cards\n *                 example: false\n *               subscribeToCardWhenCommenting:\n *                 type: boolean\n *                 description: Whether the user subscribes to cards when commenting\n *                 example: true\n *               turnOffRecentCardHighlighting:\n *                 type: boolean\n *                 description: Whether recent card highlighting is disabled\n *                 example: false\n *               enableFavoritesByDefault:\n *                 type: boolean\n *                 description: Whether favorites are enabled by default\n *                 example: true\n *               defaultEditorMode:\n *                 type: string\n *                 enum: [wysiwyg, markup]\n *                 description: Default markdown editor mode\n *                 example: wysiwyg\n *               defaultHomeView:\n *                 type: string\n *                 enum: [gridProjects, groupedProjects]\n *                 description: Default view mode for the home page\n *                 example: groupedProjects\n *               defaultProjectsOrder:\n *                 type: string\n *                 enum: [byDefault, alphabetically, byCreationTime]\n *                 description: Default sort order for projects display\n *                 example: byDefault\n *               isSsoUser:\n *                 type: boolean\n *                 description: Whether the user is SSO user (only false value to unlink SSO, for admins)\n *                 example: false\n *               isDeactivated:\n *                 type: boolean\n *                 description: Whether the user account is deactivated and cannot log in (for admins)\n *                 example: false\n *     responses:\n *       200:\n *         description: User updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/User'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       403:\n *         $ref: '#/components/responses/Forbidden'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n */\n\nconst { is } = require('../../../utils/validators');\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  NOT_ENOUGH_RIGHTS: {\n    notEnoughRights: 'Not enough rights',\n  },\n  USER_NOT_FOUND: {\n    userNotFound: 'User not found',\n  },\n  ACTIVE_LIMIT_REACHED: {\n    activeLimitReached: 'Active limit reached',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    role: {\n      type: 'string',\n      isIn: Object.values(User.Roles),\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n    avatar: {\n      type: 'json',\n      custom: _.isNull,\n    },\n    phone: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n      allowNull: true,\n    },\n    organization: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n      allowNull: true,\n    },\n    language: {\n      type: 'string',\n      isIn: User.LANGUAGES,\n    },\n    apiKey: {\n      type: 'json',\n      custom: _.isNull,\n    },\n    subscribeToOwnCards: {\n      type: 'boolean',\n    },\n    subscribeToCardWhenCommenting: {\n      type: 'boolean',\n    },\n    turnOffRecentCardHighlighting: {\n      type: 'boolean',\n    },\n    enableFavoritesByDefault: {\n      type: 'boolean',\n    },\n    defaultEditorMode: {\n      type: 'string',\n      isIn: Object.values(User.EditorModes),\n    },\n    defaultHomeView: {\n      type: 'string',\n      isIn: Object.values(User.HomeViews),\n    },\n    defaultProjectsOrder: {\n      type: 'string',\n      isIn: Object.values(User.ProjectOrders),\n    },\n    isSsoUser: {\n      type: 'boolean',\n      custom: is(false),\n    },\n    isDeactivated: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    notEnoughRights: {\n      responseType: 'forbidden',\n    },\n    userNotFound: {\n      responseType: 'notFound',\n    },\n    activeLimitReached: {\n      responseType: 'conflict',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const availableInputKeys = ['id', 'name', 'avatar', 'phone', 'organization'];\n    if (inputs.id === currentUser.id) {\n      availableInputKeys.push(...User.PERSONAL_FIELD_NAMES);\n    } else if (currentUser.role === User.Roles.ADMIN) {\n      availableInputKeys.push('role', 'isSsoUser', 'isDeactivated');\n    } else {\n      throw Errors.USER_NOT_FOUND; // Forbidden\n    }\n\n    if (currentUser.role === User.Roles.ADMIN) {\n      availableInputKeys.push('apiKey');\n    }\n\n    if (_.difference(Object.keys(inputs), availableInputKeys).length > 0) {\n      throw Errors.NOT_ENOUGH_RIGHTS;\n    }\n\n    let user = await User.qm.getOneById(inputs.id);\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    // TODO: refactor\n    if (user.email === sails.config.custom.defaultAdminEmail || sails.config.custom.demoMode) {\n      if (inputs.role || inputs.name) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    } else if (user.isSsoUser) {\n      if (!sails.config.custom.oidcIgnoreRoles && inputs.role) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n\n      if (inputs.name) {\n        throw Errors.NOT_ENOUGH_RIGHTS;\n      }\n    }\n\n    const values = {\n      ..._.pick(inputs, [\n        'role',\n        'name',\n        'avatar',\n        'phone',\n        'organization',\n        'language',\n        'apiKey',\n        'subscribeToOwnCards',\n        'subscribeToCardWhenCommenting',\n        'turnOffRecentCardHighlighting',\n        'enableFavoritesByDefault',\n        'defaultEditorMode',\n        'defaultHomeView',\n        'defaultProjectsOrder',\n        'isSsoUser',\n        'isDeactivated',\n      ]),\n    };\n\n    user = await sails.helpers.users.updateOne\n      .with({\n        values,\n        record: user,\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('activeLimitReached', () => Errors.ACTIVE_LIMIT_REACHED);\n\n    if (!user) {\n      throw Errors.USER_NOT_FOUND;\n    }\n\n    return {\n      item: sails.helpers.users.presentOne(user, currentUser),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/webhooks/create.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /webhooks:\n *   post:\n *     summary: Create webhook\n *     description: Creates a webhook. Requires admin privileges.\n *     tags:\n *       - Webhooks\n *     operationId: createWebhook\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - name\n *               - url\n *             properties:\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the webhook\n *                 example: Webhook Updates\n *               url:\n *                 type: string\n *                 format: url\n *                 maxLength: 2048\n *                 description: URL endpoint for the webhook\n *                 example: https://service.example.com/planka\n *               accessToken:\n *                 type: string\n *                 maxLength: 512\n *                 nullable: true\n *                 description: Access token for webhook authentication\n *                 example: secret_token_123\n *               events:\n *                 type: string\n *                 maxLength: 2048\n *                 nullable: true\n *                 description: Comma-separated list of events that trigger the webhook\n *                 example: cardCreate,cardUpdate,cardDelete\n *               excludedEvents:\n *                 type: string\n *                 maxLength: 2048\n *                 nullable: true\n *                 description: Comma-separated list of events excluded from the webhook\n *                 example: userCreate,userUpdate,userDelete\n *     responses:\n *       200:\n *         description: Webhook created successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Webhook'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       409:\n *         $ref: '#/components/responses/Conflict'\n */\n\nconst { isUrl } = require('../../../utils/validators');\n\nconst Errors = {\n  LIMIT_REACHED: {\n    limitReached: 'Limit reached',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    name: {\n      type: 'string',\n      maxLength: 128,\n      required: true,\n    },\n    url: {\n      type: 'string',\n      maxLength: 2048,\n      custom: isUrl,\n      required: true,\n    },\n    accessToken: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 512,\n      allowNull: true,\n    },\n    events: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 2048,\n      allowNull: true,\n    },\n    excludedEvents: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 2048,\n      allowNull: true,\n    },\n  },\n\n  exits: {\n    limitReached: {\n      responseType: 'conflict',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    const values = _.pick(inputs, ['name', 'url', 'accessToken']);\n    const events = inputs.events && inputs.events.split(',');\n    const excludedEvents = inputs.excludedEvents && inputs.excludedEvents.split(',');\n\n    const webhook = await sails.helpers.webhooks.createOne\n      .with({\n        values: {\n          ...values,\n          events,\n          excludedEvents,\n        },\n        actorUser: currentUser,\n        request: this.req,\n      })\n      .intercept('limitReached', () => Errors.LIMIT_REACHED);\n\n    return {\n      item: webhook,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/webhooks/delete.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /webhooks/{id}:\n *   delete:\n *     summary: Delete webhook\n *     description: Deletes a webhook. Requires admin privileges.\n *     tags:\n *       - Webhooks\n *     operationId: deleteWebhook\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the webhook to delete\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     responses:\n *       200:\n *         description: Webhook deleted successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Webhook'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  WEBHOOK_NOT_FOUND: {\n    webhookNotFound: 'Webhook not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n  },\n\n  exits: {\n    webhookNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    let webhook = await Webhook.qm.getOneById(inputs.id);\n\n    if (!webhook) {\n      throw Errors.WEBHOOK_NOT_FOUND;\n    }\n\n    webhook = await sails.helpers.webhooks.deleteOne.with({\n      record: webhook,\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!webhook) {\n      throw Errors.WEBHOOK_NOT_FOUND;\n    }\n\n    return {\n      item: webhook,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/webhooks/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /webhooks:\n *   get:\n *     summary: Get all webhooks\n *     description: Retrieves a list of all configured webhooks. Requires admin privileges.\n *     tags:\n *       - Webhooks\n *     operationId: getWebhooks\n *     responses:\n *       200:\n *         description: Webhooks retrieved successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - items\n *               properties:\n *                 items:\n *                   type: array\n *                   items:\n *                     $ref: '#/components/schemas/Webhook'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n */\n\nmodule.exports = {\n  async fn() {\n    const webhooks = await Webhook.qm.getAll();\n\n    return {\n      items: webhooks,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/controllers/webhooks/update.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * @swagger\n * /webhooks/{id}:\n *   patch:\n *     summary: Update webhook\n *     description: Updates a webhook. Requires admin privileges.\n *     tags:\n *       - Webhooks\n *     operationId: updateWebhook\n *     parameters:\n *       - name: id\n *         in: path\n *         required: true\n *         description: ID of the webhook to update\n *         schema:\n *           type: string\n *           example: \"1357158568008091264\"\n *     requestBody:\n *       required: true\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             properties:\n *               name:\n *                 type: string\n *                 maxLength: 128\n *                 description: Name/title of the webhook\n *                 example: Webhook Updates\n *               url:\n *                 type: string\n *                 format: url\n *                 maxLength: 2048\n *                 description: URL endpoint for the webhook\n *                 example: https://service.example.com/planka\n *               accessToken:\n *                 type: string\n *                 maxLength: 512\n *                 nullable: true\n *                 description: Access token for webhook authentication\n *                 example: secret_token_123\n *               events:\n *                 type: string\n *                 maxLength: 2048\n *                 nullable: true\n *                 description: Comma-separated list of events that trigger the webhook\n *                 example: cardCreate,cardUpdate,cardDelete\n *               excludedEvents:\n *                 type: string\n *                 maxLength: 2048\n *                 nullable: true\n *                 description: Comma-separated list of events excluded from the webhook\n *                 example: userCreate,userUpdate,userDelete\n *     responses:\n *       200:\n *         description: Webhook updated successfully\n *         content:\n *           application/json:\n *             schema:\n *               type: object\n *               required:\n *                 - item\n *               properties:\n *                 item:\n *                   $ref: '#/components/schemas/Webhook'\n *       400:\n *         $ref: '#/components/responses/ValidationError'\n *       401:\n *         $ref: '#/components/responses/Unauthorized'\n *       404:\n *         $ref: '#/components/responses/NotFound'\n */\n\nconst { isUrl } = require('../../../utils/validators');\nconst { idInput } = require('../../../utils/inputs');\n\nconst Errors = {\n  WEBHOOK_NOT_FOUND: {\n    webhookNotFound: 'Webhook not found',\n  },\n};\n\nmodule.exports = {\n  inputs: {\n    id: {\n      ...idInput,\n      required: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 128,\n    },\n    url: {\n      type: 'string',\n      maxLength: 2048,\n      custom: isUrl,\n    },\n    accessToken: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 512,\n      allowNull: true,\n    },\n    events: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 2048,\n      allowNull: true,\n    },\n    excludedEvents: {\n      type: 'string',\n      isNotEmptyString: true,\n      maxLength: 2048,\n      allowNull: true,\n    },\n  },\n\n  exits: {\n    webhookNotFound: {\n      responseType: 'notFound',\n    },\n  },\n\n  async fn(inputs) {\n    const { currentUser } = this.req;\n\n    let webhook = await Webhook.qm.getOneById(inputs.id);\n\n    if (!webhook) {\n      throw Errors.WEBHOOK_NOT_FOUND;\n    }\n\n    const values = _.pick(inputs, ['name', 'url', 'accessToken']);\n    const events = inputs.events && inputs.events.split(',');\n    const excludedEvents = inputs.excludedEvents && inputs.excludedEvents.split(',');\n\n    webhook = await sails.helpers.webhooks.updateOne.with({\n      record: webhook,\n      values: {\n        ...values,\n        events,\n        excludedEvents,\n      },\n      actorUser: currentUser,\n      request: this.req,\n    });\n\n    if (!webhook) {\n      throw Errors.WEBHOOK_NOT_FOUND;\n    }\n\n    return {\n      item: webhook,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/.gitkeep",
    "content": ""
  },
  {
    "path": "server/api/helpers/access-tokens/handle-steps.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { AccessTokenSteps } = require('../../../constants');\n\nconst Errors = {\n  ADMIN_LOGIN_REQUIRED_TO_INITIALIZE_INSTANCE: {\n    adminLoginRequiredToInitializeInstance: 'Admin login required to initialize instance',\n  },\n};\n\nconst PENDING_TOKEN_EXPIRES_IN = 10 * 60;\n\nmodule.exports = {\n  inputs: {\n    user: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n      required: true,\n    },\n    response: {\n      type: 'ref',\n      required: true,\n    },\n    remoteAddress: {\n      type: 'string',\n      required: true,\n    },\n    withHttpOnlyToken: {\n      type: 'boolean',\n    },\n  },\n\n  exits: {\n    adminLoginRequiredToInitializeInstance: {},\n    termsAcceptanceRequired: {},\n  },\n\n  async fn(inputs) {\n    const internalConfig = await InternalConfig.qm.getOneMain();\n\n    if (!internalConfig.isInitialized) {\n      if (inputs.user.role === User.Roles.ADMIN) {\n        if (inputs.user.termsSignature) {\n          await InternalConfig.qm.updateOneMain({\n            isInitialized: true,\n          });\n        }\n      } else {\n        throw Errors.ADMIN_LOGIN_REQUIRED_TO_INITIALIZE_INSTANCE;\n      }\n    }\n\n    if (!sails.hooks.terms.isSignatureValid(inputs.user.termsSignature)) {\n      const { token: pendingToken, payload: pendingTokenPayload } =\n        sails.helpers.utils.createJwtToken(\n          AccessTokenSteps.ACCEPT_TERMS,\n          undefined,\n          PENDING_TOKEN_EXPIRES_IN,\n        );\n\n      const session = await sails.helpers.sessions.createOne.with({\n        values: {\n          pendingToken,\n          userId: inputs.user.id,\n          remoteAddress: inputs.remoteAddress,\n          userAgent: inputs.request.headers['user-agent'],\n        },\n        withHttpOnlyToken: inputs.withHttpOnlyToken,\n      });\n\n      if (session.httpOnlyToken && !inputs.request.isSocket) {\n        sails.helpers.utils.setHttpOnlyTokenCookie(\n          session.httpOnlyToken,\n          pendingTokenPayload,\n          inputs.response,\n        );\n      }\n\n      throw {\n        termsAcceptanceRequired: {\n          pendingToken,\n          message: 'Terms acceptance required',\n          step: AccessTokenSteps.ACCEPT_TERMS,\n        },\n      };\n    }\n\n    const { token: accessToken, payload: accessTokenPayload } = sails.helpers.utils.createJwtToken(\n      inputs.user.id,\n    );\n\n    const session = await sails.helpers.sessions.createOne.with({\n      values: {\n        accessToken,\n        userId: inputs.user.id,\n        remoteAddress: inputs.remoteAddress,\n        userAgent: inputs.request.headers['user-agent'],\n      },\n      withHttpOnlyToken: inputs.withHttpOnlyToken,\n    });\n\n    if (session.httpOnlyToken && !inputs.request.isSocket) {\n      sails.helpers.utils.setHttpOnlyTokenCookie(\n        session.httpOnlyToken,\n        accessTokenPayload,\n        inputs.response,\n      );\n    }\n\n    return {\n      item: accessToken,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/actions/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst escapeMarkdown = require('escape-markdown');\nconst escapeHtml = require('escape-html');\n\nconst buildTitle = (action, t) => {\n  switch (action.type) {\n    case Action.Types.CREATE_CARD:\n      return t('Card Created');\n    case Action.Types.MOVE_CARD:\n      return t('Card Moved');\n    default:\n      return null;\n  }\n};\n\nconst buildBodyByFormat = (board, card, action, actorUser, t) => {\n  const markdownCardLink = `[${escapeMarkdown(card.name)}](${sails.config.custom.baseUrl}/cards/${card.id})`;\n  const htmlCardLink = `<a href=\"${sails.config.custom.baseUrl}/cards/${card.id}\">${escapeHtml(card.name)}</a>`;\n\n  switch (action.type) {\n    case Action.Types.CREATE_CARD: {\n      const listName = sails.helpers.lists.resolveName(action.data.list, t);\n\n      return {\n        text: t('%s created %s in %s on %s', actorUser.name, card.name, listName, board.name),\n        markdown: t(\n          '%s created %s in %s on %s',\n          escapeMarkdown(actorUser.name),\n          markdownCardLink,\n          `**${escapeMarkdown(listName)}**`,\n          escapeMarkdown(board.name),\n        ),\n        html: t(\n          '%s created %s in %s on %s',\n          escapeHtml(actorUser.name),\n          htmlCardLink,\n          `<b>${escapeHtml(listName)}</b>`,\n          escapeHtml(board.name),\n        ),\n      };\n    }\n    case Action.Types.MOVE_CARD: {\n      const fromListName = sails.helpers.lists.resolveName(action.data.fromList, t);\n      const toListName = sails.helpers.lists.resolveName(action.data.toList, t);\n\n      return {\n        text: t(\n          '%s moved %s from %s to %s on %s',\n          actorUser.name,\n          card.name,\n          fromListName,\n          toListName,\n          board.name,\n        ),\n        markdown: t(\n          '%s moved %s from %s to %s on %s',\n          escapeMarkdown(actorUser.name),\n          markdownCardLink,\n          `**${escapeMarkdown(fromListName)}**`,\n          `**${escapeMarkdown(toListName)}**`,\n          escapeMarkdown(board.name),\n        ),\n        html: t(\n          '%s moved %s from %s to %s on %s',\n          escapeHtml(actorUser.name),\n          htmlCardLink,\n          `<b>${escapeHtml(fromListName)}</b>`,\n          `<b>${escapeHtml(toListName)}</b>`,\n          escapeHtml(board.name),\n        ),\n      };\n    }\n    default:\n      return null;\n  }\n};\n\nconst buildAndSendNotifications = async (services, board, card, action, actorUser, t) => {\n  await sails.helpers.utils.sendNotifications(\n    services,\n    buildTitle(action, t),\n    buildBodyByFormat(board, card, action, actorUser, t),\n  );\n};\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    webhooks: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const action = await Action.qm.createOne({\n      ...values,\n      boardId: values.card.boardId,\n      cardId: values.card.id,\n      userId: values.user.id,\n    });\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'actionCreate',\n      {\n        item: action,\n      },\n      inputs.request,\n    );\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks: inputs.webhooks,\n      event: Webhook.Events.ACTION_CREATE,\n      buildData: () => ({\n        item: action,\n        included: {\n          projects: [inputs.project],\n          boards: [inputs.board],\n          lists: [inputs.list],\n          cards: [values.card],\n        },\n      }),\n      user: values.user,\n    });\n\n    if (Action.INTERNAL_NOTIFIABLE_TYPES.includes(action.type)) {\n      if (Action.PERSONAL_NOTIFIABLE_TYPES.includes(action.type)) {\n        if (values.user.id !== action.data.user.id) {\n          await sails.helpers.notifications.createOne.with({\n            values: {\n              action,\n              userId: action.data.user.id,\n              type: action.type,\n              data: action.data,\n              creatorUser: values.user,\n              card: values.card,\n            },\n            project: inputs.project,\n            board: inputs.board,\n            list: inputs.list,\n            webhooks: inputs.webhooks,\n          });\n        }\n      } else {\n        const cardSubscriptionUserIds = await sails.helpers.cards.getSubscriptionUserIds(\n          action.cardId,\n          action.userId,\n        );\n\n        const boardSubscriptionUserIds = await sails.helpers.boards.getSubscriptionUserIds(\n          inputs.board.id,\n          action.userId,\n        );\n\n        const notifiableUserIds = _.union(cardSubscriptionUserIds, boardSubscriptionUserIds);\n\n        await sails.helpers.notifications.createMany.with({\n          arrayOfValues: notifiableUserIds.map((userId) => ({\n            userId,\n            action,\n            type: action.type,\n            data: action.data,\n            creatorUser: values.user,\n            card: values.card,\n          })),\n          project: inputs.project,\n          board: inputs.board,\n          list: inputs.list,\n          webhooks: inputs.webhooks,\n        });\n      }\n    }\n\n    if (Action.EXTERNAL_NOTIFIABLE_TYPES.includes(action.type)) {\n      const notificationServices = await NotificationService.qm.getByBoardId(inputs.board.id);\n\n      if (notificationServices.length > 0) {\n        const services = notificationServices.map((notificationService) =>\n          _.pick(notificationService, ['url', 'format']),\n        );\n\n        buildAndSendNotifications(\n          services,\n          inputs.board,\n          values.card,\n          action,\n          values.user,\n          sails.helpers.utils.makeTranslator(),\n        );\n      }\n    }\n\n    return action;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/attachments/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    requestId: {\n      type: 'string',\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const attachment = await Attachment.qm.createOne({\n      ...values,\n      cardId: values.card.id,\n      creatorUserId: values.creatorUser.id,\n    });\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'attachmentCreate',\n      {\n        item: sails.helpers.attachments.presentOne(attachment),\n        requestId: inputs.requestId,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.ATTACHMENT_CREATE,\n      buildData: () => ({\n        item: sails.helpers.attachments.presentOne(attachment),\n        included: {\n          projects: [inputs.project],\n          boards: [inputs.board],\n          lists: [inputs.list],\n          cards: [values.card],\n        },\n      }),\n      user: values.creatorUser,\n    });\n\n    if (!values.card.coverAttachmentId) {\n      if (attachment.type === Attachment.Types.FILE && attachment.data.image) {\n        await sails.helpers.cards.updateOne.with({\n          webhooks,\n          record: values.card,\n          values: {\n            coverAttachmentId: attachment.id,\n          },\n          project: inputs.project,\n          board: inputs.board,\n          list: inputs.list,\n          actorUser: values.creatorUser,\n        });\n      }\n    }\n\n    return attachment;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/attachments/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const webhooks = await Webhook.qm.getAll();\n\n    if (inputs.record.id === inputs.card.coverAttachmentId) {\n      await sails.helpers.cards.updateOne.with({\n        webhooks,\n        record: inputs.card,\n        values: {\n          coverAttachmentId: null,\n        },\n        project: inputs.project,\n        board: inputs.board,\n        list: inputs.list,\n        actorUser: inputs.actorUser,\n      });\n    }\n\n    const { attachment, uploadedFile } = await Attachment.qm.deleteOne(inputs.record.id);\n\n    if (attachment) {\n      if (uploadedFile) {\n        sails.helpers.utils.removeUnreferencedUploadedFiles(uploadedFile);\n      }\n\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'attachmentDelete',\n        {\n          item: sails.helpers.attachments.presentOne(attachment),\n        },\n        inputs.request,\n      );\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.ATTACHMENT_DELETE,\n        buildData: () => ({\n          item: sails.helpers.attachments.presentOne(attachment),\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return attachment;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/attachments/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const attachment = await Attachment.qm.getOneById(inputs.id);\n\n    if (!attachment) {\n      throw 'pathNotFound';\n    }\n\n    const pathToProject = await sails.helpers.cards\n      .getPathToProjectById(attachment.cardId)\n      .intercept('pathNotFound', (nodes) => ({\n        pathNotFound: {\n          attachment,\n          ...nodes,\n        },\n      }));\n\n    return {\n      attachment,\n      ...pathToProject,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/attachments/present-many.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    records: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    return inputs.records.map((record) => sails.helpers.attachments.presentOne(record));\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/attachments/present-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    let data;\n    if (inputs.record.type === Attachment.Types.FILE) {\n      data = {\n        ...inputs.record,\n        data: {\n          ..._.omit(inputs.record.data, [\n            'uploadedFileId',\n            'filename',\n            'image.thumbnailsExtension',\n          ]),\n          url: `${sails.config.custom.baseUrl}/attachments/${inputs.record.id}/download/${inputs.record.data.filename}`,\n          thumbnailUrls: inputs.record.data.image && {\n            outside360: `${sails.config.custom.baseUrl}/attachments/${inputs.record.id}/download/thumbnails/outside-360.${inputs.record.data.image.thumbnailsExtension}`,\n            outside720: `${sails.config.custom.baseUrl}/attachments/${inputs.record.id}/download/thumbnails/outside-720.${inputs.record.data.image.thumbnailsExtension}`,\n          },\n        },\n      };\n    } else if (inputs.record.type === Attachment.Types.LINK) {\n      const faviconFilename = `${inputs.record.data.hostname}.png`;\n\n      let faviconUrl = null;\n      if (sails.helpers.utils.isPreloadedFaviconExists(inputs.record.data.hostname)) {\n        faviconUrl = `${sails.config.custom.baseUrl}/preloaded-favicons/${faviconFilename}`;\n      } else {\n        faviconUrl = `${sails.config.custom.baseUrl}/favicons/${faviconFilename}`;\n      }\n\n      data = {\n        ...inputs.record,\n        data: {\n          ..._.omit(inputs.record.data, 'hostname'),\n          faviconUrl,\n        },\n      };\n    }\n\n    return data;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/attachments/process-link.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { URL } = require('url');\n\nmodule.exports = {\n  inputs: {\n    url: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const { hostname } = new URL(inputs.url);\n\n    if (!sails.helpers.utils.isPreloadedFaviconExists(hostname)) {\n      await sails.helpers.utils.downloadFavicon(inputs.url);\n    }\n\n    return {\n      hostname,\n      url: inputs.url,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/attachments/process-uploaded-file.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst fsPromises = require('fs').promises;\nconst { rimraf } = require('rimraf');\nconst { fileTypeFromFile } = require('file-type');\nconst { getEncoding } = require('istextorbinary');\nconst sharp = require('sharp');\n\nconst filenamify = require('../../../utils/filenamify');\nconst { MAX_SIZE_TO_GET_ENCODING, MAX_SIZE_TO_PROCESS_AS_IMAGE } = require('../../../constants');\n\nmodule.exports = {\n  inputs: {\n    file: {\n      type: 'json',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const fileManager = sails.hooks['file-manager'].getInstance();\n\n    const filename = filenamify(inputs.file.filename);\n    const fileType = await fileTypeFromFile(inputs.file.fd);\n    const { mime: mimeType = null } = fileType || {};\n    const { size } = inputs.file;\n\n    const { id: uploadedFileId } = await UploadedFile.qm.createOne({\n      mimeType,\n      size,\n      type: UploadedFile.Types.ATTACHMENT,\n    });\n\n    const dirPathSegment = `${sails.config.custom.attachmentsPathSegment}/${uploadedFileId}`;\n\n    let buffer;\n    let encoding = null;\n\n    if (size <= MAX_SIZE_TO_GET_ENCODING) {\n      try {\n        buffer = await fsPromises.readFile(inputs.file.fd);\n      } catch (error) {\n        /* empty */\n      }\n\n      if (buffer) {\n        encoding = getEncoding(buffer);\n      }\n    }\n\n    const filePath = await fileManager.move(\n      inputs.file.fd,\n      `${dirPathSegment}/${filename}`,\n      inputs.file.type,\n    );\n\n    const data = {\n      uploadedFileId,\n      filename,\n      mimeType,\n      size,\n      encoding,\n      image: null,\n    };\n\n    if (mimeType && mimeType.startsWith('image/') && size <= MAX_SIZE_TO_PROCESS_AS_IMAGE) {\n      let image = sharp(buffer || filePath || inputs.file.fd, {\n        animated: true,\n      });\n\n      let metadata;\n      try {\n        metadata = await image.metadata();\n      } catch (error) {\n        /* empty */\n      }\n\n      if (metadata) {\n        let { width, pageHeight: height = metadata.height } = metadata;\n        if (metadata.orientation && metadata.orientation > 4) {\n          [image, width, height] = [image.rotate(), height, width];\n        }\n\n        const thumbnailsPathSegment = `${dirPathSegment}/thumbnails`;\n        const thumbnailsExtension = metadata.format === 'jpeg' ? 'jpg' : metadata.format;\n\n        const outside360 = image\n          .clone()\n          .resize(360, 360, {\n            fit: 'outside',\n            withoutEnlargement: true,\n          })\n          .png({\n            quality: 75,\n            force: false,\n          });\n\n        const outside720 = image\n          .clone()\n          .resize(720, 720, {\n            fit: 'outside',\n            withoutEnlargement: true,\n          })\n          .png({\n            quality: 75,\n            force: false,\n          });\n\n        try {\n          await Promise.all([\n            fileManager.save(\n              `${thumbnailsPathSegment}/outside-360.${thumbnailsExtension}`,\n              outside360,\n              inputs.file.type,\n            ),\n            fileManager.save(\n              `${thumbnailsPathSegment}/outside-720.${thumbnailsExtension}`,\n              outside720,\n              inputs.file.type,\n            ),\n          ]);\n\n          data.image = {\n            width,\n            height,\n            thumbnailsExtension,\n          };\n        } catch (error) {\n          sails.log.warn(error.stack);\n          await fileManager.deleteDir(thumbnailsPathSegment);\n        }\n      }\n    }\n\n    if (!filePath) {\n      await rimraf(inputs.file.fd);\n    }\n\n    return data;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/attachments/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const attachment = await Attachment.qm.updateOne(inputs.record.id, values);\n\n    if (attachment) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'attachmentUpdate',\n        {\n          item: sails.helpers.attachments.presentOne(attachment),\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.ATTACHMENT_UPDATE,\n        buildData: () => ({\n          item: sails.helpers.attachments.presentOne(attachment),\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n          },\n        }),\n        buildPrevData: () => ({\n          item: sails.helpers.attachments.presentOne(inputs.record),\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return attachment;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/background-images/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    requestId: {\n      type: 'string',\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const backgroundImage = await BackgroundImage.qm.createOne({\n      ...values,\n      projectId: values.project.id,\n    });\n\n    const scoper = sails.helpers.projects.makeScoper.with({\n      record: values.project,\n    });\n\n    const projectRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n    projectRelatedUserIds.forEach((userId) => {\n      sails.sockets.broadcast(\n        `user:${userId}`,\n        'backgroundImageCreate',\n        {\n          item: sails.helpers.backgroundImages.presentOne(backgroundImage),\n          requestId: inputs.requestId,\n        },\n        inputs.request,\n      );\n    });\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.BACKGROUND_IMAGE_CREATE,\n      buildData: () => ({\n        item: sails.helpers.backgroundImages.presentOne(backgroundImage),\n        included: {\n          projects: [values.project],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    await sails.helpers.projects.updateOne.with({\n      scoper,\n      webhooks,\n      record: values.project,\n      values: {\n        backgroundImage,\n        backgroundType: Project.BackgroundTypes.IMAGE,\n      },\n      actorUser: inputs.actorUser,\n    });\n\n    return backgroundImage;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/background-images/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const scoper = sails.helpers.projects.makeScoper.with({\n      record: inputs.project,\n    });\n\n    const webhooks = await Webhook.qm.getAll();\n\n    if (inputs.project.backgroundType === Project.BackgroundTypes.IMAGE) {\n      if (inputs.record.id === inputs.project.backgroundImageId) {\n        await sails.helpers.projects.updateOne.with({\n          scoper,\n          webhooks,\n          record: inputs.project,\n          values: {\n            backgroundType: null,\n          },\n          actorUser: inputs.actorUser,\n        });\n      }\n    }\n\n    const { backgroundImage, uploadedFile } = await BackgroundImage.qm.deleteOne(inputs.record.id);\n\n    if (backgroundImage) {\n      sails.helpers.utils.removeUnreferencedUploadedFiles(uploadedFile);\n\n      const projectRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n      projectRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'backgroundImageDelete',\n          {\n            item: sails.helpers.backgroundImages.presentOne(backgroundImage),\n          },\n          inputs.request,\n        );\n      });\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.BACKGROUND_IMAGE_DELETE,\n        buildData: () => ({\n          item: sails.helpers.backgroundImages.presentOne(backgroundImage),\n          included: {\n            projects: [inputs.project],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return backgroundImage;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/background-images/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const backgroundImage = await BackgroundImage.qm.getOneById(inputs.id);\n\n    if (!backgroundImage) {\n      throw 'pathNotFound';\n    }\n\n    const project = await Project.qm.getOneById(backgroundImage.projectId);\n\n    if (!project) {\n      throw {\n        pathNotFound: {\n          backgroundImage,\n        },\n      };\n    }\n\n    return {\n      backgroundImage,\n      project,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/background-images/present-many.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    records: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    return inputs.records.map((record) => sails.helpers.backgroundImages.presentOne(record));\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/background-images/present-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    return {\n      ..._.omit(inputs.record, ['uploadedFileId', 'extension']),\n      url: `${sails.config.custom.baseUrl}/background-images/${inputs.record.uploadedFileId}/original.${inputs.record.extension}`,\n      thumbnailUrls: {\n        outside360: `${sails.config.custom.baseUrl}/background-images/${inputs.record.uploadedFileId}/outside-360.${inputs.record.extension}`,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/background-images/process-uploaded-file.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { v4: uuid } = require('uuid');\nconst { rimraf } = require('rimraf');\nconst { fileTypeFromFile } = require('file-type');\nconst sharp = require('sharp');\n\nconst { MAX_SIZE_TO_PROCESS_AS_IMAGE } = require('../../../constants');\n\nmodule.exports = {\n  inputs: {\n    file: {\n      type: 'json',\n      required: true,\n    },\n  },\n\n  exits: {\n    fileIsNotImage: {},\n  },\n\n  async fn(inputs) {\n    const fileManager = sails.hooks['file-manager'].getInstance();\n\n    const fileType = await fileTypeFromFile(inputs.file.fd);\n    const { mime: mimeType = null } = fileType || {};\n    const { size } = inputs.file;\n\n    if (!mimeType || !mimeType.startsWith('image/') || size > MAX_SIZE_TO_PROCESS_AS_IMAGE) {\n      await rimraf(inputs.file.fd);\n      throw 'fileIsNotImage';\n    }\n\n    let image = sharp(inputs.file.fd, {\n      animated: true,\n    });\n\n    let metadata;\n    try {\n      metadata = await image.metadata();\n    } catch (error) {\n      await rimraf(inputs.file.fd);\n      throw 'fileIsNotImage';\n    }\n\n    if (metadata.orientation && metadata.orientation > 4) {\n      image = image.rotate();\n    }\n\n    const { id: uploadedFileId } = await UploadedFile.qm.createOne({\n      mimeType,\n      size,\n      id: uuid(),\n      type: UploadedFile.Types.BACKGROUND_IMAGE,\n    });\n\n    const dirPathSegment = `${sails.config.custom.backgroundImagesPathSegment}/${uploadedFileId}`;\n    const extension = metadata.format === 'jpeg' ? 'jpg' : metadata.format;\n\n    const outside360 = image\n      .clone()\n      .resize(360, 360, {\n        fit: 'outside',\n        withoutEnlargement: true,\n      })\n      .png({\n        quality: 75,\n        force: false,\n      });\n\n    try {\n      await Promise.all([\n        fileManager.save(`${dirPathSegment}/original.${extension}`, image, inputs.file.type),\n        fileManager.save(\n          `${dirPathSegment}/outside-360.${extension}`,\n          outside360,\n          inputs.file.type,\n        ),\n      ]);\n    } catch (error) {\n      sails.log.warn(error.stack);\n\n      await fileManager.deleteDir(dirPathSegment);\n      await rimraf(inputs.file.fd);\n      await UploadedFile.qm.deleteOne(uploadedFileId);\n\n      throw 'fileIsNotImage';\n    }\n\n    await rimraf(inputs.file.fd);\n\n    return {\n      uploadedFileId,\n      extension,\n      size,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/base-custom-field-groups/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const baseCustomFieldGroup = await BaseCustomFieldGroup.qm.createOne({\n      ...values,\n      projectId: values.project.id,\n    });\n\n    const scoper = sails.helpers.projects.makeScoper.with({\n      record: values.project,\n    });\n\n    const projectRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n    projectRelatedUserIds.forEach((userId) => {\n      sails.sockets.broadcast(\n        `user:${userId}`,\n        'baseCustomFieldGroupCreate',\n        {\n          item: baseCustomFieldGroup,\n        },\n        inputs.request,\n      );\n    });\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.BASE_CUSTOM_FIELD_GROUP_CREATE,\n      buildData: () => ({\n        item: baseCustomFieldGroup,\n        included: {\n          projects: [values.project],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return baseCustomFieldGroup;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/base-custom-field-groups/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    await sails.helpers.baseCustomFieldGroups.deleteRelated(inputs.record);\n\n    const baseCustomFieldGroup = await BaseCustomFieldGroup.qm.deleteOne(inputs.record.id);\n\n    if (baseCustomFieldGroup) {\n      const scoper = sails.helpers.projects.makeScoper.with({\n        record: inputs.project,\n      });\n\n      const projectRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n      projectRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'baseCustomFieldGroupDelete',\n          {\n            item: baseCustomFieldGroup,\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.BASE_CUSTOM_FIELD_GROUP_DELETE,\n        buildData: () => ({\n          item: baseCustomFieldGroup,\n          included: {\n            projects: [inputs.project],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return baseCustomFieldGroup;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/base-custom-field-groups/delete-related.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    recordOrRecords: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    let baseCustomFieldGroupIdOrIds;\n    if (_.isPlainObject(inputs.recordOrRecords)) {\n      ({\n        recordOrRecords: { id: baseCustomFieldGroupIdOrIds },\n      } = inputs);\n    } else if (_.every(inputs.recordOrRecords, _.isPlainObject)) {\n      baseCustomFieldGroupIdOrIds = sails.helpers.utils.mapRecords(inputs.recordOrRecords);\n    }\n\n    const customFieldGroups = await CustomFieldGroup.qm.delete({\n      baseCustomFieldGroupId: baseCustomFieldGroupIdOrIds,\n    });\n\n    await sails.helpers.customFieldGroups.deleteRelated(customFieldGroups);\n\n    await CustomField.qm.delete({\n      baseCustomFieldGroupId: baseCustomFieldGroupIdOrIds,\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/base-custom-field-groups/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const baseCustomFieldGroup = await BaseCustomFieldGroup.qm.getOneById(inputs.id);\n\n    if (!baseCustomFieldGroup) {\n      throw 'pathNotFound';\n    }\n\n    const project = await Project.qm.getOneById(baseCustomFieldGroup.projectId);\n\n    if (!project) {\n      throw {\n        pathNotFound: {\n          baseCustomFieldGroup,\n        },\n      };\n    }\n\n    return {\n      baseCustomFieldGroup,\n      project,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/base-custom-field-groups/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const baseCustomFieldGroup = await BaseCustomFieldGroup.qm.updateOne(inputs.record.id, values);\n\n    if (baseCustomFieldGroup) {\n      const scoper = sails.helpers.projects.makeScoper.with({\n        record: inputs.project,\n      });\n\n      const projectRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n      projectRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'baseCustomFieldGroupUpdate',\n          {\n            item: baseCustomFieldGroup,\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.BASE_CUSTOM_FIELD_GROUP_UPDATE,\n        buildData: () => ({\n          item: baseCustomFieldGroup,\n          included: {\n            projects: [inputs.project],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return baseCustomFieldGroup;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/board-memberships/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { v4: uuid } = require('uuid');\n\nconst normalizeValues = require('../../../utils/normalize-values');\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    userAlreadyBoardMember: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const normalizedValues = normalizeValues(\n      {\n        ...BoardMembership.SHARED_RULES,\n        ...BoardMembership.RULES_BY_ROLE[values.role],\n      },\n      values,\n    );\n\n    let boardMembership;\n    try {\n      boardMembership = await BoardMembership.qm.createOne({\n        ...normalizedValues,\n        projectId: values.board.projectId,\n        boardId: values.board.id,\n        userId: values.user.id,\n      });\n    } catch (error) {\n      if (error.code === 'E_UNIQUE') {\n        throw 'userAlreadyBoardMember';\n      }\n\n      throw error;\n    }\n\n    sails.sockets.broadcast(\n      `user:${boardMembership.userId}`,\n      'boardMembershipCreate',\n      {\n        item: boardMembership,\n      },\n      inputs.request,\n    );\n\n    const tempRoom = uuid();\n\n    sails.sockets.addRoomMembersToRooms(`board:${boardMembership.boardId}`, tempRoom, () => {\n      sails.sockets.removeRoomMembersFromRooms(`user:${boardMembership.userId}`, tempRoom, () => {\n        sails.sockets.broadcast(\n          tempRoom,\n          'boardMembershipCreate',\n          {\n            item: boardMembership,\n            included: {\n              users: [sails.helpers.users.presentOne(values.user, {})], // FIXME: hack\n            },\n          },\n          inputs.request,\n        );\n\n        sails.sockets.removeRoomMembersFromRooms(tempRoom, tempRoom);\n      });\n    });\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.BOARD_MEMBERSHIP_CREATE,\n      buildData: () => ({\n        item: boardMembership,\n        included: {\n          users: [sails.helpers.users.presentOne(values.user)],\n          projects: [inputs.project],\n          boards: [values.board],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return boardMembership;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/board-memberships/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { v4: uuid } = require('uuid');\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    user: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    await BoardSubscription.qm.delete({\n      boardId: inputs.record.boardId,\n      userId: inputs.user.id,\n    });\n\n    const cardIds = await sails.helpers.boards.getCardIds(inputs.record.boardId);\n\n    await CardSubscription.qm.delete({\n      cardId: cardIds,\n      userId: inputs.user.id,\n    });\n\n    await CardMembership.qm.delete({\n      cardId: cardIds,\n      userId: inputs.user.id,\n    });\n\n    const taskLists = await TaskList.qm.getByCardIds(cardIds);\n    const taskListIds = sails.helpers.utils.mapRecords(taskLists);\n\n    await Task.qm.update(\n      {\n        taskListId: taskListIds,\n        assigneeUserId: inputs.user.id,\n      },\n      {\n        assigneeUserId: null,\n      },\n    );\n\n    const boardMembership = await BoardMembership.qm.deleteOne(inputs.record.id);\n\n    if (boardMembership) {\n      if (inputs.user.role !== User.Roles.ADMIN || inputs.project.ownerProjectManagerId) {\n        const isProjectManager = await sails.helpers.users.isProjectManager(\n          boardMembership.userId,\n          inputs.project.id,\n        );\n\n        if (!isProjectManager) {\n          sails.sockets.removeRoomMembersFromRooms(\n            `@user:${boardMembership.userId}`,\n            `board:${boardMembership.boardId}`,\n          );\n        }\n      }\n\n      sails.sockets.broadcast(\n        `user:${boardMembership.userId}`,\n        'boardMembershipDelete',\n        {\n          item: boardMembership,\n        },\n        inputs.request,\n      );\n\n      const tempRoom = uuid();\n\n      sails.sockets.addRoomMembersToRooms(`board:${boardMembership.boardId}`, tempRoom, () => {\n        sails.sockets.removeRoomMembersFromRooms(`user:${boardMembership.userId}`, tempRoom, () => {\n          sails.sockets.broadcast(\n            tempRoom,\n            'boardMembershipDelete',\n            {\n              item: boardMembership,\n            },\n            inputs.request,\n          );\n\n          sails.sockets.removeRoomMembersFromRooms(tempRoom, tempRoom);\n        });\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.BOARD_MEMBERSHIP_DELETE,\n        buildData: () => ({\n          item: boardMembership,\n          included: {\n            users: [sails.helpers.users.presentOne(inputs.user)],\n            projects: [inputs.project],\n            boards: [inputs.board],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return boardMembership;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/board-memberships/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const boardMembership = await BoardMembership.qm.getOneById(inputs.id);\n\n    if (!boardMembership) {\n      throw 'pathNotFound';\n    }\n\n    const pathToProject = await sails.helpers.boards\n      .getPathToProjectById(boardMembership.boardId)\n      .intercept('pathNotFound', (nodes) => ({\n        pathNotFound: {\n          boardMembership,\n          ...nodes,\n        },\n      }));\n\n    return {\n      boardMembership,\n      ...pathToProject,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/board-memberships/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { v4: uuid } = require('uuid');\n\nconst normalizeValues = require('../../../utils/normalize-values');\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const normalizedValues = normalizeValues(\n      {\n        ...BoardMembership.SHARED_RULES,\n        ...BoardMembership.RULES_BY_ROLE[values.role || inputs.record.role],\n      },\n      values,\n      inputs.record,\n    );\n\n    const boardMembership = await BoardMembership.qm.updateOne(inputs.record.id, normalizedValues);\n\n    if (boardMembership) {\n      sails.sockets.broadcast(\n        `user:${boardMembership.userId}`,\n        'boardMembershipUpdate',\n        {\n          item: boardMembership,\n        },\n        inputs.request,\n      );\n\n      const tempRoom = uuid();\n\n      sails.sockets.addRoomMembersToRooms(`board:${boardMembership.boardId}`, tempRoom, () => {\n        sails.sockets.removeRoomMembersFromRooms(`user:${boardMembership.userId}`, tempRoom, () => {\n          sails.sockets.broadcast(\n            tempRoom,\n            'boardMembershipUpdate',\n            {\n              item: boardMembership,\n            },\n            inputs.request,\n          );\n\n          sails.sockets.removeRoomMembersFromRooms(tempRoom, tempRoom);\n        });\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.BOARD_MEMBERSHIP_UPDATE,\n        buildData: () => ({\n          item: boardMembership,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return boardMembership;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    import: {\n      type: 'json',\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    requestId: {\n      type: 'string',\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const scoper = sails.helpers.projects.makeScoper.with({\n      record: values.project,\n    });\n\n    const boards = await Board.qm.getByProjectId(values.project.id);\n\n    const { position, repositions } = sails.helpers.utils.insertToPositionables(\n      values.position,\n      boards,\n    );\n\n    values.position = position;\n\n    if (repositions.length > 0) {\n      await scoper.getUserIdsWithFullProjectVisibility();\n      const clonedScoper = scoper.clone();\n\n      // eslint-disable-next-line no-restricted-syntax\n      for (const reposition of repositions) {\n        // eslint-disable-next-line no-await-in-loop\n        await Board.qm.updateOne(\n          {\n            id: reposition.record.id,\n            projectId: reposition.record.projectId,\n          },\n          {\n            position: reposition.position,\n          },\n        );\n\n        clonedScoper.replaceBoard(reposition.record);\n        // eslint-disable-next-line no-await-in-loop\n        const boardRelatedUserIds = await clonedScoper.getBoardRelatedUserIds();\n\n        boardRelatedUserIds.forEach((userId) => {\n          sails.sockets.broadcast(`user:${userId}`, 'boardUpdate', {\n            item: {\n              id: reposition.record.id,\n              position: reposition.position,\n            },\n          });\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const { board, boardMembership, lists } = await Board.qm.createOne(\n      {\n        ...values,\n        projectId: values.project.id,\n      },\n      {\n        user: inputs.actorUser,\n      },\n    );\n\n    if (inputs.import && inputs.import.type === Board.ImportTypes.TRELLO) {\n      await sails.helpers.boards.importFromTrello(board, lists, inputs.import.board);\n    }\n\n    scoper.board = board;\n    scoper.boardMemberships = [boardMembership];\n\n    const boardRelatedUserIds = await scoper.getBoardRelatedUserIds();\n\n    boardRelatedUserIds.forEach((userId) => {\n      sails.sockets.broadcast(\n        `user:${userId}`,\n        'boardCreate',\n        {\n          item: board,\n          included: {\n            boardMemberships: userId === boardMembership.userId ? [boardMembership] : [],\n          },\n          requestId: inputs.requestId,\n        },\n        inputs.request,\n      );\n    });\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.BOARD_CREATE,\n      buildData: () => ({\n        item: board,\n        included: {\n          projects: [values.project],\n          boardMemberships: [boardMembership],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return {\n      board,\n      boardMembership,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { boardMemberships } = await sails.helpers.boards.deleteRelated(inputs.record);\n\n    const board = await Board.qm.deleteOne(inputs.record.id);\n\n    if (board) {\n      const scoper = sails.helpers.projects.makeScoper.with({\n        board,\n        record: inputs.project,\n      });\n\n      scoper.boardMemberships = boardMemberships;\n      const boardRelatedUserIds = await scoper.getBoardRelatedUserIds();\n\n      sails.sockets.removeRoomMembersFromRooms(`board:${board.id}`, `board:${board.id}`);\n\n      boardRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'boardDelete',\n          {\n            item: board,\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.BOARD_DELETE,\n        buildData: () => ({\n          item: board,\n          included: {\n            projects: [inputs.project],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return board;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/delete-related.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    recordOrRecords: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    let boardIdOrIds;\n    if (_.isPlainObject(inputs.recordOrRecords)) {\n      ({\n        recordOrRecords: { id: boardIdOrIds },\n      } = inputs);\n    } else if (_.every(inputs.recordOrRecords, _.isPlainObject)) {\n      boardIdOrIds = sails.helpers.utils.mapRecords(inputs.recordOrRecords);\n    }\n\n    const boardMemberships = await BoardMembership.qm.delete({\n      boardId: boardIdOrIds,\n    });\n\n    await Label.qm.delete({\n      boardId: boardIdOrIds,\n    });\n\n    const lists = await List.qm.delete({\n      boardId: boardIdOrIds,\n    });\n\n    await sails.helpers.lists.deleteRelated(lists);\n\n    await Action.qm.update(\n      {\n        boardId: boardIdOrIds,\n      },\n      {\n        boardId: null,\n      },\n    );\n\n    await NotificationService.qm.delete({\n      boardId: boardIdOrIds,\n    });\n\n    return { boardMemberships };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/get-card-ids.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const cards = await Card.qm.getByBoardId(inputs.id);\n\n    return sails.helpers.utils.mapRecords(cards);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/get-kanban-lists-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n    exceptListIdOrIds: {\n      type: 'json',\n    },\n  },\n\n  async fn(inputs) {\n    return List.qm.getByBoardId(inputs.id, {\n      exceptIdOrIds: inputs.exceptListIdOrIds,\n      typeOrTypes: List.KANBAN_TYPES,\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/get-member-user-ids.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const boardMemberships = await BoardMembership.qm.getByBoardId(inputs.id);\n\n    return sails.helpers.utils.mapRecords(boardMemberships, 'userId');\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/get-notification-services-total.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const notificationServices = await NotificationService.qm.getByBoardId(inputs.id);\n\n    return notificationServices.length;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const board = await Board.qm.getOneById(inputs.id);\n\n    if (!board) {\n      throw 'pathNotFound';\n    }\n\n    const project = await Project.qm.getOneById(board.projectId);\n\n    if (!project) {\n      throw {\n        pathNotFound: {\n          board,\n        },\n      };\n    }\n\n    return {\n      board,\n      project,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/get-subscription-user-ids.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n    exceptUserIdOrIds: {\n      type: 'json',\n    },\n  },\n\n  async fn(inputs) {\n    const boardSubscriptions = await BoardSubscription.qm.getByBoardId(inputs.id, {\n      exceptUserIdOrIds: inputs.exceptUserIdOrIds,\n    });\n\n    return sails.helpers.utils.mapRecords(boardSubscriptions, 'userId');\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/import-from-trello.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { POSITION_GAP } = require('../../../constants');\n\nmodule.exports = {\n  inputs: {\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    lists: {\n      type: 'ref',\n      required: true,\n    },\n    trelloBoard: {\n      type: 'json',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const convertLabelColor = (trelloLabelColor) =>\n      Label.COLORS.find((color) => color.includes(trelloLabelColor)) || 'desert-sand';\n\n    const labelIdByTrelloLabelId = {};\n    await Promise.all(\n      inputs.trelloBoard.labels.map(async (trelloLabel, index) => {\n        const { id } = await Label.qm.createOne({\n          boardId: inputs.board.id,\n          position: POSITION_GAP * (index + 1),\n          name: trelloLabel.name || null,\n          color: convertLabelColor(trelloLabel.color),\n        });\n\n        labelIdByTrelloLabelId[trelloLabel.id] = id;\n      }),\n    );\n\n    const openedTrelloLists = inputs.trelloBoard.lists.filter((list) => !list.closed);\n\n    const listIdByTrelloListId = {};\n    await Promise.all(\n      openedTrelloLists.map(async (trelloList) => {\n        const { id } = await List.qm.createOne({\n          boardId: inputs.board.id,\n          type: List.Types.ACTIVE,\n          position: trelloList.pos,\n          name: trelloList.name,\n        });\n\n        listIdByTrelloListId[trelloList.id] = id;\n      }),\n    );\n\n    const { id: archiveListId } = inputs.lists.find((list) => list.type === List.Types.ARCHIVE);\n\n    const cardIdByTrelloCardId = {};\n    await Promise.all(\n      inputs.trelloBoard.cards.map(async (trelloCard) => {\n        const values = {\n          boardId: inputs.board.id,\n          type: Card.Types.PROJECT,\n          position: trelloCard.pos,\n          name: trelloCard.name,\n          description: trelloCard.desc || null,\n          dueDate: trelloCard.due,\n          isDueCompleted: trelloCard.due && trelloCard.dueComplete,\n          listChangedAt: new Date().toISOString(),\n        };\n\n        const listId = listIdByTrelloListId[trelloCard.idList];\n\n        if (trelloCard.closed) {\n          Object.assign(values, {\n            listId: archiveListId,\n            prevListId: listId,\n          });\n        } else {\n          values.listId = listId || archiveListId;\n        }\n\n        const { id } = await Card.qm.createOne(values);\n        cardIdByTrelloCardId[trelloCard.id] = id;\n\n        return Promise.all(\n          trelloCard.idLabels.map(async (trelloLabelId) =>\n            CardLabel.qm.createOne({\n              cardId: id,\n              labelId: labelIdByTrelloLabelId[trelloLabelId],\n            }),\n          ),\n        );\n      }),\n    );\n\n    await Promise.all(\n      inputs.trelloBoard.checklists.map(async (trelloChecklist) => {\n        const { id } = await TaskList.qm.createOne({\n          cardId: cardIdByTrelloCardId[trelloChecklist.idCard],\n          position: trelloChecklist.pos,\n          name: trelloChecklist.name,\n        });\n\n        return Promise.all(\n          trelloChecklist.checkItems.map(async (trelloCheckItem) =>\n            Task.qm.createOne({\n              taskListId: id,\n              position: trelloCheckItem.pos,\n              name: trelloCheckItem.name,\n              isCompleted: trelloCheckItem.state === 'complete',\n            }),\n          ),\n        );\n      }),\n    );\n\n    const trelloCommentActions = inputs.trelloBoard.actions\n      .filter((action) => action.type === 'commentCard')\n      .reverse();\n\n    await Promise.all(\n      trelloCommentActions.map(async (trelloAction) =>\n        Comment.qm.createOne({\n          cardId: cardIdByTrelloCardId[trelloAction.data.card.id],\n          text: `${trelloAction.data.text}\\n\\n---\\n*Note: imported comment, originally posted by\\n${trelloAction.memberCreator.fullName} (${trelloAction.memberCreator.username}) on ${trelloAction.date}*`,\n        }),\n      ),\n    );\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/process-uploaded-trello-import-file.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst fs = require('fs');\nconst { rimraf } = require('rimraf');\n\nmodule.exports = {\n  inputs: {\n    file: {\n      type: 'json',\n      required: true,\n    },\n  },\n\n  exits: {\n    invalidFile: {},\n  },\n\n  // TODO: add better validation\n  async fn(inputs) {\n    const content = await fs.promises.readFile(inputs.file.fd);\n\n    let trelloBoard;\n    try {\n      trelloBoard = JSON.parse(content);\n    } catch (error) {\n      await rimraf(inputs.file.fd);\n      throw 'invalidFile';\n    }\n\n    if (\n      !trelloBoard ||\n      !_.isArray(trelloBoard.labels) ||\n      !_.isArray(trelloBoard.lists) ||\n      !_.isArray(trelloBoard.cards) ||\n      !_.isArray(trelloBoard.checklists) ||\n      !_.isArray(trelloBoard.actions)\n    ) {\n      await rimraf(inputs.file.fd);\n      throw 'invalidFile';\n    }\n\n    await rimraf(inputs.file.fd);\n\n    return trelloBoard;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/boards/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { isSubscribed, ...values } = inputs.values;\n\n    let board;\n    if (_.isEmpty(values)) {\n      board = inputs.record;\n    } else {\n      const scoper = sails.helpers.projects.makeScoper.with({\n        record: inputs.project,\n      });\n\n      if (!_.isUndefined(values.position)) {\n        const boards = await Board.qm.getByProjectId(inputs.record.projectId, {\n          exceptIdOrIds: inputs.record.id,\n        });\n\n        const { position, repositions } = sails.helpers.utils.insertToPositionables(\n          values.position,\n          boards,\n        );\n\n        values.position = position;\n\n        if (repositions.length > 0) {\n          await scoper.getUserIdsWithFullProjectVisibility();\n          const clonedScoper = scoper.clone();\n\n          // eslint-disable-next-line no-restricted-syntax\n          for (const reposition of repositions) {\n            // eslint-disable-next-line no-await-in-loop\n            await Board.qm.updateOne(\n              {\n                id: reposition.record.id,\n                projectId: reposition.record.projectId,\n              },\n              {\n                position: reposition.position,\n              },\n            );\n\n            clonedScoper.replaceBoard(reposition.record);\n            // eslint-disable-next-line no-await-in-loop\n            const boardRelatedUserIds = await clonedScoper.getBoardRelatedUserIds();\n\n            boardRelatedUserIds.forEach((userId) => {\n              sails.sockets.broadcast(`user:${userId}`, 'boardUpdate', {\n                item: {\n                  id: reposition.record.id,\n                  position: reposition.position,\n                },\n              });\n            });\n\n            // TODO: send webhooks\n          }\n        }\n      }\n\n      board = await Board.qm.updateOne(inputs.record.id, values);\n\n      if (!board) {\n        return board;\n      }\n\n      scoper.board = board;\n      const boardRelatedUserIds = await scoper.getBoardRelatedUserIds();\n\n      boardRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'boardUpdate',\n          {\n            item: board,\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.BOARD_UPDATE,\n        buildData: () => ({\n          item: board,\n          included: {\n            projects: [inputs.project],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    if (!_.isUndefined(isSubscribed)) {\n      const wasSubscribed = await sails.helpers.users.isBoardSubscriber(\n        inputs.actorUser.id,\n        board.id,\n      );\n\n      if (isSubscribed !== wasSubscribed) {\n        if (isSubscribed) {\n          try {\n            await BoardSubscription.qm.createOne({\n              boardId: board.id,\n              userId: inputs.actorUser.id,\n            });\n          } catch (error) {\n            if (error.code !== 'E_UNIQUE') {\n              throw error;\n            }\n          }\n        } else {\n          await BoardSubscription.qm.deleteOne({\n            boardId: board.id,\n            userId: inputs.actorUser.id,\n          });\n        }\n\n        sails.sockets.broadcast(\n          `user:${inputs.actorUser.id}`,\n          'boardUpdate',\n          {\n            item: {\n              isSubscribed,\n              id: board.id,\n            },\n          },\n          inputs.request,\n        );\n\n        // TODO: send webhooks\n      }\n    }\n\n    return board;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/bootstrap/present-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    internalConfig: {\n      type: 'ref',\n      required: true,\n    },\n    oidc: {\n      type: 'ref',\n    },\n    user: {\n      type: 'ref',\n    },\n  },\n\n  fn(inputs) {\n    const data = {\n      oidc: inputs.oidc,\n      termsLanguages: sails.hooks.terms.getLanguages(),\n      version: sails.config.custom.version,\n    };\n\n    if (inputs.user && inputs.user.role === User.Roles.ADMIN) {\n      Object.assign(data, {\n        activeUsersLimit: inputs.internalConfig.activeUsersLimit,\n        customerPanelUrl: sails.config.custom.customerPanelUrl,\n      });\n    }\n\n    if (sails.config.custom.demoMode) {\n      data.isDemoMode = true;\n    }\n\n    return data;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/card-labels/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    labelAlreadyInCard: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    let cardLabel;\n    try {\n      cardLabel = await CardLabel.qm.createOne({\n        ...values,\n        cardId: values.card.id,\n        labelId: values.label.id,\n      });\n    } catch (error) {\n      if (error.code === 'E_UNIQUE') {\n        throw 'labelAlreadyInCard';\n      }\n\n      throw error;\n    }\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'cardLabelCreate',\n      {\n        item: cardLabel,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.CARD_LABEL_CREATE,\n      buildData: () => ({\n        item: cardLabel,\n        included: {\n          projects: [inputs.project],\n          boards: [inputs.board],\n          labels: [values.label],\n          lists: [inputs.list],\n          cards: [values.card],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return cardLabel;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/card-labels/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const cardLabel = await CardLabel.qm.deleteOne(inputs.record.id);\n\n    if (cardLabel) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'cardLabelDelete',\n        {\n          item: cardLabel,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CARD_LABEL_DELETE,\n        buildData: () => ({\n          item: cardLabel,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return cardLabel;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/card-memberships/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    userAlreadyCardMember: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    let cardMembership;\n    try {\n      cardMembership = await CardMembership.qm.createOne({\n        ...values,\n        cardId: values.card.id,\n        userId: values.user.id,\n      });\n    } catch (error) {\n      if (error.code === 'E_UNIQUE') {\n        throw 'userAlreadyCardMember';\n      }\n\n      throw error;\n    }\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'cardMembershipCreate',\n      {\n        item: cardMembership,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.CARD_MEMBERSHIP_CREATE,\n      buildData: () => ({\n        item: cardMembership,\n        included: {\n          users: [values.user],\n          projects: [inputs.project],\n          boards: [inputs.board],\n          lists: [inputs.list],\n          cards: [values.card],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    let cardSubscription;\n    try {\n      cardSubscription = await CardSubscription.qm.createOne({\n        cardId: cardMembership.cardId,\n        userId: cardMembership.userId,\n        isPermanent: false,\n      });\n    } catch (error) {\n      if (error.code !== 'E_UNIQUE') {\n        throw error;\n      }\n    }\n\n    if (cardSubscription) {\n      sails.sockets.broadcast(\n        `user:${cardMembership.userId}`,\n        'cardUpdate',\n        {\n          item: {\n            id: cardMembership.cardId,\n            isSubscribed: true,\n          },\n        },\n        inputs.request,\n      );\n\n      // TODO: send webhooks\n    }\n\n    await sails.helpers.actions.createOne.with({\n      webhooks,\n      values: {\n        type: Action.Types.ADD_MEMBER_TO_CARD,\n        data: {\n          user: _.pick(values.user, ['id', 'name']),\n          card: _.pick(values.card, ['name']),\n        },\n        user: inputs.actorUser,\n        card: values.card,\n      },\n      project: inputs.project,\n      board: inputs.board,\n      list: inputs.list,\n    });\n\n    return cardMembership;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/card-memberships/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    user: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const cardMembership = await CardMembership.qm.deleteOne(inputs.record.id);\n\n    if (cardMembership) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'cardMembershipDelete',\n        {\n          item: cardMembership,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CARD_MEMBERSHIP_DELETE,\n        buildData: () => ({\n          item: cardMembership,\n          included: {\n            users: [inputs.user],\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n\n      const cardSubscription = await CardSubscription.qm.deleteOne({\n        cardId: cardMembership.cardId,\n        userId: cardMembership.userId,\n        isPermanent: false,\n      });\n\n      if (cardSubscription) {\n        sails.sockets.broadcast(`user:${cardMembership.userId}`, 'cardUpdate', {\n          item: {\n            id: cardMembership.cardId,\n            isSubscribed: false,\n          },\n        });\n      }\n\n      await sails.helpers.actions.createOne.with({\n        webhooks,\n        values: {\n          type: Action.Types.REMOVE_MEMBER_FROM_CARD,\n          data: {\n            user: _.pick(inputs.user, ['id', 'name']),\n            card: _.pick(inputs.card, ['name']),\n          },\n          user: inputs.actorUser,\n          card: inputs.card,\n        },\n        project: inputs.project,\n        board: inputs.board,\n        list: inputs.list,\n      });\n    }\n\n    return cardMembership;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/cards/copy-custom-fields.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { POSITION_GAP } = require('../../../constants');\n\nmodule.exports = {\n  inputs: {\n    fromRecord: {\n      type: 'ref',\n      required: true,\n    },\n    toRecord: {\n      type: 'ref',\n      required: true,\n    },\n    detachBoardCustomFieldGroups: {\n      type: 'boolean',\n      defaultsTo: true,\n    },\n    detachBaseCustomFieldGroups: {\n      type: 'boolean',\n      defaultsTo: true,\n    },\n  },\n\n  async fn(inputs) {\n    const boardCustomFieldGroups = inputs.detachBoardCustomFieldGroups\n      ? await CustomFieldGroup.qm.getByBoardId(inputs.fromRecord.boardId)\n      : [];\n\n    const cardCustomFieldGroups = await CustomFieldGroup.qm.getByCardId(inputs.fromRecord.id);\n\n    const customFieldGroups = [...boardCustomFieldGroups, ...cardCustomFieldGroups];\n    const customFieldGroupIds = sails.helpers.utils.mapRecords(customFieldGroups);\n\n    const customFields = await CustomField.qm.getByCustomFieldGroupIds(customFieldGroupIds);\n\n    let customFieldGroupsByBaseCustomFieldGroupId;\n    let baseCustomFieldGroupById;\n    let customFieldsByBaseCustomFieldGroupId;\n    let nextCustomFieldsTotal = customFields.length;\n\n    if (inputs.detachBaseCustomFieldGroups) {\n      customFieldGroupsByBaseCustomFieldGroupId = _.groupBy(\n        customFieldGroups.filter(({ baseCustomFieldGroupId }) => baseCustomFieldGroupId),\n        'baseCustomFieldGroupId',\n      );\n\n      const baseCustomFieldGroupIds = Object.keys(customFieldGroupsByBaseCustomFieldGroupId);\n\n      if (baseCustomFieldGroupIds.length > 0) {\n        const baseCustomFieldGroups =\n          await BaseCustomFieldGroup.qm.getByIds(baseCustomFieldGroupIds);\n\n        baseCustomFieldGroupById = _.keyBy(baseCustomFieldGroups, 'id');\n\n        const baseCustomFields = await CustomField.qm.getByBaseCustomFieldGroupIds(\n          Object.keys(baseCustomFieldGroupById),\n        );\n\n        customFieldsByBaseCustomFieldGroupId = _.groupBy(\n          baseCustomFields,\n          'baseCustomFieldGroupId',\n        );\n\n        nextCustomFieldsTotal += Object.entries(customFieldGroupsByBaseCustomFieldGroupId).reduce(\n          (result, [baseCustomFieldGroupId, customFieldGroupsItem]) => {\n            const customFieldsItem = customFieldsByBaseCustomFieldGroupId[baseCustomFieldGroupId];\n\n            if (!customFieldsItem) {\n              return result;\n            }\n\n            return result + customFieldsItem.length * customFieldGroupsItem.length;\n          },\n          0,\n        );\n      }\n    }\n\n    const customFieldValues = await CustomFieldValue.qm.getByCardId(inputs.fromRecord.id);\n\n    const ids = await sails.helpers.utils.generateIds(\n      customFieldGroups.length + nextCustomFieldsTotal,\n    );\n\n    const nextCustomFieldGroupIdByCustomFieldGroupId = {};\n    const nextCustomFieldGroupsValues = customFieldGroups.map((customFieldGroup, index) => {\n      const id = ids.shift();\n      nextCustomFieldGroupIdByCustomFieldGroupId[customFieldGroup.id] = id;\n\n      const values = {\n        ..._.pick(customFieldGroup, ['position']),\n        id,\n        cardId: inputs.toRecord.id,\n        position: customFieldGroup.boardId\n          ? POSITION_GAP * (index + 1)\n          : customFieldGroup.position + POSITION_GAP * boardCustomFieldGroups.length,\n      };\n\n      if (inputs.detachBaseCustomFieldGroups) {\n        values.name =\n          customFieldGroup.name ||\n          baseCustomFieldGroupById[customFieldGroup.baseCustomFieldGroupId].name;\n      } else {\n        Object.assign(values, {\n          name: customFieldGroup.name,\n          baseCustomFieldGroupId: customFieldGroup.baseCustomFieldGroupId,\n        });\n      }\n\n      return values;\n    });\n\n    const nextCustomFieldGroups = await CustomFieldGroup.qm.create(nextCustomFieldGroupsValues);\n\n    const nextCustomFieldIdByCustomFieldId = {};\n    const nextCustomFieldsValues = customFields.map((customField) => {\n      const id = ids.shift();\n      nextCustomFieldIdByCustomFieldId[customField.id] = id;\n\n      return {\n        ..._.pick(customField, ['position', 'name', 'showOnFrontOfCard']),\n        id,\n        customFieldGroupId:\n          nextCustomFieldGroupIdByCustomFieldGroupId[customField.customFieldGroupId],\n      };\n    });\n\n    if (inputs.detachBaseCustomFieldGroups) {\n      Object.entries(customFieldGroupsByBaseCustomFieldGroupId).forEach(\n        ([baseCustomFieldGroupId, customFieldGroupsItem]) => {\n          const customFieldsItem = customFieldsByBaseCustomFieldGroupId[baseCustomFieldGroupId];\n\n          if (!customFieldsItem) {\n            return;\n          }\n\n          customFieldGroupsItem.forEach((customFieldGroup) => {\n            customFieldsItem.forEach((customField) => {\n              const groupedId = `${customFieldGroup.id}:${customField.id}`;\n              const id = ids.shift();\n\n              nextCustomFieldIdByCustomFieldId[groupedId] = id;\n\n              nextCustomFieldsValues.push({\n                ..._.pick(customField, ['position', 'name', 'showOnFrontOfCard']),\n                id,\n                customFieldGroupId: nextCustomFieldGroupIdByCustomFieldGroupId[customFieldGroup.id],\n              });\n            });\n          });\n        },\n      );\n    }\n\n    const nextCustomFields = await CustomField.qm.create(nextCustomFieldsValues);\n\n    const nextCustomFieldValuesValues = customFieldValues.map((customFieldValue) => {\n      const groupedId = `${customFieldValue.customFieldGroupId}:${customFieldValue.customFieldId}`;\n\n      return {\n        ..._.pick(customFieldValue, ['content']),\n        cardId: inputs.toRecord.id,\n        customFieldGroupId:\n          nextCustomFieldGroupIdByCustomFieldGroupId[customFieldValue.customFieldGroupId] ||\n          customFieldValue.customFieldGroupId,\n        customFieldId:\n          nextCustomFieldIdByCustomFieldId[groupedId] ||\n          nextCustomFieldIdByCustomFieldId[customFieldValue.customFieldId] ||\n          customFieldValue.customFieldId,\n      };\n    });\n\n    const nextCustomFieldValues = await CustomFieldValue.qm.create(nextCustomFieldValuesValues);\n\n    return {\n      customFieldGroups: nextCustomFieldGroups,\n      customFields: nextCustomFields,\n      customFieldValues: nextCustomFieldValues,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/cards/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    positionMustBeInValues: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (values.dueDate) {\n      if (_.isNil(values.isDueCompleted)) {\n        values.isDueCompleted = false;\n      }\n    } else {\n      delete values.isDueCompleted;\n    }\n\n    if (sails.helpers.lists.isFinite(values.list)) {\n      if (_.isUndefined(values.position)) {\n        throw 'positionMustBeInValues';\n      }\n\n      const cards = await Card.qm.getByListId(values.list.id);\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        cards,\n      );\n\n      values.position = position;\n\n      if (repositions.length > 0) {\n        // eslint-disable-next-line no-restricted-syntax\n        for (const reposition of repositions) {\n          // eslint-disable-next-line no-await-in-loop\n          await Card.qm.updateOne(\n            {\n              id: reposition.record.id,\n              listId: reposition.record.listId,\n            },\n            {\n              position: reposition.position,\n            },\n          );\n\n          sails.sockets.broadcast(`board:${values.board.id}`, 'cardUpdate', {\n            item: {\n              id: reposition.record.id,\n              position: reposition.position,\n            },\n          });\n\n          // TODO: send webhooks\n        }\n      }\n    } else {\n      delete values.position;\n    }\n\n    if (List.TYPE_STATE_BY_TYPE[values.list.type] === List.TypeStates.CLOSED) {\n      values.isClosed = true;\n    }\n\n    const card = await Card.qm.createOne({\n      ...values,\n      boardId: values.board.id,\n      listId: values.list.id,\n      creatorUserId: values.creatorUser.id,\n      listChangedAt: new Date().toISOString(),\n    });\n\n    sails.sockets.broadcast(\n      `board:${card.boardId}`,\n      'cardCreate',\n      {\n        item: card,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.CARD_CREATE,\n      buildData: () => ({\n        item: card,\n        included: {\n          projects: [inputs.project],\n          boards: [values.board],\n          lists: [values.list],\n        },\n      }),\n      user: values.creatorUser,\n    });\n\n    if (values.creatorUser.subscribeToOwnCards) {\n      try {\n        await CardSubscription.qm.createOne({\n          cardId: card.id,\n          userId: card.creatorUserId,\n        });\n      } catch (error) {\n        if (error.code !== 'E_UNIQUE') {\n          throw error;\n        }\n      }\n\n      sails.sockets.broadcast(`user:${card.creatorUserId}`, 'cardUpdate', {\n        item: {\n          id: card.id,\n          isSubscribed: true,\n        },\n      });\n\n      // TODO: send webhooks\n    }\n\n    await sails.helpers.actions.createOne.with({\n      webhooks,\n      values: {\n        card,\n        type: Action.Types.CREATE_CARD,\n        data: {\n          card: _.pick(card, ['name']),\n          list: _.pick(values.list, ['id', 'type', 'name']),\n        },\n        user: values.creatorUser,\n      },\n      project: inputs.project,\n      board: values.board,\n      list: values.list,\n    });\n\n    return card;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/cards/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    await sails.helpers.cards.deleteRelated(inputs.record);\n\n    const card = await Card.qm.deleteOne(inputs.record.id);\n\n    if (card) {\n      sails.sockets.broadcast(\n        `board:${card.boardId}`,\n        'cardDelete',\n        {\n          item: card,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CARD_DELETE,\n        buildData: () => ({\n          item: card,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return card;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/cards/delete-related.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    recordOrRecords: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    let cardIdOrIds;\n    if (_.isPlainObject(inputs.recordOrRecords)) {\n      ({\n        recordOrRecords: { id: cardIdOrIds },\n      } = inputs);\n    } else if (_.every(inputs.recordOrRecords, _.isPlainObject)) {\n      cardIdOrIds = sails.helpers.utils.mapRecords(inputs.recordOrRecords);\n    }\n\n    await CardSubscription.qm.delete({\n      cardId: cardIdOrIds,\n    });\n\n    await CardMembership.qm.delete({\n      cardId: cardIdOrIds,\n    });\n\n    await CardLabel.qm.delete({\n      cardId: cardIdOrIds,\n    });\n\n    const taskLists = await TaskList.qm.delete({\n      cardId: cardIdOrIds,\n    });\n\n    await sails.helpers.taskLists.deleteRelated(taskLists);\n\n    await Task.qm.update(\n      {\n        linkedCardId: cardIdOrIds,\n      },\n      {\n        linkedCardId: null,\n      },\n    );\n\n    const { uploadedFiles } = await Attachment.qm.delete({\n      cardId: cardIdOrIds,\n    });\n\n    sails.helpers.utils.removeUnreferencedUploadedFiles(uploadedFiles);\n\n    const customFieldGroups = await CustomFieldGroup.qm.delete({\n      cardId: cardIdOrIds,\n    });\n\n    await sails.helpers.customFieldGroups.deleteRelated(customFieldGroups);\n\n    await Comment.qm.delete({\n      cardId: cardIdOrIds,\n    });\n\n    await Action.qm.delete({\n      cardId: cardIdOrIds,\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/cards/detach-custom-fields.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { POSITION_GAP } = require('../../../constants');\n\nmodule.exports = {\n  inputs: {\n    idOrIds: {\n      type: 'json',\n      required: true,\n    },\n    boardId: {\n      type: 'ref',\n      required: true,\n    },\n    withBaseCustomFieldGroups: {\n      type: 'boolean',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const cardIds = _.isString(inputs.idOrIds) ? [inputs.idOrIds] : inputs.idOrIds;\n\n    const boardCustomFieldGroups = await CustomFieldGroup.qm.getByBoardId(inputs.boardId);\n    const boardCustomFieldGroupIds = sails.helpers.utils.mapRecords(boardCustomFieldGroups);\n\n    const boardCustomFields =\n      await CustomField.qm.getByCustomFieldGroupIds(boardCustomFieldGroupIds);\n\n    const cardsCustomFieldGroups = await CustomFieldGroup.qm.getByCardIds(cardIds);\n    const customFieldGroupsByCardId = _.groupBy(cardsCustomFieldGroups, 'cardId');\n\n    let basedBoardCustomFieldGroups;\n    let basedCardCustomFieldGroups;\n    let baseCustomFieldGroupById;\n    let customFieldsByBaseCustomFieldGroupId;\n\n    if (inputs.withBaseCustomFieldGroups) {\n      basedBoardCustomFieldGroups = boardCustomFieldGroups.filter(\n        ({ baseCustomFieldGroupId }) => baseCustomFieldGroupId,\n      );\n\n      basedCardCustomFieldGroups = cardsCustomFieldGroups.filter(\n        ({ baseCustomFieldGroupId }) => baseCustomFieldGroupId,\n      );\n\n      const basedCustomFieldGroups = [\n        ...basedBoardCustomFieldGroups,\n        ...basedCardCustomFieldGroups,\n      ];\n\n      const baseCustomFieldGroupIds = sails.helpers.utils.mapRecords(\n        basedCustomFieldGroups,\n        'baseCustomFieldGroupId',\n        true,\n      );\n\n      const baseCustomFieldGroups = await BaseCustomFieldGroup.qm.getByIds(baseCustomFieldGroupIds);\n      baseCustomFieldGroupById = _.keyBy(baseCustomFieldGroups, 'id');\n\n      const baseCustomFields = await CustomField.qm.getByBaseCustomFieldGroupIds(\n        Object.keys(baseCustomFieldGroupById),\n      );\n\n      customFieldsByBaseCustomFieldGroupId = _.groupBy(baseCustomFields, 'baseCustomFieldGroupId');\n    }\n\n    let idsTotal = (boardCustomFieldGroups.length + boardCustomFields.length) * cardIds.length;\n\n    if (inputs.withBaseCustomFieldGroups) {\n      idsTotal += basedBoardCustomFieldGroups.reduce((result, customFieldGroup) => {\n        const customFieldsItem =\n          customFieldsByBaseCustomFieldGroupId[customFieldGroup.baseCustomFieldGroupId];\n\n        return result + (customFieldsItem ? customFieldsItem.length : 0) * cardIds.length;\n      }, 0);\n\n      idsTotal += basedCardCustomFieldGroups.reduce((result, customFieldGroup) => {\n        const customFieldsItem =\n          customFieldsByBaseCustomFieldGroupId[customFieldGroup.baseCustomFieldGroupId];\n\n        return result + (customFieldsItem ? customFieldsItem.length : 0);\n      }, 0);\n    }\n\n    const ids = await sails.helpers.utils.generateIds(idsTotal);\n\n    const nextCustomFieldGroupIdByCustomFieldGroupIdByCardId = {};\n    const nextCustomFieldGroupsValues = (\n      await Promise.all(\n        cardIds.map(async (cardId) => {\n          const customFieldGroupIdByCustomFieldGroupId = {};\n          const customFieldGroupsValues = boardCustomFieldGroups.map((customFieldGroup, index) => {\n            const id = ids.shift();\n            customFieldGroupIdByCustomFieldGroupId[customFieldGroup.id] = id;\n\n            const values = {\n              ..._.pick(customFieldGroup, ['baseCustomFieldGroupId', 'name']),\n              id,\n              cardId,\n              position: POSITION_GAP * (index + 1),\n            };\n\n            if (inputs.withBaseCustomFieldGroups && customFieldGroup.baseCustomFieldGroupId) {\n              values.baseCustomFieldGroupId = null;\n\n              if (!customFieldGroup.name) {\n                values.name =\n                  baseCustomFieldGroupById[customFieldGroup.baseCustomFieldGroupId].name;\n              }\n            }\n\n            return values;\n          });\n\n          nextCustomFieldGroupIdByCustomFieldGroupIdByCardId[cardId] =\n            customFieldGroupIdByCustomFieldGroupId;\n\n          if (customFieldGroupsValues.length > 0) {\n            const cardCustomFieldGroups = customFieldGroupsByCardId[cardId];\n\n            if (cardCustomFieldGroups && cardCustomFieldGroups.length > 0) {\n              const { position } = customFieldGroupsValues[customFieldGroupsValues.length - 1];\n\n              await Promise.all(\n                cardCustomFieldGroups.map((customFieldGroup) =>\n                  CustomFieldGroup.qm.updateOne(customFieldGroup.id, {\n                    position: customFieldGroup.position + position,\n                  }),\n                ),\n              );\n            }\n          }\n\n          return customFieldGroupsValues;\n        }),\n      )\n    ).flat();\n\n    await CustomFieldGroup.qm.create(nextCustomFieldGroupsValues);\n\n    if (inputs.withBaseCustomFieldGroups) {\n      await CustomFieldGroup.qm.update(\n        {\n          cardId: cardIds,\n          baseCustomFieldGroupId: {\n            '!=': null,\n          },\n        },\n        {\n          baseCustomFieldGroupId: null,\n        },\n      );\n\n      const unnamedCustomFieldGroups = basedCardCustomFieldGroups.filter(({ name }) => !name);\n\n      await Promise.all(\n        unnamedCustomFieldGroups.map((customFieldGroup) =>\n          CustomFieldGroup.qm.updateOne(customFieldGroup.id, {\n            name: baseCustomFieldGroupById[customFieldGroup.baseCustomFieldGroupId].name,\n          }),\n        ),\n      );\n    }\n\n    const nextCustomFieldIdByCustomFieldIdByCardId = {};\n    const nextCustomFieldsValues = cardIds.flatMap((cardId) => {\n      const customFieldIdByCustomFieldId = {};\n      const customFieldsValues = boardCustomFields.map((customField) => {\n        const id = ids.shift();\n        customFieldIdByCustomFieldId[customField.id] = id;\n\n        return {\n          ..._.pick(customField, ['name', 'showOnFrontOfCard', 'position']),\n          id,\n          customFieldGroupId:\n            nextCustomFieldGroupIdByCustomFieldGroupIdByCardId[cardId][\n              customField.customFieldGroupId\n            ],\n        };\n      });\n\n      nextCustomFieldIdByCustomFieldIdByCardId[cardId] = customFieldIdByCustomFieldId;\n      return customFieldsValues;\n    });\n\n    if (inputs.withBaseCustomFieldGroups) {\n      cardIds.forEach((cardId) => {\n        basedBoardCustomFieldGroups.forEach((customFieldGroup) => {\n          const customFieldsItem =\n            customFieldsByBaseCustomFieldGroupId[customFieldGroup.baseCustomFieldGroupId];\n\n          if (!customFieldsItem) {\n            return;\n          }\n\n          customFieldsItem.forEach((customField) => {\n            const groupedId = `${customFieldGroup.id}:${customField.id}`;\n            const id = ids.shift();\n\n            nextCustomFieldIdByCustomFieldIdByCardId[cardId][groupedId] = id;\n\n            nextCustomFieldsValues.push({\n              ..._.pick(customField, ['name', 'showOnFrontOfCard', 'position']),\n              id,\n              customFieldGroupId:\n                nextCustomFieldGroupIdByCustomFieldGroupIdByCardId[cardId][customFieldGroup.id],\n            });\n          });\n        });\n      });\n\n      basedCardCustomFieldGroups.forEach((customFieldGroup) => {\n        const customFieldsItem =\n          customFieldsByBaseCustomFieldGroupId[customFieldGroup.baseCustomFieldGroupId];\n\n        if (!customFieldsItem) {\n          return;\n        }\n\n        customFieldsItem.forEach((customField) => {\n          const groupedId = `${customFieldGroup.id}:${customField.id}`;\n          const id = ids.shift();\n\n          nextCustomFieldIdByCustomFieldIdByCardId[customFieldGroup.cardId][groupedId] = id;\n\n          nextCustomFieldsValues.push({\n            ..._.pick(customField, ['name', 'showOnFrontOfCard', 'position']),\n            id,\n            customFieldGroupId: customFieldGroup.id,\n          });\n        });\n      });\n    }\n\n    await CustomField.qm.create(nextCustomFieldsValues);\n\n    const customFieldGroupIds = boardCustomFieldGroupIds;\n    if (inputs.withBaseCustomFieldGroups) {\n      customFieldGroupIds.push(...sails.helpers.utils.mapRecords(basedCardCustomFieldGroups));\n    }\n\n    const customFieldValues = await CustomFieldValue.qm.getByCardIds(cardIds, {\n      customFieldGroupIdOrIds: customFieldGroupIds,\n    });\n\n    await Promise.all(\n      customFieldValues.map((customFieldValue) => {\n        const updateValues = {\n          customFieldGroupId:\n            nextCustomFieldGroupIdByCustomFieldGroupIdByCardId[customFieldValue.cardId][\n              customFieldValue.customFieldGroupId\n            ],\n        };\n\n        const nextCustomFieldIdByCustomFieldId =\n          nextCustomFieldIdByCustomFieldIdByCardId[customFieldValue.cardId];\n\n        if (nextCustomFieldIdByCustomFieldId) {\n          const groupedId = `${customFieldValue.customFieldGroupId}:${customFieldValue.customFieldId}`;\n\n          const nextCustomFieldId =\n            nextCustomFieldIdByCustomFieldId[groupedId] ||\n            nextCustomFieldIdByCustomFieldId[customFieldValue.customFieldId];\n\n          if (nextCustomFieldId) {\n            updateValues.customFieldId = nextCustomFieldId;\n          }\n        }\n\n        return CustomFieldValue.qm.updateOne(customFieldValue.id, updateValues);\n      }),\n    );\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/cards/duplicate-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    positionMustBeInValues: {},\n    boardInValuesMustBelongToProject: {},\n    listMustBeInValues: {},\n    listInValuesMustBelongToBoard: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (values.project && values.project.id === inputs.project.id) {\n      delete values.project;\n    }\n\n    const project = values.project || inputs.project;\n\n    if (values.board) {\n      if (values.board.projectId !== project.id) {\n        throw 'boardInValuesMustBelongToProject';\n      }\n\n      if (values.board.id === inputs.board.id) {\n        delete values.board;\n      } else {\n        values.boardId = values.board.id;\n      }\n    }\n\n    const board = values.board || inputs.board;\n\n    if (values.list) {\n      if (values.list.boardId !== board.id) {\n        throw 'listInValuesMustBelongToBoard';\n      }\n\n      if (values.list.id === inputs.list.id) {\n        delete values.list;\n      } else {\n        values.listId = values.list.id;\n      }\n    } else if (values.board) {\n      throw 'listMustBeInValues';\n    }\n\n    const list = values.list || inputs.list;\n\n    if (sails.helpers.lists.isFinite(list)) {\n      if (values.list && _.isUndefined(values.position)) {\n        throw 'positionMustBeInValues';\n      }\n    } else {\n      values.position = null;\n    }\n\n    if (sails.helpers.lists.isFinite(list)) {\n      if (_.isUndefined(values.position)) {\n        throw 'positionMustBeInValues';\n      }\n\n      const cards = await Card.qm.getByListId(list.id);\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        cards,\n      );\n\n      values.position = position;\n\n      if (repositions.length > 0) {\n        // eslint-disable-next-line no-restricted-syntax\n        for (const reposition of repositions) {\n          // eslint-disable-next-line no-await-in-loop\n          await Card.qm.updateOne(\n            {\n              id: reposition.record.id,\n              listId: reposition.record.listId,\n            },\n            {\n              position: reposition.position,\n            },\n          );\n\n          sails.sockets.broadcast(`board:${inputs.record.boardId}`, 'cardUpdate', {\n            item: {\n              id: reposition.record.id,\n              position: reposition.position,\n            },\n          });\n\n          // TODO: send webhooks\n        }\n      }\n    }\n\n    let labelIds;\n    if (values.board) {\n      const prevLabels = await sails.helpers.cards.getLabels(inputs.record.id);\n\n      const labels = await Label.qm.getByBoardId(values.board.id);\n      const labelByName = _.keyBy(labels, 'name');\n\n      labelIds = await Promise.all(\n        prevLabels.map(async (label) => {\n          if (labelByName[label.name]) {\n            return labelByName[label.name].id;\n          }\n\n          const { id } = await sails.helpers.labels.createOne.with({\n            project,\n            values: {\n              ..._.omit(label, ['id', 'boardId', 'createdAt', 'updatedAt']),\n              board,\n            },\n            actorUser: values.creatorUser,\n          });\n\n          return id;\n        }),\n      );\n    }\n\n    if (values.list) {\n      const typeState = List.TYPE_STATE_BY_TYPE[values.list.type];\n\n      if (inputs.record.isClosed) {\n        if (typeState === List.TypeStates.OPENED) {\n          values.isClosed = false;\n        }\n      } else if (typeState === List.TypeStates.CLOSED) {\n        values.isClosed = true;\n      }\n    }\n\n    if (!values.name) {\n      const t = sails.helpers.utils.makeTranslator(values.creatorUser.language);\n      values.name = `${inputs.record.name} (${t('copy')})`;\n    }\n\n    let card = await Card.qm.createOne({\n      ..._.pick(inputs.record, [\n        'boardId',\n        'listId',\n        'prevListId',\n        'type',\n        'name',\n        'description',\n        'dueDate',\n        'isDueCompleted',\n        'stopwatch',\n        'isClosed',\n      ]),\n      ...values,\n      creatorUserId: values.creatorUser.id,\n      listChangedAt: new Date().toISOString(),\n    });\n\n    const boardMemberUserIds = await sails.helpers.boards.getMemberUserIds(card.boardId);\n    const boardMemberUserIdsSet = new Set(boardMemberUserIds);\n\n    const cardMemberships = await CardMembership.qm.getByCardId(inputs.record.id, {\n      userIdOrIds: boardMemberUserIds,\n    });\n\n    const cardMembershipsValues = cardMemberships.map((cardMembership) => ({\n      ..._.pick(cardMembership, ['userId']),\n      cardId: card.id,\n    }));\n\n    const nextCardMemberships = await CardMembership.qm.create(cardMembershipsValues);\n\n    if (!values.board) {\n      const cardLabels = await CardLabel.qm.getByCardId(inputs.record.id);\n      labelIds = sails.helpers.utils.mapRecords(cardLabels, 'labelId');\n    }\n\n    const cardLabelsValues = labelIds.map((labelId) => ({\n      labelId,\n      cardId: card.id,\n    }));\n\n    const nextCardLabels = await CardLabel.qm.create(cardLabelsValues);\n\n    const taskLists = await TaskList.qm.getByCardId(inputs.record.id);\n    const taskListIds = sails.helpers.utils.mapRecords(taskLists);\n\n    const tasks = await Task.qm.getByTaskListIds(taskListIds);\n    const attachments = await Attachment.qm.getByCardId(inputs.record.id);\n\n    const ids = await sails.helpers.utils.generateIds(taskLists.length + attachments.length);\n\n    const nextTaskListIdByTaskListId = {};\n    const nextTaskListsValues = await taskLists.map((taskList) => {\n      const id = ids.shift();\n      nextTaskListIdByTaskListId[taskList.id] = id;\n\n      return {\n        ..._.pick(taskList, ['position', 'name', 'showOnFrontOfCard', 'hideCompletedTasks']),\n        id,\n        cardId: card.id,\n      };\n    });\n\n    const nextTaskLists = await TaskList.qm.create(nextTaskListsValues);\n\n    const nextTasksValues = tasks.map((task) => ({\n      ..._.pick(task, ['linkedCardId', 'position', 'name', 'isCompleted']),\n      taskListId: nextTaskListIdByTaskListId[task.taskListId],\n      assigneeUserId: boardMemberUserIdsSet.has(task.assigneeUserId) ? task.assigneeUserId : null,\n    }));\n\n    const nextTasks = await Task.qm.create(nextTasksValues);\n\n    const nextAttachmentIdByAttachmentId = {};\n    const nextAttachmentsValues = attachments.map((attachment) => {\n      const id = ids.shift();\n      nextAttachmentIdByAttachmentId[attachment.id] = id;\n\n      return {\n        ..._.pick(attachment, ['type', 'data', 'name']),\n        id,\n        cardId: card.id,\n        creatorUserId: card.creatorUserId,\n      };\n    });\n\n    const nextAttachments = await Attachment.qm.create(nextAttachmentsValues);\n\n    if (inputs.record.coverAttachmentId) {\n      const nextCoverAttachmentId = nextAttachmentIdByAttachmentId[inputs.record.coverAttachmentId];\n\n      if (nextCoverAttachmentId) {\n        ({ card } = await Card.qm.updateOne(card.id, {\n          coverAttachmentId: nextCoverAttachmentId,\n        }));\n      }\n    }\n\n    const {\n      customFieldGroups: nextCustomFieldGroups,\n      customFields: nextCustomFields,\n      customFieldValues: nextCustomFieldValues,\n    } = await sails.helpers.cards.copyCustomFields(\n      inputs.record,\n      card,\n      !!values.board,\n      !!values.project,\n    );\n\n    sails.sockets.broadcast(\n      `board:${card.boardId}`,\n      'cardCreate',\n      {\n        item: card,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.CARD_CREATE,\n      buildData: () => ({\n        item: card,\n        included: {\n          projects: [project],\n          boards: [board],\n          lists: [list],\n          cardMemberships: nextCardMemberships,\n          cardLabels: nextCardLabels,\n          taskLists: nextTaskLists,\n          tasks: nextTasks,\n          attachments: sails.helpers.attachments.presentMany(nextAttachments),\n          customFieldGroups: nextCustomFieldGroups,\n          customFields: nextCustomFields,\n          customFieldValues: nextCustomFieldValues,\n        },\n      }),\n      user: values.creatorUser,\n    });\n\n    if (values.creatorUser.subscribeToOwnCards) {\n      try {\n        await CardSubscription.qm.createOne({\n          cardId: card.id,\n          userId: card.creatorUserId,\n        });\n      } catch (error) {\n        if (error.code !== 'E_UNIQUE') {\n          throw error;\n        }\n      }\n\n      sails.sockets.broadcast(`user:${card.creatorUserId}`, 'cardUpdate', {\n        item: {\n          id: card.id,\n          isSubscribed: true,\n        },\n      });\n\n      // TODO: send webhooks\n    }\n\n    await sails.helpers.actions.createOne.with({\n      project,\n      board,\n      list,\n      webhooks,\n      values: {\n        card,\n        type: Action.Types.CREATE_CARD, // TODO: introduce separate type?\n        data: {\n          card: _.pick(card, ['name']),\n          list: _.pick(list, ['id', 'type', 'name']),\n        },\n        user: values.creatorUser,\n      },\n    });\n\n    return {\n      card,\n      cardMemberships: nextCardMemberships,\n      cardLabels: nextCardLabels,\n      taskLists: nextTaskLists,\n      tasks: nextTasks,\n      attachments: nextAttachments,\n      customFieldGroups: nextCustomFieldGroups,\n      customFields: nextCustomFields,\n      customFieldValues: nextCustomFieldValues,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/cards/get-labels.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const cardLabels = await CardLabel.qm.getByCardId(inputs.id);\n    const labelIds = sails.helpers.utils.mapRecords(cardLabels, 'labelId');\n\n    return Label.qm.getByIds(labelIds);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/cards/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const card = await Card.qm.getOneById(inputs.id);\n\n    if (!card) {\n      throw 'pathNotFound';\n    }\n\n    const pathToProject = await sails.helpers.lists\n      .getPathToProjectById(card.listId)\n      .intercept('pathNotFound', (nodes) => ({\n        pathNotFound: {\n          card,\n          ...nodes,\n        },\n      }));\n\n    return {\n      card,\n      ...pathToProject,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/cards/get-subscription-user-ids.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n    exceptUserIdOrIds: {\n      type: 'json',\n    },\n  },\n\n  async fn(inputs) {\n    const cardSubscriptions = await CardSubscription.qm.getByCardId(inputs.id, {\n      exceptUserIdOrIds: inputs.exceptUserIdOrIds,\n    });\n\n    return sails.helpers.utils.mapRecords(cardSubscriptions, 'userId');\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/cards/read-notifications-for-user.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    user: {\n      type: 'ref',\n      required: true,\n    },\n    // TODO: add actorUser as well?\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const notifications = await Notification.qm.update(\n      {\n        userId: inputs.user.id,\n        cardId: inputs.record.id,\n        isRead: false,\n      },\n      {\n        isRead: true,\n      },\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    notifications.forEach((notification) => {\n      sails.sockets.broadcast(\n        `user:${notification.userId}`,\n        'notificationUpdate',\n        {\n          item: notification,\n        },\n        inputs.request,\n      );\n\n      // TODO: with prevData?\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.NOTIFICATION_UPDATE,\n        buildData: () => ({\n          item: notification,\n        }),\n        user: inputs.user,\n      });\n    });\n\n    return notifications;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/cards/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    webhooks: {\n      type: 'ref',\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    positionMustBeInValues: {},\n    boardInValuesMustBelongToProject: {},\n    listMustBeInValues: {},\n    listInValuesMustBelongToBoard: {},\n    coverAttachmentInValuesMustContainImage: {},\n  },\n\n  // TODO: use normalizeValues and refactor\n  async fn(inputs) {\n    const { isSubscribed, ...values } = inputs.values;\n\n    if (values.project && values.project.id === inputs.project.id) {\n      delete values.project;\n    }\n\n    const project = values.project || inputs.project;\n\n    if (values.board) {\n      if (values.board.projectId !== project.id) {\n        throw 'boardInValuesMustBelongToProject';\n      }\n\n      if (values.board.id === inputs.board.id) {\n        delete values.board;\n      } else {\n        values.boardId = values.board.id;\n      }\n    }\n\n    const board = values.board || inputs.board;\n\n    if (values.list) {\n      if (values.list.boardId !== board.id) {\n        throw 'listInValuesMustBelongToBoard';\n      }\n\n      if (values.list.id === inputs.list.id) {\n        delete values.list;\n      } else {\n        values.listId = values.list.id;\n      }\n    } else if (values.board) {\n      throw 'listMustBeInValues';\n    }\n\n    const list = values.list || inputs.list;\n\n    if (sails.helpers.lists.isFinite(list)) {\n      if (values.list && _.isUndefined(values.position)) {\n        throw 'positionMustBeInValues';\n      }\n    } else {\n      values.position = null;\n    }\n\n    if (values.coverAttachment) {\n      if (!values.coverAttachment.data.image) {\n        throw 'coverAttachmentInValuesMustContainImage';\n      }\n\n      if (values.coverAttachment.id === inputs.record.coverAttachmentId) {\n        delete values.coverAttachment;\n      } else {\n        values.coverAttachmentId = values.coverAttachment.id;\n      }\n    }\n\n    const dueDate = _.isUndefined(values.dueDate) ? inputs.record.dueDate : values.dueDate;\n\n    if (dueDate) {\n      const isDueCompleted = _.isUndefined(values.isDueCompleted)\n        ? inputs.record.isDueCompleted\n        : values.isDueCompleted;\n\n      if (_.isNull(isDueCompleted)) {\n        values.isDueCompleted = false;\n      }\n    } else {\n      values.isDueCompleted = null;\n    }\n\n    let card;\n    if (_.isEmpty(values)) {\n      card = inputs.record;\n    } else {\n      const { webhooks = await Webhook.qm.getAll() } = inputs;\n\n      if (!_.isNil(values.position)) {\n        const cards = await Card.qm.getByListId(list.id, {\n          exceptIdOrIds: inputs.record.id,\n        });\n\n        const { position, repositions } = sails.helpers.utils.insertToPositionables(\n          values.position,\n          cards,\n        );\n\n        values.position = position;\n\n        if (repositions.length > 0) {\n          // eslint-disable-next-line no-restricted-syntax\n          for (const reposition of repositions) {\n            // eslint-disable-next-line no-await-in-loop\n            await Card.qm.updateOne(\n              {\n                id: reposition.record.id,\n                listId: reposition.record.listId,\n              },\n              {\n                position: reposition.position,\n              },\n            );\n\n            sails.sockets.broadcast(`board:${board.id}`, 'cardUpdate', {\n              item: {\n                id: reposition.record.id,\n                position: reposition.position,\n              },\n            });\n\n            // TODO: send webhooks\n          }\n        }\n      }\n\n      let prevLabels;\n      if (values.board) {\n        prevLabels = await sails.helpers.cards.getLabels(inputs.record.id);\n\n        const boardMemberUserIds = await sails.helpers.boards.getMemberUserIds(values.board.id);\n\n        await CardSubscription.qm.delete({\n          cardId: inputs.record.id,\n          userId: {\n            '!=': boardMemberUserIds,\n          },\n        });\n\n        await CardMembership.qm.delete({\n          cardId: inputs.record.id,\n          userId: {\n            '!=': boardMemberUserIds,\n          },\n        });\n\n        await CardLabel.qm.delete({\n          cardId: inputs.record.id,\n        });\n\n        const taskLists = await TaskList.qm.getByCardId(inputs.record.id);\n        const taskListIds = sails.helpers.utils.mapRecords(taskLists);\n\n        await Task.qm.update(\n          {\n            taskListId: taskListIds,\n            assigneeUserId: {\n              '!=': boardMemberUserIds,\n            },\n          },\n          {\n            assigneeUserId: null,\n          },\n        );\n\n        await sails.helpers.cards.detachCustomFields(\n          inputs.record.id,\n          inputs.board.id,\n          !!values.project,\n        );\n      }\n\n      if (values.list) {\n        if (values.board || inputs.list.type === List.Types.TRASH) {\n          values.prevListId = null;\n        } else if (sails.helpers.lists.isArchiveOrTrash(values.list)) {\n          values.prevListId = inputs.list.id;\n        } else if (inputs.list.type === List.Types.ARCHIVE) {\n          values.prevListId = null;\n        }\n\n        const typeState = List.TYPE_STATE_BY_TYPE[values.list.type];\n\n        if (inputs.record.isClosed) {\n          if (typeState === List.TypeStates.OPENED) {\n            values.isClosed = false;\n          }\n        } else if (typeState === List.TypeStates.CLOSED) {\n          values.isClosed = true;\n        }\n\n        values.listChangedAt = new Date().toISOString();\n      }\n\n      const updateResult = await Card.qm.updateOne(inputs.record.id, values);\n\n      ({ card } = updateResult);\n      const { tasks } = updateResult;\n\n      if (!card) {\n        return card;\n      }\n\n      if (values.board) {\n        const labels = await Label.qm.getByBoardId(card.boardId);\n        const labelByName = _.keyBy(labels, 'name');\n\n        const labelIds = await Promise.all(\n          prevLabels.map(async (label) => {\n            if (labelByName[label.name]) {\n              return labelByName[label.name].id;\n            }\n\n            const { id } = await sails.helpers.labels.createOne.with({\n              project,\n              webhooks,\n              values: {\n                ..._.omit(label, ['id', 'boardId', 'createdAt', 'updatedAt']),\n                board,\n              },\n              actorUser: inputs.actorUser,\n            });\n\n            return id;\n          }),\n        );\n\n        await Promise.all(\n          labelIds.map((labelId) => {\n            try {\n              return CardLabel.qm.createOne({\n                labelId,\n                cardId: card.id,\n              });\n            } catch (error) {\n              if (error.code !== 'E_UNIQUE') {\n                throw error;\n              }\n            }\n\n            return Promise.resolve();\n          }),\n        );\n\n        sails.sockets.broadcast(\n          `board:${inputs.board.id}`,\n          'cardUpdate',\n          {\n            item: {\n              id: card.id,\n              boardId: null,\n            },\n          },\n          inputs.request,\n        );\n\n        sails.sockets.broadcast(\n          `board:${card.boardId}`,\n          'cardUpdate',\n          {\n            item: card,\n          },\n          inputs.request,\n        );\n\n        // TODO: add transfer action\n      } else {\n        sails.sockets.broadcast(\n          `board:${card.boardId}`,\n          'cardUpdate',\n          {\n            item: card,\n          },\n          inputs.request,\n        );\n\n        if (values.list) {\n          await sails.helpers.actions.createOne.with({\n            webhooks,\n            values: {\n              card,\n              type: Action.Types.MOVE_CARD,\n              data: {\n                card: _.pick(card, ['name']),\n                fromList: _.pick(inputs.list, ['id', 'type', 'name']),\n                toList: _.pick(values.list, ['id', 'type', 'name']),\n              },\n              user: inputs.actorUser,\n            },\n            project: inputs.project,\n            board: inputs.board,\n            list: values.list,\n          });\n        }\n      }\n\n      if (tasks) {\n        const taskListIds = sails.helpers.utils.mapRecords(tasks, 'taskListId', true);\n        const taskLists = await TaskList.qm.getByIds(taskListIds);\n        const taskListById = _.keyBy(taskLists, 'id');\n\n        const cardIds = sails.helpers.utils.mapRecords(taskLists, 'cardId', true);\n        const cards = await Card.qm.getByIds(cardIds);\n        const cardById = _.keyBy(cards, 'id');\n\n        const boardIdByTaskId = tasks.reduce(\n          (result, task) => ({\n            ...result,\n            [task.id]: cardById[taskListById[task.taskListId].cardId].boardId,\n          }),\n          {},\n        );\n\n        tasks.forEach((task) => {\n          sails.sockets.broadcast(`board:${boardIdByTaskId[task.id]}`, 'taskUpdate', {\n            item: task,\n          });\n        });\n\n        // TODO: send webhooks\n      }\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CARD_UPDATE,\n        buildData: () => ({\n          item: card,\n          included: {\n            projects: [project],\n            boards: [board],\n            lists: [list],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    if (!_.isUndefined(isSubscribed)) {\n      const wasSubscribed = await sails.helpers.users.isCardSubscriber(\n        inputs.actorUser.id,\n        card.id,\n      );\n\n      if (isSubscribed !== wasSubscribed) {\n        if (isSubscribed) {\n          try {\n            await CardSubscription.qm.createOne({\n              cardId: card.id,\n              userId: inputs.actorUser.id,\n            });\n          } catch (error) {\n            if (error.code !== 'E_UNIQUE') {\n              throw error;\n            }\n          }\n        } else {\n          await CardSubscription.qm.deleteOne({\n            cardId: card.id,\n            userId: inputs.actorUser.id,\n          });\n        }\n\n        sails.sockets.broadcast(\n          `user:${inputs.actorUser.id}`,\n          'cardUpdate',\n          {\n            item: {\n              isSubscribed,\n              id: card.id,\n            },\n          },\n          inputs.request,\n        );\n\n        // TODO: send webhooks\n      }\n    }\n\n    return card;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/comments/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst escapeMarkdown = require('escape-markdown');\nconst escapeHtml = require('escape-html');\n\nconst { extractMentionIds, mentionMarkupToText } = require('../../../utils/mentions');\n\nconst buildAndSendNotifications = async (services, board, card, comment, actorUser, t) => {\n  const markdownCardLink = `[${escapeMarkdown(card.name)}](${sails.config.custom.baseUrl}/cards/${card.id})`;\n  const htmlCardLink = `<a href=\"${sails.config.custom.baseUrl}/cards/${card.id}}\">${escapeHtml(card.name)}</a>`;\n  const commentText = _.truncate(mentionMarkupToText(comment.text));\n\n  await sails.helpers.utils.sendNotifications(services, t('New Comment'), {\n    text: `${t(\n      '%s left a new comment to %s on %s',\n      actorUser.name,\n      card.name,\n      board.name,\n    )}:\\n${commentText}`,\n    markdown: `${t(\n      '%s left a new comment to %s on %s',\n      escapeMarkdown(actorUser.name),\n      markdownCardLink,\n      escapeMarkdown(board.name),\n    )}:\\n\\n*${escapeMarkdown(commentText)}*`,\n    html: `${t(\n      '%s left a new comment to %s on %s',\n      escapeHtml(actorUser.name),\n      htmlCardLink,\n      escapeHtml(board.name),\n    )}:\\n\\n<i>${escapeHtml(commentText)}</i>`,\n  });\n};\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const comment = await Comment.qm.createOne({\n      ...values,\n      cardId: values.card.id,\n      userId: values.user.id,\n    });\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'commentCreate',\n      {\n        item: comment,\n        included: {\n          users: [sails.helpers.users.presentOne(values.user, {})],\n        },\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.COMMENT_CREATE,\n      buildData: () => ({\n        item: comment,\n        included: {\n          projects: [inputs.project],\n          boards: [inputs.board],\n          lists: [inputs.list],\n          cards: [values.card],\n        },\n      }),\n      user: values.user,\n    });\n\n    let mentionUserIds = extractMentionIds(comment.text);\n\n    if (mentionUserIds.length > 0) {\n      const boardMemberUserIds = await sails.helpers.boards.getMemberUserIds(inputs.board.id);\n\n      mentionUserIds = _.difference(_.intersection(mentionUserIds, boardMemberUserIds), [\n        comment.userId,\n      ]);\n    }\n\n    const mentionUserIdsSet = new Set(mentionUserIds);\n\n    const cardSubscriptionUserIds = await sails.helpers.cards.getSubscriptionUserIds(\n      comment.cardId,\n      comment.userId,\n    );\n\n    const boardSubscriptionUserIds = await sails.helpers.boards.getSubscriptionUserIds(\n      inputs.board.id,\n      comment.userId,\n    );\n\n    const notifiableUserIds = _.union(\n      mentionUserIds,\n      cardSubscriptionUserIds,\n      boardSubscriptionUserIds,\n    );\n\n    await sails.helpers.notifications.createMany.with({\n      webhooks,\n      arrayOfValues: notifiableUserIds.map((userId) => ({\n        userId,\n        comment,\n        type: mentionUserIdsSet.has(userId)\n          ? Notification.Types.MENTION_IN_COMMENT\n          : Notification.Types.COMMENT_CARD,\n        data: {\n          card: _.pick(values.card, ['name']),\n          text: comment.text,\n        },\n        creatorUser: values.user,\n        card: values.card,\n      })),\n      project: inputs.project,\n      board: inputs.board,\n      list: inputs.list,\n    });\n\n    if (values.user.subscribeToCardWhenCommenting) {\n      let cardSubscription;\n      try {\n        cardSubscription = await CardSubscription.qm.createOne({\n          cardId: comment.cardId,\n          userId: comment.userId,\n        });\n      } catch (error) {\n        if (error.code !== 'E_UNIQUE') {\n          throw error;\n        }\n      }\n\n      if (cardSubscription) {\n        sails.sockets.broadcast(`user:${comment.userId}`, 'cardUpdate', {\n          item: {\n            id: comment.cardId,\n            isSubscribed: true,\n          },\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const notificationServices = await NotificationService.qm.getByBoardId(inputs.board.id);\n\n    if (notificationServices.length > 0) {\n      const services = notificationServices.map((notificationService) =>\n        _.pick(notificationService, ['url', 'format']),\n      );\n\n      buildAndSendNotifications(\n        services,\n        inputs.board,\n        values.card,\n        comment,\n        values.user,\n        sails.helpers.utils.makeTranslator(),\n      );\n    }\n\n    return comment;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/comments/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const comment = await Comment.qm.deleteOne(inputs.record.id);\n\n    if (comment) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'commentDelete',\n        {\n          item: comment,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.COMMENT_DELETE,\n        buildData: () => ({\n          item: comment,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return comment;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/comments/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const comment = await Comment.qm.getOneById(inputs.id);\n\n    if (!comment) {\n      throw 'pathNotFound';\n    }\n\n    const pathToProject = await sails.helpers.cards\n      .getPathToProjectById(comment.cardId)\n      .intercept('pathNotFound', (nodes) => ({\n        pathNotFound: {\n          comment,\n          ...nodes,\n        },\n      }));\n\n    return {\n      comment,\n      ...pathToProject,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/comments/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const comment = await Comment.qm.updateOne(inputs.record.id, values);\n\n    if (comment) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'commentUpdate',\n        {\n          item: comment,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.COMMENT_UPDATE,\n        buildData: () => ({\n          item: comment,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return comment;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/config/present-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    if (sails.config.custom.smtpHost) {\n      return _.omit(inputs.record, Config.SMTP_FIELD_NAMES);\n    }\n\n    if (inputs.record.smtpPassword) {\n      return _.omit(inputs.record, 'smtpPassword');\n    }\n\n    return inputs.record;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/config/update-main.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'json',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const config = await Config.qm.updateOneMain(values);\n\n    const configRelatedUserIds = await sails.helpers.users.getAllActiveIds(User.Roles.ADMIN);\n\n    configRelatedUserIds.forEach((userId) => {\n      sails.sockets.broadcast(\n        `user:${userId}`,\n        'configUpdate',\n        {\n          item: sails.helpers.config.presentOne(config),\n        },\n        inputs.request,\n      );\n    });\n\n    const webhooks = await Webhook.qm.getAll();\n\n    // TODO: with prevData?\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.CONFIG_UPDATE,\n      buildData: () => ({\n        item: sails.helpers.config.presentOne(config),\n      }),\n      user: inputs.actorUser,\n    });\n\n    return config;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-field-groups/create-one-in-board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    boardMustBeInValues: {},\n    baseCustomFieldGroupOrNameMustBeInValues: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (!values.board) {\n      throw 'boardMustBeInValues';\n    }\n\n    if (values.baseCustomFieldGroup) {\n      values.baseCustomFieldGroupId = values.baseCustomFieldGroup.id;\n    } else if (!values.name) {\n      throw 'baseCustomFieldGroupOrNameMustBeInValues';\n    }\n\n    const customFieldGroups = await CustomFieldGroup.qm.getByBoardId(values.board.id);\n\n    const { position, repositions } = sails.helpers.utils.insertToPositionables(\n      values.position,\n      customFieldGroups,\n    );\n\n    if (repositions.length > 0) {\n      // eslint-disable-next-line no-restricted-syntax\n      for (const reposition of repositions) {\n        // eslint-disable-next-line no-await-in-loop\n        await CustomFieldGroup.qm.updateOne(\n          {\n            id: reposition.record.id,\n            boardId: reposition.record.boardId,\n          },\n          {\n            position: reposition.position,\n          },\n        );\n\n        sails.sockets.broadcast(`board:${values.board.id}`, 'customFieldGroupUpdate', {\n          item: {\n            id: reposition.record.id,\n            position: reposition.position,\n          },\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const customFieldGroup = await CustomFieldGroup.qm.createOne({\n      ...values,\n      position,\n      boardId: values.board.id,\n    });\n\n    sails.sockets.broadcast(\n      `board:${customFieldGroup.boardId}`,\n      'customFieldGroupCreate',\n      {\n        item: customFieldGroup,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.CUSTOM_FIELD_GROUP_CREATE,\n      buildData: () => ({\n        item: customFieldGroup,\n        included: {\n          projects: [inputs.project],\n          boards: [values.board],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return customFieldGroup;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-field-groups/create-one-in-card.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    cardMustBeInValues: {},\n    baseCustomFieldGroupOrNameMustBeInValues: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (!values.card) {\n      throw 'cardMustBeInValues';\n    }\n\n    if (values.baseCustomFieldGroup) {\n      values.baseCustomFieldGroupId = values.baseCustomFieldGroup.id;\n    } else if (!values.name) {\n      throw 'baseCustomFieldGroupOrNameMustBeInValues';\n    }\n\n    const customFieldGroups = await CustomFieldGroup.qm.getByCardId(values.card.id);\n\n    const { position, repositions } = sails.helpers.utils.insertToPositionables(\n      values.position,\n      customFieldGroups,\n    );\n\n    if (repositions.length > 0) {\n      // eslint-disable-next-line no-restricted-syntax\n      for (const reposition of repositions) {\n        // eslint-disable-next-line no-await-in-loop\n        await CustomFieldGroup.qm.updateOne(\n          {\n            id: reposition.record.id,\n            cardId: reposition.record.cardId,\n          },\n          {\n            position: reposition.position,\n          },\n        );\n\n        sails.sockets.broadcast(`board:${inputs.board.id}`, 'customFieldGroupUpdate', {\n          item: {\n            id: reposition.record.id,\n            position: reposition.position,\n          },\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const customFieldGroup = await CustomFieldGroup.qm.createOne({\n      ...values,\n      position,\n      cardId: values.card.id,\n    });\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'customFieldGroupCreate',\n      {\n        item: customFieldGroup,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.CUSTOM_FIELD_GROUP_CREATE,\n      buildData: () => ({\n        item: customFieldGroup,\n        included: {\n          projects: [inputs.project],\n          boards: [inputs.board],\n          lists: [inputs.list],\n          cards: [values.card],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return customFieldGroup;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-field-groups/delete-one-in-board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    await sails.helpers.customFieldGroups.deleteRelated(inputs.record);\n\n    const customFieldGroup = await CustomFieldGroup.qm.deleteOne(inputs.record.id);\n\n    if (customFieldGroup) {\n      sails.sockets.broadcast(\n        `board:${customFieldGroup.boardId}`,\n        'customFieldGroupDelete',\n        {\n          item: customFieldGroup,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CUSTOM_FIELD_GROUP_DELETE,\n        buildData: () => ({\n          item: customFieldGroup,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return customFieldGroup;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-field-groups/delete-one-in-card.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    await sails.helpers.customFieldGroups.deleteRelated(inputs.record);\n\n    const customFieldGroup = await CustomFieldGroup.qm.deleteOne(inputs.record.id);\n\n    if (customFieldGroup) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'customFieldGroupDelete',\n        {\n          item: customFieldGroup,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CUSTOM_FIELD_GROUP_DELETE,\n        buildData: () => ({\n          item: customFieldGroup,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return customFieldGroup;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-field-groups/delete-related.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    recordOrRecords: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    let customFieldGroupIdOrIds;\n    if (_.isPlainObject(inputs.recordOrRecords)) {\n      ({\n        recordOrRecords: { id: customFieldGroupIdOrIds },\n      } = inputs);\n    } else if (_.every(inputs.recordOrRecords, _.isPlainObject)) {\n      customFieldGroupIdOrIds = sails.helpers.utils.mapRecords(inputs.recordOrRecords);\n    }\n\n    await CustomFieldValue.qm.delete({\n      customFieldGroupId: customFieldGroupIdOrIds,\n    });\n\n    await CustomField.qm.delete({\n      customFieldGroupId: customFieldGroupIdOrIds,\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-field-groups/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const customFieldGroup = await CustomFieldGroup.qm.getOneById(inputs.id);\n\n    if (!customFieldGroup) {\n      throw 'pathNotFound';\n    }\n\n    let pathToProject;\n    if (customFieldGroup.boardId) {\n      pathToProject = await sails.helpers.boards\n        .getPathToProjectById(customFieldGroup.boardId)\n        .intercept('pathNotFound', (nodes) => ({\n          pathNotFound: {\n            customFieldGroup,\n            ...nodes,\n          },\n        }));\n    } else if (customFieldGroup.cardId) {\n      pathToProject = await sails.helpers.cards\n        .getPathToProjectById(customFieldGroup.cardId)\n        .intercept('pathNotFound', (nodes) => ({\n          pathNotFound: {\n            customFieldGroup,\n            ...nodes,\n          },\n        }));\n    }\n\n    return {\n      customFieldGroup,\n      ...pathToProject,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-field-groups/update-one-in-board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    nameInValuesMustNotBeNull: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (!inputs.record.baseCustomFieldGroupId && _.isNull(values.name)) {\n      throw 'nameInValuesMustNotBeNull';\n    }\n\n    if (!_.isUndefined(values.position)) {\n      const customFieldGroups = await CustomFieldGroup.qm.getByBoardId(inputs.record.boardId, {\n        exceptIdOrIds: inputs.record.id,\n      });\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        customFieldGroups,\n      );\n\n      values.position = position;\n\n      if (repositions.length > 0) {\n        // eslint-disable-next-line no-restricted-syntax\n        for (const reposition of repositions) {\n          // eslint-disable-next-line no-await-in-loop\n          await CustomFieldGroup.qm.updateOne(\n            {\n              id: reposition.record.id,\n              boardId: reposition.record.boardId,\n            },\n            {\n              position: reposition.position,\n            },\n          );\n\n          sails.sockets.broadcast(`board:${inputs.record.boardId}`, 'customFieldGroupUpdate', {\n            item: {\n              id: reposition.record.id,\n              position: reposition.position,\n            },\n          });\n\n          // TODO: send webhooks\n        }\n      }\n    }\n\n    const customFieldGroup = await CustomFieldGroup.qm.updateOne(inputs.record.id, values);\n\n    if (customFieldGroup) {\n      sails.sockets.broadcast(\n        `board:${customFieldGroup.boardId}`,\n        'customFieldGroupUpdate',\n        {\n          item: customFieldGroup,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CUSTOM_FIELD_GROUP_UPDATE,\n        buildData: () => ({\n          item: customFieldGroup,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return customFieldGroup;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-field-groups/update-one-in-card.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    nameInValuesMustNotBeNull: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (!inputs.record.baseCustomFieldGroupId && _.isNull(values.name)) {\n      throw 'nameInValuesMustNotBeNull';\n    }\n\n    if (!_.isUndefined(values.position)) {\n      const customFieldGroups = await CustomFieldGroup.qm.getByCardId(inputs.record.cardId, {\n        exceptIdOrIds: inputs.record.id,\n      });\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        customFieldGroups,\n      );\n\n      values.position = position;\n\n      if (repositions.length > 0) {\n        // eslint-disable-next-line no-restricted-syntax\n        for (const reposition of repositions) {\n          // eslint-disable-next-line no-await-in-loop\n          await CustomFieldGroup.qm.updateOne(\n            {\n              id: reposition.record.id,\n              cardId: reposition.record.cardId,\n            },\n            {\n              position: reposition.position,\n            },\n          );\n\n          sails.sockets.broadcast(`board:${inputs.board.id}`, 'customFieldGroupUpdate', {\n            item: {\n              id: reposition.record.id,\n              position: reposition.position,\n            },\n          });\n\n          // TODO: send webhooks\n        }\n      }\n    }\n\n    const customFieldGroup = await CustomFieldGroup.qm.updateOne(inputs.record.id, values);\n\n    if (customFieldGroup) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'customFieldGroupUpdate',\n        {\n          item: customFieldGroup,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CUSTOM_FIELD_GROUP_UPDATE,\n        buildData: () => ({\n          item: customFieldGroup,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return customFieldGroup;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-field-values/create-or-update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const customFieldValue = await CustomFieldValue.qm.createOrUpdateOne({\n      ...values,\n      cardId: values.card.id,\n      customFieldGroupId: values.customFieldGroup.id,\n      customFieldId: values.customField.id,\n    });\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'customFieldValueUpdate',\n      {\n        item: customFieldValue,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    // TODO: with prevData?\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.CUSTOM_FIELD_VALUE_UPDATE,\n      buildData: () => ({\n        item: customFieldValue,\n        included: {\n          projects: [inputs.project],\n          boards: [inputs.board],\n          lists: [inputs.list],\n          cards: [values.card],\n          customFieldGroups: [values.customFieldGroup],\n          customFields: [values.customField],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return customFieldValue;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-field-values/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    customFieldGroup: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const customFieldValue = await CustomFieldValue.qm.deleteOne(inputs.record.id);\n\n    if (customFieldValue) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'customFieldValueDelete',\n        {\n          item: customFieldValue,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CUSTOM_FIELD_VALUE_DELETE,\n        buildData: () => ({\n          item: customFieldValue,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n            customFieldGroups: [inputs.customFieldGroup],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return customFieldValue;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-fields/create-one-in-base-custom-field-group.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    baseCustomFieldGroupMustBeInValues: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (!values.baseCustomFieldGroup) {\n      throw 'baseCustomFieldGroupMustBeInValues';\n    }\n\n    const scoper = sails.helpers.projects.makeScoper.with({\n      record: inputs.project,\n    });\n\n    const customFieldGroupRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n    if (!_.isUndefined(values.position)) {\n      const customFields = await CustomField.qm.getByBaseCustomFieldGroupId(\n        values.baseCustomFieldGroup.id,\n      );\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        customFields,\n      );\n\n      values.position = position;\n\n      // eslint-disable-next-line no-restricted-syntax\n      for (const reposition of repositions) {\n        // eslint-disable-next-line no-await-in-loop\n        await CustomField.qm.updateOne(\n          {\n            id: reposition.record.id,\n            baseCustomFieldGroupId: reposition.record.baseCustomFieldGroupId,\n          },\n          {\n            position: reposition.position,\n          },\n        );\n\n        customFieldGroupRelatedUserIds.forEach((userId) => {\n          sails.sockets.broadcast(`user:${userId}`, 'customFieldUpdate', {\n            item: {\n              id: reposition.record.id,\n              position: reposition.position,\n            },\n          });\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const customField = await CustomField.qm.createOne({\n      ...values,\n      baseCustomFieldGroupId: values.baseCustomFieldGroup.id,\n    });\n\n    customFieldGroupRelatedUserIds.forEach((userId) => {\n      sails.sockets.broadcast(\n        `user:${userId}`,\n        'customFieldCreate',\n        {\n          item: customField,\n        },\n        inputs.request,\n      );\n    });\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.CUSTOM_FIELD_CREATE,\n      buildData: () => ({\n        item: customField,\n        included: {\n          projects: [inputs.project],\n          baseCustomFieldGroups: [values.baseCustomFieldGroup],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return customField;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-fields/create-one-in-custom-field-group.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n    },\n    card: {\n      type: 'ref',\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    customFieldGroupMustBeInValues: {},\n    listMustBePresent: {},\n    cardMustBePresent: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (!values.customFieldGroup) {\n      throw 'customFieldGroupMustBeInValues';\n    }\n\n    if (values.customFieldGroup.cardId) {\n      if (!inputs.list) {\n        throw 'listMustBePresent';\n      }\n\n      if (!inputs.card) {\n        throw 'cardMustBePresent';\n      }\n    }\n\n    if (!_.isUndefined(values.position)) {\n      const customFields = await CustomField.qm.getByCustomFieldGroupId(values.customFieldGroup.id);\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        customFields,\n      );\n\n      values.position = position;\n\n      // eslint-disable-next-line no-restricted-syntax\n      for (const reposition of repositions) {\n        // eslint-disable-next-line no-await-in-loop\n        await CustomField.qm.updateOne(\n          {\n            id: reposition.record.id,\n            customFieldGroupId: reposition.record.customFieldGroupId,\n          },\n          {\n            position: reposition.position,\n          },\n        );\n\n        sails.sockets.broadcast(`board:${inputs.board.id}`, 'customFieldUpdate', {\n          item: {\n            id: reposition.record.id,\n            position: reposition.position,\n          },\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const customField = await CustomField.qm.createOne({\n      ...values,\n      customFieldGroupId: values.customFieldGroup.id,\n    });\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'customFieldCreate',\n      {\n        item: customField,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.CUSTOM_FIELD_CREATE,\n      buildData: () => ({\n        item: customField,\n        included: {\n          projects: [inputs.project],\n          boards: [inputs.board],\n          customFieldGroups: [values.customFieldGroup],\n          ...(inputs.list && {\n            lists: [inputs.list],\n          }),\n          ...(inputs.card && {\n            cards: [inputs.card],\n          }),\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return customField;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-fields/delete-one-in-base-custom-field-group.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    baseCustomFieldGroup: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    await sails.helpers.customFields.deleteRelated(inputs.record);\n\n    const customField = await CustomField.qm.deleteOne(inputs.record.id);\n\n    if (customField) {\n      const scoper = sails.helpers.projects.makeScoper.with({\n        record: inputs.project,\n      });\n\n      const customFieldGroupRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n      customFieldGroupRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'customFieldDelete',\n          {\n            item: customField,\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CUSTOM_FIELD_DELETE,\n        buildData: () => ({\n          item: customField,\n          included: {\n            projects: [inputs.project],\n            baseCustomFieldGroups: [inputs.baseCustomFieldGroup],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return customField;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-fields/delete-one-in-custom-field-group.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n    },\n    card: {\n      type: 'ref',\n    },\n    customFieldGroup: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    listMustBePresent: {},\n    cardMustBePresent: {},\n  },\n\n  async fn(inputs) {\n    if (inputs.customFieldGroup.cardId) {\n      if (!inputs.list) {\n        throw 'listMustBePresent';\n      }\n\n      if (!inputs.card) {\n        throw 'cardMustBePresent';\n      }\n    }\n\n    await sails.helpers.customFields.deleteRelated(inputs.record);\n\n    const customField = await CustomField.qm.deleteOne(inputs.record.id);\n\n    if (customField) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'customFieldDelete',\n        {\n          item: customField,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CUSTOM_FIELD_DELETE,\n        buildData: () => ({\n          item: customField,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            customFieldGroups: [inputs.customFieldGroup],\n            ...(inputs.list && {\n              lists: [inputs.list],\n            }),\n            ...(inputs.card && {\n              cards: [inputs.card],\n            }),\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return customField;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-fields/delete-related.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    recordOrRecords: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    let customFieldIdOrIds;\n    if (_.isPlainObject(inputs.recordOrRecords)) {\n      ({\n        recordOrRecords: { id: customFieldIdOrIds },\n      } = inputs);\n    } else if (_.every(inputs.recordOrRecords, _.isPlainObject)) {\n      customFieldIdOrIds = sails.helpers.utils.mapRecords(inputs.recordOrRecords);\n    }\n\n    await CustomFieldValue.qm.delete({\n      customFieldId: customFieldIdOrIds,\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-fields/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const customField = await CustomField.qm.getOneById(inputs.id);\n\n    if (!customField) {\n      throw 'pathNotFound';\n    }\n\n    let pathToProject;\n    if (customField.baseCustomFieldGroupId) {\n      pathToProject = await sails.helpers.baseCustomFieldGroups\n        .getPathToProjectById(customField.baseCustomFieldGroupId)\n        .intercept('pathNotFound', (nodes) => ({\n          pathNotFound: {\n            customField,\n            ...nodes,\n          },\n        }));\n    } else if (customField.customFieldGroupId) {\n      pathToProject = await sails.helpers.customFieldGroups\n        .getPathToProjectById(customField.customFieldGroupId)\n        .intercept('pathNotFound', (nodes) => ({\n          pathNotFound: {\n            customField,\n            ...nodes,\n          },\n        }));\n    }\n\n    return {\n      customField,\n      ...pathToProject,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-fields/update-one-in-base-custom-field-group.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    baseCustomFieldGroup: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const scoper = sails.helpers.projects.makeScoper.with({\n      record: inputs.project,\n    });\n\n    const customFieldGroupRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n    if (!_.isUndefined(values.position)) {\n      const customFields = await CustomField.qm.getByBaseCustomFieldGroupId(\n        inputs.record.baseCustomFieldGroupId,\n        {\n          exceptIdOrIds: inputs.record.id,\n        },\n      );\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        customFields,\n      );\n\n      values.position = position;\n\n      // eslint-disable-next-line no-restricted-syntax\n      for (const reposition of repositions) {\n        // eslint-disable-next-line no-await-in-loop\n        await CustomField.qm.updateOne(\n          {\n            id: reposition.record.id,\n            baseCustomFieldGroupId: reposition.record.baseCustomFieldGroupId,\n          },\n          {\n            position: reposition.position,\n          },\n        );\n\n        customFieldGroupRelatedUserIds.forEach((userId) => {\n          sails.sockets.broadcast(`user:${userId}`, 'customFieldUpdate', {\n            item: {\n              id: reposition.record.id,\n              position: reposition.position,\n            },\n          });\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const customField = await CustomField.qm.updateOne(inputs.record.id, values);\n\n    if (customField) {\n      customFieldGroupRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'customFieldUpdate',\n          {\n            item: customField,\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CUSTOM_FIELD_UPDATE,\n        buildData: () => ({\n          item: customField,\n          included: {\n            projects: [inputs.project],\n            baseCustomFieldGroups: [inputs.baseCustomFieldGroup],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return customField;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/custom-fields/update-one-in-custom-field-group.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n    },\n    card: {\n      type: 'ref',\n    },\n    customFieldGroup: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    listMustBePresent: {},\n    cardMustBePresent: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (inputs.customFieldGroup.cardId) {\n      if (!inputs.list) {\n        throw 'listMustBePresent';\n      }\n\n      if (!inputs.card) {\n        throw 'cardMustBePresent';\n      }\n    }\n\n    if (!_.isUndefined(values.position)) {\n      const customFields = await CustomField.qm.getByCustomFieldGroupId(\n        inputs.record.customFieldGroupId,\n        {\n          exceptIdOrIds: inputs.record.id,\n        },\n      );\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        customFields,\n      );\n\n      values.position = position;\n\n      // eslint-disable-next-line no-restricted-syntax\n      for (const reposition of repositions) {\n        // eslint-disable-next-line no-await-in-loop\n        await CustomField.qm.updateOne(\n          {\n            id: reposition.record.id,\n            customFieldGroupId: reposition.record.customFieldGroupId,\n          },\n          {\n            position: reposition.position,\n          },\n        );\n\n        sails.sockets.broadcast(`board:${inputs.board.id}`, 'customFieldUpdate', {\n          item: {\n            id: reposition.record.id,\n            position: reposition.position,\n          },\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const customField = await CustomField.qm.updateOne(inputs.record.id, values);\n\n    if (customField) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'customFieldUpdate',\n        {\n          item: customField,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CUSTOM_FIELD_UPDATE,\n        buildData: () => ({\n          item: customField,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            customFieldGroups: [inputs.customFieldGroup],\n            ...(inputs.list && {\n              lists: [inputs.list],\n            }),\n            ...(inputs.card && {\n              cards: [inputs.card],\n            }),\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return customField;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/internal-config/update-main.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'json',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const { internalConfig, deactivatedUserIds, prev } =\n      await InternalConfig.qm.updateOneMain(values);\n\n    if (deactivatedUserIds) {\n      deactivatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(`user:${userId}`, 'logout');\n        sails.sockets.leaveAll(`@user:${userId}`);\n      });\n    }\n\n    if (internalConfig.activeUsersLimit !== prev.activeUsersLimit) {\n      let adminUserIds;\n      if (deactivatedUserIds && deactivatedUserIds.length > 0) {\n        const users = await User.qm.getAll({\n          roleOrRoles: [User.Roles.ADMIN, User.Roles.PROJECT_OWNER],\n          isDeactivated: false,\n        });\n\n        adminUserIds = users.flatMap((user) => {\n          sails.sockets.broadcast(`user:${user.id}`, 'usersReset');\n\n          return user.role === User.Roles.ADMIN ? user.id : [];\n        });\n      } else {\n        adminUserIds = await sails.helpers.users.getAllActiveIds(User.Roles.ADMIN);\n      }\n\n      adminUserIds.forEach((userId) => {\n        sails.sockets.broadcast(`user:${userId}`, 'bootstrapUpdate', {\n          item: {\n            activeUsersLimit: internalConfig.activeUsersLimit,\n          },\n        });\n      });\n    }\n\n    return internalConfig;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/labels/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    webhooks: {\n      type: 'ref',\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const labels = await Label.qm.getByBoardId(values.board.id);\n\n    const { position, repositions } = sails.helpers.utils.insertToPositionables(\n      values.position,\n      labels,\n    );\n\n    // eslint-disable-next-line no-restricted-syntax\n    for (const reposition of repositions) {\n      // eslint-disable-next-line no-await-in-loop\n      await Label.qm.updateOne(\n        {\n          id: reposition.record.id,\n          boardId: reposition.record.boardId,\n        },\n        {\n          position: reposition.position,\n        },\n      );\n\n      sails.sockets.broadcast(`board:${values.board.id}`, 'labelUpdate', {\n        item: {\n          id: reposition.record.id,\n          position: reposition.position,\n        },\n      });\n\n      // TODO: send webhooks\n    }\n\n    const label = await Label.qm.createOne({\n      ...values,\n      position,\n      boardId: values.board.id,\n    });\n\n    sails.sockets.broadcast(\n      `board:${label.boardId}`,\n      'labelCreate',\n      {\n        item: label,\n      },\n      inputs.request,\n    );\n\n    const { webhooks = await Webhook.qm.getAll() } = inputs;\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.LABEL_CREATE,\n      buildData: () => ({\n        item: label,\n        included: {\n          projects: [inputs.project],\n          boards: [values.board],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return label;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/labels/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    await sails.helpers.labels.deleteRelated(inputs.record);\n\n    const label = await Label.qm.deleteOne(inputs.record.id);\n\n    if (label) {\n      sails.sockets.broadcast(\n        `board:${label.boardId}`,\n        'labelDelete',\n        {\n          item: label,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.LABEL_DELETE,\n        buildData: () => ({\n          item: label,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return label;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/labels/delete-related.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    recordOrRecords: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    let labelIdOrIds;\n    if (_.isPlainObject(inputs.recordOrRecords)) {\n      ({\n        recordOrRecords: { id: labelIdOrIds },\n      } = inputs);\n    } else if (_.every(inputs.recordOrRecords, _.isPlainObject)) {\n      labelIdOrIds = sails.helpers.utils.mapRecords(inputs.recordOrRecords);\n    }\n\n    await CardLabel.qm.delete({\n      labelId: labelIdOrIds,\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/labels/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const label = await Label.qm.getOneById(inputs.id);\n\n    if (!label) {\n      throw 'pathNotFound';\n    }\n\n    const pathToProject = await sails.helpers.boards\n      .getPathToProjectById(label.boardId)\n      .intercept('pathNotFound', (nodes) => ({\n        pathNotFound: {\n          label,\n          ...nodes,\n        },\n      }));\n\n    return {\n      label,\n      ...pathToProject,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/labels/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (!_.isUndefined(values.position)) {\n      const labels = await Label.qm.getByBoardId(inputs.record.boardId, {\n        exceptIdOrIds: inputs.record.id,\n      });\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        labels,\n      );\n\n      values.position = position;\n\n      // eslint-disable-next-line no-restricted-syntax\n      for (const reposition of repositions) {\n        // eslint-disable-next-line no-await-in-loop\n        await Label.qm.updateOne(\n          {\n            id: reposition.record.id,\n            boardId: reposition.record.boardId,\n          },\n          {\n            position: reposition.position,\n          },\n        );\n\n        sails.sockets.broadcast(`board:${inputs.record.boardId}`, 'labelUpdate', {\n          item: {\n            id: reposition.record.id,\n            position: reposition.position,\n          },\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const label = await Label.qm.updateOne(inputs.record.id, values);\n\n    if (label) {\n      sails.sockets.broadcast(\n        `board:${label.boardId}`,\n        'labelUpdate',\n        {\n          item: label,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.LABEL_UPDATE,\n        buildData: () => ({\n          item: label,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return label;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/clear-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    await sails.helpers.lists.deleteRelated(inputs.record);\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'listClear',\n      {\n        item: inputs.record,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.LIST_CLEAR,\n      buildData: () => ({\n        item: inputs.record,\n        included: {\n          projects: [inputs.project],\n          boards: [inputs.board],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const lists = await sails.helpers.boards.getKanbanListsById(values.board.id);\n\n    const { position, repositions } = sails.helpers.utils.insertToPositionables(\n      values.position,\n      lists,\n    );\n\n    if (repositions.length > 0) {\n      // eslint-disable-next-line no-restricted-syntax\n      for (const reposition of repositions) {\n        // eslint-disable-next-line no-await-in-loop\n        await List.qm.updateOne(\n          {\n            id: reposition.record.id,\n            boardId: reposition.record.boardId,\n          },\n          {\n            position: reposition.position,\n          },\n        );\n\n        sails.sockets.broadcast(`board:${values.board.id}`, 'listUpdate', {\n          item: {\n            id: reposition.record.id,\n            position: reposition.position,\n          },\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const list = await List.qm.createOne({\n      ...values,\n      position,\n      boardId: values.board.id,\n    });\n\n    sails.sockets.broadcast(\n      `board:${list.boardId}`,\n      'listCreate',\n      {\n        item: list,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.LIST_CREATE,\n      buildData: () => ({\n        item: list,\n        included: {\n          projects: [inputs.project],\n          boards: [values.board],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return list;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const trashList = await List.qm.getOneTrashByBoardId(inputs.board.id);\n\n    const { cards } = await Card.qm.update(\n      {\n        listId: inputs.record.id,\n      },\n      {\n        listId: trashList.id,\n        position: null,\n        listChangedAt: new Date().toISOString(),\n      },\n    );\n\n    // TODO: create actions and notifications\n\n    const list = await List.qm.deleteOne(inputs.record.id);\n\n    if (list) {\n      sails.sockets.broadcast(\n        `board:${list.boardId}`,\n        'listDelete',\n        {\n          item: list,\n          included: {\n            cards,\n          },\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.LIST_DELETE,\n        buildData: () => ({\n          item: list,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return { list, cards };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/delete-related.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    recordOrRecords: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    let listIdOrIds;\n    if (_.isPlainObject(inputs.recordOrRecords)) {\n      ({\n        recordOrRecords: { id: listIdOrIds },\n      } = inputs);\n    } else if (_.every(inputs.recordOrRecords, _.isPlainObject)) {\n      listIdOrIds = sails.helpers.utils.mapRecords(inputs.recordOrRecords);\n    }\n\n    const cards = await Card.qm.delete({\n      listId: listIdOrIds,\n    });\n\n    await sails.helpers.cards.deleteRelated(cards);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const list = await List.qm.getOneById(inputs.id);\n\n    if (!list) {\n      throw 'pathNotFound';\n    }\n\n    const pathToProject = await sails.helpers.boards\n      .getPathToProjectById(list.boardId)\n      .intercept('pathNotFound', (nodes) => ({\n        pathNotFound: {\n          list,\n          ...nodes,\n        },\n      }));\n\n    return {\n      list,\n      ...pathToProject,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/is-archive-or-trash.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    return [List.Types.ARCHIVE, List.Types.TRASH].includes(inputs.record.type);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/is-finite.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    return List.FINITE_TYPES.includes(inputs.record.type);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/is-kanban.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    return List.KANBAN_TYPES.includes(inputs.record.type);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/move-cards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    listInValuesMustBeEndless: {},\n    listInValuesMustBelongToBoard: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    // TODO: allow for finite lists?\n    if (sails.helpers.lists.isFinite(values.list)) {\n      throw 'listInValuesMustBeEndless';\n    }\n\n    if (values.list.boardId !== inputs.board.id) {\n      throw 'listInValuesMustBelongToBoard';\n    }\n\n    if (inputs.record.type === List.Types.TRASH) {\n      values.prevListId = null;\n    } else if (sails.helpers.lists.isArchiveOrTrash(values.list)) {\n      values.prevListId = inputs.record.id;\n    } else if (inputs.record.type === List.Types.ARCHIVE) {\n      values.prevListId = null;\n    }\n\n    const { cards } = await Card.qm.update(\n      {\n        listId: inputs.record.id,\n      },\n      {\n        ...values,\n        listId: values.list.id,\n        position: null,\n        listChangedAt: new Date().toISOString(),\n      },\n    );\n\n    const actions = await Action.qm.create(\n      cards.map((card) => ({\n        cardId: card.id,\n        userId: inputs.actorUser.id,\n        type: Action.Types.MOVE_CARD,\n        data: {\n          fromList: _.pick(inputs.record, ['id', 'type', 'name']),\n          toList: _.pick(values.list, ['id', 'type', 'name']),\n        },\n      })),\n    );\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'cardsUpdate',\n      {\n        items: cards,\n        included: {\n          actions,\n        },\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    cards.forEach((card) => {\n      // TODO: with prevData?\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CARD_UPDATE,\n        buildData: () => ({\n          item: card,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [values.list],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    });\n\n    // TODO: create notifications\n\n    return { cards, actions };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/resolve-name.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    t: {\n      type: 'ref',\n    },\n  },\n\n  fn(inputs) {\n    if (inputs.record.name) {\n      return inputs.record.name;\n    }\n\n    const name = _.upperFirst(inputs.record.type);\n    return inputs.t ? inputs.t(name) : name;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/sort-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { POSITION_GAP } = require('../../../constants');\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    options: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    cannotBeSortedAsEndlessList: {},\n    invalidFieldName: {},\n  },\n\n  async fn(inputs) {\n    const { options } = inputs;\n\n    if (!sails.helpers.lists.isFinite(inputs.record)) {\n      throw 'cannotBeSortedAsEndlessList';\n    }\n\n    let cards = await Card.qm.getByListId(inputs.record.id);\n\n    switch (options.fieldName) {\n      case List.SortFieldNames.NAME:\n        cards.sort((card1, card2) => card1.name.localeCompare(card2.name));\n\n        break;\n      case List.SortFieldNames.DUE_DATE:\n        cards.sort((card1, card2) => {\n          if (card1.dueDate === null) {\n            return 1;\n          }\n\n          if (card2.dueDate === null) {\n            return -1;\n          }\n\n          return new Date(card1.dueDate) - new Date(card2.dueDate);\n        });\n\n        break;\n      case List.SortFieldNames.CREATED_AT:\n        cards.sort((card1, card2) => new Date(card1.createdAt) - new Date(card2.createdAt));\n\n        break;\n      default:\n        throw 'invalidFieldName';\n    }\n\n    if (options.order === List.SortOrders.DESC) {\n      cards.reverse();\n    }\n\n    cards = await Promise.all(\n      cards.map(async (card, index) => {\n        const { card: nextCard } = await Card.qm.updateOne(\n          {\n            id: card.id,\n            listId: card.listId,\n          },\n          {\n            position: POSITION_GAP * (index + 1),\n          },\n        );\n\n        return nextCard;\n      }),\n    );\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'cardsUpdate',\n      {\n        items: cards,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    cards.forEach((card) => {\n      // TODO: with prevData?\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.CARD_UPDATE,\n        buildData: () => ({\n          item: card,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.record],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    });\n\n    return cards;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/lists/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    boardInValuesMustBelongToProject: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (values.project && values.project.id === inputs.project.id) {\n      delete values.project;\n    }\n\n    const project = values.project || inputs.project;\n\n    if (values.board) {\n      if (values.board.projectId !== project.id) {\n        throw 'boardInValuesMustBelongToProject';\n      }\n\n      if (values.board.id === inputs.board.id) {\n        delete values.board;\n      } else {\n        values.boardId = values.board.id;\n      }\n    }\n\n    const board = values.board || inputs.board;\n\n    if (!_.isUndefined(values.position)) {\n      const lists = await sails.helpers.boards.getKanbanListsById(board.id, inputs.record.id);\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        lists,\n      );\n\n      values.position = position;\n\n      if (repositions.length > 0) {\n        // eslint-disable-next-line no-restricted-syntax\n        for (const reposition of repositions) {\n          // eslint-disable-next-line no-await-in-loop\n          await List.qm.updateOne(\n            {\n              id: reposition.record.id,\n              boardId: reposition.record.boardId,\n            },\n            {\n              position: reposition.position,\n            },\n          );\n\n          sails.sockets.broadcast(`board:${board.id}`, 'listUpdate', {\n            item: {\n              id: reposition.record.id,\n              position: reposition.position,\n            },\n          });\n\n          // TODO: send webhooks\n        }\n      }\n    }\n\n    let cardIdsByLabelId;\n    let prevLabels;\n\n    if (values.board) {\n      const cards = await Card.qm.getByListId(inputs.record.id);\n      const cardIds = sails.helpers.utils.mapRecords(cards);\n\n      const cardLabels = await CardLabel.qm.getByCardIds(cardIds);\n\n      cardIdsByLabelId = cardLabels.reduce(\n        (result, { cardId, labelId }) => ({\n          ...result,\n          [labelId]: [...(result[labelId] || []), cardId],\n        }),\n        {},\n      );\n\n      prevLabels = await Label.qm.getByIds(Object.keys(cardIdsByLabelId));\n\n      const boardMemberUserIds = await sails.helpers.boards.getMemberUserIds(values.board.id);\n\n      await CardSubscription.qm.delete({\n        cardId: cardIds,\n        userId: {\n          '!=': boardMemberUserIds,\n        },\n      });\n\n      await CardMembership.qm.delete({\n        cardId: cardIds,\n        userId: {\n          '!=': boardMemberUserIds,\n        },\n      });\n\n      await CardLabel.qm.delete({\n        cardId: cardIds,\n      });\n\n      const taskLists = await TaskList.qm.getByCardIds(cardIds);\n      const taskListIds = sails.helpers.utils.mapRecords(taskLists);\n\n      await Task.qm.update(\n        {\n          taskListId: taskListIds,\n          assigneeUserId: {\n            '!=': boardMemberUserIds,\n          },\n        },\n        {\n          assigneeUserId: null,\n        },\n      );\n\n      await sails.helpers.cards.detachCustomFields(cardIds, inputs.board.id, !!values.project);\n    }\n\n    const { list, tasks } = await List.qm.updateOne(inputs.record.id, values);\n\n    if (list) {\n      if (values.board) {\n        if (prevLabels.length > 0) {\n          const labels = await Label.qm.getByBoardId(list.boardId);\n          const labelByName = _.keyBy(labels, 'name');\n\n          const cardLabelsValues = (\n            await Promise.all(\n              prevLabels.map(async (label) => {\n                let labelId;\n                if (labelByName[label.name]) {\n                  ({ id: labelId } = labelByName[label.name]);\n                } else {\n                  ({ id: labelId } = await sails.helpers.labels.createOne.with({\n                    project,\n                    values: {\n                      ..._.omit(label, ['id', 'boardId', 'createdAt', 'updatedAt']),\n                      board,\n                    },\n                    actorUser: inputs.actorUser,\n                  }));\n                }\n\n                return cardIdsByLabelId[label.id].map((cardId) => ({\n                  cardId,\n                  labelId,\n                }));\n              }),\n            )\n          ).flat();\n\n          await CardLabel.qm.create(cardLabelsValues);\n        }\n\n        sails.sockets.broadcast(\n          `board:${inputs.board.id}`,\n          'listUpdate',\n          {\n            item: {\n              id: list.id,\n              boardId: null,\n            },\n          },\n          inputs.request,\n        );\n\n        sails.sockets.broadcast(`board:${list.boardId}`, 'listUpdate', {\n          item: list,\n        });\n\n        // TODO: add transfer action\n      } else {\n        sails.sockets.broadcast(\n          `board:${list.boardId}`,\n          'listUpdate',\n          {\n            item: list,\n          },\n          inputs.request,\n        );\n      }\n\n      if (tasks) {\n        const taskListIds = sails.helpers.utils.mapRecords(tasks, 'taskListId', true);\n        const taskLists = await TaskList.qm.getByIds(taskListIds);\n        const taskListById = _.keyBy(taskLists, 'id');\n\n        const cardIds = sails.helpers.utils.mapRecords(taskLists, 'cardId', true);\n        const cards = await Card.qm.getByIds(cardIds);\n        const cardById = _.keyBy(cards, 'id');\n\n        const boardIdByTaskId = tasks.reduce(\n          (result, task) => ({\n            ...result,\n            [task.id]: cardById[taskListById[task.taskListId].cardId].boardId,\n          }),\n          {},\n        );\n\n        tasks.forEach((task) => {\n          sails.sockets.broadcast(`board:${boardIdByTaskId[task.id]}`, 'taskUpdate', {\n            item: task,\n          });\n        });\n\n        // TODO: send webhooks\n      }\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.LIST_UPDATE,\n        buildData: () => ({\n          item: list,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return list;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/notification-services/create-one-in-board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    limitReached: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const notificationServicesTotal = await sails.helpers.boards.getNotificationServicesTotal(\n      values.board.id,\n    );\n\n    // TODO: move to config?\n    if (notificationServicesTotal >= 5) {\n      throw 'limitReached';\n    }\n\n    const notificationService = await NotificationService.qm.createOne({\n      ...values,\n      boardId: values.board.id,\n    });\n\n    const scoper = sails.helpers.projects.makeScoper.with({\n      notificationService,\n      record: inputs.project,\n      board: values.board,\n    });\n\n    const notificationServiceRelatedUserIds = await scoper.getNotificationServiceRelatedUserIds();\n\n    notificationServiceRelatedUserIds.forEach((userId) => {\n      sails.sockets.broadcast(\n        `user:${userId}`,\n        'notificationServiceCreate',\n        {\n          item: notificationService,\n        },\n        inputs.request,\n      );\n    });\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.NOTIFICATION_SERVICE_CREATE,\n      buildData: () => ({\n        item: notificationService,\n        included: {\n          projects: [inputs.project],\n          boards: [values.board],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return notificationService;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/notification-services/create-one-in-user.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    limitReached: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const notificationServicesTotal = await sails.helpers.users.getNotificationServicesTotal(\n      values.user.id,\n    );\n\n    // TODO: move to config?\n    if (notificationServicesTotal >= 5) {\n      throw 'limitReached';\n    }\n\n    const notificationService = await NotificationService.qm.createOne({\n      ...values,\n      userId: values.user.id,\n    });\n\n    sails.sockets.broadcast(\n      `user:${notificationService.userId}`,\n      'notificationServiceCreate',\n      {\n        item: notificationService,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.NOTIFICATION_SERVICE_CREATE,\n      buildData: () => ({\n        item: notificationService,\n        included: {\n          users: [values.user],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return notificationService;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/notification-services/delete-one-in-board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const notificationService = await NotificationService.qm.deleteOne(inputs.record.id);\n\n    if (notificationService) {\n      const scoper = sails.helpers.projects.makeScoper.with({\n        notificationService,\n        record: inputs.project,\n        board: inputs.board,\n      });\n\n      const notificationServiceRelatedUserIds = await scoper.getNotificationServiceRelatedUserIds();\n\n      notificationServiceRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'notificationServiceDelete',\n          {\n            item: notificationService,\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.NOTIFICATION_SERVICE_DELETE,\n        buildData: () => ({\n          item: notificationService,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return notificationService;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/notification-services/delete-one-in-user.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    user: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const notificationService = await NotificationService.qm.deleteOne(inputs.record.id);\n\n    if (notificationService) {\n      sails.sockets.broadcast(\n        `user:${notificationService.userId}`,\n        'notificationServiceDelete',\n        {\n          item: notificationService,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.NOTIFICATION_SERVICE_DELETE,\n        buildData: () => ({\n          item: notificationService,\n          included: {\n            users: [inputs.user],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return notificationService;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/notification-services/get-path-to-user-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const notificationService = await NotificationService.qm.getOneById(inputs.id);\n\n    if (!notificationService) {\n      throw 'pathNotFound';\n    }\n\n    let pathToProject;\n    if (notificationService.userId) {\n      const user = await User.qm.getOneById(notificationService.userId);\n\n      if (!user) {\n        throw {\n          pathNotFound: {\n            notificationService,\n          },\n        };\n      }\n\n      pathToProject = {\n        user,\n      };\n    } else if (notificationService.boardId) {\n      pathToProject = await sails.helpers.boards\n        .getPathToProjectById(notificationService.boardId)\n        .intercept('pathNotFound', (nodes) => ({\n          pathNotFound: {\n            notificationService,\n            ...nodes,\n          },\n        }));\n    }\n\n    return {\n      notificationService,\n      ...pathToProject,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/notification-services/update-one-in-board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const notificationService = await NotificationService.qm.updateOne(inputs.record.id, values);\n\n    if (notificationService) {\n      const scoper = sails.helpers.projects.makeScoper.with({\n        notificationService,\n        record: inputs.project,\n        board: inputs.board,\n      });\n\n      const notificationServiceRelatedUserIds = await scoper.getNotificationServiceRelatedUserIds();\n\n      notificationServiceRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'notificationServiceUpdate',\n          {\n            item: notificationService,\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.NOTIFICATION_SERVICE_UPDATE,\n        buildData: () => ({\n          item: notificationService,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return notificationService;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/notification-services/update-one-in-user.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    user: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const notificationService = await NotificationService.qm.updateOne(inputs.record.id, values);\n\n    if (notificationService) {\n      sails.sockets.broadcast(\n        `user:${notificationService.userId}`,\n        'notificationServiceUpdate',\n        {\n          item: notificationService,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.NOTIFICATION_SERVICE_UPDATE,\n        buildData: () => ({\n          item: notificationService,\n          included: {\n            users: [inputs.user],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return notificationService;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/notifications/create-many.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst escapeMarkdown = require('escape-markdown');\nconst escapeHtml = require('escape-html');\n\nconst { mentionMarkupToText } = require('../../../utils/mentions');\n\nconst buildTitle = (notification, t) => {\n  switch (notification.type) {\n    case Notification.Types.MOVE_CARD:\n      return t('Card Moved');\n    case Notification.Types.COMMENT_CARD:\n      return t('New Comment');\n    case Notification.Types.ADD_MEMBER_TO_CARD:\n      return t('You Were Added to Card');\n    case Notification.Types.MENTION_IN_COMMENT:\n      return t('You Were Mentioned in Comment');\n    default:\n      return null;\n  }\n};\n\nconst buildBodyByFormat = (board, card, notification, actorUser, t) => {\n  const markdownCardLink = `[${escapeMarkdown(card.name)}](${sails.config.custom.baseUrl}/cards/${card.id})`;\n  const htmlCardLink = `<a href=\"${sails.config.custom.baseUrl}/cards/${card.id}\">${escapeHtml(card.name)}</a>`;\n\n  switch (notification.type) {\n    case Notification.Types.MOVE_CARD: {\n      const fromListName = sails.helpers.lists.resolveName(notification.data.fromList, t);\n      const toListName = sails.helpers.lists.resolveName(notification.data.toList, t);\n\n      return {\n        text: t(\n          '%s moved %s from %s to %s on %s',\n          actorUser.name,\n          card.name,\n          fromListName,\n          toListName,\n          board.name,\n        ),\n        markdown: t(\n          '%s moved %s from %s to %s on %s',\n          escapeMarkdown(actorUser.name),\n          markdownCardLink,\n          `**${escapeMarkdown(fromListName)}**`,\n          `**${escapeMarkdown(toListName)}**`,\n          escapeMarkdown(board.name),\n        ),\n        html: t(\n          '%s moved %s from %s to %s on %s',\n          escapeHtml(actorUser.name),\n          htmlCardLink,\n          `<b>${escapeHtml(fromListName)}</b>`,\n          `<b>${escapeHtml(toListName)}</b>`,\n          escapeHtml(board.name),\n        ),\n      };\n    }\n    case Notification.Types.COMMENT_CARD: {\n      const commentText = _.truncate(mentionMarkupToText(notification.data.text));\n\n      return {\n        text: `${t(\n          '%s left a new comment to %s on %s',\n          actorUser.name,\n          card.name,\n          board.name,\n        )}:\\n${commentText}`,\n        markdown: `${t(\n          '%s left a new comment to %s on %s',\n          escapeMarkdown(actorUser.name),\n          markdownCardLink,\n          escapeMarkdown(board.name),\n        )}:\\n\\n*${escapeMarkdown(commentText)}*`,\n        html: `${t(\n          '%s left a new comment to %s on %s',\n          escapeHtml(actorUser.name),\n          htmlCardLink,\n          escapeHtml(board.name),\n        )}:\\n\\n<i>${escapeHtml(commentText)}</i>`,\n      };\n    }\n    case Notification.Types.ADD_MEMBER_TO_CARD:\n      return {\n        text: t('%s added you to %s on %s', actorUser.name, card.name, board.name),\n        markdown: t(\n          '%s added you to %s on %s',\n          escapeMarkdown(actorUser.name),\n          markdownCardLink,\n          escapeMarkdown(board.name),\n        ),\n        html: t(\n          '%s added you to %s on %s',\n          escapeHtml(actorUser.name),\n          htmlCardLink,\n          escapeHtml(board.name),\n        ),\n      };\n    case Notification.Types.MENTION_IN_COMMENT: {\n      const commentText = _.truncate(mentionMarkupToText(notification.data.text));\n\n      return {\n        text: `${t(\n          '%s mentioned you in %s on %s',\n          actorUser.name,\n          card.name,\n          board.name,\n        )}:\\n${commentText}`,\n        markdown: `${t(\n          '%s mentioned you in %s on %s',\n          escapeMarkdown(actorUser.name),\n          markdownCardLink,\n          escapeMarkdown(board.name),\n        )}:\\n\\n*${escapeMarkdown(commentText)}*`,\n        html: `${t(\n          '%s mentioned you in %s on %s',\n          escapeHtml(actorUser.name),\n          htmlCardLink,\n          escapeHtml(board.name),\n        )}:\\n\\n<i>${escapeHtml(commentText)}</i>`,\n      };\n    }\n    default:\n      return null;\n  }\n};\n\nconst buildAndSendNotifications = async (services, board, card, notification, actorUser, t) => {\n  await sails.helpers.utils.sendNotifications(\n    services,\n    buildTitle(notification, t),\n    buildBodyByFormat(board, card, notification, actorUser, t),\n  );\n};\n\n// TODO: use templates (views) to build html\nconst buildEmail = (board, card, notification, actorUser, notifiableUser, t) => {\n  const cardLink = `<a href=\"${sails.config.custom.baseUrl}/cards/${card.id}\">${escapeHtml(card.name)}</a>`;\n  const boardLink = `<a href=\"${sails.config.custom.baseUrl}/boards/${board.id}\">${escapeHtml(board.name)}</a>`;\n\n  let html;\n  switch (notification.type) {\n    case Notification.Types.MOVE_CARD: {\n      const fromListName = sails.helpers.lists.resolveName(notification.data.fromList, t);\n      const toListName = sails.helpers.lists.resolveName(notification.data.toList, t);\n\n      html = `<p>${t(\n        '%s moved %s from %s to %s on %s',\n        escapeHtml(actorUser.name),\n        cardLink,\n        escapeHtml(fromListName),\n        escapeHtml(toListName),\n        boardLink,\n      )}</p>`;\n\n      break;\n    }\n    case Notification.Types.COMMENT_CARD:\n      html = `<p>${t(\n        '%s left a new comment to %s on %s',\n        escapeHtml(actorUser.name),\n        cardLink,\n        boardLink,\n      )}</p><p>${escapeHtml(mentionMarkupToText(notification.data.text))}</p>`;\n\n      break;\n    case Notification.Types.ADD_MEMBER_TO_CARD:\n      html = `<p>${t(\n        '%s added you to %s on %s',\n        escapeHtml(actorUser.name),\n        cardLink,\n        boardLink,\n      )}</p>`;\n\n      break;\n    case Notification.Types.MENTION_IN_COMMENT:\n      html = `<p>${t(\n        '%s mentioned you in %s on %s',\n        escapeHtml(actorUser.name),\n        cardLink,\n        boardLink,\n      )}</p><p>${escapeHtml(mentionMarkupToText(notification.data.text))}</p>`;\n\n      break;\n    default:\n      return null; // TODO: throw error?\n  }\n\n  return {\n    html,\n    to: notifiableUser.email,\n    subject: buildTitle(notification, t),\n  };\n};\n\nconst sendEmails = async (transporter, emails) => {\n  await Promise.all(\n    emails.map((email) =>\n      sails.helpers.utils.sendEmail.with({\n        ...email,\n        transporter,\n      }),\n    ),\n  );\n\n  transporter.close();\n};\n\nmodule.exports = {\n  inputs: {\n    arrayOfValues: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    webhooks: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const { arrayOfValues } = inputs;\n\n    const ids = await sails.helpers.utils.generateIds(arrayOfValues.length);\n    const valuesById = {};\n\n    const notifications = await Notification.qm.create(\n      arrayOfValues.map((values) => {\n        const id = ids.shift();\n\n        const nextValues = {\n          ...values,\n          id,\n          creatorUserId: values.creatorUser.id,\n          boardId: values.card.boardId,\n          cardId: values.card.id,\n        };\n        if (values.comment) {\n          nextValues.commentId = values.comment.id;\n        }\n        if (values.action) {\n          nextValues.actionId = values.action.id;\n        }\n\n        valuesById[id] = { ...nextValues }; // FIXME: hack\n        return nextValues;\n      }),\n    );\n\n    notifications.forEach((notification) => {\n      const values = valuesById[notification.id];\n\n      sails.sockets.broadcast(`user:${notification.userId}`, 'notificationCreate', {\n        item: notification,\n        included: {\n          users: [sails.helpers.users.presentOne(values.creatorUser, {})], // FIXME: hack\n        },\n      });\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks: inputs.webhooks,\n        event: Webhook.Events.NOTIFICATION_CREATE,\n        buildData: () => ({\n          item: notification,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [values.card],\n            ...(notification.commentId\n              ? {\n                  comments: [values.comment],\n                }\n              : {\n                  actions: [values.action],\n                }),\n          },\n        }),\n        user: values.creatorUser,\n      });\n    });\n\n    const notificationsByUserId = _.groupBy(notifications, 'userId');\n\n    const notifiableUsers = await User.qm.getByIds(Object.keys(notificationsByUserId), {\n      withDeactivated: false,\n    });\n\n    if (notifiableUsers.length > 0) {\n      const notifiableUserIds = sails.helpers.utils.mapRecords(notifiableUsers);\n\n      const notificationServices = await NotificationService.qm.getByUserIds(notifiableUserIds);\n      const { transporter } = await sails.helpers.utils.makeSmtpTransporter();\n\n      if (notificationServices.length > 0 || transporter) {\n        const notificationServicesByUserId = _.groupBy(notificationServices, 'userId');\n\n        notifiableUsers.forEach(async (notifiableUser) => {\n          const t = sails.helpers.utils.makeTranslator(notifiableUser.language);\n\n          const emails = notificationsByUserId[notifiableUser.id].flatMap((notification) => {\n            const values = valuesById[notification.id];\n\n            if (notificationServicesByUserId[notifiableUser.id]) {\n              const services = notificationServicesByUserId[notifiableUser.id].map(\n                (notificationService) => _.pick(notificationService, ['url', 'format']),\n              );\n\n              buildAndSendNotifications(\n                services,\n                inputs.board,\n                values.card,\n                notification,\n                values.creatorUser,\n                t,\n              );\n            }\n\n            if (transporter) {\n              return buildEmail(\n                inputs.board,\n                values.card,\n                notification,\n                values.creatorUser,\n                notifiableUser,\n                t,\n              );\n            }\n\n            return [];\n          });\n\n          if (emails.length > 0) {\n            sendEmails(transporter, emails);\n          }\n        });\n      }\n    }\n\n    return notifications;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/notifications/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst escapeMarkdown = require('escape-markdown');\nconst escapeHtml = require('escape-html');\n\nconst { mentionMarkupToText } = require('../../../utils/mentions');\n\nconst buildTitle = (notification, t) => {\n  switch (notification.type) {\n    case Notification.Types.MOVE_CARD:\n      return t('Card Moved');\n    case Notification.Types.COMMENT_CARD:\n      return t('New Comment');\n    case Notification.Types.ADD_MEMBER_TO_CARD:\n      return t('You Were Added to Card');\n    case Notification.Types.MENTION_IN_COMMENT:\n      return t('You Were Mentioned in Comment');\n    default:\n      return null;\n  }\n};\n\nconst buildBodyByFormat = (board, card, notification, actorUser, t) => {\n  const markdownCardLink = `[${escapeMarkdown(card.name)}](${sails.config.custom.baseUrl}/cards/${card.id})`;\n  const htmlCardLink = `<a href=\"${sails.config.custom.baseUrl}/cards/${card.id}\">${escapeHtml(card.name)}</a>`;\n\n  switch (notification.type) {\n    case Notification.Types.MOVE_CARD: {\n      const fromListName = sails.helpers.lists.resolveName(notification.data.fromList, t);\n      const toListName = sails.helpers.lists.resolveName(notification.data.toList, t);\n\n      return {\n        text: t(\n          '%s moved %s from %s to %s on %s',\n          actorUser.name,\n          card.name,\n          fromListName,\n          toListName,\n          board.name,\n        ),\n        markdown: t(\n          '%s moved %s from %s to %s on %s',\n          escapeMarkdown(actorUser.name),\n          markdownCardLink,\n          `**${escapeMarkdown(fromListName)}**`,\n          `**${escapeMarkdown(toListName)}**`,\n          escapeMarkdown(board.name),\n        ),\n        html: t(\n          '%s moved %s from %s to %s on %s',\n          escapeHtml(actorUser.name),\n          htmlCardLink,\n          `<b>${escapeHtml(fromListName)}</b>`,\n          `<b>${escapeHtml(toListName)}</b>`,\n          escapeHtml(board.name),\n        ),\n      };\n    }\n    case Notification.Types.COMMENT_CARD: {\n      const commentText = _.truncate(mentionMarkupToText(notification.data.text));\n\n      return {\n        text: `${t(\n          '%s left a new comment to %s on %s',\n          actorUser.name,\n          card.name,\n          board.name,\n        )}:\\n${commentText}`,\n        markdown: `${t(\n          '%s left a new comment to %s on %s',\n          escapeMarkdown(actorUser.name),\n          markdownCardLink,\n          escapeMarkdown(board.name),\n        )}:\\n\\n*${escapeMarkdown(commentText)}*`,\n        html: `${t(\n          '%s left a new comment to %s on %s',\n          escapeHtml(actorUser.name),\n          htmlCardLink,\n          escapeHtml(board.name),\n        )}:\\n\\n<i>${escapeHtml(commentText)}</i>`,\n      };\n    }\n    case Notification.Types.ADD_MEMBER_TO_CARD:\n      return {\n        text: t('%s added you to %s on %s', actorUser.name, card.name, board.name),\n        markdown: t(\n          '%s added you to %s on %s',\n          escapeMarkdown(actorUser.name),\n          markdownCardLink,\n          escapeMarkdown(board.name),\n        ),\n        html: t(\n          '%s added you to %s on %s',\n          escapeHtml(actorUser.name),\n          htmlCardLink,\n          escapeHtml(board.name),\n        ),\n      };\n    case Notification.Types.MENTION_IN_COMMENT: {\n      const commentText = _.truncate(mentionMarkupToText(notification.data.text));\n\n      return {\n        text: `${t(\n          '%s mentioned you in %s on %s',\n          actorUser.name,\n          card.name,\n          board.name,\n        )}:\\n${commentText}`,\n        markdown: `${t(\n          '%s mentioned you in %s on %s',\n          escapeMarkdown(actorUser.name),\n          markdownCardLink,\n          escapeMarkdown(board.name),\n        )}:\\n\\n*${escapeMarkdown(commentText)}*`,\n        html: `${t(\n          '%s mentioned you in %s on %s',\n          escapeHtml(actorUser.name),\n          htmlCardLink,\n          escapeHtml(board.name),\n        )}:\\n\\n<i>${escapeHtml(commentText)}</i>`,\n      };\n    }\n    default:\n      return null;\n  }\n};\n\nconst buildAndSendNotifications = async (services, board, card, notification, actorUser, t) => {\n  await sails.helpers.utils.sendNotifications(\n    services,\n    buildTitle(notification, t),\n    buildBodyByFormat(board, card, notification, actorUser, t),\n  );\n};\n\n// TODO: use templates (views) to build html\nconst buildAndSendEmail = async (\n  transporter,\n  board,\n  card,\n  notification,\n  actorUser,\n  notifiableUser,\n  t,\n) => {\n  const cardLink = `<a href=\"${sails.config.custom.baseUrl}/cards/${card.id}\">${escapeHtml(card.name)}</a>`;\n  const boardLink = `<a href=\"${sails.config.custom.baseUrl}/boards/${board.id}\">${escapeHtml(board.name)}</a>`;\n\n  let html;\n  switch (notification.type) {\n    case Notification.Types.MOVE_CARD: {\n      const fromListName = sails.helpers.lists.resolveName(notification.data.fromList, t);\n      const toListName = sails.helpers.lists.resolveName(notification.data.toList, t);\n\n      html = `<p>${t(\n        '%s moved %s from %s to %s on %s',\n        escapeHtml(actorUser.name),\n        cardLink,\n        escapeHtml(fromListName),\n        escapeHtml(toListName),\n        boardLink,\n      )}</p>`;\n\n      break;\n    }\n    case Notification.Types.COMMENT_CARD:\n      html = `<p>${t(\n        '%s left a new comment to %s on %s',\n        escapeHtml(actorUser.name),\n        cardLink,\n        boardLink,\n      )}</p><p>${escapeHtml(mentionMarkupToText(notification.data.text))}</p>`;\n\n      break;\n    case Notification.Types.ADD_MEMBER_TO_CARD:\n      html = `<p>${t(\n        '%s added you to %s on %s',\n        escapeHtml(actorUser.name),\n        cardLink,\n        boardLink,\n      )}</p>`;\n\n      break;\n    case Notification.Types.MENTION_IN_COMMENT:\n      html = `<p>${t(\n        '%s mentioned you in %s on %s',\n        escapeHtml(actorUser.name),\n        cardLink,\n        boardLink,\n      )}</p><p>${escapeHtml(mentionMarkupToText(notification.data.text))}</p>`;\n\n      break;\n    default:\n      return;\n  }\n\n  await sails.helpers.utils.sendEmail.with({\n    transporter,\n    html,\n    to: notifiableUser.email,\n    subject: buildTitle(notification, t),\n  });\n\n  transporter.close();\n};\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    webhooks: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (values.comment) {\n      values.commentId = values.comment.id;\n    }\n\n    if (values.action) {\n      values.actionId = values.action.id;\n    }\n\n    const notification = await Notification.qm.createOne({\n      ...values,\n      creatorUserId: values.creatorUser.id,\n      boardId: values.card.boardId,\n      cardId: values.card.id,\n    });\n\n    sails.sockets.broadcast(`user:${notification.userId}`, 'notificationCreate', {\n      item: notification,\n      included: {\n        users: [sails.helpers.users.presentOne(values.creatorUser, {})], // FIXME: hack\n      },\n    });\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks: inputs.webhooks,\n      event: Webhook.Events.NOTIFICATION_CREATE,\n      buildData: () => ({\n        item: notification,\n        included: {\n          projects: [inputs.project],\n          boards: [inputs.board],\n          lists: [inputs.list],\n          cards: [values.card],\n          ...(values.comment && {\n            comments: [values.comment],\n          }),\n          ...(values.action && {\n            actions: [values.action],\n          }),\n        },\n      }),\n      user: values.creatorUser,\n    });\n\n    const notifiableUser = await User.qm.getOneById(notification.userId, {\n      withDeactivated: false,\n    });\n\n    if (notifiableUser) {\n      const notificationServices = await NotificationService.qm.getByUserId(notification.userId);\n      const { transporter } = await sails.helpers.utils.makeSmtpTransporter();\n\n      if (notificationServices.length > 0 || transporter) {\n        const t = sails.helpers.utils.makeTranslator(notifiableUser.language);\n\n        if (notificationServices.length > 0) {\n          const services = notificationServices.map((notificationService) =>\n            _.pick(notificationService, ['url', 'format']),\n          );\n\n          buildAndSendNotifications(\n            services,\n            inputs.board,\n            values.card,\n            notification,\n            values.creatorUser,\n            t,\n          );\n        }\n\n        if (transporter) {\n          buildAndSendEmail(\n            transporter,\n            inputs.board,\n            values.card,\n            notification,\n            values.creatorUser,\n            notifiableUser,\n            t,\n          );\n        }\n      }\n    }\n\n    return notification;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/notifications/read-all-for-user.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    user: {\n      type: 'ref',\n      required: true,\n    },\n    // TODO: add actorUser as well?\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const notifications = await Notification.qm.update(\n      {\n        userId: inputs.user.id,\n        isRead: false,\n      },\n      {\n        isRead: true,\n      },\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    notifications.forEach((notification) => {\n      sails.sockets.broadcast(\n        `user:${notification.userId}`,\n        'notificationUpdate',\n        {\n          item: notification,\n        },\n        inputs.request,\n      );\n\n      // TODO: with prevData?\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.NOTIFICATION_UPDATE,\n        buildData: () => ({\n          item: notification,\n        }),\n        user: inputs.user,\n      });\n    });\n\n    return notifications;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/notifications/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const notification = await Notification.qm.updateOne(inputs.record.id, values);\n\n    if (notification) {\n      sails.sockets.broadcast(\n        `user:${notification.userId}`,\n        'notificationUpdate',\n        {\n          item: notification,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.NOTIFICATION_UPDATE,\n        buildData: () => ({\n          item: notification,\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return notification;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/project-managers/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    webhooks: {\n      type: 'ref',\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    projectInValuesMustBePrivate: {},\n    userInValuesMustBeAdminOrProjectOwner: {},\n    userAlreadyProjectManager: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (values.project.ownerProjectManagerId) {\n      throw 'projectInValuesMustBePrivate';\n    }\n\n    if (!sails.helpers.users.isAdminOrProjectOwner(values.user)) {\n      throw 'userInValuesMustBeAdminOrProjectOwner';\n    }\n\n    let projectManager;\n    try {\n      projectManager = await ProjectManager.qm.createOne({\n        projectId: values.project.id,\n        userId: values.user.id,\n      });\n    } catch (error) {\n      if (error.code === 'E_UNIQUE') {\n        throw 'userAlreadyProjectManager';\n      }\n\n      throw error;\n    }\n\n    const scoper = sails.helpers.projects.makeScoper.with({\n      record: values.project,\n    });\n\n    const projectRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n    projectRelatedUserIds.forEach((userId) => {\n      sails.sockets.broadcast(\n        `user:${userId}`,\n        'projectManagerCreate',\n        {\n          item: projectManager,\n          included: {\n            users: [sails.helpers.users.presentOne(values.user, {})], // FIXME: hack\n          },\n        },\n        inputs.request,\n      );\n    });\n\n    const { webhooks = await Webhook.qm.getAll() } = inputs;\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.PROJECT_MANAGER_CREATE,\n      buildData: () => ({\n        item: projectManager,\n        included: {\n          users: [sails.helpers.users.presentOne(values.user)],\n          projects: [values.project],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return projectManager;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/project-managers/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    user: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    mustNotBeLast: {},\n  },\n\n  async fn(inputs) {\n    const projectManagersLeft = await sails.helpers.projects.getProjectManagersTotalById(\n      inputs.project.id,\n      inputs.record.id,\n    );\n\n    if (projectManagersLeft === 0) {\n      throw 'mustNotBeLast';\n    }\n\n    const scoper = await sails.helpers.projects.makeScoper.with({\n      record: inputs.project,\n    });\n\n    await scoper.getProjectManagerUserIds();\n\n    const projectManager = await ProjectManager.qm.deleteOne(inputs.record.id);\n\n    if (projectManager) {\n      if (inputs.user.role !== User.Roles.ADMIN || inputs.project.ownerProjectManagerId) {\n        const boardIds = await sails.helpers.projects.getBoardIdsById(projectManager.projectId);\n        const boardMemberships = await scoper.getBoardMembershipsForWholeProject();\n\n        const membershipBoardIds = boardMemberships.reduce((result, boardMembership) => {\n          if (boardMembership.userId !== projectManager.userId) {\n            return result;\n          }\n\n          result.push(boardMembership.boardId);\n          return result;\n        }, []);\n\n        const missingBoardIds = _.difference(boardIds, membershipBoardIds);\n\n        missingBoardIds.forEach((boardId) => {\n          sails.sockets.removeRoomMembersFromRooms(\n            `@user:${projectManager.userId}`,\n            `board:${boardId}`,\n          );\n        });\n      }\n\n      const projectRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n      projectRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'projectManagerDelete',\n          {\n            item: projectManager,\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.PROJECT_MANAGER_DELETE,\n        buildData: () => ({\n          item: projectManager,\n          included: {\n            users: [sails.helpers.users.presentOne(inputs.user)],\n            projects: [inputs.project],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return projectManager;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/project-managers/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const projectManager = await ProjectManager.qm.getOneById(inputs.id);\n\n    if (!projectManager) {\n      throw 'pathNotFound';\n    }\n\n    const project = await Project.qm.getOneById(projectManager.projectId);\n\n    if (!project) {\n      throw {\n        pathNotFound: {\n          projectManager,\n        },\n      };\n    }\n\n    return {\n      projectManager,\n      project,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/projects/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'json',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const { project, projectManager } = await Project.qm.createOne(values, {\n      user: inputs.actorUser,\n    });\n\n    const scoper = sails.helpers.projects.makeScoper.with({\n      record: project,\n    });\n\n    scoper.projectManagerUserIds = [projectManager.userId];\n    const userIdsWithFullProjectVisibility = await scoper.getUserIdsWithFullProjectVisibility();\n\n    userIdsWithFullProjectVisibility.forEach((userId) => {\n      // TODO: send projectManager in included\n      sails.sockets.broadcast(\n        `user:${userId}`,\n        'projectCreate',\n        {\n          item: project,\n        },\n        inputs.request,\n      );\n    });\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.PROJECT_CREATE,\n      buildData: () => ({\n        item: project,\n      }),\n      user: inputs.actorUser,\n    });\n\n    return {\n      project,\n      projectManager,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/projects/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    mustNotHaveBoards: {},\n  },\n\n  async fn(inputs) {\n    const boardsTotal = await sails.helpers.projects.getBoardsTotalById(inputs.record.id);\n\n    if (boardsTotal > 0) {\n      throw 'mustNotHaveBoards';\n    }\n\n    const { projectManagers } = await sails.helpers.projects.deleteRelated(inputs.record);\n    const project = await Project.qm.deleteOne(inputs.record.id);\n\n    if (project) {\n      const scoper = sails.helpers.projects.makeScoper.with({\n        record: project,\n      });\n\n      scoper.projectManagerUserIds = sails.helpers.utils.mapRecords(projectManagers, 'userId');\n      const projectRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n      projectRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'projectDelete',\n          {\n            item: project,\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.PROJECT_DELETE,\n        buildData: () => ({\n          item: project,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return project;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/projects/delete-related.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    recordOrRecords: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    let projectIdOrIds;\n    if (_.isPlainObject(inputs.recordOrRecords)) {\n      ({\n        recordOrRecords: { id: projectIdOrIds },\n      } = inputs);\n    } else if (_.every(inputs.recordOrRecords, _.isPlainObject)) {\n      projectIdOrIds = sails.helpers.utils.mapRecords(inputs.recordOrRecords);\n    }\n\n    await ProjectFavorite.qm.delete({\n      projectId: projectIdOrIds,\n    });\n\n    const projectManagers = await ProjectManager.qm.delete({\n      projectId: projectIdOrIds,\n    });\n\n    const { uploadedFiles } = await BackgroundImage.qm.delete({\n      projectId: projectIdOrIds,\n    });\n\n    sails.helpers.utils.removeUnreferencedUploadedFiles(uploadedFiles);\n\n    const baseCustomFieldGroups = await BaseCustomFieldGroup.qm.delete({\n      projectId: projectIdOrIds,\n    });\n\n    await sails.helpers.baseCustomFieldGroups.deleteRelated(baseCustomFieldGroups);\n\n    const boards = await Board.qm.delete({\n      projectId: projectIdOrIds,\n    });\n\n    await sails.helpers.boards.deleteRelated(boards);\n\n    return { projectManagers };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/projects/get-board-ids-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const boards = await Board.qm.getByProjectId(inputs.id);\n\n    return sails.helpers.utils.mapRecords(boards);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/projects/get-board-memberships-total-by-id-and-user-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n    userId: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const boardMemberships = await BoardMembership.qm.getByProjectIdAndUserId(\n      inputs.id,\n      inputs.userId,\n    );\n\n    return boardMemberships.length;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/projects/get-boards-total-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const boards = await Board.qm.getByProjectId(inputs.id);\n\n    return boards.length;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/projects/get-lonely-by-ids.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    ids: {\n      type: 'json',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const projectManagers = await ProjectManager.qm.getByProjectIds(inputs.ids);\n\n    const managerProjectIdsSet = new Set(\n      sails.helpers.utils.mapRecords(projectManagers, 'projectId', true),\n    );\n\n    const lonelyProjectIds = inputs.ids.filter((id) => !managerProjectIdsSet.has(id));\n\n    return Project.qm.getByIds(lonelyProjectIds);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/projects/get-manager-user-ids.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const projectManagers = await ProjectManager.qm.getByProjectId(inputs.id);\n\n    return sails.helpers.utils.mapRecords(projectManagers, 'userId');\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/projects/get-project-managers-total-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n    exceptProjectManagerIdOrIds: {\n      type: 'json',\n    },\n  },\n\n  async fn(inputs) {\n    const projectManagers = await ProjectManager.qm.getByProjectId(inputs.id, {\n      exceptIdOrIds: inputs.exceptProjectManagerIdOrIds,\n    });\n\n    return projectManagers.length;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/projects/make-scoper.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nclass Scoper {\n  constructor(project, board, { notificationService }) {\n    this.project = project;\n    this.board = board;\n    this.notificationService = notificationService;\n\n    this.adminUserIds = null;\n    this.projectManagerUserIds = null;\n    this.boardMemberships = null;\n\n    this.userIdsWithFullProjectVisibility = null;\n    this.boardMembershipsForWholeProject = null;\n    this.boardMemberUserIdsForWholeProject = null;\n    this.boardMemberUserIds = null;\n\n    this.projectRelatedUserIds = null;\n    this.boardRelatedUserIds = null;\n    this.notificationServiceRelatedUserIds = null;\n  }\n\n  replaceProject(project) {\n    if (this.project && project.id !== this.project.id) {\n      this.projectManagerUserIds = null;\n\n      this.boardMembershipsForWholeProject = null;\n      this.boardMemberUserIdsForWholeProject = null;\n    }\n\n    this.project = project;\n\n    this.userIdsWithFullProjectVisibility = null;\n\n    this.projectRelatedUserIds = null;\n    this.boardRelatedUserIds = null;\n  }\n\n  replaceBoard(board) {\n    if (this.board && board.id !== this.board.id) {\n      this.boardMemberships = null;\n\n      this.boardMemberUserIds = null;\n\n      this.boardRelatedUserIds = null;\n    }\n\n    this.board = board;\n  }\n\n  clone() {\n    return _.cloneDeep(this);\n  }\n\n  cloneForProject(project) {\n    const scoper = this.clone();\n    scoper.replaceProject(project);\n\n    return scoper;\n  }\n\n  cloneForBoard(board) {\n    const scoper = this.clone();\n    scoper.replaceBoard(board);\n\n    return scoper;\n  }\n\n  async getAdminUserIds() {\n    if (!this.adminUserIds) {\n      this.adminUserIds = await sails.helpers.users.getAllActiveIds(User.Roles.ADMIN);\n    }\n\n    return this.adminUserIds;\n  }\n\n  async getProjectManagerUserIds() {\n    if (!this.projectManagerUserIds) {\n      this.projectManagerUserIds = await sails.helpers.projects.getManagerUserIds(this.project.id);\n    }\n\n    return this.projectManagerUserIds;\n  }\n\n  async getBoardMemberships() {\n    if (!this.boardMemberships) {\n      this.boardMemberships = await BoardMembership.qm.getByBoardId(this.board.id);\n    }\n\n    return this.boardMemberships;\n  }\n\n  async getUserIdsWithFullProjectVisibility() {\n    if (!this.userIdsWithFullProjectVisibility) {\n      const projectManagerUserIds = await this.getProjectManagerUserIds();\n\n      if (this.project.ownerProjectManagerId) {\n        this.userIdsWithFullProjectVisibility = projectManagerUserIds;\n      } else {\n        const adminUserIds = await this.getAdminUserIds();\n\n        this.userIdsWithFullProjectVisibility = _.union(adminUserIds, projectManagerUserIds);\n      }\n    }\n\n    return this.userIdsWithFullProjectVisibility;\n  }\n\n  async getBoardMembershipsForWholeProject() {\n    if (!this.boardMembershipsForWholeProject) {\n      this.boardMembershipsForWholeProject = await BoardMembership.qm.getByProjectId(\n        this.project.id,\n      );\n    }\n\n    return this.boardMembershipsForWholeProject;\n  }\n\n  async getBoardMemberUserIdsForWholeProject() {\n    if (!this.boardMemberUserIdsForWholeProject) {\n      const boardMembershipsForWholeProject = await this.getBoardMembershipsForWholeProject();\n\n      this.boardMemberUserIdsForWholeProject = sails.helpers.utils.mapRecords(\n        boardMembershipsForWholeProject,\n        'userId',\n        true,\n      );\n    }\n\n    return this.boardMemberUserIdsForWholeProject;\n  }\n\n  async getBoardMemberUserIds() {\n    if (!this.boardMemberUserIds) {\n      const boardMemberships = await this.getBoardMemberships();\n\n      this.boardMemberUserIds = sails.helpers.utils.mapRecords(boardMemberships, 'userId');\n    }\n\n    return this.boardMemberUserIds;\n  }\n\n  async getProjectRelatedUserIds() {\n    if (!this.projectRelatedUserIds) {\n      const userIdsWithFullProjectVisibility = await this.getUserIdsWithFullProjectVisibility();\n      const boardMemberUserIdsForWholeProject = await this.getBoardMemberUserIdsForWholeProject();\n\n      this.projectRelatedUserIds = _.union(\n        userIdsWithFullProjectVisibility,\n        boardMemberUserIdsForWholeProject,\n      );\n    }\n\n    return this.projectRelatedUserIds;\n  }\n\n  async getBoardRelatedUserIds() {\n    if (!this.boardRelatedUserIds) {\n      const userIdsWithFullProjectVisibility = await this.getUserIdsWithFullProjectVisibility();\n      const boardMemberUserIds = await this.getBoardMemberUserIds();\n\n      this.boardRelatedUserIds = _.union(userIdsWithFullProjectVisibility, boardMemberUserIds);\n    }\n\n    return this.boardRelatedUserIds;\n  }\n\n  async getNotificationServiceRelatedUserIds() {\n    if (!this.notificationServiceRelatedUserIds) {\n      this.notificationServiceRelatedUserIds = await this.getProjectManagerUserIds();\n    }\n\n    return this.notificationServiceRelatedUserIds;\n  }\n}\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n    },\n    notificationService: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    boardMustBePresent: {},\n  },\n\n  fn(inputs) {\n    if (inputs.notificationService && !inputs.board) {\n      throw 'boardMustBePresent';\n    }\n\n    return new Scoper(inputs.record, inputs.board, {\n      notificationService: inputs.notificationService,\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/projects/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    scoper: {\n      type: 'ref',\n    },\n    webhooks: {\n      type: 'ref',\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    ownerProjectManagerInValuesMustBeLastManager: {},\n    backgroundImageInValuesMustBePresent: {},\n    backgroundGradientInValuesMustBePresent: {},\n    alreadyHasOwnerProjectManager: {},\n  },\n\n  // TODO: use normalizeValues\n  async fn(inputs) {\n    const { isFavorite, ...values } = inputs.values;\n\n    if (values.backgroundImage) {\n      values.backgroundImageId = values.backgroundImage.id;\n    }\n\n    if (values.ownerProjectManager) {\n      if (inputs.record.ownerProjectManagerId) {\n        if (values.ownerProjectManager.id === inputs.record.ownerProjectManagerId) {\n          delete values.ownerProjectManager;\n        } else {\n          throw 'alreadyHasOwnerProjectManager';\n        }\n      } else {\n        const projectManagersLeft = await sails.helpers.projects.getProjectManagersTotalById(\n          inputs.record.id,\n          values.ownerProjectManager.id,\n        );\n\n        if (projectManagersLeft > 0) {\n          throw 'ownerProjectManagerInValuesMustBeLastManager';\n        }\n\n        values.ownerProjectManagerId = values.ownerProjectManager.id;\n      }\n    }\n\n    const backgroundType = _.isUndefined(values.backgroundType)\n      ? inputs.record.backgroundType\n      : values.backgroundType;\n\n    if (_.isNull(backgroundType)) {\n      Object.assign(values, {\n        backgroundImageId: null,\n        backgroundGradient: null,\n      });\n    } else if (backgroundType === Project.BackgroundTypes.GRADIENT) {\n      const backgroundGradient = _.isUndefined(values.backgroundGradient)\n        ? inputs.record.backgroundGradient\n        : values.backgroundGradient;\n\n      if (!backgroundGradient) {\n        throw 'backgroundGradientInValuesMustBePresent';\n      }\n\n      values.backgroundImageId = null;\n    } else if (backgroundType === Project.BackgroundTypes.IMAGE) {\n      const backgroundImageId = _.isUndefined(values.backgroundImageId)\n        ? inputs.record.backgroundImageId\n        : values.backgroundImageId;\n\n      if (!backgroundImageId) {\n        throw 'backgroundImageInValuesMustBePresent';\n      }\n\n      values.backgroundGradient = null;\n    }\n\n    let project;\n    if (_.isEmpty(values)) {\n      project = inputs.record;\n    } else {\n      project = await Project.qm.updateOne(inputs.record.id, values);\n\n      if (!project) {\n        return project;\n      }\n\n      const {\n        scoper = sails.helpers.projects.makeScoper.with({\n          record: project,\n        }),\n      } = inputs;\n\n      const projectRelatedUserIds = await scoper.getProjectRelatedUserIds();\n\n      if (values.ownerProjectManager) {\n        const boardIds = await sails.helpers.projects.getBoardIdsById(project.id);\n\n        const prevScoper = scoper.cloneForProject(inputs.record);\n        const adminUserIds = await prevScoper.getAdminUserIds();\n        const projectManagerUserIds = await prevScoper.getProjectManagerUserIds();\n        const boardMemberships = await prevScoper.getBoardMembershipsForWholeProject();\n\n        const nonProjectManagerAdminUserIds = _.difference(adminUserIds, projectManagerUserIds);\n\n        const boardMemberUserIdsByBoardId = boardMemberships.reduce(\n          (result, boardMembership) => ({\n            ...result,\n            [boardMembership.boardId]: [\n              ...(result[boardMembership.boardId] || []),\n              boardMembership.userId,\n            ],\n          }),\n          {},\n        );\n\n        boardIds.forEach((boardId) => {\n          const missingUserIds = _.difference(\n            nonProjectManagerAdminUserIds,\n            boardMemberUserIdsByBoardId[boardId] || [],\n          );\n\n          missingUserIds.forEach((userId) => {\n            sails.sockets.removeRoomMembersFromRooms(`@user:${userId}`, `board:${boardId}`);\n          });\n        });\n\n        const projectRelatedUserIdsSet = new Set(projectRelatedUserIds);\n        const prevProjectRelatedUserIds = await prevScoper.getProjectRelatedUserIds();\n\n        const missingProjectRelatedUserIds = prevProjectRelatedUserIds.filter(\n          (userId) => !projectRelatedUserIdsSet.has(userId),\n        );\n\n        missingProjectRelatedUserIds.forEach((userId) => {\n          sails.sockets.broadcast(\n            `user:${userId}`,\n            'projectUpdate',\n            {\n              item: _.pick(project, ['id', 'ownerProjectManagerId']),\n            },\n            inputs.request,\n          );\n        });\n      }\n\n      projectRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'projectUpdate',\n          {\n            item: project,\n          },\n          inputs.request,\n        );\n      });\n\n      const { webhooks = await Webhook.qm.getAll() } = inputs;\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.PROJECT_UPDATE,\n        buildData: () => ({\n          item: project,\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    if (!_.isUndefined(isFavorite)) {\n      const wasFavorite = await sails.helpers.users.isProjectFavorite(\n        inputs.actorUser.id,\n        project.id,\n      );\n\n      if (isFavorite !== wasFavorite) {\n        if (isFavorite) {\n          try {\n            await ProjectFavorite.qm.createOne({\n              projectId: project.id,\n              userId: inputs.actorUser.id,\n            });\n          } catch (error) {\n            if (error.code !== 'E_UNIQUE') {\n              throw error;\n            }\n          }\n        } else {\n          await ProjectFavorite.qm.deleteOne({\n            projectId: project.id,\n            userId: inputs.actorUser.id,\n          });\n        }\n\n        sails.sockets.broadcast(\n          `user:${inputs.actorUser.id}`,\n          'projectUpdate',\n          {\n            item: {\n              isFavorite,\n              id: project.id,\n            },\n          },\n          inputs.request,\n        );\n\n        // TODO: send webhooks\n      }\n    }\n\n    return project;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/sessions/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { v4: uuid } = require('uuid');\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'json',\n      required: true,\n    },\n    withHttpOnlyToken: {\n      type: 'boolean',\n      defaultsTo: false,\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    return Session.qm.createOne({\n      ...values,\n      httpOnlyToken: inputs.withHttpOnlyToken ? uuid() : null,\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/task-lists/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const taskLists = await TaskList.qm.getByCardId(values.card.id);\n\n    const { position, repositions } = sails.helpers.utils.insertToPositionables(\n      values.position,\n      taskLists,\n    );\n\n    if (repositions.length > 0) {\n      // eslint-disable-next-line no-restricted-syntax\n      for (const reposition of repositions) {\n        // eslint-disable-next-line no-await-in-loop\n        await TaskList.qm.updateOne(\n          {\n            id: reposition.record.id,\n            cardId: reposition.record.cardId,\n          },\n          {\n            position: reposition.position,\n          },\n        );\n\n        sails.sockets.broadcast(`board:${inputs.board.id}`, 'taskListUpdate', {\n          item: {\n            id: reposition.record.id,\n            position: reposition.position,\n          },\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const taskList = await TaskList.qm.createOne({\n      ...values,\n      position,\n      cardId: values.card.id,\n    });\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'taskListCreate',\n      {\n        item: taskList,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.TASK_LIST_CREATE,\n      buildData: () => ({\n        item: taskList,\n        included: {\n          projects: [inputs.project],\n          boards: [inputs.board],\n          lists: [inputs.list],\n          cards: [values.card],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return taskList;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/task-lists/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    await sails.helpers.taskLists.deleteRelated(inputs.record);\n\n    const taskList = await TaskList.qm.deleteOne(inputs.record.id);\n\n    if (taskList) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'taskListDelete',\n        {\n          item: taskList,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.TASK_LIST_DELETE,\n        buildData: () => ({\n          item: taskList,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return taskList;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/task-lists/delete-related.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    recordOrRecords: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    let taskListIdOrIds;\n    if (_.isPlainObject(inputs.recordOrRecords)) {\n      ({\n        recordOrRecords: { id: taskListIdOrIds },\n      } = inputs);\n    } else if (_.every(inputs.recordOrRecords, _.isPlainObject)) {\n      taskListIdOrIds = sails.helpers.utils.mapRecords(inputs.recordOrRecords);\n    }\n\n    await Task.qm.delete({\n      taskListId: taskListIdOrIds,\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/task-lists/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const taskList = await TaskList.qm.getOneById(inputs.id);\n\n    if (!taskList) {\n      throw 'pathNotFound';\n    }\n\n    const pathToProject = await sails.helpers.cards\n      .getPathToProjectById(taskList.cardId)\n      .intercept('pathNotFound', (nodes) => ({\n        pathNotFound: {\n          taskList,\n          ...nodes,\n        },\n      }));\n\n    return {\n      taskList,\n      ...pathToProject,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/task-lists/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (!_.isUndefined(values.position)) {\n      const taskLists = await TaskList.qm.getByCardId(inputs.record.cardId, {\n        exceptIdOrIds: inputs.record.id,\n      });\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        taskLists,\n      );\n\n      values.position = position;\n\n      if (repositions.length > 0) {\n        // eslint-disable-next-line no-restricted-syntax\n        for (const reposition of repositions) {\n          // eslint-disable-next-line no-await-in-loop\n          await TaskList.qm.updateOne(\n            {\n              id: reposition.record.id,\n              cardId: reposition.record.cardId,\n            },\n            {\n              position: reposition.position,\n            },\n          );\n\n          sails.sockets.broadcast(`board:${inputs.board.id}`, 'taskListUpdate', {\n            item: {\n              id: reposition.record.id,\n              position: reposition.position,\n            },\n          });\n\n          // TODO: send webhooks\n        }\n      }\n    }\n\n    const taskList = await TaskList.qm.updateOne(inputs.record.id, values);\n\n    if (taskList) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'taskListUpdate',\n        {\n          item: taskList,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.TASK_LIST_UPDATE,\n        buildData: () => ({\n          item: taskList,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return taskList;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/tasks/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exists: {\n    linkedCardOrNameMustBeInValues: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (!values.linkedCard && !values.name) {\n      throw 'linkedCardOrNameMustBeInValues';\n    }\n\n    const tasks = await Task.qm.getByTaskListId(values.taskList.id);\n\n    const { position, repositions } = sails.helpers.utils.insertToPositionables(\n      values.position,\n      tasks,\n    );\n\n    // eslint-disable-next-line no-restricted-syntax\n    for (const reposition of repositions) {\n      // eslint-disable-next-line no-await-in-loop\n      await Task.qm.updateOne(\n        {\n          id: reposition.record.id,\n          taskListId: reposition.record.taskListId,\n        },\n        {\n          position: reposition.position,\n        },\n      );\n\n      sails.sockets.broadcast(`board:${inputs.board.id}`, 'taskUpdate', {\n        item: {\n          id: reposition.record.id,\n          position: reposition.position,\n        },\n      });\n\n      // TODO: send webhooks\n    }\n\n    if (values.linkedCard) {\n      Object.assign(values, {\n        linkedCardId: values.linkedCard.id,\n        name: values.linkedCard.name,\n      });\n\n      if (values.linkedCard.isClosed) {\n        values.isCompleted = true;\n      }\n    }\n\n    const task = await Task.qm.createOne({\n      ...values,\n      position,\n      taskListId: values.taskList.id,\n    });\n\n    sails.sockets.broadcast(\n      `board:${inputs.board.id}`,\n      'taskCreate',\n      {\n        item: task,\n      },\n      inputs.request,\n    );\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.TASK_CREATE,\n      buildData: () => ({\n        item: task,\n        included: {\n          projects: [inputs.project],\n          boards: [inputs.board],\n          lists: [inputs.list],\n          cards: [inputs.card],\n          taskLists: [values.taskList],\n        },\n      }),\n      user: inputs.actorUser,\n    });\n\n    return task;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/tasks/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    taskList: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const task = await Task.qm.deleteOne(inputs.record.id);\n\n    if (task) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'taskDelete',\n        {\n          item: task,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.TASK_DELETE,\n        buildData: () => ({\n          item: task,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n            taskLists: [inputs.taskList],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return task;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/tasks/get-path-to-project-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    pathNotFound: {},\n  },\n\n  async fn(inputs) {\n    const task = await Task.qm.getOneById(inputs.id);\n\n    if (!task) {\n      throw 'pathNotFound';\n    }\n\n    const pathToProject = await sails.helpers.taskLists\n      .getPathToProjectById(task.taskListId)\n      .intercept('pathNotFound', (nodes) => ({\n        pathNotFound: {\n          task,\n          ...nodes,\n        },\n      }));\n\n    return {\n      task,\n      ...pathToProject,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/tasks/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    project: {\n      type: 'ref',\n      required: true,\n    },\n    board: {\n      type: 'ref',\n      required: true,\n    },\n    list: {\n      type: 'ref',\n      required: true,\n    },\n    card: {\n      type: 'ref',\n      required: true,\n    },\n    taskList: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    taskListInValuesMustBelongToCard: {},\n  },\n\n  // TODO: use normalizeValues\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (values.taskList) {\n      if (values.taskList.cardId !== inputs.card.id) {\n        throw 'taskListInValuesMustBelongToCard';\n      }\n\n      if (values.taskList.id === inputs.taskList.id) {\n        delete values.taskList;\n      } else {\n        values.taskListId = values.taskList.id;\n      }\n    }\n\n    const taskList = values.taskList || inputs.taskList;\n\n    if (!_.isUndefined(values.position)) {\n      const tasks = await Task.qm.getByTaskListId(taskList.id, {\n        exceptIdOrIds: inputs.record.id,\n      });\n\n      const { position, repositions } = sails.helpers.utils.insertToPositionables(\n        values.position,\n        tasks,\n      );\n\n      values.position = position;\n\n      // eslint-disable-next-line no-restricted-syntax\n      for (const reposition of repositions) {\n        // eslint-disable-next-line no-await-in-loop\n        await Task.qm.updateOne(\n          {\n            id: reposition.record.id,\n            taskListId: reposition.record.taskListId,\n          },\n          {\n            position: reposition.position,\n          },\n        );\n\n        sails.sockets.broadcast(`board:${inputs.board.id}`, 'taskUpdate', {\n          item: {\n            id: reposition.record.id,\n            position: reposition.position,\n          },\n        });\n\n        // TODO: send webhooks\n      }\n    }\n\n    const task = await Task.qm.updateOne(inputs.record.id, values);\n\n    if (task) {\n      sails.sockets.broadcast(\n        `board:${inputs.board.id}`,\n        'taskUpdate',\n        {\n          item: task,\n        },\n        inputs.request,\n      );\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.TASK_UPDATE,\n        buildData: () => ({\n          item: task,\n          included: {\n            projects: [inputs.project],\n            boards: [inputs.board],\n            lists: [inputs.list],\n            cards: [inputs.card],\n            taskLists: [taskList],\n          },\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n          included: {\n            taskLists: [inputs.taskList],\n          },\n        }),\n        user: inputs.actorUser,\n      });\n\n      if (inputs.record.isCompleted !== task.isCompleted) {\n        await sails.helpers.actions.createOne.with({\n          webhooks,\n          values: {\n            type: task.isCompleted ? Action.Types.COMPLETE_TASK : Action.Types.UNCOMPLETE_TASK,\n            data: {\n              card: _.pick(inputs.card, ['name']),\n              task: _.pick(task, ['id', 'name']),\n            },\n            user: inputs.actorUser,\n            card: inputs.card,\n          },\n          project: inputs.project,\n          board: inputs.board,\n          list: inputs.list,\n        });\n      }\n    }\n\n    return task;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/build-gravatar-url.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst crypto = require('crypto');\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    if (!sails.config.custom.gravatarBaseUrl) {\n      return null;\n    }\n\n    const hash = crypto.createHash('sha256').update(inputs.record.email).digest('hex');\n    return `${sails.config.custom.gravatarBaseUrl}${hash}?s=180&d=initials`;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst bcrypt = require('bcrypt');\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'json',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    emailAlreadyInUse: {},\n    usernameAlreadyInUse: {},\n    activeLimitReached: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (values.password) {\n      values.password = await bcrypt.hash(values.password, 10);\n    }\n\n    if (values.username) {\n      values.username = values.username.toLowerCase();\n    }\n\n    let user;\n    try {\n      user = await User.qm.createOne({\n        ...values,\n        email: values.email.toLowerCase(),\n      });\n    } catch (error) {\n      if (error.code === 'E_UNIQUE') {\n        throw 'emailAlreadyInUse';\n      }\n\n      if (\n        error.name === 'AdapterError' &&\n        error.raw.constraint === 'user_account_username_unique'\n      ) {\n        throw 'usernameAlreadyInUse';\n      }\n\n      if (error.message === 'activeLimitReached') {\n        throw 'activeLimitReached';\n      }\n\n      throw error;\n    }\n\n    const scoper = sails.helpers.users.makeScoper(user);\n    const privateUserRelatedUserIds = await scoper.getPrivateUserRelatedUserIds();\n\n    privateUserRelatedUserIds.forEach((userId) => {\n      sails.sockets.broadcast(\n        `user:${userId}`,\n        'userCreate',\n        {\n          // FIXME: hack\n          item: sails.helpers.users.presentOne(user, {\n            id: userId,\n            role: User.Roles.ADMIN,\n          }),\n        },\n        inputs.request,\n      );\n    });\n\n    const publicUserRelatedUserIds = await scoper.getPublicUserRelatedUserIds(true);\n\n    publicUserRelatedUserIds.forEach((userId) => {\n      sails.sockets.broadcast(\n        `user:${userId}`,\n        'userCreate',\n        {\n          // FIXME: hack\n          item: sails.helpers.users.presentOne(user, {\n            id: userId,\n          }),\n        },\n        inputs.request,\n      );\n    });\n\n    const webhooks = await Webhook.qm.getAll();\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.USER_CREATE,\n      buildData: () => ({\n        item: sails.helpers.users.presentOne(user),\n      }),\n      user: inputs.actorUser,\n    });\n\n    return user;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const { projectManagers, boardMemberships } = await sails.helpers.users.deleteRelated(\n      inputs.record,\n    );\n\n    const { user, uploadedFile } = await User.qm.deleteOne(inputs.record.id);\n\n    if (user) {\n      if (uploadedFile) {\n        sails.helpers.utils.removeUnreferencedUploadedFiles(uploadedFile);\n      }\n\n      const scoper = sails.helpers.users.makeScoper(user);\n      scoper.boardMemberships = boardMemberships;\n\n      const privateUserRelatedUserIds = await scoper.getPrivateUserRelatedUserIds();\n\n      privateUserRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'userDelete',\n          {\n            // FIXME: hack\n            item: sails.helpers.users.presentOne(user, {\n              id: userId,\n              role: User.Roles.ADMIN,\n            }),\n          },\n          inputs.request,\n        );\n      });\n\n      const publicUserRelatedUserIds = await scoper.getPublicUserRelatedUserIds();\n\n      publicUserRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'userDelete',\n          {\n            // FIXME: hack\n            item: sails.helpers.users.presentOne(user, {\n              id: userId,\n            }),\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.USER_DELETE,\n        buildData: () => ({\n          item: sails.helpers.users.presentOne(user),\n        }),\n        user: inputs.actorUser,\n      });\n\n      sails.sockets.leaveAll(`@user:${user.id}`);\n\n      const projectIds = await sails.helpers.utils.mapRecords(projectManagers, 'projectId', true);\n      const lonelyProjects = await sails.helpers.projects.getLonelyByIds(projectIds);\n\n      await Promise.all(\n        lonelyProjects.map((project) =>\n          // TODO: optimize with scoper\n          sails.helpers.projectManagers.createOne.with({\n            webhooks,\n            values: {\n              project,\n              user: inputs.actorUser,\n            },\n            actorUser: inputs.actorUser,\n          }),\n        ),\n      );\n    }\n\n    return user;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/delete-related.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    recordOrRecords: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    let userIdOrIds;\n    if (_.isPlainObject(inputs.recordOrRecords)) {\n      ({\n        recordOrRecords: { id: userIdOrIds },\n      } = inputs);\n    } else if (_.every(inputs.recordOrRecords, _.isPlainObject)) {\n      userIdOrIds = sails.helpers.utils.mapRecords(inputs.recordOrRecords);\n    }\n\n    await IdentityProviderUser.qm.delete({\n      userId: userIdOrIds,\n    });\n\n    await Session.qm.delete({\n      userId: userIdOrIds,\n    });\n\n    await ProjectFavorite.qm.delete({\n      userId: userIdOrIds,\n    });\n\n    const projectManagers = await ProjectManager.qm.delete({\n      userId: userIdOrIds,\n    });\n\n    const projectManagerIds = sails.helpers.utils.mapRecords(projectManagers);\n\n    const projects = await Project.qm.delete({\n      ownerProjectManagerId: projectManagerIds,\n    });\n\n    await sails.helpers.projects.deleteRelated(projects);\n\n    const boardMemberships = await BoardMembership.qm.delete({\n      userId: userIdOrIds,\n    });\n\n    await Card.qm.update(\n      {\n        creatorUserId: userIdOrIds,\n      },\n      {\n        creatorUserId: null,\n      },\n    );\n\n    await CardSubscription.qm.delete({\n      userId: userIdOrIds,\n    });\n\n    await CardMembership.qm.delete({\n      userId: userIdOrIds,\n    });\n\n    await Task.qm.update(\n      {\n        assigneeUserId: userIdOrIds,\n      },\n      {\n        assigneeUserId: null,\n      },\n    );\n\n    await Attachment.qm.update(\n      {\n        creatorUserId: userIdOrIds,\n      },\n      {\n        creatorUserId: null,\n      },\n    );\n\n    await Comment.qm.update(\n      {\n        userId: userIdOrIds,\n      },\n      {\n        userId: null,\n      },\n    );\n\n    await Action.qm.update(\n      {\n        userId: userIdOrIds,\n      },\n      {\n        userId: null,\n      },\n    );\n\n    await Notification.qm.update(\n      {\n        creatorUserId: userIdOrIds,\n      },\n      {\n        creatorUserId: null,\n      },\n    );\n\n    await Notification.qm.delete({\n      userId: userIdOrIds,\n    });\n\n    await NotificationService.qm.delete({\n      userId: userIdOrIds,\n    });\n\n    return {\n      projectManagers,\n      boardMemberships,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/get-all-active-ids.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    roleOrRoles: {\n      type: 'json',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const users = await User.qm.getAll({\n      roleOrRoles: inputs.roleOrRoles,\n      isDeactivated: false,\n    });\n\n    return sails.helpers.utils.mapRecords(users);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/get-manager-project-ids.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const projectManagers = await ProjectManager.qm.getByUserId(inputs.id);\n\n    return sails.helpers.utils.mapRecords(projectManagers, 'projectId');\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/get-notification-services-total.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const notificationServices = await NotificationService.qm.getByUserId(inputs.id);\n\n    return notificationServices.length;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/get-or-create-one-with-oidc.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    code: {\n      type: 'string',\n      required: true,\n    },\n    nonce: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    invalidOidcConfiguration: {},\n    invalidCodeOrNonce: {},\n    invalidUserinfoConfiguration: {},\n    missingValues: {},\n    emailAlreadyInUse: {},\n    usernameAlreadyInUse: {},\n    activeLimitReached: {},\n  },\n\n  async fn(inputs) {\n    const client = await sails.hooks.oidc.getClient();\n\n    if (!client) {\n      throw 'invalidOidcConfiguration';\n    }\n\n    let tokenSet;\n    try {\n      if (sails.config.custom.oidcUseOauthCallback) {\n        tokenSet = await client.oauthCallback(\n          sails.config.custom.oidcRedirectUri,\n          {\n            iss: sails.config.custom.oidcIssuer,\n            code: inputs.code,\n          },\n          {\n            nonce: inputs.nonce,\n          },\n        );\n      } else {\n        tokenSet = await client.callback(\n          sails.config.custom.oidcRedirectUri,\n          {\n            iss: sails.config.custom.oidcIssuer,\n            code: inputs.code,\n          },\n          {\n            nonce: inputs.nonce,\n          },\n        );\n      }\n    } catch (error) {\n      sails.log.warn(`Error while exchanging OIDC code: ${error}`);\n      throw 'invalidCodeOrNonce';\n    }\n\n    let claims;\n    if (sails.config.custom.oidcClaimsSource === 'id_token') {\n      claims = tokenSet.claims();\n    } else {\n      try {\n        claims = await client.userinfo(tokenSet);\n      } catch (error) {\n        let errorText;\n        if (\n          error instanceof SyntaxError &&\n          error.message.includes('Unexpected token e in JSON at position 0')\n        ) {\n          errorText = 'response is signed';\n        } else {\n          errorText = error.toString();\n        }\n\n        sails.log.warn(`Error while fetching OIDC userinfo: ${errorText}`);\n        throw 'invalidUserinfoConfiguration';\n      }\n    }\n\n    const email = _.get(claims, sails.config.custom.oidcEmailAttribute);\n    const name = _.get(claims, sails.config.custom.oidcNameAttribute);\n\n    if (!email || !name) {\n      throw 'missingValues';\n    }\n\n    let role = User.Roles.BOARD_USER;\n    if (!sails.config.custom.oidcIgnoreRoles) {\n      const claimsRoles = _.get(claims, sails.config.custom.oidcRolesAttribute);\n\n      if (Array.isArray(claimsRoles)) {\n        // Use a Set here to avoid quadratic time complexity\n        const claimsRolesSet = new Set(claimsRoles);\n\n        const foundRole = [User.Roles.ADMIN, User.Roles.PROJECT_OWNER, User.Roles.BOARD_USER].find(\n          (roleItem) => {\n            const configRoles = sails.config.custom[`oidc${_.upperFirst(roleItem)}Roles`];\n\n            if (configRoles.includes('*')) {\n              return true;\n            }\n\n            return configRoles.some((configRole) => claimsRolesSet.has(configRole));\n          },\n        );\n\n        if (foundRole) {\n          role = foundRole;\n        }\n      }\n    }\n\n    const values = {\n      email,\n      role,\n      name,\n      isSsoUser: true,\n    };\n    if (!sails.config.custom.oidcIgnoreUsername) {\n      values.username = _.get(claims, sails.config.custom.oidcUsernameAttribute);\n    }\n\n    // This whole block technically needs to be executed in a transaction\n    // with SERIALIZABLE isolation level (but Waterline does not support\n    // that), so this will result in errors if for example users are deleted\n    // concurrently with logging in via OIDC.\n    let identityProviderUser = await IdentityProviderUser.qm.getOneByIssuerAndSub(\n      sails.config.custom.oidcIssuer,\n      claims.sub,\n    );\n\n    let user;\n    let isCreated = false;\n\n    if (identityProviderUser) {\n      user = await User.qm.getOneById(identityProviderUser.userId);\n    } else {\n      // If no IDP/User mapping exists, search for the user by email.\n      user = await User.qm.getOneByEmail(values.email);\n\n      // Otherwise, create a new user.\n      if (!user) {\n        user = await sails.helpers.users.createOne\n          .with({\n            values,\n            actorUser: User.OIDC,\n          })\n          .intercept('usernameAlreadyInUse', 'usernameAlreadyInUse')\n          .intercept('activeLimitReached', 'activeLimitReached');\n\n        isCreated = true;\n      }\n\n      identityProviderUser = await IdentityProviderUser.qm.createOne({\n        userId: user.id,\n        issuer: sails.config.custom.oidcIssuer,\n        sub: claims.sub || `${user.id}@${sails.config.custom.oidcIssuer}`,\n      });\n    }\n\n    if (!isCreated) {\n      values.isDeactivated = false;\n\n      const updateFieldKeys = ['email', 'name', 'isSsoUser', 'isDeactivated'];\n      if (!sails.config.custom.oidcIgnoreUsername) {\n        updateFieldKeys.push('username');\n      }\n      if (!sails.config.custom.oidcIgnoreRoles) {\n        updateFieldKeys.push('role');\n      }\n\n      const updateValues = {};\n      // eslint-disable-next-line no-restricted-syntax\n      for (const k of updateFieldKeys) {\n        if (values[k] !== user[k]) updateValues[k] = values[k];\n      }\n\n      if (Object.keys(updateValues).length > 0) {\n        user = await sails.helpers.users.updateOne\n          .with({\n            record: user,\n            values: updateValues,\n            actorUser: User.OIDC,\n          })\n          .intercept('emailAlreadyInUse', 'emailAlreadyInUse')\n          .intercept('usernameAlreadyInUse', 'usernameAlreadyInUse')\n          .intercept('activeLimitReached', 'activeLimitReached');\n      }\n    }\n\n    return user;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/get-project-managers-total-by-id.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const projectManagers = await ProjectManager.qm.getByUserId(inputs.id);\n\n    return projectManagers.length;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/is-admin-or-project-owner.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    return [User.Roles.ADMIN, User.Roles.PROJECT_OWNER].includes(inputs.record.role);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/is-board-member.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n    boardId: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(\n      inputs.boardId,\n      inputs.id,\n    );\n\n    return !!boardMembership;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/is-board-subscriber.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n    boardId: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const boardSubscription = await BoardSubscription.qm.getOneByBoardIdAndUserId(\n      inputs.boardId,\n      inputs.id,\n    );\n\n    return !!boardSubscription;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/is-card-subscriber.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n    cardId: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const cardSubscription = await CardSubscription.qm.getOneByCardIdAndUserId(\n      inputs.cardId,\n      inputs.id,\n    );\n\n    return !!cardSubscription;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/is-project-favorite.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n    projectId: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const projectFavorite = await ProjectFavorite.qm.getOneByProjectIdAndUserId(\n      inputs.projectId,\n      inputs.id,\n    );\n\n    return !!projectFavorite;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/is-project-manager.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    id: {\n      type: 'string',\n      required: true,\n    },\n    projectId: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const projectManager = await ProjectManager.qm.getOneByProjectIdAndUserId(\n      inputs.projectId,\n      inputs.id,\n    );\n\n    return !!projectManager;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/make-scoper.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nclass Scoper {\n  constructor(user) {\n    this.user = user;\n\n    this.projectManagers = null;\n\n    this.separatedUserIds = null;\n    this.userRelatedProjectManagerAndBoardMemberUserIds = null;\n\n    this.privateUserRelatedUserIds = null;\n    this.publicUserRelatedUserIds = null;\n  }\n\n  async getProjectManagers() {\n    if (!this.projectManagers) {\n      this.projectManagers = await ProjectManager.qm.getByUserId(this.user.id);\n    }\n\n    return this.projectManagers;\n  }\n\n  async getSeparatedUserIds() {\n    if (!this.separatedUserIds) {\n      const users = await User.qm.getAll({\n        roleOrRoles: [User.Roles.ADMIN, User.Roles.PROJECT_OWNER],\n        isDeactivated: false,\n      });\n\n      const adminUserIds = [];\n      const projectOwnerUserIds = [];\n\n      users.forEach((user) => {\n        if (user.role === User.Roles.ADMIN) {\n          adminUserIds.push(user.id);\n        } else {\n          projectOwnerUserIds.push(user.id);\n        }\n      });\n\n      this.separatedUserIds = {\n        adminUserIds,\n        projectOwnerUserIds,\n      };\n    }\n\n    return this.separatedUserIds;\n  }\n\n  async getUserRelatedProjectManagerAndBoardMemberUserIds() {\n    if (!this.userRelatedProjectManagerAndBoardMemberUserIds) {\n      const projectManagers = await this.getProjectManagers();\n      const projectIds = sails.helpers.utils.mapRecords(projectManagers, 'projectId');\n\n      const relatedProjectManagers = await ProjectManager.qm.getByProjectIds(projectIds, {\n        exceptUserIdOrIds: this.user.id,\n      });\n\n      const relatedProjectManagerUserIds = sails.helpers.utils.mapRecords(\n        relatedProjectManagers,\n        'userId',\n      );\n\n      const relatedProjectBoardMemberships = await BoardMembership.qm.getByProjectIds(projectIds);\n\n      const relatedProjectBoardMemberUserIds = sails.helpers.utils.mapRecords(\n        relatedProjectBoardMemberships,\n        'userId',\n      );\n\n      const boardMemberships = await BoardMembership.qm.getByUserId(this.user.id, {\n        exceptProjectIdOrIds: projectIds,\n      });\n\n      const boardIds = sails.helpers.utils.mapRecords(boardMemberships, 'boardId');\n\n      const relatedBoardMemberships = await BoardMembership.qm.getByBoardIds(boardIds, {\n        exceptUserIdOrIds: this.user.id,\n      });\n\n      const relatedBoardMemberUserIds = sails.helpers.utils.mapRecords(\n        relatedBoardMemberships,\n        'userId',\n      );\n\n      this.userRelatedProjectManagerAndBoardMemberUserIds = _.union(\n        relatedProjectManagerUserIds,\n        relatedProjectBoardMemberUserIds,\n        relatedBoardMemberUserIds,\n      );\n    }\n\n    return this.userRelatedProjectManagerAndBoardMemberUserIds;\n  }\n\n  async getPrivateUserRelatedUserIds() {\n    if (!this.privateUserRelatedUserIds) {\n      const { adminUserIds } = await this.getSeparatedUserIds();\n\n      this.privateUserRelatedUserIds = _.union([this.user.id], adminUserIds);\n    }\n\n    return this.privateUserRelatedUserIds;\n  }\n\n  async getPublicUserRelatedUserIds(skipRelatedProjectManagerAndBoardMemberUserIds = false) {\n    if (!this.publicUserRelatedUserIds) {\n      const privateUserRelatedUserIds = await this.getPrivateUserRelatedUserIds();\n      const privateUserRelatedUserIdsSet = new Set(privateUserRelatedUserIds);\n\n      const { projectOwnerUserIds } = await this.getSeparatedUserIds();\n\n      let userRelatedProjectManagerAndBoardMemberUserIds;\n      if (!skipRelatedProjectManagerAndBoardMemberUserIds) {\n        userRelatedProjectManagerAndBoardMemberUserIds =\n          await this.getUserRelatedProjectManagerAndBoardMemberUserIds();\n      }\n\n      const externalPublicUserRelatedUserIds = _.union(\n        projectOwnerUserIds,\n        userRelatedProjectManagerAndBoardMemberUserIds,\n      );\n\n      this.publicUserRelatedUserIds = externalPublicUserRelatedUserIds.filter(\n        (userId) => !privateUserRelatedUserIdsSet.has(userId),\n      );\n    }\n\n    return this.publicUserRelatedUserIds;\n  }\n}\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    return new Scoper(inputs.record);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/present-many.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    records: {\n      type: 'ref',\n      required: true,\n    },\n    user: {\n      type: 'ref',\n      require: true,\n    },\n  },\n\n  fn(inputs) {\n    return inputs.records.map((record) => sails.helpers.users.presentOne(record, inputs.user));\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/present-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    user: {\n      type: 'ref',\n    },\n  },\n\n  fn(inputs) {\n    const data = {\n      ..._.omit(inputs.record, [\n        'password',\n        'avatar',\n        'apiKeyHash',\n        'termsSignature',\n        'passwordChangedAt',\n        'apiKeyCreatedAt',\n        'termsAcceptedAt',\n      ]),\n      avatar: inputs.record.avatar && {\n        url: `${sails.config.custom.baseUrl}/user-avatars/${inputs.record.avatar.uploadedFileId}/original.${inputs.record.avatar.extension}`,\n        thumbnailUrls: {\n          cover180: `${sails.config.custom.baseUrl}/user-avatars/${inputs.record.avatar.uploadedFileId}/cover-180.${inputs.record.avatar.extension}`,\n        },\n      },\n      language: inputs.record.language || sails.config.i18n.defaultLocale,\n    };\n\n    const gravatarUrl = sails.helpers.users.buildGravatarUrl(inputs.record);\n\n    if (gravatarUrl) {\n      data.gravatarUrl = gravatarUrl;\n    }\n\n    if (inputs.user) {\n      const isForCurrentUser = inputs.record.id === inputs.user.id;\n      const isForAdmin = inputs.user.role === User.Roles.ADMIN;\n\n      if (isForCurrentUser || isForAdmin) {\n        const isDefaultAdmin = inputs.record.email === sails.config.custom.defaultAdminEmail;\n\n        const lockedFieldNames = [];\n        if (sails.config.custom.demoMode) {\n          lockedFieldNames.push('email', 'password', 'role', 'name', 'username');\n        } else if (isDefaultAdmin || inputs.record.isSsoUser) {\n          lockedFieldNames.push('email', 'password', 'name');\n\n          if (isDefaultAdmin) {\n            lockedFieldNames.push('role', 'username');\n          } else if (inputs.record.isSsoUser) {\n            if (!sails.config.custom.oidcIgnoreRoles) {\n              lockedFieldNames.push('role');\n            }\n            if (!sails.config.custom.oidcIgnoreUsername) {\n              lockedFieldNames.push('username');\n            }\n          }\n        }\n\n        if (sails.config.custom.oidcEnforced) {\n          lockedFieldNames.push('isSsoUser');\n        }\n\n        Object.assign(data, {\n          isDefaultAdmin,\n          lockedFieldNames,\n        });\n\n        if (isForCurrentUser) {\n          return data;\n        }\n\n        return _.omit(data, User.PERSONAL_FIELD_NAMES);\n      }\n\n      return _.omit(data, [...User.PRIVATE_FIELD_NAMES, ...User.PERSONAL_FIELD_NAMES]);\n    }\n\n    return data;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/process-uploaded-avatar-file.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { v4: uuid } = require('uuid');\nconst { rimraf } = require('rimraf');\nconst { fileTypeFromFile } = require('file-type');\nconst sharp = require('sharp');\n\nconst { MAX_SIZE_TO_PROCESS_AS_IMAGE } = require('../../../constants');\n\nmodule.exports = {\n  inputs: {\n    file: {\n      type: 'json',\n      required: true,\n    },\n  },\n\n  exits: {\n    fileIsNotImage: {},\n  },\n\n  async fn(inputs) {\n    const fileManager = sails.hooks['file-manager'].getInstance();\n\n    const fileType = await fileTypeFromFile(inputs.file.fd);\n    const { mime: mimeType = null } = fileType || {};\n    const { size } = inputs.file;\n\n    if (!mimeType || !mimeType.startsWith('image/') || size > MAX_SIZE_TO_PROCESS_AS_IMAGE) {\n      await rimraf(inputs.file.fd);\n      throw 'fileIsNotImage';\n    }\n\n    let image = sharp(inputs.file.fd, {\n      animated: true,\n    });\n\n    let metadata;\n    try {\n      metadata = await image.metadata();\n    } catch (error) {\n      await rimraf(inputs.file.fd);\n      throw 'fileIsNotImage';\n    }\n\n    if (metadata.orientation && metadata.orientation > 4) {\n      image = image.rotate();\n    }\n\n    const { id: uploadedFileId } = await UploadedFile.qm.createOne({\n      mimeType,\n      size,\n      id: uuid(),\n      type: UploadedFile.Types.USER_AVATAR,\n    });\n\n    const dirPathSegment = `${sails.config.custom.userAvatarsPathSegment}/${uploadedFileId}`;\n    const extension = metadata.format === 'jpeg' ? 'jpg' : metadata.format;\n\n    const cover180 = image\n      .clone()\n      .resize(180, 180, {\n        withoutEnlargement: true,\n      })\n      .png({\n        quality: 75,\n        force: false,\n      });\n\n    try {\n      await Promise.all([\n        fileManager.save(`${dirPathSegment}/original.${extension}`, image, inputs.file.type),\n        fileManager.save(`${dirPathSegment}/cover-180.${extension}`, cover180, inputs.file.type),\n      ]);\n    } catch (error) {\n      sails.log.warn(error.stack);\n\n      await fileManager.deleteDir(dirPathSegment);\n      await rimraf(inputs.file.fd);\n      await UploadedFile.qm.deleteOne(uploadedFileId);\n\n      throw 'fileIsNotImage';\n    }\n\n    await rimraf(inputs.file.fd);\n\n    return {\n      uploadedFileId,\n      extension,\n      size,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/users/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst bcrypt = require('bcrypt');\nconst { v4: uuid } = require('uuid');\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    emailAlreadyInUse: {},\n    usernameAlreadyInUse: {},\n    activeLimitReached: {},\n  },\n\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (!_.isUndefined(values.email)) {\n      values.email = values.email.toLowerCase();\n    }\n\n    if (_.isNull(values.apiKey)) {\n      Object.assign(values, {\n        apiKeyPrefix: null,\n        apiKeyHash: null,\n        apiKeyCreatedAt: null,\n      });\n\n      delete values.apiKey;\n    }\n\n    let isOnlyPrivateFieldsChange = false;\n    let isOnlyPersonalFieldsChange = false;\n    let isOnlyPasswordChange = false;\n    let isDeactivatedChangeToTrue = false;\n\n    if (_.difference(Object.keys(values), User.PRIVATE_FIELD_NAMES).length === 0) {\n      isOnlyPrivateFieldsChange = true;\n    }\n\n    if (_.difference(Object.keys(values), User.PERSONAL_FIELD_NAMES).length === 0) {\n      isOnlyPersonalFieldsChange = true;\n    }\n\n    if (!_.isUndefined(values.password)) {\n      if (Object.keys(values).length === 1) {\n        isOnlyPasswordChange = true;\n      }\n\n      values.password = await bcrypt.hash(values.password, 10);\n      values.passwordChangedAt = new Date().toUTCString(); // FIXME: hack\n    }\n\n    if (values.username) {\n      values.username = values.username.toLowerCase();\n    }\n\n    if (values.apiKeyHash) {\n      values.apiKeyCreatedAt = new Date().toISOString();\n    }\n\n    if (values.isDeactivated && values.isDeactivated !== inputs.record.isDeactivated) {\n      isDeactivatedChangeToTrue = true;\n    }\n\n    let user;\n    let uploadedFile;\n\n    try {\n      ({ user, uploadedFile } = await User.qm.updateOne(inputs.record.id, values));\n    } catch (error) {\n      if (error.code === 'E_UNIQUE') {\n        throw 'emailAlreadyInUse';\n      }\n\n      if (\n        error.name === 'AdapterError' &&\n        error.raw.constraint === 'user_account_username_unique'\n      ) {\n        throw 'usernameAlreadyInUse';\n      }\n\n      if (error.message === 'activeLimitReached') {\n        throw 'activeLimitReached';\n      }\n\n      throw error;\n    }\n\n    if (user) {\n      if (uploadedFile) {\n        sails.helpers.utils.removeUnreferencedUploadedFiles(uploadedFile);\n      }\n\n      if (!_.isUndefined(values.password) || isDeactivatedChangeToTrue) {\n        sails.sockets.broadcast(`user:${user.id}`, 'logout', undefined, inputs.request);\n\n        if (\n          !isDeactivatedChangeToTrue &&\n          user.id === inputs.actorUser.id &&\n          inputs.request &&\n          inputs.request.isSocket\n        ) {\n          const tempRoom = uuid();\n\n          sails.sockets.addRoomMembersToRooms(`@user:${user.id}`, tempRoom, () => {\n            sails.sockets.leave(inputs.request, tempRoom, () => {\n              sails.sockets.leaveAll(tempRoom);\n            });\n          });\n        } else {\n          sails.sockets.leaveAll(`@user:${user.id}`);\n        }\n      }\n\n      if (!isOnlyPasswordChange) {\n        if (isOnlyPersonalFieldsChange) {\n          sails.sockets.broadcast(\n            `user:${user.id}`,\n            'userUpdate',\n            {\n              item: sails.helpers.users.presentOne(user, user),\n            },\n            inputs.request,\n          );\n        } else {\n          const scoper = sails.helpers.users.makeScoper(user);\n          const privateUserRelatedUserIds = await scoper.getPrivateUserRelatedUserIds();\n\n          privateUserRelatedUserIds.forEach((userId) => {\n            sails.sockets.broadcast(\n              `user:${userId}`,\n              'userUpdate',\n              {\n                // FIXME: hack\n                item: sails.helpers.users.presentOne(user, {\n                  id: userId,\n                  role: User.Roles.ADMIN,\n                }),\n              },\n              inputs.request,\n            );\n          });\n\n          if (!isOnlyPrivateFieldsChange) {\n            if (inputs.record.role === User.Roles.ADMIN && user.role !== User.Roles.ADMIN) {\n              const managerProjectIds = await sails.helpers.users.getManagerProjectIds(user.id);\n\n              const sharedProjects = await Project.qm.getShared({\n                exceptIdOrIds: managerProjectIds,\n              });\n\n              const projectIds = sails.helpers.utils.mapRecords(sharedProjects);\n\n              const boards = await Board.qm.getByProjectIds(projectIds);\n              const boardIds = sails.helpers.utils.mapRecords(boards);\n\n              const boardMemberships = await BoardMembership.qm.getByBoardIdsAndUserId(\n                boardIds,\n                user.id,\n              );\n\n              const missingBoardIds = _.difference(\n                boardIds,\n                sails.helpers.utils.mapRecords(boardMemberships, 'boardId'),\n              );\n\n              missingBoardIds.forEach((boardId) => {\n                sails.sockets.removeRoomMembersFromRooms(`@user:${user.id}`, `board:${boardId}`);\n              });\n            }\n\n            const publicUserRelatedUserIds = await scoper.getPublicUserRelatedUserIds();\n\n            publicUserRelatedUserIds.forEach((userId) => {\n              sails.sockets.broadcast(\n                `user:${userId}`,\n                'userUpdate',\n                {\n                  // FIXME: hack\n                  item: sails.helpers.users.presentOne(user, {\n                    id: userId,\n                  }),\n                },\n                inputs.request,\n              );\n            });\n          }\n        }\n\n        const webhooks = await Webhook.qm.getAll();\n\n        sails.helpers.utils.sendWebhooks.with({\n          webhooks,\n          event: Webhook.Events.USER_UPDATE,\n          buildData: () => ({\n            item: sails.helpers.users.presentOne(user),\n          }),\n          buildPrevData: () => ({\n            item: sails.helpers.users.presentOne(inputs.record),\n          }),\n          user: inputs.actorUser,\n        });\n      }\n    }\n\n    return user;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/clear-http-only-token-cookie.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    response: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    inputs.response.clearCookie('httpOnlyToken', {\n      path: sails.config.custom.baseUrlPath || '/',\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/create-jwt-token.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { v4: uuid } = require('uuid');\nconst jwt = require('jsonwebtoken');\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    subject: {\n      type: 'json',\n      required: true,\n    },\n    issuedAt: {\n      type: 'ref',\n    },\n    expiresIn: {\n      type: 'number',\n    },\n  },\n\n  fn(inputs) {\n    const { issuedAt = new Date(), expiresIn = sails.config.custom.tokenExpiresIn } = inputs;\n\n    const iat = Math.floor(issuedAt / 1000);\n    const exp = iat + expiresIn;\n\n    const payload = {\n      iat,\n      exp,\n      sub: inputs.subject,\n    };\n\n    const token = jwt.sign(payload, sails.config.session.secret, {\n      keyid: uuid(),\n    });\n\n    return {\n      token,\n      payload,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/download-favicon.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { URL } = require('url');\nconst { ProxyAgent } = require('undici');\nconst icoToPng = require('ico-to-png');\nconst sharp = require('sharp');\n\nconst FETCH_TIMEOUT = 4000;\nconst MAX_RESPONSE_LENGTH = 1024 * 1024;\n\nconst FAVICON_TAGS_REGEX = /<link [^>]*rel=\"([^\"]* )?icon( [^\"]*)?\"[^>]*>/gi;\nconst HREF_REGEX = /href=\"(.*?)\"/i;\nconst SIZES_REGEX = /sizes=\"(.*?)\"/i;\n\nconst fetchWithTimeout = (url) => {\n  const abortController = new AbortController();\n  setTimeout(() => abortController.abort(), FETCH_TIMEOUT);\n\n  return fetch(url, {\n    signal: abortController.signal,\n    dispatcher: sails.config.custom.outgoingProxy\n      ? new ProxyAgent(sails.config.custom.outgoingProxy)\n      : undefined,\n  });\n};\n\nconst readResponse = async (response) => {\n  const reader = response.body.getReader();\n\n  const chunks = [];\n  let receivedLength = 0;\n\n  for (;;) {\n    const { value, done } = await reader.read(); // eslint-disable-line no-await-in-loop\n\n    if (done) {\n      break;\n    }\n\n    chunks.push(value);\n    receivedLength += value.length;\n\n    if (receivedLength > MAX_RESPONSE_LENGTH) {\n      reader.cancel();\n\n      return {\n        ok: false,\n        buffer: Buffer.concat(chunks),\n      };\n    }\n  }\n\n  return {\n    ok: true,\n    buffer: Buffer.concat(chunks),\n  };\n};\n\nconst isWantedFaviconTag = (faviconTag) => {\n  const sizesMatch = faviconTag.match(SIZES_REGEX);\n\n  if (!sizesMatch) {\n    return false;\n  }\n\n  const sizes = sizesMatch[1].split('x');\n  return parseInt(sizes[0], 10) >= 32 && parseInt(sizes[1], 10) >= 32;\n};\n\nmodule.exports = {\n  inputs: {\n    url: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const { hostname, origin } = new URL(inputs.url);\n\n    let response;\n    let readedResponse;\n\n    try {\n      response = await fetchWithTimeout(origin);\n\n      if (!response.ok) {\n        return;\n      }\n\n      readedResponse = await readResponse(response);\n    } catch (error) {\n      return;\n    }\n\n    const content = readedResponse.buffer.toString();\n    const faviconTagsMatch = content.match(FAVICON_TAGS_REGEX);\n\n    let faviconUrl;\n    if (faviconTagsMatch && faviconTagsMatch.length > 0) {\n      let faviconTag;\n      if (faviconTagsMatch.length > 1) {\n        faviconTag = faviconTagsMatch.find(isWantedFaviconTag);\n      }\n\n      if (!faviconTag) {\n        [faviconTag] = faviconTagsMatch;\n      }\n\n      const hrefMatch = faviconTag.match(HREF_REGEX);\n\n      if (hrefMatch) {\n        faviconUrl = new URL(hrefMatch[1], response.url).href;\n      }\n    }\n\n    if (!faviconUrl) {\n      faviconUrl = new URL('/favicon.ico', response.url).href;\n    }\n\n    try {\n      response = await fetchWithTimeout(faviconUrl);\n\n      if (!response.ok) {\n        return;\n      }\n\n      readedResponse = await readResponse(response);\n    } catch (error) {\n      return;\n    }\n\n    if (!readedResponse.ok) {\n      return;\n    }\n\n    const availableStorage = await sails.helpers.utils.getAvailableStorage();\n\n    if (availableStorage !== null && readedResponse.buffer.length >= availableStorage) {\n      return;\n    }\n\n    let image = sharp(readedResponse.buffer);\n\n    let metadata;\n    try {\n      metadata = await image.metadata();\n    } catch (error) {\n      /* empty */\n    }\n\n    if (!metadata || metadata.format === 'magick') {\n      try {\n        const buffer = await icoToPng(readedResponse.buffer, 32);\n\n        image = sharp(buffer);\n        metadata = await image.metadata();\n      } catch (error) {\n        return;\n      }\n    }\n\n    const fileManager = sails.hooks['file-manager'].getInstance();\n    const { width, height } = metadata;\n\n    image = image\n      .resize(\n        32,\n        32,\n        width < 32 || height < 32\n          ? {\n              kernel: sharp.kernel.nearest,\n            }\n          : undefined,\n      )\n      .png();\n\n    try {\n      await fileManager.save(\n        `${sails.config.custom.faviconsPathSegment}/${hostname}.png`,\n        image,\n        'image/png',\n      );\n    } catch (error) {\n      /* empty */\n    }\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/generate-api-key.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  fn() {\n    const prefix = sails.helpers.utils.generateRandomString(8);\n    const secret = sails.helpers.utils.generateRandomString(32);\n    const key = `${prefix}_${secret}`;\n\n    return {\n      key,\n      prefix,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/generate-ids.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    total: {\n      type: 'number',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    const queryResult = await sails.sendNativeQuery(\n      'SELECT next_id() as id from generate_series(1, $1) ORDER BY id',\n      [inputs.total],\n    );\n\n    return sails.helpers.utils.mapRecords(queryResult.rows);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/generate-random-string.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst crypto = require('crypto');\n\nconst CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    size: {\n      type: 'number',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    const bytes = crypto.randomBytes(inputs.size);\n\n    let result = '';\n    for (let i = 0; i < inputs.size; i += 1) {\n      result += CHARS[bytes[i] % 62];\n    }\n\n    return result;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/get-available-storage.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst bytes = require('bytes');\n\nmodule.exports = {\n  async fn() {\n    let { storageLimit } = await InternalConfig.qm.getOneMain();\n    storageLimit = bytes(storageLimit);\n\n    if (storageLimit === null) {\n      return null;\n    }\n\n    const storageUsage = await StorageUsage.qm.getOneMain();\n    return BigInt(storageLimit) - BigInt(storageUsage.total);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/hash.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst crypto = require('crypto');\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    data: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    return crypto.createHash('sha256').update(inputs.data).digest('hex');\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/insert-to-positionables.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst GAP = 2 ** 14;\nconst MIN_GAP = 0.125;\nconst MAX_POSITION = 2 ** 50;\n\nconst findBeginnings = (positions) => {\n  positions.unshift(0);\n\n  let prevPosition = positions.pop();\n  const beginnings = [prevPosition];\n\n  // eslint-disable-next-line consistent-return\n  _.forEachRight(positions, (position) => {\n    if (prevPosition - MIN_GAP >= position) {\n      return false;\n    }\n\n    prevPosition = position;\n    beginnings.unshift(prevPosition);\n  });\n\n  return beginnings;\n};\n\nconst getRepositionsMap = (positions) => {\n  const repositionsMap = {};\n\n  if (positions.length <= 1) {\n    if (!_.isUndefined(positions[0]) && positions[0] > MAX_POSITION) {\n      return null;\n    }\n\n    return repositionsMap;\n  }\n\n  let prevPosition = positions.shift();\n\n  for (let i = 0; i < positions.length; i += 1) {\n    const position = positions[i];\n    const nextPosition = positions[i + 1];\n\n    if (prevPosition + MIN_GAP <= position) {\n      break;\n    }\n\n    if (!_.isUndefined(nextPosition) && prevPosition + MIN_GAP * 2 <= nextPosition) {\n      (repositionsMap[position] || (repositionsMap[position] = [])).push(\n        prevPosition + (nextPosition - prevPosition) / 2,\n      );\n\n      break;\n    }\n\n    prevPosition += GAP;\n\n    if (prevPosition > MAX_POSITION) {\n      return null;\n    }\n\n    (repositionsMap[position] || (repositionsMap[position] = [])).push(prevPosition);\n  }\n\n  return repositionsMap;\n};\n\nconst getFullRepositionsMap = (positions) => {\n  const repositionsMap = {};\n\n  _.forEach(positions, (position, index) => {\n    (repositionsMap[position] || (repositionsMap[position] = [])).push(GAP * (index + 1));\n  });\n\n  return repositionsMap;\n};\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    position: {\n      type: 'number',\n      required: true,\n    },\n    records: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    const lowers = [];\n    const uppers = [];\n\n    inputs.records.forEach(({ position }) => {\n      (position <= inputs.position ? lowers : uppers).push(position);\n    });\n\n    const beginnings = findBeginnings([...lowers, inputs.position]);\n\n    const repositionsMap =\n      getRepositionsMap([...beginnings, ...uppers]) ||\n      getFullRepositionsMap([...lowers, inputs.position, ...uppers]);\n\n    const position = repositionsMap[inputs.position]\n      ? repositionsMap[inputs.position].pop()\n      : inputs.position;\n\n    const repositions = [];\n\n    _.forEachRight(inputs.records, (record) => {\n      if (_.isEmpty(repositionsMap[record.position])) {\n        return;\n      }\n\n      repositions.unshift({\n        record,\n        position: repositionsMap[record.position].pop(),\n      });\n    });\n\n    return {\n      position,\n      repositions,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/is-preloaded-favicon-exists.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst fs = require('fs');\nconst path = require('path');\n\nconst PRELOADED_FAVICON_FILENAMES = fs\n  .readdirSync(path.join(sails.config.paths.public, 'preloaded-favicons'))\n  .filter((filename) => filename.endsWith('.png'));\n\nconst PRELOADED_FAVICON_HOSTNAMES_SET = new Set(\n  PRELOADED_FAVICON_FILENAMES.map((faviconFilename) => faviconFilename.slice(0, -4)),\n);\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    hostname: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    return PRELOADED_FAVICON_HOSTNAMES_SET.has(inputs.hostname);\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/make-smtp-transporter.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst nodemailer = require('nodemailer');\n\nmodule.exports = {\n  inputs: {\n    defaultOptions: {\n      type: 'json',\n    },\n  },\n\n  async fn(inputs) {\n    let config;\n    let sourceConfig;\n\n    if (sails.config.custom.smtpHost) {\n      sourceConfig = sails.config.custom;\n    } else {\n      config = await Config.qm.getOneMain();\n\n      if (config.smtpHost) {\n        sourceConfig = config;\n\n        // TODO: hack to make it work with proxy\n        if (sourceConfig.smtpPort === null) {\n          sourceConfig.smtpPort = sourceConfig.smtpSecure ? 465 : 587;\n        }\n      }\n    }\n\n    if (!sourceConfig) {\n      return {\n        config,\n        transporter: null,\n      };\n    }\n\n    const transporter = nodemailer.createTransport(\n      {\n        ...inputs.defaultOptions,\n        host: sourceConfig.smtpHost,\n        port: sourceConfig.smtpPort,\n        name: sourceConfig.smtpName,\n        secure: sourceConfig.smtpSecure,\n        auth: sourceConfig.smtpUser && {\n          user: sourceConfig.smtpUser,\n          pass: sourceConfig.smtpPassword,\n        },\n        tls: {\n          rejectUnauthorized: sourceConfig.smtpTlsRejectUnauthorized,\n        },\n        proxy:\n          sails.config.custom.outgoingProxy && !sails.config.custom.smtpHost\n            ? sails.config.custom.outgoingProxy\n            : undefined,\n      },\n      {\n        from: sourceConfig.smtpFrom,\n      },\n    );\n\n    return {\n      transporter,\n      config,\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/make-translator.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst path = require('path');\nconst I18n = require('i18n-2');\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    language: {\n      type: 'string',\n      allowNull: true,\n    },\n  },\n\n  fn(inputs) {\n    const i18n = new I18n({\n      locales: sails.config.i18n.locales,\n      defaultLocale: sails.config.i18n.defaultLocale,\n      directory: path.join(sails.config.appPath, sails.config.i18n.localesDirectory),\n      extension: '.json',\n      devMode: false,\n    });\n\n    i18n.setLocale(inputs.language || sails.config.i18n.defaultLocale);\n\n    /* eslint-disable no-underscore-dangle */\n    const translator = i18n.__.bind(i18n);\n    translator.n = i18n.__n.bind(i18n);\n    /* eslint-enable no-underscore-dangle */\n\n    return translator;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/map-records.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    records: {\n      type: 'ref',\n      required: true,\n    },\n    attribute: {\n      type: 'string',\n      defaultsTo: 'id',\n    },\n    unique: {\n      type: 'boolean',\n      defaultsTo: false,\n    },\n    withoutNull: {\n      type: 'boolean',\n      defaultsTo: false,\n    },\n  },\n\n  fn(inputs) {\n    let result = _.map(inputs.records, inputs.attribute);\n\n    if (inputs.unique) {\n      result = _.uniq(result);\n    }\n\n    if (inputs.withoutNull) {\n      result = _.without(result, null);\n    }\n\n    return result;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/receive-file.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst util = require('util');\n\nmodule.exports = {\n  inputs: {\n    file: {\n      type: 'ref',\n      required: true,\n    },\n    enforceStorageLimit: {\n      type: 'boolean',\n      defaultsTo: true,\n    },\n  },\n\n  async fn(inputs, exits) {\n    const { maxUploadFileSize } = sails.config.custom;\n\n    let availableStorage = null;\n    if (inputs.enforceStorageLimit) {\n      availableStorage = await sails.helpers.utils.getAvailableStorage();\n    }\n\n    let maxBytes = maxUploadFileSize;\n    if (availableStorage !== null) {\n      if (maxBytes) {\n        maxBytes = availableStorage < maxBytes ? availableStorage : maxBytes;\n      } else {\n        maxBytes = availableStorage;\n      }\n    }\n\n    const upload = util.promisify((options, callback) =>\n      inputs.file.upload(options, (error, files) => {\n        if (\n          error &&\n          error.code === 'E_EXCEEDS_UPLOAD_LIMIT' &&\n          availableStorage !== null &&\n          (maxUploadFileSize === null || error.maxBytes < maxUploadFileSize)\n        ) {\n          return callback(new Error('Storage limit reached'), files);\n        }\n\n        return callback(error, files);\n      }),\n    );\n\n    return exits.success(\n      await upload({\n        maxBytes,\n        dirname: sails.config.custom.uploadsTempPath,\n      }),\n    );\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/remove-unreferenced-uploaded-files.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { Types } = require('../../models/UploadedFile');\n\nconst PATH_SEGMENT_BY_TYPE = {\n  [Types.USER_AVATAR]: sails.config.custom.userAvatarsPathSegment,\n  [Types.BACKGROUND_IMAGE]: sails.config.custom.backgroundImagesPathSegment,\n  [Types.ATTACHMENT]: sails.config.custom.attachmentsPathSegment,\n};\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    uploadedFileOrUploadedFiles: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    const uploadedFiles = _.isPlainObject(inputs.uploadedFileOrUploadedFiles)\n      ? [inputs.uploadedFileOrUploadedFiles]\n      : inputs.uploadedFileOrUploadedFiles;\n\n    const fileManager = sails.hooks['file-manager'].getInstance();\n\n    // TODO: optimize?\n    uploadedFiles.forEach(async (uploadedFile) => {\n      if (uploadedFile.referencesTotal !== null) {\n        return;\n      }\n\n      await fileManager.deleteDir(`${PATH_SEGMENT_BY_TYPE[uploadedFile.type]}/${uploadedFile.id}`);\n      await UploadedFile.qm.deleteOne(uploadedFile.id);\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/send-email.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    transporter: {\n      type: 'ref',\n      required: true,\n    },\n    to: {\n      type: 'string',\n      required: true,\n    },\n    subject: {\n      type: 'string',\n      required: true,\n    },\n    html: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    try {\n      const info = await inputs.transporter.sendMail(inputs);\n\n      sails.log.info(`Email sent: ${info.messageId}`);\n    } catch (error) {\n      sails.log.error(`Error sending email: ${error}`);\n    }\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/send-notifications.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { execFile } = require('child_process');\nconst path = require('path');\nconst util = require('util');\n\nconst PYTHON_PATH =\n  process.platform === 'win32'\n    ? path.join(sails.config.appPath, '.venv', 'Scripts', 'python.exe')\n    : path.join(sails.config.appPath, '.venv', 'bin', 'python');\n\nconst promisifyExecFile = util.promisify(execFile);\n\nmodule.exports = {\n  inputs: {\n    services: {\n      type: 'json',\n      required: true,\n    },\n    title: {\n      type: 'string',\n      required: true,\n    },\n    bodyByFormat: {\n      type: 'json',\n      required: true,\n    },\n  },\n\n  async fn(inputs) {\n    try {\n      await promisifyExecFile(\n        PYTHON_PATH,\n        [\n          path.join(sails.config.appPath, 'utils', 'send_notifications.py'),\n          JSON.stringify(inputs.services),\n          inputs.title,\n          JSON.stringify(inputs.bodyByFormat),\n        ],\n        {\n          env: {\n            HTTP_PROXY: sails.config.custom.outgoingProxy,\n            HTTPS_PROXY: sails.config.custom.outgoingProxy,\n          },\n        },\n      );\n    } catch (error) {\n      sails.log.error(`Error sending notifications:\\n${error.stderr || error.message}`);\n    }\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/send-webhooks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { ProxyAgent } = require('undici');\n\nconst Webhook = require('../../models/Webhook');\n\n/**\n * @typedef {Object} Included\n * @property {any[]} [users] - Array of users (optional).\n * @property {any[]} [projects] - Array of projects (optional).\n * @property {any[]} [baseCustomFieldGroups] - Array of base custom field groups (optional).\n * @property {any[]} [boards] - Array of boards (optional).\n * @property {any[]} [boardMemberships] - Array of board memberships (optional).\n * @property {any[]} [labels] - Array of labels (optional).\n * @property {any[]} [lists] - Array of lists (optional).\n * @property {any[]} [cards] - Array of cards (optional).\n * @property {any[]} [cardMemberships] - Array of card memberships (optional).\n * @property {any[]} [taskLists] - Array of task lists (optional).\n * @property {any[]} [customFieldGroups] - Array of custom field groups (optional).\n * @property {any[]} [customFields] - Array of custom fields (optional).\n * @property {any[]} [comments] - Array of comments (optional).\n * @property {any[]} [actions] - Array of actions (optional).\n */\n\n/**\n * @typedef {Object} Data\n * @property {any} item - Actual event data.\n * @property {Included} [included] - Optional included data.\n */\n\n/**\n * Sends a webhook notification to a configured URL.\n *\n * @param {*} webhook - Webhook configuration.\n * @param {string} event - The event.\n * @param {Data} data - The data object containing event data and optionally included data.\n * @param {Data} [prevData] - The data object containing previous state of data (optional).\n * @param {ref} user - User object associated with the event.\n * @returns {Promise<void>}\n */\nasync function sendWebhook(webhook, event, data, prevData, user) {\n  const headers = {\n    'Content-Type': 'application/json',\n    'User-Agent': `planka (+${sails.config.custom.baseUrl})`,\n  };\n\n  if (webhook.accessToken) {\n    headers.Authorization = `Bearer ${webhook.accessToken}`;\n  }\n\n  const body = JSON.stringify({\n    event,\n    data,\n    prevData,\n    user,\n  });\n\n  try {\n    const response = await fetch(webhook.url, {\n      headers,\n      body,\n      method: 'POST',\n      dispatcher: sails.config.custom.outgoingProxy\n        ? new ProxyAgent(sails.config.custom.outgoingProxy)\n        : undefined,\n    });\n\n    if (!response.ok) {\n      const message = await response.text();\n\n      sails.log.error(\n        `Webhook ${webhook.name} failed with status ${response.status} and message: ${message}`,\n      );\n    }\n  } catch (error) {\n    sails.log.error(`Webhook ${webhook.name} failed with error: ${error}`);\n  }\n}\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    webhooks: {\n      type: 'ref',\n      required: true,\n    },\n    event: {\n      type: 'string',\n      required: true,\n      isIn: Object.values(Webhook.Events),\n    },\n    buildData: {\n      type: 'ref',\n      required: true,\n    },\n    buildPrevData: {\n      type: 'ref',\n    },\n    user: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    const webhooks = inputs.webhooks.filter((webhook) => {\n      if (!webhook.url) {\n        return false;\n      }\n\n      if (webhook.excludedEvents && webhook.excludedEvents.includes(inputs.event)) {\n        return false;\n      }\n\n      if (webhook.events && !webhook.events.includes(inputs.event)) {\n        return false;\n      }\n\n      return true;\n    });\n\n    if (webhooks.length === 0) {\n      return;\n    }\n\n    const data = inputs.buildData();\n    const prevData = inputs.buildPrevData && inputs.buildPrevData();\n\n    webhooks.forEach((webhook) => {\n      sendWebhook(\n        webhook,\n        inputs.event,\n        data,\n        prevData,\n        sails.helpers.users.presentOne(inputs.user),\n      );\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/set-http-only-token-cookie.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    value: {\n      type: 'string',\n      required: true,\n    },\n    accessTokenPayload: {\n      type: 'json',\n      required: true,\n    },\n    response: {\n      type: 'ref',\n      required: true,\n    },\n  },\n\n  fn(inputs) {\n    inputs.response.cookie('httpOnlyToken', inputs.value, {\n      expires: new Date(inputs.accessTokenPayload.exp * 1000),\n      path: sails.config.custom.baseUrlPath || '/',\n      secure: sails.config.custom.baseUrlSecure,\n      httpOnly: true,\n      sameSite: 'strict',\n    });\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/utils/verify-jwt-token.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst jwt = require('jsonwebtoken');\n\nmodule.exports = {\n  sync: true,\n\n  inputs: {\n    token: {\n      type: 'string',\n      required: true,\n    },\n  },\n\n  exits: {\n    invalidToken: {},\n  },\n\n  fn(inputs) {\n    let payload;\n    try {\n      payload = jwt.verify(inputs.token, sails.config.session.secret);\n    } catch (error) {\n      throw { invalidToken: error };\n    }\n\n    return {\n      subject: payload.sub,\n      issuedAt: new Date(payload.iat * 1000),\n    };\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/webhooks/create-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    values: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  exits: {\n    limitReached: {},\n  },\n\n  // TODO: use normalizeValues\n  async fn(inputs) {\n    const { values } = inputs;\n\n    const webhooks = await Webhook.qm.getAll();\n\n    // TODO: move to config?\n    if (webhooks.length >= 10) {\n      throw 'limitReached';\n    }\n\n    if (values.events) {\n      values.events = _.intersection(values.events, Object.values(Webhook.Events));\n      delete values.excludedEvents;\n    } else if (values.excludedEvents) {\n      values.excludedEvents = _.intersection(values.excludedEvents, Object.values(Webhook.Events));\n      delete values.events;\n    }\n\n    const webhook = await Webhook.qm.createOne(values);\n    webhooks.push(webhook);\n\n    const scoper = sails.helpers.users.makeScoper(inputs.actorUser);\n    const privateUserRelatedUserIds = await scoper.getPrivateUserRelatedUserIds();\n\n    privateUserRelatedUserIds.forEach((userId) => {\n      sails.sockets.broadcast(\n        `user:${userId}`,\n        'webhookCreate',\n        {\n          item: webhook,\n        },\n        inputs.request,\n      );\n    });\n\n    sails.helpers.utils.sendWebhooks.with({\n      webhooks,\n      event: Webhook.Events.WEBHOOK_CREATE,\n      buildData: () => ({\n        item: webhook,\n      }),\n      user: inputs.actorUser,\n    });\n\n    return webhook;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/webhooks/delete-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  async fn(inputs) {\n    const webhooks = await Webhook.qm.getAll();\n\n    const webhook = await Webhook.qm.deleteOne(inputs.record.id);\n\n    if (webhook) {\n      const scoper = sails.helpers.users.makeScoper(inputs.actorUser);\n      const privateUserRelatedUserIds = await scoper.getPrivateUserRelatedUserIds();\n\n      privateUserRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'webhookDelete',\n          {\n            item: webhook,\n          },\n          inputs.request,\n        );\n      });\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.WEBHOOK_DELETE,\n        buildData: () => ({\n          item: webhook,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return webhook;\n  },\n};\n"
  },
  {
    "path": "server/api/helpers/webhooks/update-one.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = {\n  inputs: {\n    record: {\n      type: 'ref',\n      required: true,\n    },\n    values: {\n      type: 'json',\n      required: true,\n    },\n    actorUser: {\n      type: 'ref',\n      required: true,\n    },\n    request: {\n      type: 'ref',\n    },\n  },\n\n  // TODO: use normalizeValues\n  async fn(inputs) {\n    const { values } = inputs;\n\n    if (values.events) {\n      Object.assign(values, {\n        events: _.intersection(values.events, Object.values(Webhook.Events)),\n        excludedEvents: null,\n      });\n    } else if (values.excludedEvents) {\n      Object.assign(values, {\n        events: null,\n        excludedEvents: _.intersection(values.excludedEvents, Object.values(Webhook.Events)),\n      });\n    }\n\n    const webhook = await Webhook.qm.updateOne(inputs.record.id, values);\n\n    if (webhook) {\n      const scoper = sails.helpers.users.makeScoper(inputs.actorUser);\n      const privateUserRelatedUserIds = await scoper.getPrivateUserRelatedUserIds();\n\n      privateUserRelatedUserIds.forEach((userId) => {\n        sails.sockets.broadcast(\n          `user:${userId}`,\n          'webhookUpdate',\n          {\n            item: webhook,\n          },\n          inputs.request,\n        );\n      });\n\n      const webhooks = await Webhook.qm.getAll();\n\n      sails.helpers.utils.sendWebhooks.with({\n        webhooks,\n        event: Webhook.Events.WEBHOOK_UPDATE,\n        buildData: () => ({\n          item: webhook,\n        }),\n        buildPrevData: () => ({\n          item: inputs.record,\n        }),\n        user: inputs.actorUser,\n      });\n    }\n\n    return webhook;\n  },\n};\n"
  },
  {
    "path": "server/api/hooks/.gitkeep",
    "content": ""
  },
  {
    "path": "server/api/hooks/current-user/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * current-user hook\n *\n * @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,\n *                 and/or initialization logic.\n * @docs        :: https://sailsjs.com/docs/concepts/extending-sails/hooks\n */\n\nmodule.exports = function defineCurrentUserHook(sails) {\n  const TOKEN_PATTERN = /^Bearer /;\n  const API_KEY_HEADER_NAME = 'x-api-key';\n\n  const getSessionAndUserByAccessToken = async (accessToken, httpOnlyToken) => {\n    let payload;\n    try {\n      payload = sails.helpers.utils.verifyJwtToken(accessToken);\n    } catch (error) {\n      return null;\n    }\n\n    const session = await Session.qm.getOneUndeletedByAccessToken(accessToken);\n\n    if (!session) {\n      return null;\n    }\n\n    if (session.httpOnlyToken && httpOnlyToken !== session.httpOnlyToken) {\n      return null;\n    }\n\n    const user = await User.qm.getOneById(payload.subject, {\n      withDeactivated: false,\n    });\n\n    if (!user) {\n      return null;\n    }\n\n    if (user.passwordChangedAt > payload.issuedAt) {\n      return null;\n    }\n\n    return {\n      session,\n      user,\n    };\n  };\n\n  const getUserByApiKey = (apiKey) => {\n    const apiKeyHash = sails.helpers.utils.hash(apiKey);\n\n    return User.qm.getOneActiveByApiKeyHash(apiKeyHash);\n  };\n\n  return {\n    /**\n     * Runs when this Sails app loads/lifts.\n     */\n\n    async initialize() {\n      sails.log.info('Initializing custom hook (`current-user`)');\n    },\n\n    routes: {\n      before: {\n        '/api/*': {\n          async fn(req, res, next) {\n            const { authorization: authorizationHeader, [API_KEY_HEADER_NAME]: apiKey } =\n              req.headers;\n\n            if (authorizationHeader && TOKEN_PATTERN.test(authorizationHeader)) {\n              const accessToken = authorizationHeader.replace(TOKEN_PATTERN, '');\n              const { internalAccessToken } = sails.config.custom;\n\n              if (internalAccessToken && accessToken === internalAccessToken) {\n                req.currentUser = User.INTERNAL;\n              } else {\n                const { httpOnlyToken } = req.cookies;\n\n                const sessionAndUser = await getSessionAndUserByAccessToken(\n                  accessToken,\n                  httpOnlyToken,\n                );\n\n                if (sessionAndUser) {\n                  const { session, user } = sessionAndUser;\n\n                  if (user.language) {\n                    req.setLocale(user.language);\n                  }\n\n                  Object.assign(req, {\n                    currentSession: session,\n                    currentUser: user,\n                  });\n\n                  if (req.isSocket) {\n                    sails.sockets.join(req, `@accessToken:${session.accessToken}`);\n                    sails.sockets.join(req, `@user:${user.id}`);\n                  }\n                }\n              }\n            } else if (apiKey) {\n              const user = await getUserByApiKey(apiKey);\n\n              if (user) {\n                if (user.language) {\n                  req.setLocale(user.language);\n                }\n\n                req.currentUser = user;\n\n                if (req.isSocket) {\n                  sails.sockets.join(req, `@user:${user.id}`);\n                }\n              }\n            }\n\n            return next();\n          },\n        },\n        '/attachments/*': {\n          async fn(req, res, next) {\n            const { accessToken, httpOnlyToken } = req.cookies;\n\n            if (accessToken) {\n              const sessionAndUser = await getSessionAndUserByAccessToken(\n                accessToken,\n                httpOnlyToken,\n              );\n\n              if (sessionAndUser) {\n                const { session, user } = sessionAndUser;\n\n                Object.assign(req, {\n                  currentSession: session,\n                  currentUser: user,\n                });\n              }\n            } else {\n              const { [API_KEY_HEADER_NAME]: apiKey } = req.headers;\n\n              if (apiKey) {\n                const user = await getUserByApiKey(apiKey);\n\n                if (user) {\n                  req.currentUser = user;\n                }\n              }\n            }\n\n            return next();\n          },\n        },\n      },\n    },\n  };\n};\n"
  },
  {
    "path": "server/api/hooks/file-manager/LocalFileManager.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst fs = require('fs');\nconst fse = require('fs-extra');\nconst path = require('path');\nconst { pipeline } = require('stream/promises');\nconst mime = require('mime-types');\nconst { rimraf } = require('rimraf');\n\n// const PATH_SEGMENT_TO_URL_REPLACE_REGEX = /(public|private)\\//;\n\nconst buildPath = (pathSegment) => path.join(sails.config.custom.uploadsBasePath, pathSegment);\n\nclass LocalFileManager {\n  // eslint-disable-next-line class-methods-use-this\n  async move(sourceFilePath, filePathSegment) {\n    const { dir, base } = path.parse(filePathSegment);\n\n    const dirPath = buildPath(dir);\n    const filePath = path.join(dirPath, base);\n\n    await fs.promises.mkdir(dirPath, { recursive: true });\n    await fse.move(sourceFilePath, filePath);\n\n    return filePath;\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  async save(filePathSegment, stream) {\n    const filePath = buildPath(filePathSegment);\n    const { dir: dirPath } = path.parse(filePath);\n\n    await fs.promises.mkdir(dirPath, { recursive: true });\n    await pipeline(stream, fs.createWriteStream(filePath));\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  async read(filePathSegment, { withHeaders = false } = {}) {\n    const filePath = buildPath(filePathSegment);\n\n    let stat;\n    try {\n      stat = await fs.promises.stat(filePath);\n    } catch (error) {\n      throw new Error('File does not exist');\n    }\n\n    const readStream = fs.createReadStream(filePath);\n\n    if (withHeaders) {\n      return [\n        readStream,\n        {\n          'Content-Type': mime.lookup(filePathSegment) || 'application/octet-stream',\n          'Content-Length': stat.size,\n          'Last-Modified': stat.mtime.toUTCString(),\n          ETag: `W/\"${stat.size.toString(16)}-${stat.mtime.getTime().toString(16)}\"`,\n          'Accept-Ranges': 'bytes',\n        },\n      ];\n    }\n\n    return readStream;\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  async getSize(filePathSegment) {\n    let stat;\n    try {\n      stat = await fs.promises.stat(buildPath(filePathSegment));\n    } catch (error) {\n      return null;\n    }\n\n    return stat.size;\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  async rename(filePathSegment, nextFilePathSegment) {\n    try {\n      await fs.promises.rename(buildPath(filePathSegment), buildPath(nextFilePathSegment));\n    } catch (error) {\n      /* empty */\n    }\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  async delete(filePathSegment) {\n    try {\n      await fs.promises.unlink(buildPath(filePathSegment));\n    } catch (error) {\n      /* empty */\n    }\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  async listDir(dirPathSegment) {\n    let dirents;\n    try {\n      dirents = await fs.promises.readdir(buildPath(dirPathSegment), {\n        withFileTypes: true,\n      });\n    } catch (error) {\n      return null;\n    }\n\n    return dirents.flatMap((dirent) => (dirent.isDirectory() ? dirent.name : []));\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  async renameDir(dirPathSegment, nextDirPathSegment) {\n    try {\n      await fs.promises.rename(buildPath(dirPathSegment), buildPath(nextDirPathSegment));\n    } catch (error) {\n      /* empty */\n    }\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  async deleteDir(dirPathSegment) {\n    await rimraf(buildPath(dirPathSegment));\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  async isExists(pathSegment) {\n    return fse.pathExists(buildPath(pathSegment));\n  }\n\n  /* // eslint-disable-next-line class-methods-use-this\n  buildUrl(filePathSegment) {\n    return `${sails.config.custom.baseUrl}/${filePathSegment.replace(PATH_SEGMENT_TO_URL_REPLACE_REGEX, '')}`;\n  } */\n}\n\nmodule.exports = LocalFileManager;\n"
  },
  {
    "path": "server/api/hooks/file-manager/S3FileManager.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst fs = require('fs');\nconst mime = require('mime-types');\nconst {\n  CopyObjectCommand,\n  DeleteObjectsCommand,\n  DeleteObjectCommand,\n  GetObjectCommand,\n  HeadObjectCommand,\n  ListObjectsV2Command,\n  PutObjectCommand,\n} = require('@aws-sdk/client-s3');\nconst { Upload } = require('@aws-sdk/lib-storage');\n\nclass S3FileManager {\n  constructor(client) {\n    this.client = client;\n  }\n\n  async move(sourceFilePath, filePathSegment, contentType) {\n    const command = new PutObjectCommand({\n      Bucket: sails.config.custom.s3Bucket,\n      Key: filePathSegment,\n      Body: fs.createReadStream(sourceFilePath),\n      ContentType: contentType,\n    });\n\n    await this.client.send(command);\n    return null;\n  }\n\n  async save(filePathSegment, stream, contentType) {\n    const upload = new Upload({\n      client: this.client,\n      params: {\n        Bucket: sails.config.custom.s3Bucket,\n        Key: filePathSegment,\n        Body: stream,\n        ContentType: contentType,\n      },\n    });\n\n    await upload.done();\n  }\n\n  async read(filePathSegment, { withHeaders = false } = {}) {\n    const command = new GetObjectCommand({\n      Bucket: sails.config.custom.s3Bucket,\n      Key: filePathSegment,\n    });\n\n    const result = await this.client.send(command);\n\n    if (withHeaders) {\n      const headers = {\n        'Content-Type':\n          result.ContentType || mime.lookup(filePathSegment) || 'application/octet-stream',\n        'Accept-Ranges': result.AcceptRanges || 'bytes',\n      };\n\n      if (!_.isUndefined(result.ContentLength)) {\n        headers['Content-Length'] = result.ContentLength;\n      }\n\n      if (!_.isUndefined(result.LastModified)) {\n        headers['Last-Modified'] = result.LastModified.toUTCString();\n      }\n\n      if (_.isUndefined(result.ETag)) {\n        if (!_.isUndefined(result.ContentLength) && !_.isUndefined(result.LastModified)) {\n          headers.ETag = `W/\"${result.ContentLength.toString(16)}-${result.LastModified.getTime().toString(16)}\"`;\n        }\n      } else {\n        headers.ETag = result.ETag;\n      }\n\n      return [result.Body, headers];\n    }\n\n    return result.Body;\n  }\n\n  async getSize(filePathSegment) {\n    const headObjectCommand = new HeadObjectCommand({\n      Bucket: sails.config.custom.s3Bucket,\n      Key: filePathSegment,\n    });\n\n    let result;\n    try {\n      result = await this.client.send(headObjectCommand);\n    } catch (error) {\n      return null;\n    }\n\n    return result.ContentLength;\n  }\n\n  async rename(filePathSegment, nextFilePathSegment) {\n    const copyObjectCommand = new CopyObjectCommand({\n      Bucket: sails.config.custom.s3Bucket,\n      Key: nextFilePathSegment,\n      CopySource: `${sails.config.custom.s3Bucket}/${filePathSegment}`,\n    });\n\n    try {\n      await this.client.send(copyObjectCommand);\n    } catch (error) {\n      return;\n    }\n\n    await this.delete(filePathSegment);\n  }\n\n  async delete(filePathSegment) {\n    const deleteObjectCommand = new DeleteObjectCommand({\n      Bucket: sails.config.custom.s3Bucket,\n      Key: filePathSegment,\n    });\n\n    await this.client.send(deleteObjectCommand);\n  }\n\n  async listDir(dirPathSegment, { ContinuationToken } = {}) {\n    const listObjectsCommand = new ListObjectsV2Command({\n      ContinuationToken,\n      Bucket: sails.config.custom.s3Bucket,\n      Prefix: `${dirPathSegment}/`,\n      Delimiter: '/',\n    });\n\n    const result = await this.client.send(listObjectsCommand);\n\n    if (!result.CommonPrefixes) {\n      return null;\n    }\n\n    const dirnames = result.CommonPrefixes.map(({ Prefix }) =>\n      Prefix.slice(dirPathSegment.length + 1, -1),\n    );\n\n    if (result.IsTruncated) {\n      const otherDirnames = await this.listDir(dirPathSegment, {\n        ContinuationToken: result.NextContinuationToken,\n      });\n\n      if (otherDirnames) {\n        dirnames.push(...otherDirnames);\n      }\n    }\n\n    return dirnames;\n  }\n\n  async renameDir(dirPathSegment, nextDirPathSegment, { ContinuationToken } = {}) {\n    const listObjectsCommand = new ListObjectsV2Command({\n      ContinuationToken,\n      Bucket: sails.config.custom.s3Bucket,\n      Prefix: dirPathSegment,\n    });\n\n    const result = await this.client.send(listObjectsCommand);\n\n    if (!result.Contents) {\n      return;\n    }\n\n    // eslint-disable-next-line no-restricted-syntax\n    for (const { Key } of result.Contents) {\n      // eslint-disable-next-line no-await-in-loop\n      await this.rename(Key, `${nextDirPathSegment}/${Key.substring(dirPathSegment.length + 1)}`);\n    }\n\n    if (result.IsTruncated) {\n      await this.renameDir(dirPathSegment, nextDirPathSegment, {\n        ContinuationToken: result.NextContinuationToken,\n      });\n    }\n  }\n\n  async deleteDir(dirPathSegment, { ContinuationToken } = {}) {\n    const listObjectsCommand = new ListObjectsV2Command({\n      ContinuationToken,\n      Bucket: sails.config.custom.s3Bucket,\n      Prefix: dirPathSegment,\n    });\n\n    const result = await this.client.send(listObjectsCommand);\n\n    if (!result.Contents) {\n      return;\n    }\n\n    if (result.Contents.length > 0) {\n      const deleteObjectsCommand = new DeleteObjectsCommand({\n        Bucket: sails.config.custom.s3Bucket,\n        Delete: {\n          Objects: result.Contents.map(({ Key }) => ({ Key })),\n        },\n      });\n\n      await this.client.send(deleteObjectsCommand);\n    }\n\n    if (result.IsTruncated) {\n      await this.deleteDir(dirPathSegment, {\n        ContinuationToken: result.NextContinuationToken,\n      });\n    }\n  }\n\n  async isExists(pathSegment) {\n    const listObjectsCommand = new ListObjectsV2Command({\n      Bucket: sails.config.custom.s3Bucket,\n      Prefix: pathSegment,\n      MaxKeys: 1,\n    });\n\n    const result = await this.client.send(listObjectsCommand);\n    return !!result.Contents && result.Contents.length === 1;\n  }\n\n  /* // eslint-disable-next-line class-methods-use-this\n  buildUrl(filePathSegment) {\n    return `${sails.hooks.s3.getBaseUrl()}/${filePathSegment}`;\n  } */\n}\n\nmodule.exports = S3FileManager;\n"
  },
  {
    "path": "server/api/hooks/file-manager/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * file-manager hook\n *\n * @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,\n *                 and/or initialization logic.\n * @docs        :: https://sailsjs.com/docs/concepts/extending-sails/hooks\n */\n\nconst LocalFileManager = require('./LocalFileManager');\nconst S3FileManager = require('./S3FileManager');\n\nmodule.exports = function defineFileManagerHook(sails) {\n  let instance = null;\n\n  const createInstance = () => {\n    instance = sails.hooks.s3.isEnabled()\n      ? new S3FileManager(sails.hooks.s3.getClient())\n      : new LocalFileManager();\n  };\n\n  return {\n    /**\n     * Runs when this Sails app loads/lifts.\n     */\n\n    async initialize() {\n      sails.log.info('Initializing custom hook (`file-manager`)');\n\n      return new Promise((resolve) => {\n        sails.after('hook:s3:loaded', () => {\n          createInstance();\n          resolve();\n        });\n      });\n    },\n\n    getInstance() {\n      return instance;\n    },\n  };\n};\n"
  },
  {
    "path": "server/api/hooks/oidc/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * oidc hook\n *\n * @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,\n *                 and/or initialization logic.\n * @docs        :: https://sailsjs.com/docs/concepts/extending-sails/hooks\n */\n\nconst openidClient = require('openid-client');\n\nmodule.exports = function defineOidcHook(sails) {\n  let client = null;\n  let clientInitPromise = null;\n\n  return {\n    /**\n     * Runs when this Sails app loads/lifts.\n     */\n    async initialize() {\n      if (!this.isEnabled()) {\n        return;\n      }\n\n      sails.log.info('Initializing custom hook (`oidc`)');\n    },\n\n    async getClient() {\n      if (!this.isEnabled()) {\n        return null;\n      }\n\n      if (client) {\n        return client;\n      }\n\n      if (clientInitPromise) {\n        return clientInitPromise;\n      }\n\n      clientInitPromise = (async () => {\n        sails.log.info('Initializing OIDC client');\n\n        if (sails.config.custom.oidcTimeout !== null) {\n          openidClient.custom.setHttpOptionsDefaults({\n            timeout: sails.config.custom.oidcTimeout,\n          });\n        }\n\n        let issuer;\n        try {\n          issuer = await openidClient.Issuer.discover(sails.config.custom.oidcIssuer);\n        } catch (error) {\n          sails.log.warn(`Error while initializing OIDC client: ${error}`);\n\n          clientInitPromise = null;\n          return null;\n        }\n\n        const metadata = {\n          client_id: sails.config.custom.oidcClientId,\n          client_secret: sails.config.custom.oidcClientSecret,\n          redirect_uris: [sails.config.custom.oidcRedirectUri],\n          response_types: ['code'],\n          userinfo_signed_response_alg: sails.config.custom.oidcUserinfoSignedResponseAlg,\n        };\n\n        if (sails.config.custom.oidcIdTokenSignedResponseAlg) {\n          metadata.id_token_signed_response_alg = sails.config.custom.oidcIdTokenSignedResponseAlg;\n        }\n\n        client = new issuer.Client(metadata);\n        return client;\n      })();\n\n      return clientInitPromise;\n    },\n\n    async getBootstrap() {\n      const instance = await this.getClient();\n\n      if (!instance) {\n        return null;\n      }\n\n      const authorizationUrlParams = {\n        scope: sails.config.custom.oidcScopes,\n      };\n\n      if (!sails.config.custom.oidcUseDefaultResponseMode) {\n        authorizationUrlParams.response_mode = sails.config.custom.oidcResponseMode;\n      }\n\n      const bootstrap = {\n        authorizationUrl: instance.authorizationUrl(authorizationUrlParams),\n        endSessionUrl: instance.issuer.end_session_endpoint ? instance.endSessionUrl({}) : null,\n        isEnforced: sails.config.custom.oidcEnforced,\n      };\n      if (sails.config.custom.oidcDebug) {\n        bootstrap.debug = true;\n      }\n\n      return bootstrap;\n    },\n\n    isEnabled() {\n      return !!sails.config.custom.oidcIssuer;\n    },\n  };\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/helpers.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst makeWhereQueryBuilder = (Model) => (criteria) => {\n  if (_.isPlainObject(criteria)) {\n    if (Object.keys(criteria).length === 0) {\n      throw new Error('Empty criteria');\n    }\n\n    const parts = [];\n    const values = [];\n\n    // eslint-disable-next-line no-restricted-syntax\n    for (const [key, value] of Object.entries(criteria)) {\n      // eslint-disable-next-line no-underscore-dangle\n      const columnName = Model._transformer._transformations[key];\n\n      if (!columnName) {\n        throw new Error('Unknown column');\n      }\n\n      values.push(value);\n      parts.push(`${columnName} = $${values.length}`);\n    }\n\n    return [parts.join(' AND '), values];\n  }\n\n  return ['id = $1', [criteria]];\n};\n\nconst makeRowToModelTransformer = (Model) => {\n  // eslint-disable-next-line no-underscore-dangle\n  const transformations = _.invert(Model._transformer._transformations);\n\n  return (row) => _.mapKeys(row, (_, key) => transformations[key]);\n};\n\nmodule.exports = {\n  makeWhereQueryBuilder,\n  makeRowToModelTransformer,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * query-methods hook\n *\n * @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,\n *                 and/or initialization logic.\n * @docs        :: https://sailsjs.com/docs/concepts/extending-sails/hooks\n */\n\nconst fs = require('fs');\nconst path = require('path');\n\nmodule.exports = function defineQueryMethodsHook(sails) {\n  const addQueryMethods = () => {\n    const queryMethodsByModelName = fs.readdirSync(path.join(__dirname, 'models')).reduce(\n      (result, filename) => ({\n        ...result,\n        // eslint-disable-next-line global-require, import/no-dynamic-require\n        [path.parse(filename).name]: require(path.join(__dirname, 'models', filename)),\n      }),\n      {},\n    );\n\n    _(sails.models).forEach((Model) => {\n      const queryMethods = queryMethodsByModelName[Model.globalId];\n\n      if (queryMethods) {\n        Object.assign(Model, {\n          qm: queryMethods,\n        });\n      }\n    });\n  };\n\n  return {\n    /**\n     * Runs when this Sails app loads/lifts.\n     */\n\n    async initialize() {\n      sails.log.info('Initializing custom hook (`query-methods`)');\n\n      return new Promise((resolve) => {\n        sails.after('hook:orm:loaded', () => {\n          addQueryMethods();\n          resolve();\n        });\n      });\n    },\n  };\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Action.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst LIMIT = 50;\n\n/* Query methods */\n\nconst create = (arrayOfValues) => Action.createEach(arrayOfValues).fetch();\n\nconst createOne = (values) => Action.create({ ...values }).fetch();\n\nconst getByBoardId = (boardId, { beforeId } = {}) => {\n  const criteria = {\n    boardId,\n  };\n\n  if (beforeId) {\n    criteria.id = {\n      '<': beforeId,\n    };\n  }\n\n  return Action.find(criteria).sort('id DESC').limit(LIMIT);\n};\n\nconst getByCardId = (cardId, { beforeId } = {}) => {\n  const criteria = {\n    cardId,\n  };\n\n  if (beforeId) {\n    criteria.id = {\n      '<': beforeId,\n    };\n  }\n\n  return Action.find(criteria).sort('id DESC').limit(LIMIT);\n};\n\nconst update = (criteria, values) => Action.update(criteria).set(values).fetch();\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => Action.destroy(criteria).fetch();\n\nmodule.exports = {\n  create,\n  createOne,\n  getByBoardId,\n  getByCardId,\n  update,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Attachment.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n// TODO: refactor?\n\nconst defaultFind = (criteria) => Attachment.find(criteria).sort('id');\n\n/* Query methods */\n\nconst create = (arrayOfValues) => {\n  const arrayOfFileValues = arrayOfValues.filter(({ type }) => type === Attachment.Types.FILE);\n\n  if (arrayOfFileValues.length > 0) {\n    const arrayOfValuesByUploadedFileId = _.groupBy(arrayOfFileValues, 'data.uploadedFileId');\n    const uploadedFileIds = Object.keys(arrayOfValuesByUploadedFileId);\n\n    const uploadedFileIdsByTotal = Object.entries(arrayOfValuesByUploadedFileId).reduce(\n      (result, [uploadedFileId, arrayOfValuesItem]) => ({\n        ...result,\n        [arrayOfValuesItem.length]: [...(result[arrayOfValuesItem.length] || []), uploadedFileId],\n      }),\n      {},\n    );\n\n    return sails.getDatastore().transaction(async (db) => {\n      const queryValues = [];\n      let query = `UPDATE uploaded_file SET references_total = references_total + CASE `;\n\n      Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIdsItem]) => {\n        const inValues = uploadedFileIdsItem.map((uploadedFileId) => {\n          queryValues.push(uploadedFileId);\n          return `$${queryValues.length}`;\n        });\n\n        queryValues.push(total);\n        query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;\n      });\n\n      const inValues = uploadedFileIds.map((uploadedFileId) => {\n        queryValues.push(uploadedFileId);\n        return `$${queryValues.length}`;\n      });\n\n      queryValues.push(new Date().toISOString());\n      query += `END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND references_total IS NOT NULL RETURNING id`;\n\n      const queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);\n      const nextUploadedFileIds = sails.helpers.utils.mapRecords(queryResult.rows);\n\n      if (nextUploadedFileIds.length < uploadedFileIds.length) {\n        const nextUploadedFileIdsSet = new Set(nextUploadedFileIds);\n\n        // eslint-disable-next-line no-param-reassign\n        arrayOfValues = arrayOfValues.filter(\n          (values) =>\n            values.type !== Attachment.Types.FILE ||\n            nextUploadedFileIdsSet.has(values.data.uploadedFileId),\n        );\n      }\n\n      return Attachment.createEach(arrayOfValues).fetch().usingConnection(db);\n    });\n  }\n\n  return Attachment.createEach(arrayOfValues).fetch();\n};\n\nconst createOne = (values) => {\n  if (values.type === Attachment.Types.FILE) {\n    return sails.getDatastore().transaction(async (db) => {\n      const attachment = await Attachment.create({ ...values })\n        .fetch()\n        .usingConnection(db);\n\n      const queryResult = await sails\n        .sendNativeQuery(\n          'UPDATE uploaded_file SET references_total = references_total + 1, updated_at = $1 WHERE id = $2 AND references_total IS NOT NULL',\n          [new Date().toISOString(), values.data.uploadedFileId],\n        )\n        .usingConnection(db);\n\n      if (queryResult.rowCount === 0) {\n        throw 'uploadedFileNotFound';\n      }\n\n      return attachment;\n    });\n  }\n\n  return Attachment.create({ ...values }).fetch();\n};\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByCardId = (cardId) =>\n  defaultFind({\n    cardId,\n  });\n\nconst getByCardIds = (cardIds) =>\n  defaultFind({\n    cardId: cardIds,\n  });\n\nconst getOneById = (id, { cardId } = {}) => {\n  const criteria = {\n    id,\n  };\n\n  if (cardId) {\n    criteria.cardId = cardId;\n  }\n\n  return Attachment.findOne(criteria);\n};\n\nconst update = (criteria, values) => Attachment.update(criteria).set(values).fetch();\n\nconst updateOne = (criteria, values) => Attachment.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) =>\n  sails.getDatastore().transaction(async (db) => {\n    const attachments = await Attachment.destroy(criteria).fetch().usingConnection(db);\n    const fileAttachments = attachments.filter(({ type }) => type === Attachment.Types.FILE);\n\n    let uploadedFiles = [];\n    if (fileAttachments.length > 0) {\n      const attachmentsByUploadedFileId = _.groupBy(fileAttachments, 'data.uploadedFileId');\n\n      const uploadedFileIdsByTotal = Object.entries(attachmentsByUploadedFileId).reduce(\n        (result, [uploadedFileId, attachmentsItem]) => ({\n          ...result,\n          [attachmentsItem.length]: [...(result[attachmentsItem.length] || []), uploadedFileId],\n        }),\n        {},\n      );\n\n      const queryValues = [];\n      let query = 'UPDATE uploaded_file SET references_total = CASE WHEN references_total = CASE ';\n\n      Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {\n        const inValues = uploadedFileIds.map((uploadedFileId) => {\n          queryValues.push(uploadedFileId);\n          return `$${queryValues.length}`;\n        });\n\n        queryValues.push(total);\n        query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;\n      });\n\n      query += 'END THEN NULL ELSE references_total - CASE ';\n\n      Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {\n        const inValues = uploadedFileIds.map((uploadedFileId) => {\n          queryValues.push(uploadedFileId);\n          return `$${queryValues.length}`;\n        });\n\n        queryValues.push(total);\n        query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;\n      });\n\n      const inValues = Object.keys(attachmentsByUploadedFileId).map((uploadedFileId) => {\n        queryValues.push(uploadedFileId);\n        return `$${queryValues.length}`;\n      });\n\n      queryValues.push(new Date().toISOString());\n      query += `END END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND references_total IS NOT NULL RETURNING *`;\n\n      const queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);\n      uploadedFiles = queryResult.rows.map((row) => UploadedFile.qm.transformRowToModel(row));\n    }\n\n    return { attachments, uploadedFiles };\n  });\n\nconst deleteOne = (criteria) =>\n  sails.getDatastore().transaction(async (db) => {\n    const attachment = await Attachment.destroyOne(criteria).usingConnection(db);\n\n    let uploadedFile;\n    if (attachment.type === Attachment.Types.FILE) {\n      const queryResult = await sails\n        .sendNativeQuery(\n          'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',\n          [new Date().toISOString(), attachment.data.uploadedFileId],\n        )\n        .usingConnection(db);\n\n      uploadedFile = UploadedFile.qm.transformRowToModel(queryResult.rows[0]);\n    }\n\n    return { attachment, uploadedFile };\n  });\n\nmodule.exports = {\n  create,\n  createOne,\n  getByIds,\n  getByCardId,\n  getByCardIds,\n  getOneById,\n  update,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/BackgroundImage.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria) => BackgroundImage.find(criteria).sort('id');\n\n/* Query methods */\n\nconst createOne = (values) =>\n  sails.getDatastore().transaction(async (db) => {\n    const backgroundImage = await BackgroundImage.create({ ...values })\n      .fetch()\n      .usingConnection(db);\n\n    const queryResult = await sails\n      .sendNativeQuery(\n        'UPDATE uploaded_file SET references_total = references_total + 1, updated_at = $1 WHERE id = $2 AND references_total IS NOT NULL',\n        [new Date().toISOString(), values.uploadedFileId],\n      )\n      .usingConnection(db);\n\n    if (queryResult.rowCount === 0) {\n      throw 'uploadedFileNotFound';\n    }\n\n    return backgroundImage;\n  });\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByProjectId = (projectId) =>\n  defaultFind({\n    projectId,\n  });\n\nconst getByProjectIds = (projectIds) =>\n  defaultFind({\n    projectId: projectIds,\n  });\n\nconst getOneById = (id, { projectId } = {}) => {\n  const criteria = {\n    id,\n  };\n\n  if (projectId) {\n    criteria.projectId = projectId;\n  }\n\n  return BackgroundImage.findOne(criteria);\n};\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) =>\n  sails.getDatastore().transaction(async (db) => {\n    const backgroundImages = await BackgroundImage.destroy(criteria).fetch().usingConnection(db);\n\n    let uploadedFiles = [];\n    if (backgroundImages.length > 0) {\n      const backgroundImagesByUploadedFileId = _.groupBy(backgroundImages, 'uploadedFileId');\n\n      const uploadedFileIdsByTotal = Object.entries(backgroundImagesByUploadedFileId).reduce(\n        (result, [uploadedFileId, backgroundImagesItem]) => ({\n          ...result,\n          [backgroundImagesItem.length]: [\n            ...(result[backgroundImagesItem.length] || []),\n            uploadedFileId,\n          ],\n        }),\n        {},\n      );\n\n      const queryValues = [];\n      let query = 'UPDATE uploaded_file SET references_total = CASE WHEN references_total = CASE ';\n\n      Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {\n        const inValues = uploadedFileIds.map((uploadedFileId) => {\n          queryValues.push(uploadedFileId);\n          return `$${queryValues.length}`;\n        });\n\n        queryValues.push(total);\n        query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;\n      });\n\n      query += 'END THEN NULL ELSE references_total - CASE ';\n\n      Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {\n        const inValues = uploadedFileIds.map((uploadedFileId) => {\n          queryValues.push(uploadedFileId);\n          return `$${queryValues.length}`;\n        });\n\n        queryValues.push(total);\n        query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;\n      });\n\n      const inValues = Object.keys(backgroundImagesByUploadedFileId).map((uploadedFileId) => {\n        queryValues.push(uploadedFileId);\n        return `$${queryValues.length}`;\n      });\n\n      queryValues.push(new Date().toISOString());\n      query += `END END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND references_total IS NOT NULL RETURNING *`;\n\n      const queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);\n      uploadedFiles = queryResult.rows.map((row) => UploadedFile.qm.transformRowToModel(row));\n    }\n\n    return { backgroundImages, uploadedFiles };\n  });\n\nconst deleteOne = (criteria) =>\n  sails.getDatastore().transaction(async (db) => {\n    const backgroundImage = await BackgroundImage.destroyOne(criteria).usingConnection(db);\n\n    const queryResult = await sails\n      .sendNativeQuery(\n        'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',\n        [new Date().toISOString(), backgroundImage.uploadedFileId],\n      )\n      .usingConnection(db);\n\n    const uploadedFile = UploadedFile.qm.transformRowToModel(queryResult.rows[0]);\n\n    return { backgroundImage, uploadedFile };\n  });\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getByProjectId,\n  getByProjectIds,\n  getOneById,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/BaseCustomFieldGroup.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria) => BaseCustomFieldGroup.find(criteria).sort('id');\n\n/* Query methods */\n\nconst createOne = (values) => BaseCustomFieldGroup.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByProjectId = (projectId) =>\n  defaultFind({\n    projectId,\n  });\n\nconst getByProjectIds = (projectIds) =>\n  defaultFind({\n    projectId: projectIds,\n  });\n\nconst getOneById = (id, { projectId } = {}) => {\n  const criteria = {\n    id,\n  };\n\n  if (projectId) {\n    criteria.projectId = projectId;\n  }\n\n  return BaseCustomFieldGroup.findOne(criteria);\n};\n\nconst updateOne = (criteria, values) => BaseCustomFieldGroup.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => BaseCustomFieldGroup.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => BaseCustomFieldGroup.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getByProjectId,\n  getByProjectIds,\n  getOneById,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria, { sort = 'id' } = {}) => Board.find(criteria).sort(sort);\n\n/* Query methods */\n\nconst createOne = (values, { user } = {}) =>\n  sails.getDatastore().transaction(async (db) => {\n    const board = await Board.create({ ...values })\n      .fetch()\n      .usingConnection(db);\n\n    const boardMembership = await BoardMembership.create({\n      projectId: board.projectId,\n      boardId: board.id,\n      userId: user.id,\n      role: BoardMembership.Roles.EDITOR,\n    })\n      .fetch()\n      .usingConnection(db);\n\n    const lists = await List.createEach(\n      [List.Types.ARCHIVE, List.Types.TRASH].map((type) => ({\n        type,\n        boardId: board.id,\n      })),\n    )\n      .fetch()\n      .usingConnection(db);\n\n    return { board, boardMembership, lists };\n  });\n\nconst getByIds = (ids, { exceptProjectIdOrIds } = {}) => {\n  const criteria = {\n    id: ids,\n  };\n\n  if (exceptProjectIdOrIds) {\n    criteria.projectId = {\n      '!=': exceptProjectIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria);\n};\n\nconst getByProjectId = (projectId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {\n  const criteria = {\n    projectId,\n  };\n\n  if (exceptIdOrIds) {\n    criteria.id = {\n      '!=': exceptIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria, { sort });\n};\n\nconst getByProjectIds = (projectIds, { sort = ['position', 'id'] } = {}) =>\n  defaultFind(\n    {\n      projectId: projectIds,\n    },\n    { sort },\n  );\n\nconst getOneById = (id) => Board.findOne(id);\n\nconst updateOne = (criteria, values) => Board.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => Board.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => Board.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getByProjectId,\n  getByProjectIds,\n  getOneById,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/BoardMembership.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria) => BoardMembership.find(criteria).sort('id');\n\n/* Query methods */\n\nconst createOne = (values) => BoardMembership.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByProjectId = (projectId) =>\n  defaultFind({\n    projectId,\n  });\n\nconst getByProjectIdAndUserId = (projectId, userId) =>\n  defaultFind({\n    projectId,\n    userId,\n  });\n\nconst getByProjectIds = (projectIds) =>\n  defaultFind({\n    projectId: projectIds,\n  });\n\nconst getByBoardId = (boardId) =>\n  defaultFind({\n    boardId,\n  });\n\nconst getByBoardIds = (boardIds, { exceptUserIdOrIds } = {}) => {\n  const criteria = {\n    boardId: boardIds,\n  };\n\n  if (exceptUserIdOrIds) {\n    criteria.userId = {\n      '!=': exceptUserIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria);\n};\n\nconst getByBoardIdsAndUserId = (boardIds, userId) =>\n  defaultFind({\n    userId,\n    boardId: boardIds,\n  });\n\nconst getByUserId = (userId, { exceptProjectIdOrIds } = {}) => {\n  const criteria = {\n    userId,\n  };\n\n  if (exceptProjectIdOrIds) {\n    criteria.projectId = {\n      '!=': exceptProjectIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria);\n};\n\nconst getOneById = (id) => BoardMembership.findOne(id);\n\nconst getOneByBoardIdAndUserId = (boardId, userId) =>\n  BoardMembership.findOne({\n    boardId,\n    userId,\n  });\n\nconst updateOne = async (criteria, values) =>\n  BoardMembership.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => BoardMembership.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => BoardMembership.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getByProjectId,\n  getByProjectIdAndUserId,\n  getByProjectIds,\n  getByBoardId,\n  getByBoardIds,\n  getByBoardIdsAndUserId,\n  getByUserId,\n  getOneById,\n  getOneByBoardIdAndUserId,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/BoardSubscription.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria) => BoardSubscription.find(criteria).sort('id');\n\n/* Query methods */\n\nconst createOne = (values) => BoardSubscription.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByBoardId = (boardId, { exceptUserIdOrIds } = {}) => {\n  const criteria = {\n    boardId,\n  };\n\n  if (exceptUserIdOrIds) {\n    criteria.userId = {\n      '!=': exceptUserIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria);\n};\n\nconst getOneByBoardIdAndUserId = (boardId, userId) =>\n  BoardSubscription.findOne({\n    boardId,\n    userId,\n  });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => BoardSubscription.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => BoardSubscription.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getByBoardId,\n  getOneByBoardIdAndUserId,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Card.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst buildSearchParts = require('../../../../utils/build-query-parts');\nconst { makeRowToModelTransformer } = require('../helpers');\n\nconst LIMIT = 50;\n\nconst transformRowToModel = makeRowToModelTransformer(Card);\n\nconst defaultFind = (criteria, { sort = 'id', limit } = {}) =>\n  Card.find(criteria).sort(sort).limit(limit);\n\n/* Query methods */\n\nconst createOne = (values) => Card.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByBoardId = (boardId) =>\n  defaultFind({\n    boardId,\n  });\n\nconst getByListId = async (listId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {\n  const criteria = {\n    listId,\n  };\n\n  if (exceptIdOrIds) {\n    criteria.id = {\n      '!=': exceptIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria, { sort });\n};\n\nconst getByEndlessListId = async (listId, { before, search, userIds, labelIds }) => {\n  if (search || userIds || labelIds) {\n    if (userIds && userIds.length === 0) {\n      return [];\n    }\n\n    if (labelIds && labelIds.length === 0) {\n      return [];\n    }\n\n    const queryValues = [];\n    let query = 'SELECT DISTINCT card.* FROM card';\n\n    if (userIds) {\n      query += ' LEFT JOIN card_membership ON card.id = card_membership.card_id';\n      query += ' LEFT JOIN task_list ON card.id = task_list.card_id';\n      query += ' LEFT JOIN task ON task_list.id = task.task_list_id';\n    }\n\n    if (labelIds) {\n      query += ' LEFT JOIN card_label ON card.id = card_label.card_id';\n    }\n\n    queryValues.push(listId);\n    query += ` WHERE card.list_id = $${queryValues.length}`;\n\n    if (before) {\n      queryValues.push(before.listChangedAt);\n      query += ` AND (card.list_changed_at < $${queryValues.length} OR (card.list_changed_at = $${queryValues.length}`;\n\n      queryValues.push(before.id);\n      query += ` AND card.id < $${queryValues.length}))`;\n    }\n\n    if (search) {\n      if (search.startsWith('/')) {\n        queryValues.push(search.substring(1));\n        query += ` AND (card.name ~* $${queryValues.length} OR card.description ~* $${queryValues.length})`;\n      } else {\n        const searchParts = buildSearchParts(search);\n\n        if (searchParts.length > 0) {\n          const ilikeValues = searchParts.map((searchPart) => {\n            queryValues.push(searchPart);\n            return `'%' || $${queryValues.length} || '%'`;\n          });\n\n          query += ` AND ((card.name ILIKE ALL(ARRAY[${ilikeValues.join(', ')}])) OR (card.description ILIKE ALL(ARRAY[${ilikeValues.join(', ')}])))`;\n        }\n      }\n    }\n\n    if (userIds) {\n      const inValues = userIds.map((userId) => {\n        queryValues.push(userId);\n        return `$${queryValues.length}`;\n      });\n\n      query += ` AND (card_membership.user_id IN (${inValues.join(', ')}) OR task.assignee_user_id IN (${inValues.join(', ')}))`;\n    }\n\n    if (labelIds) {\n      const inValues = labelIds.map((labelId) => {\n        queryValues.push(labelId);\n        return `$${queryValues.length}`;\n      });\n\n      query += ` AND card_label.label_id IN (${inValues.join(', ')})`;\n    }\n\n    query += ` LIMIT ${LIMIT}`;\n\n    let queryResult;\n    try {\n      queryResult = await sails.sendNativeQuery(query, queryValues);\n    } catch (error) {\n      if (\n        error.code === 'E_QUERY_FAILED' &&\n        error.message.includes('Query failed: invalid regular expression')\n      ) {\n        return [];\n      }\n\n      throw error;\n    }\n\n    return queryResult.rows.map((row) => transformRowToModel(row));\n  }\n\n  const criteria = {\n    and: [{ listId }],\n  };\n\n  if (before) {\n    criteria.and.push({\n      or: [\n        {\n          listChangedAt: {\n            '<': before.listChangedAt,\n          },\n        },\n        {\n          listChangedAt: before.listChangedAt,\n          id: {\n            '<': before.id,\n          },\n        },\n      ],\n    });\n  }\n\n  return defaultFind(criteria, {\n    sort: ['listChangedAt DESC', 'id DESC'],\n    limit: LIMIT,\n  });\n};\n\nconst getByListIds = async (listIds, { sort = ['position', 'id'] } = {}) =>\n  defaultFind(\n    {\n      listId: listIds,\n    },\n    { sort },\n  );\n\nconst getOneById = (id, { listId } = {}) => {\n  const criteria = {\n    id,\n  };\n\n  if (listId) {\n    criteria.listId = listId;\n  }\n\n  return Card.findOne(criteria);\n};\n\nconst update = async (criteria, values) => {\n  if (!_.isUndefined(values.isClosed)) {\n    return sails.getDatastore().transaction(async (db) => {\n      const cards = await Card.update(criteria).set(values).fetch().usingConnection(db);\n\n      let tasks = [];\n      if (!_.isUndefined(values.isClosed)) {\n        tasks = await Task.update({\n          linkedCardId: sails.helpers.utils.mapRecords(cards),\n        })\n          .set({\n            isCompleted: values.isClosed,\n          })\n          .fetch()\n          .usingConnection(db);\n      }\n\n      return { cards, tasks };\n    });\n  }\n\n  const cards = await Card.update(criteria).set(values).fetch();\n  return { cards };\n};\n\nconst updateOne = async (criteria, values) => {\n  if (!_.isUndefined(values.isClosed)) {\n    return sails.getDatastore().transaction(async (db) => {\n      const card = await Card.updateOne(criteria)\n        .set({ ...values })\n        .usingConnection(db);\n\n      let tasks;\n      if (!_.isUndefined(values.isClosed) && card) {\n        tasks = await Task.update({\n          linkedCardId: card.id,\n        })\n          .set({\n            isCompleted: card.isClosed,\n          })\n          .fetch()\n          .usingConnection(db);\n      }\n\n      return { card, tasks };\n    });\n  }\n\n  const card = await Card.updateOne(criteria).set({ ...values });\n  return { card };\n};\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => Card.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => Card.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getByBoardId,\n  getByListId,\n  getByEndlessListId,\n  getByListIds,\n  getOneById,\n  update,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/CardLabel.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria) => CardLabel.find(criteria).sort('id');\n\n/* Query methods */\n\nconst create = (arrayOfValues) => CardLabel.createEach(arrayOfValues).fetch();\n\nconst createOne = (values) => CardLabel.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByCardId = (cardId) =>\n  defaultFind({\n    cardId,\n  });\n\nconst getByCardIds = (cardIds) =>\n  defaultFind({\n    cardId: cardIds,\n  });\n\nconst getOneByCardIdAndLabelId = (cardId, labelId) =>\n  CardLabel.findOne({\n    cardId,\n    labelId,\n  });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => CardLabel.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => CardLabel.destroyOne(criteria);\n\nmodule.exports = {\n  create,\n  createOne,\n  getByIds,\n  getByCardId,\n  getByCardIds,\n  getOneByCardIdAndLabelId,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/CardMembership.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria) => CardMembership.find(criteria).sort('id');\n\n/* Query methods */\n\nconst create = (arrayOfValues) => CardMembership.createEach(arrayOfValues).fetch();\n\nconst createOne = (values) => CardMembership.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByCardId = (cardId, { userIdOrIds } = {}) => {\n  const criteria = {\n    cardId,\n  };\n  if (userIdOrIds) {\n    criteria.userId = userIdOrIds;\n  }\n\n  return defaultFind(criteria);\n};\n\nconst getByCardIds = (cardIds) =>\n  defaultFind({\n    cardId: cardIds,\n  });\n\nconst getOneByCardIdAndUserId = (cardId, userId) =>\n  CardMembership.findOne({\n    cardId,\n    userId,\n  });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => CardMembership.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => CardMembership.destroyOne(criteria);\n\nmodule.exports = {\n  create,\n  createOne,\n  getByIds,\n  getByCardId,\n  getByCardIds,\n  getOneByCardIdAndUserId,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/CardSubscription.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria) => CardSubscription.find(criteria).sort('id');\n\n/* Query methods */\n\nconst createOne = (values) => CardSubscription.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByCardId = (cardId, { exceptUserIdOrIds } = {}) => {\n  const criteria = {\n    cardId,\n  };\n\n  if (exceptUserIdOrIds) {\n    criteria.userId = {\n      '!=': exceptUserIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria);\n};\n\nconst getByCardIdsAndUserId = (cardIds, userId) =>\n  defaultFind({\n    userId,\n    cardId: cardIds,\n  });\n\nconst getOneByCardIdAndUserId = (cardId, userId) =>\n  CardSubscription.findOne({\n    cardId,\n    userId,\n  });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => CardSubscription.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => CardSubscription.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getByCardId,\n  getByCardIdsAndUserId,\n  getOneByCardIdAndUserId,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Comment.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst LIMIT = 50;\n\nconst defaultFind = (criteria, { limit } = {}) =>\n  Comment.find(criteria).sort('id DESC').limit(limit);\n\n/* Query methods */\n\nconst createOne = (values) =>\n  sails.getDatastore().transaction(async (db) => {\n    const comment = await Comment.create({ ...values })\n      .fetch()\n      .usingConnection(db);\n\n    const queryResult = await sails\n      .sendNativeQuery(\n        'UPDATE card SET comments_total = comments_total + 1, updated_at = $1 WHERE id = $2',\n        [new Date().toISOString(), comment.cardId],\n      )\n      .usingConnection(db);\n\n    if (queryResult.rowCount === 0) {\n      throw 'cardNotFound';\n    }\n\n    return comment;\n  });\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByCardId = (cardId, { beforeId } = {}) => {\n  const criteria = {\n    cardId,\n  };\n\n  if (beforeId) {\n    criteria.id = {\n      '<': beforeId,\n    };\n  }\n\n  return defaultFind(criteria, { limit: LIMIT });\n};\n\nconst getOneById = (id) => Comment.findOne(id);\n\nconst update = (criteria, values) => Comment.update(criteria).set(values).fetch();\n\nconst updateOne = (criteria, values) => Comment.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) =>\n  sails.getDatastore().transaction(async (db) => {\n    const comments = await Comment.destroy(criteria).fetch().usingConnection(db);\n\n    if (comments.length > 0) {\n      const commentsByCardId = _.groupBy(comments, 'cardId');\n\n      const cardIdsByTotal = Object.entries(commentsByCardId).reduce(\n        (result, [cardId, commentsItem]) => ({\n          ...result,\n          [commentsItem.length]: [...(result[commentsItem.length] || []), cardId],\n        }),\n        {},\n      );\n\n      const queryValues = [];\n      let query = 'UPDATE card SET comments_total = comments_total - CASE ';\n\n      Object.entries(cardIdsByTotal).forEach(([total, cardIds]) => {\n        const inValues = cardIds.map((cardId) => {\n          queryValues.push(cardId);\n          return `$${queryValues.length}`;\n        });\n\n        queryValues.push(total);\n        query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;\n      });\n\n      const inValues = Object.keys(commentsByCardId).map((cardId) => {\n        queryValues.push(cardId);\n        return `$${queryValues.length}`;\n      });\n\n      queryValues.push(new Date().toISOString());\n      query += `END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')})`;\n\n      await sails.sendNativeQuery(query, queryValues).usingConnection(db);\n    }\n\n    return comments;\n  });\n\nconst deleteOne = (criteria) =>\n  sails.getDatastore().transaction(async (db) => {\n    const comment = await Comment.destroyOne(criteria).usingConnection(db);\n\n    const queryResult = await sails\n      .sendNativeQuery(\n        'UPDATE card SET comments_total = comments_total - 1, updated_at = $1 WHERE id = $2',\n        [new Date().toISOString(), comment.cardId],\n      )\n      .usingConnection(db);\n\n    if (queryResult.rowCount === 0) {\n      throw 'cardNotFound';\n    }\n\n    return comment;\n  });\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getByCardId,\n  getOneById,\n  update,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Config.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/* Query methods */\n\nconst getOneMain = () => Config.findOne(Config.MAIN_ID);\n\nconst updateOneMain = (values) => Config.updateOne(Config.MAIN_ID).set({ ...values });\n\nmodule.exports = {\n  getOneMain,\n  updateOneMain,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/CustomField.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria, { exceptIdOrIds, sort = 'id' } = {}) => {\n  if (exceptIdOrIds) {\n    // eslint-disable-next-line no-param-reassign\n    criteria.id = {\n      '!=': exceptIdOrIds,\n    };\n  }\n\n  return CustomField.find(criteria).sort(sort);\n};\n\n/* Query methods */\n\nconst create = (arrayOfValues) => CustomField.createEach(arrayOfValues).fetch();\n\nconst createOne = (values) => CustomField.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByBaseCustomFieldGroupId = (\n  baseCustomFieldGroupId,\n  { exceptIdOrIds, sort = ['position', 'id'] } = {},\n) =>\n  defaultFind(\n    {\n      baseCustomFieldGroupId,\n    },\n    { exceptIdOrIds, sort },\n  );\n\nconst getByBaseCustomFieldGroupIds = (\n  baseCustomFieldGroupIds,\n  { sort = ['position', 'id'] } = {},\n) =>\n  defaultFind(\n    {\n      baseCustomFieldGroupId: baseCustomFieldGroupIds,\n    },\n    { sort },\n  );\n\nconst getByCustomFieldGroupId = async (\n  customFieldGroupId,\n  { exceptIdOrIds, sort = ['position', 'id'] } = {},\n) =>\n  defaultFind(\n    {\n      customFieldGroupId,\n    },\n    { exceptIdOrIds, sort },\n  );\n\nconst getByCustomFieldGroupIds = async (customFieldGroupIds, { sort = ['position', 'id'] } = {}) =>\n  defaultFind(\n    {\n      customFieldGroupId: customFieldGroupIds,\n    },\n    { sort },\n  );\n\nconst getOneById = (id) => CustomField.findOne(id);\n\nconst updateOne = (criteria, values) => CustomField.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => CustomField.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => CustomField.destroyOne(criteria);\n\nmodule.exports = {\n  create,\n  createOne,\n  getByIds,\n  getByBaseCustomFieldGroupId,\n  getByBaseCustomFieldGroupIds,\n  getByCustomFieldGroupId,\n  getByCustomFieldGroupIds,\n  getOneById,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/CustomFieldGroup.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria, { exceptIdOrIds, sort = 'id' } = {}) => {\n  if (exceptIdOrIds) {\n    // eslint-disable-next-line no-param-reassign\n    criteria.id = {\n      '!=': exceptIdOrIds,\n    };\n  }\n\n  return CustomFieldGroup.find(criteria).sort(sort);\n};\n\n/* Query methods */\n\nconst create = (arrayOfValues) => CustomFieldGroup.createEach(arrayOfValues).fetch();\n\nconst createOne = (values) => CustomFieldGroup.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByBoardId = (boardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) =>\n  defaultFind(\n    {\n      boardId,\n    },\n    { exceptIdOrIds, sort },\n  );\n\nconst getByCardId = (cardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) =>\n  defaultFind(\n    {\n      cardId,\n    },\n    { exceptIdOrIds, sort },\n  );\n\nconst getByCardIds = (cardIds, { sort = ['position', 'id'] } = {}) =>\n  defaultFind(\n    {\n      cardId: cardIds,\n    },\n    { sort },\n  );\n\nconst getOneById = (id) => CustomFieldGroup.findOne(id);\n\nconst update = (criteria, values) => CustomFieldGroup.update(criteria).set(values).fetch();\n\nconst updateOne = (criteria, values) => CustomFieldGroup.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => CustomFieldGroup.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => CustomFieldGroup.destroyOne(criteria);\n\nmodule.exports = {\n  create,\n  createOne,\n  getByBoardId,\n  getByIds,\n  getByCardId,\n  getByCardIds,\n  getOneById,\n  update,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/CustomFieldValue.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { makeRowToModelTransformer } = require('../helpers');\n\nconst transformRowToModel = makeRowToModelTransformer(CustomFieldValue);\n\nconst defaultFind = (criteria, { customFieldGroupIdOrIds } = {}) => {\n  if (customFieldGroupIdOrIds) {\n    criteria.customFieldGroupId = customFieldGroupIdOrIds; // eslint-disable-line no-param-reassign\n  }\n\n  return CustomFieldValue.find(criteria).sort('id');\n};\n\n/* Query methods */\n\nconst create = (arrayOfValues) => CustomFieldValue.createEach(arrayOfValues).fetch();\n\nconst createOrUpdateOne = async (values) => {\n  const query = `\n    INSERT INTO custom_field_value (card_id, custom_field_group_id, custom_field_id, content, created_at)\n    VALUES ($1, $2, $3, $4, $5)\n    ON CONFLICT (card_id, custom_field_group_id, custom_field_id)\n    DO UPDATE SET content = EXCLUDED.content, updated_at = EXCLUDED.created_at\n    RETURNING *\n  `;\n\n  const queryResult = await sails.sendNativeQuery(query, [\n    values.cardId,\n    values.customFieldGroupId,\n    values.customFieldId,\n    values.content,\n    new Date().toISOString(),\n  ]);\n\n  return transformRowToModel(queryResult.rows[0]);\n};\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByCardId = (cardId, { customFieldGroupIdOrIds } = {}) =>\n  defaultFind(\n    {\n      cardId,\n    },\n    { customFieldGroupIdOrIds },\n  );\n\nconst getByCardIds = (cardIds, { customFieldGroupIdOrIds } = {}) =>\n  defaultFind(\n    {\n      cardId: cardIds,\n    },\n    { customFieldGroupIdOrIds },\n  );\n\nconst getByCustomFieldGroupId = (customFieldGroupId) =>\n  defaultFind({\n    customFieldGroupId,\n  });\n\nconst getOneByCardIdAndCustomFieldGroupIdAndCustomFieldId = (\n  cardId,\n  customFieldGroupId,\n  customFieldId,\n) =>\n  CustomFieldValue.findOne({\n    cardId,\n    customFieldGroupId,\n    customFieldId,\n  });\n\nconst updateOne = (criteria, values) => CustomFieldValue.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => CustomFieldValue.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => CustomFieldValue.destroyOne(criteria);\n\nmodule.exports = {\n  create,\n  createOrUpdateOne,\n  getByIds,\n  getByCardId,\n  getByCardIds,\n  getByCustomFieldGroupId,\n  getOneByCardIdAndCustomFieldGroupIdAndCustomFieldId,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/IdentityProviderUser.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/* Query methods */\n\nconst createOne = (values) => IdentityProviderUser.create({ ...values }).fetch();\n\nconst getOneByIssuerAndSub = (issuer, sub) =>\n  IdentityProviderUser.findOne({\n    issuer,\n    sub,\n  });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => IdentityProviderUser.destroy(criteria).fetch();\n\nmodule.exports = {\n  createOne,\n  getOneByIssuerAndSub,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/InternalConfig.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { makeRowToModelTransformer } = require('../helpers');\n\nconst transformRowToModel = makeRowToModelTransformer(InternalConfig);\n\n/* Query methods */\n\nconst getOneMain = () => InternalConfig.findOne(InternalConfig.MAIN_ID);\n\nconst updateOneMain = (values) =>\n  sails.getDatastore().transaction(async (db) => {\n    let queryResult = await sails\n      .sendNativeQuery(\n        'SELECT active_users_limit FROM internal_config WHERE id = $1 LIMIT 1 FOR UPDATE',\n        [InternalConfig.MAIN_ID],\n      )\n      .usingConnection(db);\n\n    const prev = transformRowToModel(queryResult.rows[0]);\n\n    const internalConfig = await InternalConfig.updateOne(InternalConfig.MAIN_ID)\n      .set({ ...values })\n      .usingConnection(db);\n\n    let deactivatedUserIds;\n    if (\n      _.isInteger(internalConfig.activeUsersLimit) &&\n      (prev.activeUsersLimit === null || internalConfig.activeUsersLimit < prev.activeUsersLimit)\n    ) {\n      const { defaultAdminEmail } = sails.config.custom;\n\n      const query = `\n        WITH user_to_deactivate AS (\n          SELECT id\n          FROM user_account\n          WHERE is_deactivated = false\n          ORDER BY\n            CASE ${defaultAdminEmail ? 'WHEN email = $1 THEN 0 WHEN role = $2 THEN 1' : 'WHEN role = $1 THEN 0'} ELSE ${defaultAdminEmail ? '2' : '1'} END,\n            id\n          OFFSET $${defaultAdminEmail ? 3 : 2}\n        )\n        UPDATE user_account\n        SET is_deactivated = true\n        WHERE id IN (SELECT id FROM user_to_deactivate)\n        RETURNING id\n      `;\n\n      const queryValues = defaultAdminEmail\n        ? [defaultAdminEmail, User.Roles.ADMIN, internalConfig.activeUsersLimit]\n        : [User.Roles.ADMIN, internalConfig.activeUsersLimit];\n\n      queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);\n      deactivatedUserIds = queryResult.rows.map((row) => row.id);\n    }\n\n    return { internalConfig, deactivatedUserIds, prev };\n  });\n\nmodule.exports = {\n  getOneMain,\n  updateOneMain,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Label.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria, { sort = 'id' } = {}) => Label.find(criteria).sort(sort);\n\n/* Query methods */\n\nconst createOne = (values) => Label.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByBoardId = (boardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {\n  const criteria = {\n    boardId,\n  };\n\n  if (exceptIdOrIds) {\n    criteria.id = {\n      '!=': exceptIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria, { sort });\n};\n\nconst getOneById = (id, { boardId } = {}) => {\n  const criteria = {\n    id,\n  };\n\n  if (boardId) {\n    criteria.boardId = boardId;\n  }\n\n  return Label.findOne(criteria);\n};\n\nconst updateOne = (criteria, values) => Label.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => Label.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => Label.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getByBoardId,\n  getOneById,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/List.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { makeRowToModelTransformer, makeWhereQueryBuilder } = require('../helpers');\n\nconst buildWhereQuery = makeWhereQueryBuilder(List);\nconst transformRowToModel = makeRowToModelTransformer(List);\n\nconst defaultFind = (criteria, { sort = 'id' } = {}) => List.find(criteria).sort(sort);\n\n/* Query methods */\n\nconst createOne = (values) => List.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByBoardId = (boardId, { exceptIdOrIds, typeOrTypes, sort = ['position', 'id'] } = {}) => {\n  const criteria = {\n    boardId,\n  };\n\n  if (exceptIdOrIds) {\n    criteria.id = {\n      '!=': exceptIdOrIds,\n    };\n  }\n\n  if (typeOrTypes) {\n    criteria.type = typeOrTypes;\n  }\n\n  return defaultFind(criteria, { sort });\n};\n\nconst getOneById = (id, { boardId } = {}) => {\n  const criteria = {\n    id,\n  };\n\n  if (boardId) {\n    criteria.boardId = boardId;\n  }\n\n  return List.findOne(criteria);\n};\n\nconst getOneTrashByBoardId = (boardId) =>\n  List.findOne({\n    boardId,\n    type: List.Types.TRASH,\n  });\n\nconst updateOne = async (criteria, values) => {\n  if (values.boardId || values.type) {\n    return sails.getDatastore().transaction(async (db) => {\n      const [whereQuery, whereQueryValues] = buildWhereQuery(criteria);\n\n      const queryResult = await sails\n        .sendNativeQuery(\n          `SELECT board_id, type FROM list WHERE ${whereQuery} LIMIT 1 FOR UPDATE`,\n          whereQueryValues,\n        )\n        .usingConnection(db);\n\n      if (queryResult.rowCount === 0) {\n        return { list: null };\n      }\n\n      const prev = transformRowToModel(queryResult.rows[0]);\n\n      const list = await List.updateOne(criteria)\n        .set({ ...values })\n        .usingConnection(db);\n\n      let tasks = [];\n      if (list) {\n        if (list.boardId !== prev.boardId) {\n          await Card.update({\n            listId: list.id,\n          })\n            .set({\n              boardId: list.boardId,\n            })\n            .usingConnection(db);\n        }\n\n        const prevTypeState = List.TYPE_STATE_BY_TYPE[prev.type];\n        const typeState = List.TYPE_STATE_BY_TYPE[list.type];\n\n        const transitions = {\n          [`${List.TypeStates.CLOSED}->${List.TypeStates.OPENED}`]: false,\n          [`${List.TypeStates.OPENED}->${List.TypeStates.CLOSED}`]: true,\n        };\n\n        const isClosed = transitions[`${prevTypeState}->${typeState}`];\n\n        if (!_.isUndefined(isClosed)) {\n          const cards = await Card.update({\n            listId: list.id,\n          })\n            .set({\n              isClosed,\n            })\n            .fetch()\n            .usingConnection(db);\n\n          if (cards.length > 0) {\n            tasks = await Task.update({\n              linkedCardId: sails.helpers.utils.mapRecords(cards),\n            })\n              .set({\n                isCompleted: isClosed,\n              })\n              .fetch()\n              .usingConnection(db);\n          }\n        }\n      }\n\n      return { list, tasks };\n    });\n  }\n\n  const list = await List.updateOne(criteria).set({ ...values });\n  return { list };\n};\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => List.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => List.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getByBoardId,\n  getOneById,\n  getOneTrashByBoardId,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Notification.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst LIMIT = 100;\n\nconst defaultFind = (criteria) => Notification.find(criteria).sort('id DESC');\n\n/* Query methods */\n\nconst create = (arrayOfValues) =>\n  sails.getDatastore().transaction(async (db) => {\n    const notifications = await Notification.createEach(arrayOfValues).fetch().usingConnection(db);\n    const userIds = sails.helpers.utils.mapRecords(notifications, 'userId', true, true);\n\n    if (userIds.length > 0) {\n      const queryValues = [];\n      const inValues = userIds.map((userId) => {\n        queryValues.push(userId);\n        return `$${queryValues.length}`;\n      });\n\n      queryValues.push(LIMIT);\n\n      const query = `\n        WITH exceeded_notification AS (\n          SELECT id, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY id DESC) AS rank\n          FROM notification\n          WHERE user_id IN (${inValues.join(', ')}) AND is_read = FALSE\n        )\n        UPDATE notification\n        SET is_read = TRUE\n        WHERE id IN (SELECT id FROM exceeded_notification WHERE rank > $${queryValues.length})\n      `;\n\n      await sails.sendNativeQuery(query, queryValues).usingConnection(db);\n    }\n\n    return notifications;\n  });\n\nconst createOne = (values) => {\n  if (values.userId) {\n    return sails.getDatastore().transaction(async (db) => {\n      const notification = await Notification.create({ ...values })\n        .fetch()\n        .usingConnection(db);\n\n      const query = `\n        WITH exceeded_notification AS (\n          SELECT id\n          FROM notification\n          WHERE user_id = $1 AND is_read = FALSE\n          ORDER BY id DESC\n          OFFSET $2\n        )\n        UPDATE notification\n        SET is_read = TRUE\n        WHERE id IN (SELECT id FROM exceeded_notification)\n      `;\n\n      await sails.sendNativeQuery(query, [values.userId, LIMIT]).usingConnection(db);\n\n      return notification;\n    });\n  }\n\n  return Notification.create({ ...values }).fetch();\n};\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getUnreadByUserId = (userId) =>\n  defaultFind({\n    userId,\n    isRead: false,\n  });\n\nconst getOneById = (id, { userId } = {}) => {\n  const criteria = {\n    id,\n  };\n\n  if (userId) {\n    criteria.userId = userId;\n  }\n\n  return Notification.findOne(criteria);\n};\n\nconst update = (criteria, values) => Notification.update(criteria).set(values).fetch();\n\nconst updateOne = (criteria, values) => Notification.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => Notification.destroy(criteria).fetch();\n\nmodule.exports = {\n  create,\n  createOne,\n  getByIds,\n  getUnreadByUserId,\n  getOneById,\n  update,\n  updateOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/NotificationService.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria) => NotificationService.find(criteria).sort('id');\n\n/* Query methods */\n\nconst createOne = (values) => NotificationService.create({ ...values }).fetch();\n\nconst getByUserId = (userId) =>\n  defaultFind({\n    userId,\n  });\n\nconst getByUserIds = (userIds) =>\n  defaultFind({\n    userId: userIds,\n  });\n\nconst getByBoardId = (boardId) =>\n  defaultFind({\n    boardId,\n  });\n\nconst getByBoardIds = (boardIds) =>\n  defaultFind({\n    boardId: boardIds,\n  });\n\nconst getOneById = (id) => NotificationService.findOne(id);\n\nconst updateOne = (criteria, values) => NotificationService.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => NotificationService.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => NotificationService.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByUserId,\n  getByUserIds,\n  getByBoardId,\n  getByBoardIds,\n  getOneById,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Project.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria) => Project.find(criteria).sort('id');\n\n/* Query methods */\n\nconst createOne = (values, { user } = {}) =>\n  sails.getDatastore().transaction(async (db) => {\n    let project = await Project.create({ ...values })\n      .fetch()\n      .usingConnection(db);\n\n    const projectManager = await ProjectManager.create({\n      projectId: project.id,\n      userId: user.id,\n    })\n      .fetch()\n      .usingConnection(db);\n\n    if (values.type === Project.Types.PRIVATE) {\n      project = await Project.updateOne(project.id)\n        .set({\n          ownerProjectManagerId: projectManager.id,\n        })\n        .usingConnection(db);\n    }\n\n    return { project, projectManager };\n  });\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getShared = ({ exceptIdOrIds } = {}) => {\n  const criteria = {\n    ownerProjectManagerId: null,\n  };\n\n  if (exceptIdOrIds) {\n    criteria.id = {\n      '!=': exceptIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria);\n};\n\nconst getOneById = (id) => Project.findOne(id);\n\nconst updateOne = (criteria, values) => Project.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => Project.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => Project.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getShared,\n  getOneById,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/ProjectFavorite.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria) => ProjectFavorite.find(criteria).sort('id');\n\n/* Query methods */\n\nconst createOne = (values) => ProjectFavorite.create({ ...values }).fetch();\n\nconst getByProjectIdsAndUserId = (projectIds, userId) =>\n  defaultFind({\n    userId,\n    projectId: projectIds,\n  });\n\nconst getOneByProjectIdAndUserId = (projectId, userId) =>\n  ProjectFavorite.findOne({\n    projectId,\n    userId,\n  });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => ProjectFavorite.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => ProjectFavorite.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByProjectIdsAndUserId,\n  getOneByProjectIdAndUserId,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/ProjectManager.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria) => ProjectManager.find(criteria).sort('id');\n\n/* Query methods */\n\nconst createOne = (values) => ProjectManager.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByProjectId = (projectId, { exceptIdOrIds } = {}) => {\n  const criteria = {\n    projectId,\n  };\n\n  if (exceptIdOrIds) {\n    criteria.id = {\n      '!=': exceptIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria);\n};\n\nconst getByProjectIds = (projectIds, { exceptUserIdOrIds } = {}) => {\n  const criteria = {\n    projectId: projectIds,\n  };\n\n  if (exceptUserIdOrIds) {\n    criteria.userId = {\n      '!=': exceptUserIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria);\n};\n\nconst getByUserId = (userId) =>\n  defaultFind({\n    userId,\n  });\n\nconst getOneById = (id, { projectId } = {}) => {\n  const criteria = {\n    id,\n  };\n\n  if (projectId) {\n    criteria.projectId = projectId;\n  }\n\n  return ProjectManager.findOne(criteria);\n};\n\nconst getOneByProjectIdAndUserId = (projectId, userId) =>\n  ProjectManager.findOne({\n    projectId,\n    userId,\n  });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => ProjectManager.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => ProjectManager.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getByProjectId,\n  getByProjectIds,\n  getByUserId,\n  getOneById,\n  getOneByProjectIdAndUserId,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Session.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/* Query methods */\n\nconst createOne = (values) => Session.create({ ...values }).fetch();\n\nconst getOneUndeletedByAccessToken = (accessToken) =>\n  Session.findOne({\n    accessToken,\n    deletedAt: null,\n  });\n\nconst getOneUndeletedByPendingToken = (pendingToken) =>\n  Session.findOne({\n    pendingToken,\n    deletedAt: null,\n  });\n\nconst updateOne = (criteria, values) => Session.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => Session.destroy(criteria).fetch();\n\nconst deleteOneById = (id) =>\n  Session.updateOne({\n    id,\n    deletedAt: null,\n  }).set({\n    deletedAt: new Date().toISOString(),\n  });\n\nmodule.exports = {\n  createOne,\n  getOneUndeletedByAccessToken,\n  getOneUndeletedByPendingToken,\n  updateOne,\n  deleteOneById,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/StorageUsage.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/* Query methods */\n\nconst getOneMain = () => StorageUsage.findOne(StorageUsage.MAIN_ID);\n\nmodule.exports = {\n  getOneMain,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Task.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria, { sort = 'id', limit } = {}) =>\n  Task.find(criteria).sort(sort).limit(limit);\n\n/* Query methods */\n\nconst create = (arrayOfValues) => Task.createEach(arrayOfValues).fetch();\n\nconst createOne = (values) => Task.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByTaskListId = async (taskListId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {\n  const criteria = {\n    taskListId,\n  };\n\n  if (exceptIdOrIds) {\n    criteria.id = {\n      '!=': exceptIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria, { sort });\n};\n\nconst getByTaskListIds = async (taskListIds, { sort = ['position', 'id'] } = {}) =>\n  defaultFind(\n    {\n      taskListId: taskListIds,\n    },\n    { sort },\n  );\n\nconst getOneById = (id, { taskListId } = {}) => {\n  const criteria = {\n    id,\n  };\n\n  if (taskListId) {\n    criteria.taskListId = taskListId;\n  }\n\n  return Task.findOne(criteria);\n};\n\nconst update = (criteria, values) => Task.update(criteria).set(values).fetch();\n\nconst updateOne = (criteria, values) => Task.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => Task.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => Task.destroyOne(criteria);\n\nmodule.exports = {\n  create,\n  createOne,\n  getByIds,\n  getByTaskListId,\n  getByTaskListIds,\n  getOneById,\n  update,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/TaskList.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst defaultFind = (criteria, { sort = 'id' } = {}) => TaskList.find(criteria).sort(sort);\n\n/* Query methods */\n\nconst create = (arrayOfValues) => TaskList.createEach(arrayOfValues).fetch();\n\nconst createOne = (values) => TaskList.create({ ...values }).fetch();\n\nconst getByIds = (ids) => defaultFind(ids);\n\nconst getByCardId = (cardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {\n  const criteria = {\n    cardId,\n  };\n\n  if (exceptIdOrIds) {\n    criteria.id = {\n      '!=': exceptIdOrIds,\n    };\n  }\n\n  return defaultFind(criteria, { sort });\n};\n\nconst getByCardIds = (cardIds, { sort = ['position', 'id'] } = {}) =>\n  defaultFind(\n    {\n      cardId: cardIds,\n    },\n    { sort },\n  );\n\nconst getOneById = (id, { cardId } = {}) => {\n  const criteria = {\n    id,\n  };\n\n  if (cardId) {\n    criteria.cardId = cardId;\n  }\n\n  return TaskList.findOne(criteria);\n};\n\nconst updateOne = (criteria, values) => TaskList.updateOne(criteria).set({ ...values });\n\n// eslint-disable-next-line no-underscore-dangle\nconst delete_ = (criteria) => TaskList.destroy(criteria).fetch();\n\nconst deleteOne = (criteria) => TaskList.destroyOne(criteria);\n\nmodule.exports = {\n  create,\n  createOne,\n  getByIds,\n  getByCardId,\n  getByCardIds,\n  getOneById,\n  updateOne,\n  deleteOne,\n  delete: delete_,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/UploadedFile.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { makeRowToModelTransformer } = require('../helpers');\n\nconst COLUMN_NAME_BY_TYPE = {\n  [UploadedFile.Types.USER_AVATAR]: 'user_avatars',\n  [UploadedFile.Types.BACKGROUND_IMAGE]: 'background_images',\n  [UploadedFile.Types.ATTACHMENT]: 'attachments',\n};\n\nconst transformRowToModel = makeRowToModelTransformer(UploadedFile);\n\n/* Query methods */\n\nconst createOne = (values) =>\n  sails.getDatastore().transaction(async (db) => {\n    const uploadedFile = await UploadedFile.create({ ...values })\n      .fetch()\n      .usingConnection(db);\n\n    const columnName = COLUMN_NAME_BY_TYPE[uploadedFile.type];\n\n    await sails\n      .sendNativeQuery(\n        `UPDATE storage_usage SET total = total + $1, ${columnName} = ${columnName} + $1, updated_at = $2 WHERE id = $3`,\n        [uploadedFile.size, new Date().toISOString(), StorageUsage.MAIN_ID],\n      )\n      .usingConnection(db);\n\n    return uploadedFile;\n  });\n\nconst deleteOne = (criteria) =>\n  sails.getDatastore().transaction(async (db) => {\n    const uploadedFile = await UploadedFile.destroyOne(criteria).usingConnection(db);\n    const columnName = COLUMN_NAME_BY_TYPE[uploadedFile.type];\n\n    await sails\n      .sendNativeQuery(\n        `UPDATE storage_usage SET total = total - $1, ${columnName} = ${columnName} - $1, updated_at = $2 WHERE id = $3`,\n        [uploadedFile.size, new Date().toISOString(), StorageUsage.MAIN_ID],\n      )\n      .usingConnection(db);\n\n    return uploadedFile;\n  });\n\nmodule.exports = {\n  createOne,\n  deleteOne,\n\n  transformRowToModel,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/User.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { makeRowToModelTransformer, makeWhereQueryBuilder } = require('../helpers');\n\nconst hasAvatarChanged = (avatar, prevAvatar) => {\n  if (!avatar && !prevAvatar) {\n    return false;\n  }\n\n  if (!avatar || !prevAvatar) {\n    return true;\n  }\n\n  return avatar.uploadedFileId !== prevAvatar.uploadedFileId;\n};\n\nconst buildWhereQuery = makeWhereQueryBuilder(User);\nconst transformRowToModel = makeRowToModelTransformer(User);\n\nconst defaultFind = (criteria) => User.find(criteria).sort('id');\n\n/* Query methods */\n\nconst createOne = async (values) => {\n  const { activeUsersLimit } = await InternalConfig.qm.getOneMain();\n\n  if (activeUsersLimit !== null) {\n    return sails.getDatastore().transaction(async (db) => {\n      const queryResult = await sails\n        .sendNativeQuery('SELECT NULL FROM user_account WHERE is_deactivated = $1 FOR UPDATE', [\n          false,\n        ])\n        .usingConnection(db);\n\n      if (queryResult.rowCount >= activeUsersLimit) {\n        throw 'activeLimitReached';\n      }\n\n      return User.create({ ...values })\n        .fetch()\n        .usingConnection(db);\n    });\n  }\n\n  return User.create({ ...values }).fetch();\n};\n\nconst getByIds = (ids, { withDeactivated = true } = {}) => {\n  const criteria = {\n    id: ids,\n  };\n\n  if (!withDeactivated) {\n    criteria.isDeactivated = false;\n  }\n\n  return defaultFind(criteria);\n};\n\nconst getAll = ({ roleOrRoles, isDeactivated } = {}) =>\n  defaultFind({\n    isDeactivated,\n    role: roleOrRoles,\n  });\n\nconst getOneById = (id, { withDeactivated = true } = {}) => {\n  const criteria = {\n    id,\n  };\n\n  if (!withDeactivated) {\n    criteria.isDeactivated = false;\n  }\n\n  return User.findOne(criteria);\n};\n\nconst getOneByEmail = (email) =>\n  User.findOne({\n    email: email.toLowerCase(),\n  });\n\nconst getOneActiveByEmailOrUsername = (emailOrUsername) => {\n  const fieldName = emailOrUsername.includes('@') ? 'email' : 'username';\n\n  return User.findOne({\n    [fieldName]: emailOrUsername.toLowerCase(),\n    isDeactivated: false,\n  });\n};\n\nconst getOneActiveByApiKeyHash = (apiKeyHash) =>\n  User.findOne({\n    apiKeyHash,\n    isDeactivated: false,\n  });\n\nconst updateOne = async (criteria, values) => {\n  const { activeUsersLimit } = await InternalConfig.qm.getOneMain();\n  const enforceActiveLimit = values.isDeactivated === false && activeUsersLimit !== null;\n\n  if (!_.isUndefined(values.avatar) || enforceActiveLimit) {\n    return sails.getDatastore().transaction(async (db) => {\n      if (enforceActiveLimit) {\n        const queryResult = await sails\n          .sendNativeQuery('SELECT NULL FROM user_account WHERE is_deactivated = $1 FOR UPDATE', [\n            false,\n          ])\n          .usingConnection(db);\n\n        if (queryResult.rowCount >= activeUsersLimit) {\n          throw 'activeLimitReached';\n        }\n      }\n\n      let prev;\n      if (!_.isUndefined(values.avatar)) {\n        const [whereQuery, whereQueryValues] = buildWhereQuery(criteria);\n\n        const queryResult = await sails\n          .sendNativeQuery(\n            `SELECT avatar FROM user_account WHERE ${whereQuery} LIMIT 1 FOR UPDATE`,\n            whereQueryValues,\n          )\n          .usingConnection(db);\n\n        if (queryResult.rowCount === 0) {\n          return { user: null };\n        }\n\n        prev = transformRowToModel(queryResult.rows[0]);\n      }\n\n      const user = await User.updateOne(criteria)\n        .set({ ...values })\n        .usingConnection(db);\n\n      let uploadedFile;\n      if (!_.isUndefined(values.avatar) && hasAvatarChanged(user.avatar, prev.avatar)) {\n        if (prev.avatar) {\n          const queryResult = await sails\n            .sendNativeQuery(\n              'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',\n              [new Date().toISOString(), prev.avatar.uploadedFileId],\n            )\n            .usingConnection(db);\n\n          uploadedFile = UploadedFile.qm.transformRowToModel(queryResult.rows[0]);\n        }\n\n        if (user.avatar) {\n          const queryResult = await sails\n            .sendNativeQuery(\n              'UPDATE uploaded_file SET references_total = references_total + 1, updated_at = $1 WHERE id = $2 AND references_total IS NOT NULL',\n              [new Date().toISOString(), user.avatar.uploadedFileId],\n            )\n            .usingConnection(db);\n\n          if (queryResult.rowCount === 0) {\n            throw 'uploadedFileNotFound';\n          }\n        }\n      }\n\n      return { user, uploadedFile };\n    });\n  }\n\n  const user = await User.updateOne(criteria).set({ ...values });\n  return { user };\n};\n\nconst deleteOne = (criteria) =>\n  sails.getDatastore().transaction(async (db) => {\n    const user = await User.destroyOne(criteria).usingConnection(db);\n\n    let uploadedFile;\n    if (user.avatar) {\n      const queryResult = await sails\n        .sendNativeQuery(\n          'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',\n          [new Date().toISOString(), user.avatar.uploadedFileId],\n        )\n        .usingConnection(db);\n\n      uploadedFile = UploadedFile.qm.transformRowToModel(queryResult.rows[0]);\n    }\n\n    return { user, uploadedFile };\n  });\n\nmodule.exports = {\n  createOne,\n  getByIds,\n  getAll,\n  getOneById,\n  getOneByEmail,\n  getOneActiveByEmailOrUsername,\n  getOneActiveByApiKeyHash,\n  updateOne,\n  deleteOne,\n};\n"
  },
  {
    "path": "server/api/hooks/query-methods/models/Webhook.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/* Query methods */\n\nconst createOne = (values) => Webhook.create({ ...values }).fetch();\n\nconst getAll = () => Webhook.find().sort('id');\n\nconst getOneById = (id) => Webhook.findOne(id);\n\nconst updateOne = (criteria, values) => Webhook.updateOne(criteria).set({ ...values });\n\nconst deleteOne = (criteria) => Webhook.destroyOne(criteria);\n\nmodule.exports = {\n  createOne,\n  getAll,\n  getOneById,\n  updateOne,\n  deleteOne,\n};\n"
  },
  {
    "path": "server/api/hooks/s3/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * s3 hook\n *\n * @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,\n *                 and/or initialization logic.\n * @docs        :: https://sailsjs.com/docs/concepts/extending-sails/hooks\n */\n\nconst { URL } = require('url');\nconst { S3Client } = require('@aws-sdk/client-s3');\n\nmodule.exports = function defineS3Hook(sails) {\n  let client = null;\n\n  return {\n    /**\n     * Runs when this Sails app loads/lifts.\n     */\n\n    async initialize() {\n      if (!this.isEnabled()) {\n        return;\n      }\n\n      sails.log.info('Initializing custom hook (`s3`)');\n\n      client = new S3Client({\n        endpoint: sails.config.custom.s3Endpoint,\n        region: sails.config.custom.s3Region || 'eu-central-1',\n        credentials: {\n          accessKeyId: sails.config.custom.s3AccessKeyId,\n          secretAccessKey: sails.config.custom.s3SecretAccessKey,\n        },\n        forcePathStyle: sails.config.custom.s3ForcePathStyle,\n      });\n    },\n\n    getClient() {\n      return client;\n    },\n\n    getBaseUrl() {\n      if (sails.config.custom.s3Endpoint) {\n        const { protocol, host } = new URL(sails.config.custom.s3Endpoint);\n\n        if (sails.config.custom.s3ForcePathStyle) {\n          return `${protocol}//${host}/${sails.config.custom.s3Bucket}`;\n        }\n\n        return `${protocol}//${sails.config.custom.s3Bucket}.${host}`;\n      }\n\n      if (sails.config.custom.s3ForcePathStyle) {\n        return `https://s3.${sails.config.custom.s3Region}.amazonaws.com/${sails.config.custom.s3Bucket}`;\n      }\n\n      return `https://${sails.config.custom.s3Bucket}.s3.${sails.config.custom.s3Region}.amazonaws.com`;\n    },\n\n    isEnabled() {\n      return !!sails.config.custom.s3Endpoint || !!sails.config.custom.s3Region;\n    },\n  };\n};\n"
  },
  {
    "path": "server/api/hooks/terms/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * terms hook\n *\n * @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,\n *                 and/or initialization logic.\n * @docs        :: https://sailsjs.com/docs/concepts/extending-sails/hooks\n */\n\nconst fsPromises = require('fs').promises;\nconst path = require('path');\nconst crypto = require('crypto');\n\nconst PATH = path.join(sails.config.appPath, 'terms');\nconst TEMPLATE_TYPE = '_template';\n\nconst hashContent = (content) => crypto.createHash('sha256').update(content).digest('hex');\n\nmodule.exports = function defineTermsHook(sails) {\n  let type;\n  let languages;\n  let defaultLanguage;\n  let signature;\n\n  const getLanguages = async () => {\n    const entries = await fsPromises.readdir(path.join(PATH, type), {\n      withFileTypes: true,\n    });\n\n    return entries\n      .filter(\n        (entry) => (entry.isFile() || entry.isSymbolicLink()) && path.extname(entry.name) === '.md',\n      )\n      .map((entry) => path.basename(entry.name, '.md'))\n      .sort();\n  };\n\n  const getContent = (language) =>\n    fsPromises.readFile(path.join(PATH, type, `${language}.md`), 'utf8');\n\n  return {\n    /**\n     * Runs when this Sails app loads/lifts.\n     */\n\n    async initialize() {\n      sails.log.info('Initializing custom hook (`terms`)');\n\n      type = sails.config.custom.termsType;\n\n      try {\n        languages = await getLanguages();\n      } catch (error) {\n        /* empty */\n      }\n\n      if (!languages || languages.length === 0) {\n        sails.log.warn('Custom terms not found, falling back to template');\n\n        type = TEMPLATE_TYPE;\n        languages = await getLanguages();\n      }\n\n      defaultLanguage = languages.includes(sails.config.i18n.defaultLocale)\n        ? sails.config.i18n.defaultLocale\n        : languages[0];\n\n      const content = await getContent(defaultLanguage);\n      signature = hashContent(content);\n    },\n\n    async getPayload(language) {\n      if (!language || !languages.includes(language)) {\n        language = defaultLanguage; // eslint-disable-line no-param-reassign\n      }\n\n      const content = await getContent(language);\n\n      return {\n        language,\n        content,\n        signature,\n      };\n    },\n\n    getLanguages() {\n      return languages;\n    },\n\n    isSignatureValid(value) {\n      return value === signature;\n    },\n  };\n};\n"
  },
  {
    "path": "server/api/hooks/watcher/index.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * watcher hook\n *\n * @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,\n *                 and/or initialization logic.\n * @docs        :: https://sailsjs.com/docs/concepts/extending-sails/hooks\n */\n\nmodule.exports = function defineWatcherHook(sails) {\n  const checkSocketConnectionsToLogout = () => {\n    [...sails.io.sockets.adapter.rooms.keys()].forEach((room) => {\n      if (!room.startsWith('@accessToken:')) {\n        return;\n      }\n\n      const accessToken = room.split(':')[1];\n\n      try {\n        sails.helpers.utils.verifyJwtToken(accessToken);\n      } catch (error) {\n        sails.sockets.broadcast(room, 'logout');\n        sails.sockets.leaveAll(room);\n      }\n    });\n  };\n\n  return {\n    /**\n     * Runs when this Sails app loads/lifts.\n     */\n\n    async initialize() {\n      sails.log.info('Initializing custom hook (`watcher`)');\n\n      setInterval(checkSocketConnectionsToLogout, 60 * 1000);\n    },\n  };\n};\n"
  },
  {
    "path": "server/api/models/.gitkeep",
    "content": ""
  },
  {
    "path": "server/api/models/Action.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Action.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     Action:\n *       type: object\n *       required:\n *         - id\n *         - boardId\n *         - cardId\n *         - userId\n *         - type\n *         - data\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the action\n *           example: \"1357158568008091264\"\n *         boardId:\n *           type: string\n *           nullable: true\n *           description: ID of the board where the action occurred\n *           example: \"1357158568008091265\"\n *         cardId:\n *           type: string\n *           description: ID of the card where the action occurred\n *           example: \"1357158568008091266\"\n *         userId:\n *           type: string\n *           nullable: true\n *           description: ID of the user who performed the action\n *           example: \"1357158568008091267\"\n *         type:\n *           type: string\n *           enum: [createCard, moveCard, addMemberToCard, removeMemberFromCard, completeTask, uncompleteTask]\n *           description: Type of the action\n *           example: moveCard\n *         data:\n *           type: object\n *           description: Action specific data (varies by type)\n *           example: {\"card\": {\"name\": \"Implement user authentication\"}, \"toList\": {\"id\": \"1357158568008091268\", \"name\": \"To Do\", \"type\": \"active\"}, \"fromList\": {\"id\": \"1357158568008091269\", \"name\": \"Done\", \"type\": \"closed\"}}\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the action was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the action was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst Types = {\n  CREATE_CARD: 'createCard',\n  MOVE_CARD: 'moveCard',\n  ADD_MEMBER_TO_CARD: 'addMemberToCard',\n  REMOVE_MEMBER_FROM_CARD: 'removeMemberFromCard',\n  COMPLETE_TASK: 'completeTask',\n  UNCOMPLETE_TASK: 'uncompleteTask',\n};\n\nconst INTERNAL_NOTIFIABLE_TYPES = [Types.MOVE_CARD, Types.ADD_MEMBER_TO_CARD];\nconst EXTERNAL_NOTIFIABLE_TYPES = [Types.CREATE_CARD, Types.MOVE_CARD];\nconst PERSONAL_NOTIFIABLE_TYPES = [Types.ADD_MEMBER_TO_CARD];\n\nmodule.exports = {\n  Types,\n  INTERNAL_NOTIFIABLE_TYPES,\n  EXTERNAL_NOTIFIABLE_TYPES,\n  PERSONAL_NOTIFIABLE_TYPES,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    type: {\n      type: 'string',\n      isIn: Object.values(Types),\n      required: true,\n    },\n    data: {\n      type: 'json',\n      required: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    boardId: {\n      model: 'Board',\n      columnName: 'board_id',\n    },\n    cardId: {\n      model: 'Card',\n      required: true,\n      columnName: 'card_id',\n    },\n    userId: {\n      model: 'User',\n      columnName: 'user_id',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/models/Attachment.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Attachment.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     Attachment:\n *       type: object\n *       required:\n *         - id\n *         - cardId\n *         - creatorUserId\n *         - type\n *         - data\n *         - name\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the attachment\n *           example: \"1357158568008091264\"\n *         cardId:\n *           type: string\n *           description: ID of the card the attachment belongs to\n *           example: \"1357158568008091265\"\n *         creatorUserId:\n *           type: string\n *           nullable: true\n *           description: ID of the user who created the attachment\n *           example: \"1357158568008091266\"\n *         type:\n *           type: string\n *           enum: [file, link]\n *           description: Type of the attachment\n *           example: link\n *         data:\n *           type: object\n *           description: Attachment specific data (varies by type)\n *           example: {\"url\": \"https://google.com/search?q=planka\", \"faviconUrl\": \"https://storage.example.com/favicons/google.com.png\"}\n *         name:\n *           type: string\n *           description: Name/title of the attachment\n *           example: Important Attachment\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the attachment was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the attachment was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst Types = {\n  FILE: 'file',\n  LINK: 'link',\n};\n\nmodule.exports = {\n  Types,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    type: {\n      type: 'string',\n      isIn: Object.values(Types),\n      required: true,\n    },\n    data: {\n      type: 'json',\n      required: true,\n    },\n    name: {\n      type: 'string',\n      required: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    cardId: {\n      model: 'Card',\n      required: true,\n      columnName: 'card_id',\n    },\n    creatorUserId: {\n      model: 'User',\n      columnName: 'creator_user_id',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/models/BackgroundImage.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * BackgroundImage.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     BackgroundImage:\n *       type: object\n *       required:\n *         - id\n *         - projectId\n *         - size\n *         - url\n *         - thumbnailUrls\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the background image\n *           example: \"1357158568008091264\"\n *         projectId:\n *           type: string\n *           description: ID of the project the background image belongs to\n *           example: \"1357158568008091265\"\n *         size:\n *           type: string\n *           description: File size of the background image in bytes\n *           example: \"1024576\"\n *         url:\n *           type: string\n *           format: uri\n *           description: URL to the full-size background image\n *           example: https://storage.example.com/background-images/1357158568008091264/original.jpg\n *         thumbnailUrls:\n *           type: object\n *           required:\n *             - outside360\n *           description: URLs for different thumbnail sizes of the background image\n *           properties:\n *             outside360:\n *               type: string\n *               format: uri\n *               description: URL for 360px thumbnail version\n *               example: https://storage.example.com/background-images/1357158568008091264/outside-360.jpg\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the background image was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the background image was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    extension: {\n      type: 'string',\n      required: true,\n    },\n    size: {\n      type: 'string',\n      required: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    uploadedFileId: {\n      model: 'UploadedFile',\n      required: true,\n      columnName: 'uploaded_file_id',\n    },\n    projectId: {\n      model: 'Project',\n      required: true,\n      columnName: 'project_id',\n    },\n  },\n\n  tableName: 'background_image',\n};\n"
  },
  {
    "path": "server/api/models/BaseCustomFieldGroup.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * BaseCustomFieldGroup.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     BaseCustomFieldGroup:\n *       type: object\n *       required:\n *         - id\n *         - projectId\n *         - name\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the base custom field group\n *           example: \"1357158568008091264\"\n *         projectId:\n *           type: string\n *           description: ID of the project the base custom field group belongs to\n *           example: \"1357158568008091265\"\n *         name:\n *           type: string\n *           description: Name/title of the base custom field group\n *           example: Base Properties\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the base custom field group was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the base custom field group was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    name: {\n      type: 'string',\n      required: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    projectId: {\n      model: 'Project',\n      required: true,\n      columnName: 'project_id',\n    },\n  },\n\n  tableName: 'base_custom_field_group',\n};\n"
  },
  {
    "path": "server/api/models/Board.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Board.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     Board:\n *       type: object\n *       required:\n *         - id\n *         - projectId\n *         - position\n *         - name\n *         - defaultView\n *         - defaultCardType\n *         - limitCardTypesToDefaultOne\n *         - alwaysDisplayCardCreator\n *         - displayCardAges\n *         - expandTaskListsByDefault\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the board\n *           example: \"1357158568008091264\"\n *         projectId:\n *           type: string\n *           description: ID of the project the board belongs to\n *           example: \"1357158568008091265\"\n *         position:\n *           type: number\n *           description: Position of the board within the project\n *           example: 65536\n *         name:\n *           type: string\n *           description: Name/title of the board\n *           example: Development Board\n *         defaultView:\n *           type: string\n *           enum: [kanban, grid, list]\n *           default: kanban\n *           description: Default view for the board\n *           example: kanban\n *         defaultCardType:\n *           type: string\n *           enum: [project, story]\n *           default: project\n *           description: Default card type for new cards\n *           example: project\n *         limitCardTypesToDefaultOne:\n *           type: boolean\n *           default: false\n *           description: Whether to limit card types to default one\n *           example: false\n *         alwaysDisplayCardCreator:\n *           type: boolean\n *           default: false\n *           description: Whether to always display the card creator\n *           example: false\n *         displayCardAges:\n *           type: boolean\n *           default: false\n *           description: Whether to display card ages\n *           example: false\n *         expandTaskListsByDefault:\n *           type: boolean\n *           default: false\n *           description: Whether to expand task lists by default\n *           example: false\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the board was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the board was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst Card = require('./Card');\n\nconst Views = {\n  KANBAN: 'kanban',\n  GRID: 'grid',\n  LIST: 'list',\n};\n\nconst ImportTypes = {\n  TRELLO: 'trello',\n};\n\nmodule.exports = {\n  Views,\n  ImportTypes,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    position: {\n      type: 'number',\n      required: true,\n    },\n    name: {\n      type: 'string',\n      required: true,\n    },\n    defaultView: {\n      type: 'string',\n      isIn: Object.values(Views),\n      defaultsTo: Views.KANBAN,\n      columnName: 'default_view',\n    },\n    defaultCardType: {\n      type: 'string',\n      isIn: Object.values(Card.Types),\n      defaultsTo: Card.Types.PROJECT,\n      columnName: 'default_card_type',\n    },\n    limitCardTypesToDefaultOne: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'limit_card_types_to_default_one',\n    },\n    alwaysDisplayCardCreator: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'always_display_card_creator',\n    },\n    displayCardAges: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'display_card_ages',\n    },\n    expandTaskListsByDefault: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'expand_task_lists_by_default',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    projectId: {\n      model: 'Project',\n      required: true,\n      columnName: 'project_id',\n    },\n    memberUsers: {\n      collection: 'User',\n      via: 'boardId',\n      through: 'BoardMembership',\n    },\n    lists: {\n      collection: 'List',\n      via: 'boardId',\n    },\n    labels: {\n      collection: 'Label',\n      via: 'boardId',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/models/BoardMembership.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * BoardMembership.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     BoardMembership:\n *       type: object\n *       required:\n *         - id\n *         - projectId\n *         - boardId\n *         - userId\n *         - role\n *         - canComment\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the board membership\n *           example: \"1357158568008091264\"\n *         projectId:\n *           type: string\n *           description: ID of the project the board membership belongs to (denormalized)\n *           example: \"1357158568008091265\"\n *         boardId:\n *           type: string\n *           description: ID of the board the membership is associated with\n *           example: \"1357158568008091266\"\n *         userId:\n *           type: string\n *           description: ID of the user who is a member of the board\n *           example: \"1357158568008091267\"\n *         role:\n *           type: string\n *           enum: [editor, viewer]\n *           description: Role of the user in the board\n *           example: editor\n *         canComment:\n *           type: boolean\n *           nullable: true\n *           description: Whether the user can comment on cards (applies only to viewers)\n *           example: true\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the board membership was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the board membership was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst Roles = {\n  EDITOR: 'editor',\n  VIEWER: 'viewer',\n};\n\nconst SHARED_RULES = {\n  role: {},\n  canComment: { setTo: null },\n};\n\nconst RULES_BY_ROLE = {\n  [Roles.EDITOR]: {\n    canComment: { setTo: null },\n  },\n  [Roles.VIEWER]: {\n    canComment: { defaultTo: false },\n  },\n};\n\nmodule.exports = {\n  Roles,\n  SHARED_RULES,\n  RULES_BY_ROLE,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    role: {\n      type: 'string',\n      isIn: Object.values(Roles),\n      required: true,\n    },\n    canComment: {\n      type: 'boolean',\n      allowNull: true,\n      columnName: 'can_comment',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    // Denormalization\n    projectId: {\n      model: 'Project',\n      required: true,\n      columnName: 'project_id',\n    },\n    boardId: {\n      model: 'Board',\n      required: true,\n      columnName: 'board_id',\n    },\n    userId: {\n      model: 'User',\n      required: true,\n      columnName: 'user_id',\n    },\n  },\n\n  tableName: 'board_membership',\n};\n"
  },
  {
    "path": "server/api/models/BoardSubscription.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * BoardSubscription.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    boardId: {\n      model: 'Board',\n      required: true,\n      columnName: 'board_id',\n    },\n    userId: {\n      model: 'User',\n      required: true,\n      columnName: 'user_id',\n    },\n  },\n\n  tableName: 'board_subscription',\n};\n"
  },
  {
    "path": "server/api/models/Card.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Card.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     Card:\n *       type: object\n *       required:\n *         - id\n *         - boardId\n *         - listId\n *         - creatorUserId\n *         - prevListId\n *         - coverAttachmentId\n *         - type\n *         - position\n *         - name\n *         - description\n *         - dueDate\n *         - isDueCompleted\n *         - stopwatch\n *         - commentsTotal\n *         - isClosed\n *         - listChangedAt\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the card\n *           example: \"1357158568008091264\"\n *         boardId:\n *           type: string\n *           description: ID of the board the card belongs to (denormalized)\n *           example: \"1357158568008091265\"\n *         listId:\n *           type: string\n *           description: ID of the list the card belongs to\n *           example: \"1357158568008091266\"\n *         creatorUserId:\n *           type: string\n *           nullable: true\n *           description: ID of the user who created the card\n *           example: \"1357158568008091267\"\n *         prevListId:\n *           type: string\n *           nullable: true\n *           description: ID of the previous list the card was in (available when in archive or trash)\n *           example: \"1357158568008091268\"\n *         coverAttachmentId:\n *           type: string\n *           nullable: true\n *           description: ID of the attachment used as cover\n *           example: \"1357158568008091269\"\n *         type:\n *           type: string\n *           enum: [project, story]\n *           description: Type of the card\n *           example: project\n *         position:\n *           type: number\n *           nullable: true\n *           description: Position of the card within the list\n *           example: 65536\n *         name:\n *           type: string\n *           description: Name/title of the card\n *           example: Implement user authentication\n *         description:\n *           type: string\n *           nullable: true\n *           description: Detailed description of the card\n *           example: Add JWT-based authentication system...\n *         dueDate:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: Due date for the card\n *           example: 2024-01-01T00:00:00.000Z\n *         isDueCompleted:\n *           type: boolean\n *           nullable: true\n *           description: Whether the due date is completed\n *           example: false\n *         stopwatch:\n *           type: object\n *           required:\n *             - startedAt\n *             - total\n *           nullable: true\n *           description: Stopwatch data for time tracking\n *           properties:\n *             startedAt:\n *               type: string\n *               format: date-time\n *               description: When the stopwatch was started\n *               example: 2024-01-01T00:00:00.000Z\n *             total:\n *               type: number\n *               description: Total time in seconds\n *               example: 3600\n *         commentsTotal:\n *           type: number\n *           default: 0\n *           description: Total number of comments on the card\n *           example: 100\n *         isClosed:\n *           type: boolean\n *           default: false\n *           description: Whether the card is closed\n *           example: false\n *         listChangedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the card was last moved between lists\n *           example: 2024-01-01T00:00:00.000Z\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the card was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the card was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst Types = {\n  PROJECT: 'project',\n  STORY: 'story',\n};\n\nmodule.exports = {\n  Types,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    type: {\n      type: 'string',\n      isIn: Object.values(Types),\n      required: true,\n    },\n    position: {\n      type: 'number',\n      allowNull: true,\n    },\n    name: {\n      type: 'string',\n      required: true,\n    },\n    description: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n    },\n    dueDate: {\n      type: 'ref',\n      columnName: 'due_date',\n    },\n    isDueCompleted: {\n      type: 'boolean',\n      allowNull: true,\n      columnName: 'is_due_completed',\n    },\n    stopwatch: {\n      type: 'json',\n    },\n    commentsTotal: {\n      type: 'number',\n      defaultsTo: 0,\n      columnName: 'comments_total',\n    },\n    isClosed: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'is_closed',\n    },\n    listChangedAt: {\n      type: 'ref',\n      columnName: 'list_changed_at',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    // Denormalization\n    boardId: {\n      model: 'Board',\n      required: true,\n      columnName: 'board_id',\n    },\n    listId: {\n      model: 'List',\n      required: true,\n      columnName: 'list_id',\n    },\n    creatorUserId: {\n      model: 'User',\n      columnName: 'creator_user_id',\n    },\n    prevListId: {\n      model: 'List',\n      columnName: 'prev_list_id',\n    },\n    coverAttachmentId: {\n      model: 'Attachment',\n      columnName: 'cover_attachment_id',\n    },\n    subscriptionUsers: {\n      collection: 'User',\n      via: 'cardId',\n      through: 'CardSubscription',\n    },\n    memberUsers: {\n      collection: 'User',\n      via: 'cardId',\n      through: 'CardMembership',\n    },\n    labels: {\n      collection: 'Label',\n      via: 'cardId',\n      through: 'CardLabel',\n    },\n    taskLists: {\n      collection: 'TaskList',\n      via: 'cardId',\n    },\n    attachments: {\n      collection: 'Attachment',\n      via: 'cardId',\n    },\n    comments: {\n      collection: 'Comment',\n      via: 'cardId',\n    },\n    actions: {\n      collection: 'Action',\n      via: 'cardId',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/models/CardLabel.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * CardLabel.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     CardLabel:\n *       type: object\n *       required:\n *         - id\n *         - cardId\n *         - labelId\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the card-label association\n *           example: \"1357158568008091264\"\n *         cardId:\n *           type: string\n *           description: ID of the card the label is associated with\n *           example: \"1357158568008091265\"\n *         labelId:\n *           type: string\n *           description: ID of the label associated with the card\n *           example: \"1357158568008091266\"\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the card-label association was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the card-label association was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    cardId: {\n      model: 'Card',\n      required: true,\n      columnName: 'card_id',\n    },\n    labelId: {\n      model: 'Label',\n      required: true,\n      columnName: 'label_id',\n    },\n  },\n\n  tableName: 'card_label',\n};\n"
  },
  {
    "path": "server/api/models/CardMembership.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * CardMembership.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     CardMembership:\n *       type: object\n *       required:\n *         - id\n *         - cardId\n *         - userId\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the card membership\n *           example: \"1357158568008091264\"\n *         cardId:\n *           type: string\n *           description: ID of the card the user is a member of\n *           example: \"1357158568008091265\"\n *         userId:\n *           type: string\n *           description: ID of the user who is a member of the card\n *           example: \"1357158568008091266\"\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the card membership was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the card membership was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    cardId: {\n      model: 'Card',\n      required: true,\n      columnName: 'card_id',\n    },\n    userId: {\n      model: 'User',\n      required: true,\n      columnName: 'user_id',\n    },\n  },\n\n  tableName: 'card_membership',\n};\n"
  },
  {
    "path": "server/api/models/CardSubscription.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * CardSubscription.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    isPermanent: {\n      type: 'boolean',\n      defaultsTo: true,\n      columnName: 'is_permanent',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    cardId: {\n      model: 'Card',\n      required: true,\n      columnName: 'card_id',\n    },\n    userId: {\n      model: 'User',\n      required: true,\n      columnName: 'user_id',\n    },\n  },\n\n  tableName: 'card_subscription',\n};\n"
  },
  {
    "path": "server/api/models/Comment.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Comment.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     Comment:\n *       type: object\n *       required:\n *         - id\n *         - cardId\n *         - userId\n *         - text\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the comment\n *           example: \"1357158568008091264\"\n *         cardId:\n *           type: string\n *           description: ID of the card the comment belongs to\n *           example: \"1357158568008091265\"\n *         userId:\n *           type: string\n *           nullable: true\n *           description: ID of the user who created the comment\n *           example: \"1357158568008091266\"\n *         text:\n *           type: string\n *           description: Content of the comment\n *           example: This task is almost complete...\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the comment was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the comment was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    text: {\n      type: 'string',\n      required: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    cardId: {\n      model: 'Card',\n      required: true,\n      columnName: 'card_id',\n    },\n    userId: {\n      model: 'User',\n      columnName: 'user_id',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/models/Config.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Config.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     Config:\n *       type: object\n *       required:\n *         - id\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the config (always set to '1')\n *           example: \"1\"\n *         smtpHost:\n *           type: string\n *           nullable: true\n *           description: Hostname or IP address of the SMTP server\n *           example: smtp.example.com\n *         smtpPort:\n *           type: number\n *           nullable: true\n *           description: Port number of the SMTP server\n *           example: 587\n *         smtpName:\n *           type: string\n *           nullable: true\n *           description: Client hostname used in the EHLO command for SMTP\n *           example: localhost\n *         smtpSecure:\n *           type: boolean\n *           description: Whether to use a secure connection for SMTP\n *           example: false\n *         smtpTlsRejectUnauthorized:\n *           type: boolean\n *           description: Whether to reject unauthorized or self-signed TLS certificates for SMTP connections\n *           example: true\n *         smtpUser:\n *           type: string\n *           nullable: true\n *           description: Username for authenticating with the SMTP server\n *           example: no-reply@example.com\n *         smtpPassword:\n *           type: string\n *           nullable: true\n *           description: Password for authenticating with the SMTP server\n *           example: SecurePassword123!\n *         smtpFrom:\n *           type: string\n *           nullable: true\n *           description: Default \"from\" used for outgoing SMTP emails\n *           example: no-reply@example.com\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the config was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the config was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst MAIN_ID = '1';\n\nconst SMTP_FIELD_NAMES = [\n  'smtpHost',\n  'smtpPort',\n  'smtpName',\n  'smtpSecure',\n  'smtpTlsRejectUnauthorized',\n  'smtpUser',\n  'smtpPassword',\n  'smtpFrom',\n];\n\nmodule.exports = {\n  MAIN_ID,\n  SMTP_FIELD_NAMES,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    smtpHost: {\n      type: 'string',\n      allowNull: true,\n      columnName: 'smtp_host',\n    },\n    smtpPort: {\n      type: 'number',\n      allowNull: true,\n      columnName: 'smtp_port',\n    },\n    smtpName: {\n      type: 'string',\n      allowNull: true,\n      columnName: 'smtp_name',\n    },\n    smtpSecure: {\n      type: 'boolean',\n      required: true,\n      columnName: 'smtp_secure',\n    },\n    smtpTlsRejectUnauthorized: {\n      type: 'boolean',\n      required: true,\n      columnName: 'smtp_tls_reject_unauthorized',\n    },\n    smtpUser: {\n      type: 'string',\n      allowNull: true,\n      columnName: 'smtp_user',\n    },\n    smtpPassword: {\n      type: 'string',\n      allowNull: true,\n      columnName: 'smtp_password',\n    },\n    smtpFrom: {\n      type: 'string',\n      allowNull: true,\n      columnName: 'smtp_from',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n  },\n};\n"
  },
  {
    "path": "server/api/models/CustomField.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * CustomField.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     CustomField:\n *       type: object\n *       required:\n *         - id\n *         - baseCustomFieldGroupId\n *         - customFieldGroupId\n *         - position\n *         - name\n *         - showOnFrontOfCard\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the custom field\n *           example: \"1357158568008091264\"\n *         baseCustomFieldGroupId:\n *           type: string\n *           nullable: true\n *           description: ID of the base custom field group the custom field belongs to\n *           example: \"1357158568008091265\"\n *         customFieldGroupId:\n *           type: string\n *           nullable: true\n *           description: ID of the custom field group the custom field belongs to\n *           example: \"1357158568008091266\"\n *         position:\n *           type: number\n *           description: Position of the custom field within the group\n *           example: 65536\n *         name:\n *           type: string\n *           description: Name/title of the custom field\n *           example: Priority\n *         showOnFrontOfCard:\n *           type: boolean\n *           default: false\n *           description: Whether to show the field on the front of cards\n *           example: false\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the custom field was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the custom field was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    position: {\n      type: 'number',\n      required: true,\n    },\n    name: {\n      type: 'string',\n      required: true,\n    },\n    showOnFrontOfCard: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'show_on_front_of_card',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    baseCustomFieldGroupId: {\n      model: 'BaseCustomFieldGroup',\n      columnName: 'base_custom_field_group_id',\n    },\n    customFieldGroupId: {\n      model: 'CustomFieldGroup',\n      columnName: 'custom_field_group_id',\n    },\n  },\n\n  tableName: 'custom_field',\n};\n"
  },
  {
    "path": "server/api/models/CustomFieldGroup.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * CustomFieldGroup.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     CustomFieldGroup:\n *       type: object\n *       required:\n *         - id\n *         - boardId\n *         - cardId\n *         - baseCustomFieldGroupId\n *         - position\n *         - name\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the custom field group\n *           example: \"1357158568008091264\"\n *         boardId:\n *           type: string\n *           nullable: true\n *           description: ID of the board the custom field group belongs to\n *           example: \"1357158568008091265\"\n *         cardId:\n *           type: string\n *           nullable: true\n *           description: ID of the card the custom field group belongs to\n *           example: \"1357158568008091266\"\n *         baseCustomFieldGroupId:\n *           type: string\n *           nullable: true\n *           description: ID of the base custom field group used as a template\n *           example: \"1357158568008091267\"\n *         position:\n *           type: number\n *           description: Position of the custom field group within the board/card\n *           example: 65536\n *         name:\n *           type: string\n *           nullable: true\n *           description: Name/title of the custom field group\n *           example: Properties\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the custom field group was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the custom field group was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    position: {\n      type: 'number',\n      required: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    boardId: {\n      model: 'Board',\n      columnName: 'board_id',\n    },\n    cardId: {\n      model: 'Card',\n      columnName: 'card_id',\n    },\n    baseCustomFieldGroupId: {\n      model: 'BaseCustomFieldGroup',\n      columnName: 'base_custom_field_group_id',\n    },\n  },\n\n  tableName: 'custom_field_group',\n};\n"
  },
  {
    "path": "server/api/models/CustomFieldValue.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * CustomFieldValue.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     CustomFieldValue:\n *       type: object\n *       required:\n *         - id\n *         - cardId\n *         - customFieldGroupId\n *         - customFieldId\n *         - content\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the custom field value\n *           example: \"1357158568008091264\"\n *         cardId:\n *           type: string\n *           description: ID of the card the value belongs to\n *           example: \"1357158568008091265\"\n *         customFieldGroupId:\n *           type: string\n *           description: ID of the custom field group the value belongs to\n *           example: \"1357158568008091266\"\n *         customFieldId:\n *           type: string\n *           description: ID of the custom field the value belongs to\n *           example: \"1357158568008091267\"\n *         content:\n *           type: string\n *           description: Content/value of the custom field\n *           example: High Priority\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the custom field value was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the custom field value was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    content: {\n      type: 'string',\n      required: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    cardId: {\n      model: 'Card',\n      required: true,\n      columnName: 'card_id',\n    },\n    customFieldGroupId: {\n      model: 'CustomFieldGroup',\n      required: true,\n      columnName: 'custom_field_group_id',\n    },\n    customFieldId: {\n      model: 'CustomField',\n      required: true,\n      columnName: 'custom_field_id',\n    },\n  },\n\n  tableName: 'custom_field_value',\n};\n"
  },
  {
    "path": "server/api/models/IdentityProviderUser.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * IdentityProviderUser.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    issuer: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n    },\n    sub: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    userId: {\n      model: 'User',\n      required: true,\n      columnName: 'user_id',\n    },\n  },\n\n  tableName: 'identity_provider_user',\n};\n"
  },
  {
    "path": "server/api/models/InternalConfig.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * InternalConfig.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\nconst MAIN_ID = '1';\n\nmodule.exports = {\n  MAIN_ID,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    storageLimit: {\n      type: 'string',\n      allowNull: true,\n      columnName: 'storage_limit',\n    },\n    activeUsersLimit: {\n      type: 'number',\n      allowNull: true,\n      columnName: 'active_users_limit',\n    },\n    isInitialized: {\n      type: 'boolean',\n      required: true,\n      columnName: 'is_initialized',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n  },\n\n  tableName: 'internal_config',\n};\n"
  },
  {
    "path": "server/api/models/Label.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Label.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     Label:\n *       type: object\n *       required:\n *         - id\n *         - boardId\n *         - position\n *         - name\n *         - color\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the label\n *           example: \"1357158568008091264\"\n *         boardId:\n *           type: string\n *           description: ID of the board the label belongs to\n *           example: \"1357158568008091265\"\n *         position:\n *           type: number\n *           description: Position of the label within the board\n *           example: 65536\n *         name:\n *           type: string\n *           nullable: true\n *           description: Name/title of the label\n *           example: Bug\n *         color:\n *           type: string\n *           enum: [muddy-grey, autumn-leafs, morning-sky, antique-blue, egg-yellow, desert-sand, dark-granite, fresh-salad, lagoon-blue, midnight-blue, light-orange, pumpkin-orange, light-concrete, sunny-grass, navy-blue, lilac-eyes, apricot-red, orange-peel, silver-glint, bright-moss, deep-ocean, summer-sky, berry-red, light-cocoa, grey-stone, tank-green, coral-green, sugar-plum, pink-tulip, shady-rust, wet-rock, wet-moss, turquoise-sea, lavender-fields, piggy-red, light-mud, gun-metal, modern-green, french-coast, sweet-lilac, red-burgundy, pirate-gold]\n *           description: Color of the label\n *           example: berry-red\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the label was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the label was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst COLORS = [\n  'muddy-grey',\n  'autumn-leafs',\n  'morning-sky',\n  'antique-blue',\n  'egg-yellow',\n  'desert-sand',\n  'dark-granite',\n  'fresh-salad',\n  'lagoon-blue',\n  'midnight-blue',\n  'light-orange',\n  'pumpkin-orange',\n  'light-concrete',\n  'sunny-grass',\n  'navy-blue',\n  'lilac-eyes',\n  'apricot-red',\n  'orange-peel',\n  'silver-glint',\n  'bright-moss',\n  'deep-ocean',\n  'summer-sky',\n  'berry-red',\n  'light-cocoa',\n  'grey-stone',\n  'tank-green',\n  'coral-green',\n  'sugar-plum',\n  'pink-tulip',\n  'shady-rust',\n  'wet-rock',\n  'wet-moss',\n  'turquoise-sea',\n  'lavender-fields',\n  'piggy-red',\n  'light-mud',\n  'gun-metal',\n  'modern-green',\n  'french-coast',\n  'sweet-lilac',\n  'red-burgundy',\n  'pirate-gold',\n];\n\nmodule.exports = {\n  COLORS,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    position: {\n      type: 'number',\n      required: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n    },\n    color: {\n      type: 'string',\n      isIn: COLORS,\n      required: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    boardId: {\n      model: 'Board',\n      required: true,\n      columnName: 'board_id',\n    },\n    cards: {\n      collection: 'Card',\n      via: 'labelId',\n      through: 'CardLabel',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/models/List.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * List.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     List:\n *       type: object\n *       required:\n *         - id\n *         - boardId\n *         - type\n *         - position\n *         - name\n *         - color\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the list\n *           example: \"1357158568008091264\"\n *         boardId:\n *           type: string\n *           description: ID of the board the list belongs to\n *           example: \"1357158568008091265\"\n *         type:\n *           type: string\n *           enum: [active, closed, archive, trash]\n *           description: Type/status of the list\n *           example: active\n *         position:\n *           type: number\n *           nullable: true\n *           description: Position of the list within the board\n *           example: 65536\n *         name:\n *           type: string\n *           nullable: true\n *           description: Name/title of the list\n *           example: To Do\n *         color:\n *           type: string\n *           enum: [berry-red, pumpkin-orange, lagoon-blue, pink-tulip, light-mud, orange-peel, bright-moss, antique-blue, dark-granite, turquoise-sea]\n *           nullable: true\n *           description: Color for the list\n *           example: lagoon-blue\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the list was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the list was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst Types = {\n  ACTIVE: 'active',\n  CLOSED: 'closed',\n  ARCHIVE: 'archive',\n  TRASH: 'trash',\n};\n\nconst TypeStates = {\n  OPENED: 'opened',\n  CLOSED: 'closed',\n};\n\nconst SortFieldNames = {\n  NAME: 'name',\n  DUE_DATE: 'dueDate',\n  CREATED_AT: 'createdAt',\n};\n\n// TODO: should not be here\nconst SortOrders = {\n  ASC: 'asc',\n  DESC: 'desc',\n};\n\nconst FINITE_TYPES = [Types.ACTIVE, Types.CLOSED];\n\nconst KANBAN_TYPES = [Types.ACTIVE, Types.CLOSED];\n\nconst TYPE_STATE_BY_TYPE = {\n  [Types.ACTIVE]: TypeStates.OPENED,\n  [Types.CLOSED]: Types.CLOSED,\n};\n\nconst COLORS = [\n  'berry-red',\n  'pumpkin-orange',\n  'lagoon-blue',\n  'pink-tulip',\n  'light-mud',\n  'orange-peel',\n  'bright-moss',\n  'antique-blue',\n  'dark-granite',\n  'turquoise-sea',\n];\n\nmodule.exports = {\n  Types,\n  TypeStates,\n  SortFieldNames,\n  SortOrders,\n  FINITE_TYPES,\n  KANBAN_TYPES,\n  TYPE_STATE_BY_TYPE,\n  COLORS,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    type: {\n      type: 'string',\n      isIn: Object.values(Types),\n      required: true,\n    },\n    position: {\n      type: 'number',\n      allowNull: true,\n    },\n    name: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n    },\n    color: {\n      type: 'string',\n      isIn: COLORS,\n      allowNull: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    boardId: {\n      model: 'Board',\n      required: true,\n      columnName: 'board_id',\n    },\n    cards: {\n      collection: 'Card',\n      via: 'listId',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/models/Notification.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Notification.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     Notification:\n *       type: object\n *       required:\n *         - id\n *         - userId\n *         - creatorUserId\n *         - boardId\n *         - cardId\n *         - commentId\n *         - actionId\n *         - type\n *         - data\n *         - isRead\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the notification\n *           example: \"1357158568008091264\"\n *         userId:\n *           type: string\n *           description: ID of the user who receives the notification\n *           example: \"1357158568008091265\"\n *         creatorUserId:\n *           type: string\n *           nullable: true\n *           description: ID of the user who created the notification\n *           example: \"1357158568008091266\"\n *         boardId:\n *           type: string\n *           description: ID of the board associated with the notification (denormalized)\n *           example: \"1357158568008091267\"\n *         cardId:\n *           type: string\n *           description: ID of the card associated with the notification\n *           example: \"1357158568008091268\"\n *         commentId:\n *           type: string\n *           nullable: true\n *           description: ID of the comment associated with the notification\n *           example: \"1357158568008091269\"\n *         actionId:\n *           type: string\n *           nullable: true\n *           description: ID of the action associated with the notification\n *           example: \"1357158568008091270\"\n *         type:\n *           type: string\n *           enum: [moveCard, commentCard, addMemberToCard, mentionInComment]\n *           description: Type of the notification\n *           example: commentCard\n *         data:\n *           type: object\n *           description: Notification specific data (varies by type)\n *           example: {\"card\": {\"name\": \"Implement user authentication\"}, \"text\": \"This task is almost complete...\"}\n *         isRead:\n *           type: boolean\n *           default: false\n *           description: Whether the notification has been read\n *           example: false\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the notification was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the notification was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst Types = {\n  MOVE_CARD: 'moveCard',\n  COMMENT_CARD: 'commentCard',\n  ADD_MEMBER_TO_CARD: 'addMemberToCard',\n  MENTION_IN_COMMENT: 'mentionInComment',\n};\n\nmodule.exports = {\n  Types,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    type: {\n      type: 'string',\n      isIn: Object.values(Types),\n      required: true,\n    },\n    data: {\n      type: 'json',\n      required: true,\n    },\n    isRead: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'is_read',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    userId: {\n      model: 'User',\n      required: true,\n      columnName: 'user_id',\n    },\n    creatorUserId: {\n      model: 'User',\n      columnName: 'creator_user_id',\n    },\n    // Denormalization\n    boardId: {\n      model: 'Board',\n      required: true,\n      columnName: 'board_id',\n    },\n    cardId: {\n      model: 'Card',\n      required: true,\n      columnName: 'card_id',\n    },\n    commentId: {\n      model: 'Comment',\n      columnName: 'comment_id',\n    },\n    actionId: {\n      model: 'Action',\n      columnName: 'action_id',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/models/NotificationService.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * NotificationService.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     NotificationService:\n *       type: object\n *       required:\n *         - id\n *         - userId\n *         - boardId\n *         - url\n *         - format\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the notification service\n *           example: \"1357158568008091264\"\n *         userId:\n *           type: string\n *           nullable: true\n *           description: ID of the user the service is associated with\n *           example: \"1357158568008091265\"\n *         boardId:\n *           type: string\n *           nullable: true\n *           description: ID of the board the service is associated with\n *           example: \"1357158568008091266\"\n *         url:\n *           type: string\n *           description: URL endpoint for notifications\n *           example: https://service.example.com/planka\n *         format:\n *           type: string\n *           enum: [text, markdown, html]\n *           description: Format for notification messages\n *           example: text\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the notification service was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the notification service was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst Formats = {\n  TEXT: 'text',\n  MARKDOWN: 'markdown',\n  HTML: 'html',\n};\n\nmodule.exports = {\n  Formats,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    url: {\n      type: 'string',\n      required: true,\n    },\n    format: {\n      type: 'string',\n      isIn: Object.values(Formats),\n      required: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    userId: {\n      model: 'User',\n      columnName: 'user_id',\n    },\n    boardId: {\n      model: 'Board',\n      columnName: 'board_id',\n    },\n  },\n\n  tableName: 'notification_service',\n};\n"
  },
  {
    "path": "server/api/models/Project.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Project.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     Project:\n *       type: object\n *       required:\n *         - id\n *         - ownerProjectManagerId\n *         - backgroundImageId\n *         - name\n *         - description\n *         - backgroundType\n *         - backgroundGradient\n *         - isHidden\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the project\n *           example: \"1357158568008091264\"\n *         ownerProjectManagerId:\n *           type: string\n *           nullable: true\n *           description: ID of the project manager who owns the project\n *           example: \"1357158568008091265\"\n *         backgroundImageId:\n *           type: string\n *           nullable: true\n *           description: ID of the background image used as background\n *           example: \"1357158568008091266\"\n *         name:\n *           type: string\n *           description: Name/title of the project\n *           example: Development Project\n *         description:\n *           type: string\n *           nullable: true\n *           description: Detailed description of the project\n *           example: A project for developing new features...\n *         backgroundType:\n *           type: string\n *           enum: [gradient, image]\n *           nullable: true\n *           description: Type of background for the project\n *           example: gradient\n *         backgroundGradient:\n *           type: string\n *           enum: [old-lime, ocean-dive, tzepesch-style, jungle-mesh, strawberry-dust, purple-rose, sun-scream, warm-rust, sky-change, green-eyes, blue-xchange, blood-orange, sour-peel, green-ninja, algae-green, coral-reef, steel-grey, heat-waves, velvet-lounge, purple-rain, blue-steel, blueish-curve, prism-light, green-mist, red-curtain]\n *           nullable: true\n *           description: Gradient background for the project\n *           example: ocean-dive\n *         isHidden:\n *           type: boolean\n *           default: false\n *           description: Whether the project is hidden\n *           example: false\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the project was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the project was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst Types = {\n  PRIVATE: 'private',\n  SHARED: 'shared',\n};\n\nconst BackgroundTypes = {\n  GRADIENT: 'gradient',\n  IMAGE: 'image',\n};\n\nconst BACKGROUND_GRADIENTS = [\n  'old-lime',\n  'ocean-dive',\n  'tzepesch-style',\n  'jungle-mesh',\n  'strawberry-dust',\n  'purple-rose',\n  'sun-scream',\n  'warm-rust',\n  'sky-change',\n  'green-eyes',\n  'blue-xchange',\n  'blood-orange',\n  'sour-peel',\n  'green-ninja',\n  'algae-green',\n  'coral-reef',\n  'steel-grey',\n  'heat-waves',\n  'velvet-lounge',\n  'purple-rain',\n  'blue-steel',\n  'blueish-curve',\n  'prism-light',\n  'green-mist',\n  'red-curtain',\n];\n\nmodule.exports = {\n  Types,\n  BackgroundTypes,\n  BACKGROUND_GRADIENTS,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    name: {\n      type: 'string',\n      required: true,\n    },\n    description: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n    },\n    backgroundType: {\n      type: 'string',\n      isIn: Object.values(BackgroundTypes),\n      allowNull: true,\n      columnName: 'background_type',\n    },\n    backgroundGradient: {\n      type: 'string',\n      isIn: BACKGROUND_GRADIENTS,\n      allowNull: true,\n      columnName: 'background_gradient',\n    },\n    isHidden: {\n      type: 'boolean',\n      defaultsTo: false, // TODO: implement via normalizeValues?\n      columnName: 'is_hidden',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    ownerProjectManagerId: {\n      model: 'ProjectManager',\n      columnName: 'owner_project_manager_id',\n    },\n    backgroundImageId: {\n      model: 'BackgroundImage',\n      columnName: 'background_image_id',\n    },\n    managerUsers: {\n      collection: 'User',\n      via: 'projectId',\n      through: 'ProjectManager',\n    },\n    boards: {\n      collection: 'Board',\n      via: 'projectId',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/models/ProjectFavorite.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * ProjectFavorite.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    projectId: {\n      model: 'Project',\n      required: true,\n      columnName: 'project_id',\n    },\n    userId: {\n      model: 'User',\n      required: true,\n      columnName: 'user_id',\n    },\n  },\n\n  tableName: 'project_favorite',\n};\n"
  },
  {
    "path": "server/api/models/ProjectManager.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * ProjectManager.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     ProjectManager:\n *       type: object\n *       required:\n *         - id\n *         - projectId\n *         - userId\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the project manager\n *           example: \"1357158568008091264\"\n *         projectId:\n *           type: string\n *           description: ID of the project the manager is associated with\n *           example: \"1357158568008091265\"\n *         userId:\n *           type: string\n *           description: ID of the user who is assigned as project manager\n *           example: \"1357158568008091266\"\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the project manager was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the project manager was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    projectId: {\n      model: 'Project',\n      required: true,\n      columnName: 'project_id',\n    },\n    userId: {\n      model: 'User',\n      required: true,\n      columnName: 'user_id',\n    },\n  },\n\n  tableName: 'project_manager',\n};\n"
  },
  {
    "path": "server/api/models/Session.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Session.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    accessToken: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n      columnName: 'access_token',\n    },\n    pendingToken: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n      columnName: 'pending_token',\n    },\n    httpOnlyToken: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n      columnName: 'http_only_token',\n    },\n    remoteAddress: {\n      type: 'string',\n      required: true,\n      columnName: 'remote_address',\n    },\n    userAgent: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n      columnName: 'user_agent',\n    },\n    deletedAt: {\n      type: 'ref',\n      columnName: 'deleted_at',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    userId: {\n      model: 'User',\n      required: true,\n      columnName: 'user_id',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/models/StorageUsage.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * StorageUsage.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\nconst MAIN_ID = '1';\n\nmodule.exports = {\n  MAIN_ID,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    total: {\n      type: 'string',\n      required: true,\n    },\n    userAvatars: {\n      type: 'string',\n      required: true,\n      columnName: 'user_avatars',\n    },\n    backgroundImages: {\n      type: 'string',\n      required: true,\n      columnName: 'background_images',\n    },\n    attachments: {\n      type: 'string',\n      required: true,\n      columnName: 'attachments',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n  },\n\n  tableName: 'storage_usage',\n};\n"
  },
  {
    "path": "server/api/models/Task.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Task.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     Task:\n *       type: object\n *       required:\n *         - id\n *         - taskListId\n *         - linkedCardId\n *         - assigneeUserId\n *         - position\n *         - name\n *         - isCompleted\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the task\n *           example: \"1357158568008091264\"\n *         taskListId:\n *           type: string\n *           description: ID of the task list the task belongs to\n *           example: \"1357158568008091265\"\n *         linkedCardId:\n *           type: string\n *           nullable: true\n *           description: ID of the card linked to the task\n *           example: \"1357158568008091266\"\n *         assigneeUserId:\n *           type: string\n *           nullable: true\n *           description: ID of the user assigned to the task\n *           example: \"1357158568008091267\"\n *         position:\n *           type: number\n *           description: Position of the task within the task list\n *           example: 65536\n *         name:\n *           type: string\n *           description: Name/title of the task\n *           example: Write unit tests\n *         isCompleted:\n *           type: boolean\n *           default: false\n *           description: Whether the task is completed\n *           example: false\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the task was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the task was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    position: {\n      type: 'number',\n      required: true,\n    },\n    name: {\n      type: 'string',\n      required: true,\n    },\n    isCompleted: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'is_completed',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    taskListId: {\n      model: 'TaskList',\n      required: true,\n      columnName: 'task_list_id',\n    },\n    linkedCardId: {\n      model: 'Card',\n      columnName: 'linked_card_id',\n    },\n    assigneeUserId: {\n      model: 'User',\n      columnName: 'assignee_user_id',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/models/TaskList.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * TaskList.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     TaskList:\n *       type: object\n *       required:\n *         - id\n *         - cardId\n *         - position\n *         - name\n *         - showOnFrontOfCard\n *         - hideCompletedTasks\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the task list\n *           example: \"1357158568008091264\"\n *         cardId:\n *           type: string\n *           description: ID of the card the task list belongs to\n *           example: \"1357158568008091265\"\n *         position:\n *           type: number\n *           description: Position of the task list within the card\n *           example: 65536\n *         name:\n *           type: string\n *           description: Name/title of the task list\n *           example: Development Tasks\n *         showOnFrontOfCard:\n *           type: boolean\n *           default: true\n *           description: Whether to show the task list on the front of the card\n *           example: true\n *         hideCompletedTasks:\n *           type: boolean\n *           default: false\n *           description: Whether to hide completed tasks\n *           example: false\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the task list was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the task list was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nmodule.exports = {\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    position: {\n      type: 'number',\n      required: true,\n    },\n    name: {\n      type: 'string',\n      required: true,\n    },\n    showOnFrontOfCard: {\n      type: 'boolean',\n      defaultsTo: true,\n      columnName: 'show_on_front_of_card',\n    },\n    hideCompletedTasks: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'hide_completed_tasks',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    cardId: {\n      model: 'Card',\n      required: true,\n      columnName: 'card_id',\n    },\n  },\n\n  tableName: 'task_list',\n};\n"
  },
  {
    "path": "server/api/models/UploadedFile.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * UploadedFile.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\nconst Types = {\n  USER_AVATAR: 'userAvatar',\n  BACKGROUND_IMAGE: 'backgroundImage',\n  ATTACHMENT: 'attachment',\n};\n\nmodule.exports = {\n  Types,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    type: {\n      type: 'string',\n      isIn: Object.values(Types),\n      required: true,\n    },\n    referencesTotal: {\n      type: 'number',\n      allowNull: true,\n      defaultsTo: 0,\n      columnName: 'references_total',\n    },\n    mimeType: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n      columnName: 'mime_type',\n    },\n    size: {\n      type: 'string',\n      required: true,\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n  },\n\n  tableName: 'uploaded_file',\n};\n"
  },
  {
    "path": "server/api/models/User.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * User.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     User:\n *       type: object\n *       required:\n *         - id\n *         - role\n *         - name\n *         - username\n *         - avatar\n *         - phone\n *         - organization\n *         - isDeactivated\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the user\n *           example: \"1357158568008091264\"\n *         email:\n *           type: string\n *           format: email\n *           description: Email address for login and notifications (private field)\n *           example: john.doe@example.com\n *         role:\n *           type: string\n *           enum: [admin, projectOwner, boardUser]\n *           description: User role defining access permissions\n *           example: admin\n *         name:\n *           type: string\n *           description: Full display name of the user\n *           example: John Doe\n *         username:\n *           type: string\n *           minLength: 3\n *           maxLength: 64\n *           pattern: \"^[a-z0-9._-]*$\"\n *           nullable: true\n *           description: Unique username for user identification\n *           example: john_doe\n *         avatar:\n *           type: object\n *           required:\n *             - url\n *             - thumbnailUrls\n *           nullable: true\n *           description: Avatar information for the user with generated URLs\n *           properties:\n *             url:\n *               type: string\n *               format: uri\n *               description: URL to the full-size avatar image\n *               example: https://storage.example.com/user-avatars/1357158568008091264/original.jpg\n *             thumbnailUrls:\n *               type: object\n *               required:\n *                 - cover180\n *               description: URLs for different thumbnail sizes\n *               properties:\n *                 cover180:\n *                   type: string\n *                   format: uri\n *                   description: URL for 180px thumbnail version\n *                   example: https://storage.example.com/user-avatars/1357158568008091264/cover-180.jpg\n *         gravatarUrl:\n *           type: string\n *           format: uri\n *           description: Gravatar URL for the user (conditionally added if configured)\n *           example: https://www.gravatar.com/avatar/abc123\n *         phone:\n *           type: string\n *           nullable: true\n *           description: Contact phone number\n *           example: \"+1234567890\"\n *         organization:\n *           type: string\n *           nullable: true\n *           description: Organization or company name\n *           example: Acme Corporation\n *         language:\n *           type: string\n *           enum: [ar-YE, bg-BG, ca-ES, cs-CZ, da-DK, de-DE, el-GR, en-GB, en-US, es-ES, et-EE, fa-IR, fi-FI, fr-FR, hu-HU, id-ID, it-IT, ja-JP, ko-KR, nl-NL, pl-PL, pt-BR, pt-PT, ro-RO, ru-RU, sk-SK, sr-Cyrl-RS, sr-Latn-RS, sv-SE, tr-TR, uk-UA, uz-UZ, vi-VN, zh-CN, zh-TW]\n *           nullable: true\n *           description: Preferred language for user interface and notifications (personal field)\n *           example: en-US\n *         apiKeyPrefix:\n *           type: string\n *           nullable: true\n *           description: Prefix of the API key for display purposes (private field)\n *           example: D89VszVs\n *         subscribeToOwnCards:\n *           type: boolean\n *           default: false\n *           description: Whether the user subscribes to their own cards (personal field)\n *           example: false\n *         subscribeToCardWhenCommenting:\n *           type: boolean\n *           default: true\n *           description: Whether the user subscribes to cards when commenting (personal field)\n *           example: true\n *         turnOffRecentCardHighlighting:\n *           type: boolean\n *           default: false\n *           description: Whether recent card highlighting is disabled (personal field)\n *           example: false\n *         enableFavoritesByDefault:\n *           type: boolean\n *           default: true\n *           description: Whether favorites are enabled by default (personal field)\n *           example: true\n *         defaultEditorMode:\n *           type: string\n *           enum: [wysiwyg, markup]\n *           default: wysiwyg\n *           description: Default markdown editor mode (personal field)\n *           example: wysiwyg\n *         defaultHomeView:\n *           type: string\n *           enum: [gridProjects, groupedProjects]\n *           default: groupedProjects\n *           description: Default view mode for the home page (personal field)\n *           example: groupedProjects\n *         defaultProjectsOrder:\n *           type: string\n *           enum: [byDefault, alphabetically, byCreationTime]\n *           default: byDefault\n *           description: Default sort order for projects display (personal field)\n *           example: byDefault\n *         isSsoUser:\n *           type: boolean\n *           default: false\n *           description: Whether the user is SSO user (private field)\n *           example: false\n *         isDeactivated:\n *           type: boolean\n *           default: false\n *           description: Whether the user account is deactivated and cannot log in\n *           example: false\n *         isDefaultAdmin:\n *           type: boolean\n *           description: Whether the user is the default admin (visible only to current user or admin)\n *           example: false\n *         lockedFieldNames:\n *           type: array\n *           description: List of fields locked from editing (visible only to current user or admin)\n *           items:\n *             type: string\n *           example: [email, password, name]\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the user was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the user was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst Roles = {\n  ADMIN: 'admin',\n  PROJECT_OWNER: 'projectOwner',\n  BOARD_USER: 'boardUser',\n};\n\n// TODO: should not be here\nconst EditorModes = {\n  WYSIWYG: 'wysiwyg',\n  MARKUP: 'markup',\n};\n\nconst HomeViews = {\n  GRID_PROJECTS: 'gridProjects',\n  GROUPED_PROJECTS: 'groupedProjects',\n};\n\nconst ProjectOrders = {\n  BY_DEFAULT: 'byDefault',\n  ALPHABETICALLY: 'alphabetically',\n  BY_CREATION_TIME: 'byCreationTime',\n};\n\nconst LANGUAGES = [\n  'ar-YE',\n  'bg-BG',\n  'ca-ES',\n  'cs-CZ',\n  'da-DK',\n  'de-DE',\n  'el-GR',\n  'en-GB',\n  'en-US',\n  'es-ES',\n  'et-EE',\n  'fa-IR',\n  'fi-FI',\n  'fr-FR',\n  'hu-HU',\n  'id-ID',\n  'it-IT',\n  'ja-JP',\n  'ko-KR',\n  'nl-NL',\n  'pl-PL',\n  'pt-BR',\n  'pt-PT',\n  'ro-RO',\n  'ru-RU',\n  'sk-SK',\n  'sr-Cyrl-RS',\n  'sr-Latn-RS',\n  'sv-SE',\n  'tr-TR',\n  'uk-UA',\n  'uz-UZ',\n  'vi-VN',\n  'zh-CN',\n  'zh-TW',\n];\n\n// TODO: find better way to handle apiKeyHash and apiKeyCreatedAt\nconst PRIVATE_FIELD_NAMES = ['email', 'apiKeyPrefix', 'apiKeyHash', 'isSsoUser', 'apiKeyCreatedAt'];\n\nconst PERSONAL_FIELD_NAMES = [\n  'language',\n  'subscribeToOwnCards',\n  'subscribeToCardWhenCommenting',\n  'turnOffRecentCardHighlighting',\n  'enableFavoritesByDefault',\n  'defaultEditorMode',\n  'defaultHomeView',\n  'defaultProjectsOrder',\n];\n\nconst INTERNAL = {\n  id: '_internal',\n  role: Roles.ADMIN,\n};\n\nconst OIDC = {\n  id: '_oidc',\n  role: Roles.ADMIN,\n};\n\nmodule.exports = {\n  Roles,\n  EditorModes,\n  HomeViews,\n  ProjectOrders,\n  LANGUAGES,\n  PRIVATE_FIELD_NAMES,\n  PERSONAL_FIELD_NAMES,\n  INTERNAL,\n  OIDC,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    email: {\n      type: 'string',\n      isEmail: true,\n      required: true,\n    },\n    password: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n    },\n    role: {\n      type: 'string',\n      isIn: Object.values(Roles),\n      required: true,\n    },\n    name: {\n      type: 'string',\n      required: true,\n    },\n    username: {\n      type: 'string',\n      isNotEmptyString: true,\n      minLength: 3,\n      maxLength: 64,\n      regex: /^[a-z0-9._-]*$/,\n      allowNull: true,\n    },\n    avatar: {\n      type: 'json',\n    },\n    phone: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n    },\n    organization: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n    },\n    language: {\n      type: 'string',\n      isIn: LANGUAGES,\n      allowNull: true,\n    },\n    apiKeyPrefix: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n      columnName: 'api_key_prefix',\n    },\n    apiKeyHash: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n      columnName: 'api_key_hash',\n    },\n    subscribeToOwnCards: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'subscribe_to_own_cards',\n    },\n    subscribeToCardWhenCommenting: {\n      type: 'boolean',\n      defaultsTo: true,\n      columnName: 'subscribe_to_card_when_commenting',\n    },\n    turnOffRecentCardHighlighting: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'turn_off_recent_card_highlighting',\n    },\n    enableFavoritesByDefault: {\n      type: 'boolean',\n      defaultsTo: true,\n      columnName: 'enable_favorites_by_default',\n    },\n    defaultEditorMode: {\n      type: 'string',\n      isIn: Object.values(EditorModes),\n      defaultsTo: EditorModes.WYSIWYG,\n      columnName: 'default_editor_mode',\n    },\n    defaultHomeView: {\n      type: 'string',\n      isIn: Object.values(HomeViews),\n      defaultsTo: HomeViews.GROUPED_PROJECTS,\n      columnName: 'default_home_view',\n    },\n    defaultProjectsOrder: {\n      type: 'string',\n      isIn: Object.values(ProjectOrders),\n      defaultsTo: ProjectOrders.BY_DEFAULT,\n      columnName: 'default_projects_order',\n    },\n    termsSignature: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n      columnName: 'terms_signature',\n    },\n    isSsoUser: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'is_sso_user',\n    },\n    isDeactivated: {\n      type: 'boolean',\n      defaultsTo: false,\n      columnName: 'is_deactivated',\n    },\n    passwordChangedAt: {\n      type: 'ref',\n      columnName: 'password_changed_at',\n    },\n    apiKeyCreatedAt: {\n      type: 'ref',\n      columnName: 'api_key_created_at',\n    },\n    termsAcceptedAt: {\n      type: 'ref',\n      columnName: 'terms_accepted_at',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    managerProjects: {\n      collection: 'Project',\n      via: 'userId',\n      through: 'ProjectManager',\n    },\n    membershipBoards: {\n      collection: 'Board',\n      via: 'userId',\n      through: 'BoardMembership',\n    },\n    subscriptionCards: {\n      collection: 'Card',\n      via: 'userId',\n      through: 'CardSubscription',\n    },\n    membershipCards: {\n      collection: 'Card',\n      via: 'userId',\n      through: 'CardMembership',\n    },\n  },\n\n  tableName: 'user_account',\n};\n"
  },
  {
    "path": "server/api/models/Webhook.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * Webhook.js\n *\n * @description :: A model definition represents a database table/collection.\n * @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models\n */\n\n/**\n * @swagger\n * components:\n *   schemas:\n *     Webhook:\n *       type: object\n *       required:\n *         - id\n *         - name\n *         - url\n *         - accessToken\n *         - events\n *         - excludedEvents\n *         - createdAt\n *         - updatedAt\n *       properties:\n *         id:\n *           type: string\n *           description: Unique identifier for the webhook\n *           example: \"1357158568008091264\"\n *         name:\n *           type: string\n *           description: Name/title of the webhook\n *           example: Webhook Updates\n *         url:\n *           type: string\n *           description: URL endpoint for the webhook\n *           example: https://webhook.example.com/planka\n *         accessToken:\n *           type: string\n *           nullable: true\n *           description: Access token for webhook authentication\n *           example: secret_token_123\n *         events:\n *           type: array\n *           nullable: true\n *           description: List of events that trigger the webhook\n *           items:\n *             type: string\n *           example: [cardCreate, cardUpdate, cardDelete]\n *         excludedEvents:\n *           type: array\n *           nullable: true\n *           description: List of events excluded from the webhook\n *           items:\n *             type: string\n *           example: [userCreate, userUpdate, userDelete]\n *         createdAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the webhook was created\n *           example: 2024-01-01T00:00:00.000Z\n *         updatedAt:\n *           type: string\n *           format: date-time\n *           nullable: true\n *           description: When the webhook was last updated\n *           example: 2024-01-01T00:00:00.000Z\n */\n\nconst Events = {\n  ACTION_CREATE: 'actionCreate',\n\n  ATTACHMENT_CREATE: 'attachmentCreate',\n  ATTACHMENT_UPDATE: 'attachmentUpdate',\n  ATTACHMENT_DELETE: 'attachmentDelete',\n\n  BACKGROUND_IMAGE_CREATE: 'backgroundImageCreate',\n  BACKGROUND_IMAGE_DELETE: 'backgroundImageDelete',\n\n  BASE_CUSTOM_FIELD_GROUP_CREATE: 'baseCustomFieldGroupCreate',\n  BASE_CUSTOM_FIELD_GROUP_UPDATE: 'baseCustomFieldGroupUpdate',\n  BASE_CUSTOM_FIELD_GROUP_DELETE: 'baseCustomFieldGroupDelete',\n\n  BOARD_CREATE: 'boardCreate',\n  BOARD_UPDATE: 'boardUpdate',\n  BOARD_DELETE: 'boardDelete',\n\n  BOARD_MEMBERSHIP_CREATE: 'boardMembershipCreate',\n  BOARD_MEMBERSHIP_UPDATE: 'boardMembershipUpdate',\n  BOARD_MEMBERSHIP_DELETE: 'boardMembershipDelete',\n\n  CARD_CREATE: 'cardCreate',\n  CARD_UPDATE: 'cardUpdate',\n  CARD_DELETE: 'cardDelete',\n\n  CARD_LABEL_CREATE: 'cardLabelCreate',\n  CARD_LABEL_DELETE: 'cardLabelDelete',\n\n  CARD_MEMBERSHIP_CREATE: 'cardMembershipCreate',\n  CARD_MEMBERSHIP_DELETE: 'cardMembershipDelete',\n\n  COMMENT_CREATE: 'commentCreate',\n  COMMENT_UPDATE: 'commentUpdate',\n  COMMENT_DELETE: 'commentDelete',\n\n  CONFIG_UPDATE: 'configUpdate',\n\n  CUSTOM_FIELD_CREATE: 'customFieldCreate',\n  CUSTOM_FIELD_UPDATE: 'customFieldUpdate',\n  CUSTOM_FIELD_DELETE: 'customFieldDelete',\n\n  CUSTOM_FIELD_GROUP_CREATE: 'customFieldGroupCreate',\n  CUSTOM_FIELD_GROUP_UPDATE: 'customFieldGroupUpdate',\n  CUSTOM_FIELD_GROUP_DELETE: 'customFieldGroupDelete',\n\n  CUSTOM_FIELD_VALUE_UPDATE: 'customFieldValueUpdate',\n  CUSTOM_FIELD_VALUE_DELETE: 'customFieldValueDelete',\n\n  LABEL_CREATE: 'labelCreate',\n  LABEL_UPDATE: 'labelUpdate',\n  LABEL_DELETE: 'labelDelete',\n\n  LIST_CREATE: 'listCreate',\n  LIST_UPDATE: 'listUpdate',\n  LIST_CLEAR: 'listClear',\n  LIST_DELETE: 'listDelete',\n\n  NOTIFICATION_CREATE: 'notificationCreate',\n  NOTIFICATION_UPDATE: 'notificationUpdate',\n\n  NOTIFICATION_SERVICE_CREATE: 'notificationServiceCreate',\n  NOTIFICATION_SERVICE_UPDATE: 'notificationServiceUpdate',\n  NOTIFICATION_SERVICE_DELETE: 'notificationServiceDelete',\n\n  PROJECT_CREATE: 'projectCreate',\n  PROJECT_UPDATE: 'projectUpdate',\n  PROJECT_DELETE: 'projectDelete',\n\n  PROJECT_MANAGER_CREATE: 'projectManagerCreate',\n  PROJECT_MANAGER_DELETE: 'projectManagerDelete',\n\n  TASK_CREATE: 'taskCreate',\n  TASK_UPDATE: 'taskUpdate',\n  TASK_DELETE: 'taskDelete',\n\n  TASK_LIST_CREATE: 'taskListCreate',\n  TASK_LIST_UPDATE: 'taskListUpdate',\n  TASK_LIST_DELETE: 'taskListDelete',\n\n  USER_CREATE: 'userCreate',\n  USER_UPDATE: 'userUpdate',\n  USER_DELETE: 'userDelete',\n\n  WEBHOOK_CREATE: 'webhookCreate',\n  WEBHOOK_UPDATE: 'webhookUpdate',\n  WEBHOOK_DELETE: 'webhookDelete',\n};\n\nmodule.exports = {\n  Events,\n\n  attributes: {\n    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗\n    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗\n    //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝\n\n    name: {\n      type: 'string',\n      required: true,\n    },\n    url: {\n      type: 'string',\n      required: true,\n    },\n    accessToken: {\n      type: 'string',\n      isNotEmptyString: true,\n      allowNull: true,\n      columnName: 'access_token',\n    },\n    events: {\n      type: 'ref',\n      columnType: 'text[]',\n    },\n    excludedEvents: {\n      type: 'ref',\n      columnType: 'text[]',\n      columnName: 'excluded_events',\n    },\n\n    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗\n    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗\n    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝\n\n    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\n    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗\n    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝\n\n    boardId: {\n      model: 'Board',\n      columnName: 'board_id',\n    },\n  },\n};\n"
  },
  {
    "path": "server/api/policies/.gitkeep",
    "content": ""
  },
  {
    "path": "server/api/policies/is-admin-or-project-owner.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = async function isAuthenticated(req, res, proceed) {\n  if (!sails.helpers.users.isAdminOrProjectOwner(req.currentUser)) {\n    return res.notFound(); // Forbidden\n  }\n\n  return proceed();\n};\n"
  },
  {
    "path": "server/api/policies/is-admin.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = async function isAuthenticated(req, res, proceed) {\n  if (req.currentUser.role !== User.Roles.ADMIN) {\n    return res.notFound(); // Forbidden\n  }\n\n  return proceed();\n};\n"
  },
  {
    "path": "server/api/policies/is-authenticated.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = async function isAuthenticated(req, res, proceed) {\n  if (!req.currentUser) {\n    // TODO: provide separate error for API keys?\n    return res.unauthorized('Access token is missing, invalid or expired');\n  }\n\n  return proceed();\n};\n"
  },
  {
    "path": "server/api/policies/is-external.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = async function isExternal(req, res, proceed) {\n  if (req.currentUser.id === User.INTERNAL.id) {\n    return res.notFound(); // Forbidden\n  }\n\n  return proceed();\n};\n"
  },
  {
    "path": "server/api/policies/is-internal.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = async function isInternal(req, res, proceed) {\n  if (req.currentUser.id !== User.INTERNAL.id) {\n    return res.notFound(); // Forbidden\n  }\n\n  return proceed();\n};\n"
  },
  {
    "path": "server/api/policies/is-session.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports = async function isSession(req, res, proceed) {\n  if (!req.currentSession) {\n    return res.notFound(); // Forbidden\n  }\n\n  return proceed();\n};\n"
  },
  {
    "path": "server/api/responses/.gitkeep",
    "content": ""
  },
  {
    "path": "server/api/responses/conflict.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * conflict.js\n *\n * A custom response.\n *\n * Example usage:\n * ```\n *     return res.conflict();\n *     // -or-\n *     return res.conflict(optionalData);\n * ```\n *\n * Or with actions2:\n * ```\n *     exits: {\n *       somethingHappened: {\n *         responseType: 'conflict'\n *       }\n *     }\n * ```\n *\n * ```\n *     throw 'somethingHappened';\n *     // -or-\n *     throw { somethingHappened: optionalData }\n * ```\n */\n\n/**\n * @swagger\n * components:\n *   responses:\n *     Conflict:\n *       description: Request conflicts with current state of the resource\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - code\n *               - message\n *             properties:\n *               code:\n *                 type: string\n *                 description: Error code\n *                 example: E_CONFLICT\n *               message:\n *                 type: string\n *                 description: Error message\n *                 example: Resource already exists\n */\n\nmodule.exports = function conflict(message) {\n  const { res } = this;\n\n  return res.status(409).json({\n    code: 'E_CONFLICT',\n    message,\n  });\n};\n"
  },
  {
    "path": "server/api/responses/forbidden.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * forbidden.js\n *\n * A custom response.\n *\n * Example usage:\n * ```\n *     return res.forbidden();\n *     // -or-\n *     return res.forbidden(optionalData);\n * ```\n *\n * Or with actions2:\n * ```\n *     exits: {\n *       somethingHappened: {\n *         responseType: 'forbidden'\n *       }\n *     }\n * ```\n *\n * ```\n *     throw 'somethingHappened';\n *     // -or-\n *     throw { somethingHappened: optionalData }\n * ```\n */\n\n/**\n * @swagger\n * components:\n *   responses:\n *     Forbidden:\n *       description: Access forbidden - insufficient permissions\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - code\n *               - message\n *             properties:\n *               code:\n *                 type: string\n *                 description: Error code\n *                 example: E_FORBIDDEN\n *               message:\n *                 type: string\n *                 description: Error message\n *                 example: Not enough rights\n */\n\nmodule.exports = function forbidden(message) {\n  const { res } = this;\n\n  const data = {\n    code: 'E_FORBIDDEN',\n  };\n\n  if (_.isPlainObject(message)) {\n    Object.assign(data, message);\n  } else {\n    data.message = message;\n  }\n\n  return res.status(403).json(data);\n};\n"
  },
  {
    "path": "server/api/responses/notFound.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * notFound.js\n *\n * A custom response.\n *\n * Example usage:\n * ```\n *     return res.notFound();\n *     // -or-\n *     return res.notFound(optionalData);\n * ```\n *\n * Or with actions2:\n * ```\n *     exits: {\n *       somethingHappened: {\n *         responseType: 'notFound'\n *       }\n *     }\n * ```\n *\n * ```\n *     throw 'somethingHappened';\n *     // -or-\n *     throw { somethingHappened: optionalData }\n * ```\n */\n\n/**\n * @swagger\n * components:\n *   responses:\n *     NotFound:\n *       description: Resource not found\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - code\n *               - message\n *             properties:\n *               code:\n *                 type: string\n *                 description: Error code\n *                 example: E_NOT_FOUND\n *               message:\n *                 type: string\n *                 description: Error message\n *                 example: Resource not found\n */\n\nmodule.exports = function notFound(message) {\n  const { res } = this;\n\n  return res.status(404).json({\n    code: 'E_NOT_FOUND',\n    message,\n  });\n};\n"
  },
  {
    "path": "server/api/responses/unauthorized.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * unauthorized.js\n *\n * A custom response.\n *\n * Example usage:\n * ```\n *     return res.unauthorized();\n *     // -or-\n *     return res.unauthorized(optionalData);\n * ```\n *\n * Or with actions2:\n * ```\n *     exits: {\n *       somethingHappened: {\n *         responseType: 'unauthorized'\n *       }\n *     }\n * ```\n *\n * ```\n *     throw 'somethingHappened';\n *     // -or-\n *     throw { somethingHappened: optionalData }\n * ```\n */\n\n/**\n * @swagger\n * components:\n *   responses:\n *     Unauthorized:\n *       description: Authentication required or invalid credentials\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - code\n *               - message\n *             properties:\n *               code:\n *                 type: string\n *                 description: Error code\n *                 example: E_UNAUTHORIZED\n *               message:\n *                 type: string\n *                 description: Error message\n *                 example: Access token is missing, invalid or expired\n */\n\nmodule.exports = function unauthorized(message) {\n  const { res } = this;\n\n  return res.status(401).json({\n    code: 'E_UNAUTHORIZED',\n    message,\n  });\n};\n"
  },
  {
    "path": "server/api/responses/unprocessableEntity.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * unprocessableEntity.js\n *\n * A custom response.\n *\n * Example usage:\n * ```\n *     return res.unprocessableEntity();\n *     // -or-\n *     return res.unprocessableEntity(optionalData);\n * ```\n *\n * Or with actions2:\n * ```\n *     exits: {\n *       somethingHappened: {\n *         responseType: 'unprocessableEntity'\n *       }\n *     }\n * ```\n *\n * ```\n *     throw 'somethingHappened';\n *     // -or-\n *     throw { somethingHappened: optionalData }\n * ```\n */\n\n/**\n * @swagger\n * components:\n *   responses:\n *     UnprocessableEntity:\n *       description: Request contains semantic errors or validation failures\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - code\n *               - message\n *             properties:\n *               code:\n *                 type: string\n *                 description: Error code\n *                 example: E_UNPROCESSABLE_ENTITY\n *               message:\n *                 type: string\n *                 description: Error message\n *                 example: Validation failed\n */\n\nmodule.exports = function unprocessableEntity(message) {\n  const { res } = this;\n\n  return res.status(422).json({\n    code: 'E_UNPROCESSABLE_ENTITY',\n    message,\n  });\n};\n"
  },
  {
    "path": "server/api/responses/validationError.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * validationError.js\n *\n * A custom response.\n *\n * Example usage:\n * ```\n *     return res.validationError();\n *     // -or-\n *     return res.validationError(optionalData);\n * ```\n *\n * Or with actions2:\n * ```\n *     exits: {\n *       somethingHappened: {\n *         responseType: 'validationError'\n *       }\n *     }\n * ```\n *\n * ```\n *     throw 'somethingHappened';\n *     // -or-\n *     throw { somethingHappened: optionalData }\n * ```\n */\n\n/**\n * @swagger\n * components:\n *   responses:\n *     ValidationError:\n *       description: Request validation failed due to missing or invalid parameters\n *       content:\n *         application/json:\n *           schema:\n *             type: object\n *             required:\n *               - code\n *               - problems\n *               - message\n *             properties:\n *               code:\n *                 type: string\n *                 description: Error code\n *                 example: E_MISSING_OR_INVALID_PARAMS\n *               problems:\n *                 type: array\n *                 description: Array of specific validation error messages\n *                 items:\n *                   type: string\n *                 example: [\n *                   \"\\\"emailOrUsername\\\" is required, but it was not defined.\",\n *                   \"\\\"password\\\" is required, but it was not defined.\"\n *                 ]\n *               message:\n *                 type: string\n *                 description: Error message\n *                 example: The server could not fulfill this request (`POST /api/access-tokens`) due to 2 missing or invalid parameters.\n */\n\nmodule.exports = function validationError() {};\n"
  },
  {
    "path": "server/app.js",
    "content": "/**\n * app.js\n *\n * Use `app.js` to run your app without `sails lift`.\n * To start the server, run: `node app.js`.\n *\n * This is handy in situations where the sails CLI is not relevant or useful,\n * such as when you deploy to a server, or a PaaS like Heroku.\n *\n * For example:\n *   => `node app.js`\n *   => `npm start`\n *   => `forever start app.js`\n *   => `node debug app.js`\n *\n * The same command-line arguments and env vars are supported, e.g.:\n * `NODE_ENV=production node app.js --port=80 --verbose`\n *\n * For more information see:\n *   https://sailsjs.com/anatomy/app.js\n */\n\nconst dotenv = require('dotenv');\n\n// Ensure we're in the project directory, so cwd-relative paths work as expected\n// no matter where we actually lift from.\n// > Note: This is not required in order to lift, but it is a convenient default.\nprocess.chdir(__dirname);\n\ndotenv.config({ quiet: true });\n\n// Attempt to import `sails` dependency, as well as `rc` (for loading `.sailsrc` files).\nlet sails;\nlet rc;\n\ntry {\n  /* eslint-disable global-require */\n  sails = require('sails');\n  rc = require('sails/accessible/rc');\n  /* eslint-enable global-require */\n} catch (error) {\n  /* eslint-disable no-console */\n  console.error(\"Encountered an error when attempting to require('sails'):\");\n  console.error(error.stack);\n  console.error('--');\n  console.error('To run an app using `node app.js`, you need to have Sails installed');\n  console.error(\"locally (`./node_modules/sails`).  To do that, just make sure you're\");\n  console.error('in the same directory as your app and run `npm install`.');\n  console.error();\n  console.error('If Sails is installed globally (i.e. `npm install -g sails`) you can');\n  console.error('also run this app with `sails lift`.  Running with `sails lift` will');\n  console.error('not run this file (`app.js`), but it will do exactly the same thing.');\n  console.error(\"(It even uses your app directory's local Sails install, if possible.)\");\n  /* eslint-enable no-console */\n\n  process.exit(1);\n}\n\n// Start server\nsails.lift(rc('sails'));\n"
  },
  {
    "path": "server/build.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n// eslint-disable-next-line import/no-extraneous-dependencies\nconst ignore = require('ignore');\n// eslint-disable-next-line import/no-extraneous-dependencies\nconst swaggerJsdoc = require('swagger-jsdoc');\n\nconst swaggerConfig = require('./config/swagger');\n\nconst OUT_DIR = 'dist';\n\nconst ig = ignore();\nif (fs.existsSync('.buildignore')) {\n  const patterns = fs.readFileSync('.buildignore', 'utf8');\n  ig.add(patterns);\n}\n\nif (fs.existsSync(OUT_DIR)) {\n  fs.rmSync(OUT_DIR, { recursive: true, force: true });\n}\n\nfs.mkdirSync(OUT_DIR, { recursive: true });\n\nconst build = (src, dest) => {\n  const dirents = fs.readdirSync(src, { withFileTypes: true });\n\n  // eslint-disable-next-line no-restricted-syntax\n  for (const dirent of dirents) {\n    const srcPath = path.join(src, dirent.name);\n\n    if (ig.ignores(srcPath)) {\n      continue; // eslint-disable-line no-continue\n    }\n\n    const destPath = path.join(dest, dirent.name);\n\n    if (dirent.isDirectory()) {\n      fs.mkdirSync(destPath, { recursive: true });\n      build(srcPath, destPath);\n    } else {\n      fs.copyFileSync(srcPath, destPath);\n    }\n  }\n};\n\nbuild('./', OUT_DIR);\n\nconst specification = swaggerJsdoc(swaggerConfig);\nfs.writeFileSync(path.join(OUT_DIR, 'swagger.json'), JSON.stringify(specification, null, 2));\n"
  },
  {
    "path": "server/config/blueprints.js",
    "content": "/**\n * Blueprint API Configuration\n * (sails.config.blueprints)\n *\n * For background on the blueprint API in Sails, check out:\n * https://sailsjs.com/docs/reference/blueprint-api\n *\n * For details and more available options, see:\n * https://sailsjs.com/config/blueprints\n */\n\nmodule.exports.blueprints = {\n  /**\n   *\n   * Automatically expose implicit routes for every action in your app?\n   *\n   */\n\n  // actions: false,\n\n  /**\n   *\n   * Automatically expose RESTful routes for your models?\n   *\n   */\n\n  // rest: true,\n\n  /**\n   *\n   * Automatically expose CRUD \"shortcut\" routes to GET requests?\n   * (These are enabled by default in development only.)\n   *\n   */\n\n  shortcuts: false,\n};\n"
  },
  {
    "path": "server/config/bootstrap.js",
    "content": "/**\n * Seed Function\n * (sails.config.bootstrap)\n *\n * A function that runs just before your Sails app gets lifted.\n * > Need more flexibility?  You can also create a hook.\n *\n * For more information on seeding your app with fake data, check out:\n * https://sailsjs.com/config/bootstrap\n */\n\nmodule.exports.bootstrap = async () => {\n  // By convention, this is a good place to set up fake data during development.\n  //\n  // For example:\n  // ```\n  // // Set up fake development data (or if we already have some, avast)\n  // if (await User.count() > 0) {\n  //   return;\n  // }\n  //\n  // await User.createEach([\n  //   { emailAddress: 'ry@example.com', fullName: 'Ryan Dahl', },\n  //   { emailAddress: 'rachael@example.com', fullName: 'Rachael Shaw', },\n  //   // etc.\n  // ]);\n  // ```\n};\n"
  },
  {
    "path": "server/config/custom.js",
    "content": "/**\n * Custom configuration\n * (sails.config.custom)\n *\n * One-off settings specific to your application.\n *\n * For more information on custom configuration, visit:\n * https://sailsjs.com/config/custom\n */\n\nconst path = require('path');\nconst { URL } = require('url');\nconst bytes = require('bytes');\nconst sails = require('sails');\n\nconst version = require('../version');\n\nconst envToNumber = (value) => {\n  if (!value) {\n    return null;\n  }\n\n  const number = parseInt(value, 10);\n  return Number.isNaN(number) ? null : number;\n};\n\nconst envToBytes = (value) => bytes(value);\n\nconst envToArray = (value) => (value ? value.split(',') : []);\n\nconst baseUrl = envToArray(process.env.BASE_URL)[0];\nconst parsedBasedUrl = new URL(baseUrl);\n\nmodule.exports.custom = {\n  /**\n   *\n   * Any other custom config this Sails app should use during development.\n   *\n   */\n\n  version,\n\n  baseUrl,\n  baseUrlPath: parsedBasedUrl.pathname.replace(/\\/$/, ''), // Remove trailing slash\n  baseUrlSecure: parsedBasedUrl.protocol === 'https:',\n\n  maxUploadFileSize: envToBytes(process.env.MAX_UPLOAD_FILE_SIZE),\n  tokenExpiresIn: (parseInt(process.env.TOKEN_EXPIRES_IN, 10) || 365) * 24 * 60 * 60,\n\n  storageLimit: envToBytes(process.env.STORAGE_LIMIT),\n  activeUsersLimit: envToNumber(process.env.ACTIVE_USERS_LIMIT),\n\n  // Location to receive uploaded files in. Default (non-string value) is a Sails-specific location.\n  uploadsTempPath: null,\n  uploadsBasePath: path.join(sails.config.appPath, 'data'),\n\n  faviconsPathSegment: 'protected/favicons',\n  userAvatarsPathSegment: 'protected/user-avatars',\n  backgroundImagesPathSegment: 'protected/background-images',\n  attachmentsPathSegment: 'private/attachments',\n\n  defaultAdminEmail:\n    process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase(),\n\n  showDetailedAuthErrors: process.env.SHOW_DETAILED_AUTH_ERRORS === 'true',\n  outgoingProxy: process.env.OUTGOING_PROXY,\n  swaggerExposed: process.env.SWAGGER_EXPOSED === 'true',\n\n  s3Endpoint: process.env.S3_ENDPOINT,\n  s3Region: process.env.S3_REGION,\n  s3AccessKeyId: process.env.S3_ACCESS_KEY_ID,\n  s3SecretAccessKey: process.env.S3_SECRET_ACCESS_KEY,\n  s3Bucket: process.env.S3_BUCKET,\n  s3ForcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true',\n\n  oidcIssuer: process.env.OIDC_ISSUER,\n  oidcClientId: process.env.OIDC_CLIENT_ID,\n  oidcClientSecret: process.env.OIDC_CLIENT_SECRET,\n  oidcUseOauthCallback: process.env.OIDC_USE_OAUTH_CALLBACK === 'true',\n  oidcIdTokenSignedResponseAlg: process.env.OIDC_ID_TOKEN_SIGNED_RESPONSE_ALG,\n  oidcUserinfoSignedResponseAlg: process.env.OIDC_USERINFO_SIGNED_RESPONSE_ALG,\n  oidcScopes: process.env.OIDC_SCOPES || 'openid email profile',\n  oidcResponseMode: process.env.OIDC_RESPONSE_MODE || 'fragment',\n  oidcUseDefaultResponseMode: process.env.OIDC_USE_DEFAULT_RESPONSE_MODE === 'true',\n  oidcAdminRoles: envToArray(process.env.OIDC_ADMIN_ROLES),\n  oidcProjectOwnerRoles: envToArray(process.env.OIDC_PROJECT_OWNER_ROLES),\n  oidcBoardUserRoles: envToArray(process.env.OIDC_BOARD_USER_ROLES),\n  oidcClaimsSource: process.env.OIDC_CLAIMS_SOURCE || 'userinfo',\n  oidcEmailAttribute: process.env.OIDC_EMAIL_ATTRIBUTE || 'email',\n  oidcNameAttribute: process.env.OIDC_NAME_ATTRIBUTE || 'name',\n  oidcUsernameAttribute: process.env.OIDC_USERNAME_ATTRIBUTE || 'preferred_username',\n  oidcRolesAttribute: process.env.OIDC_ROLES_ATTRIBUTE || 'groups',\n  oidcIgnoreUsername: process.env.OIDC_IGNORE_USERNAME === 'true',\n  oidcIgnoreRoles: process.env.OIDC_IGNORE_ROLES === 'true',\n  oidcEnforced: process.env.OIDC_ENFORCED === 'true',\n  oidcTimeout: envToNumber(process.env.OIDC_TIMEOUT),\n  oidcDebug: process.env.OIDC_DEBUG === 'true',\n\n  // TODO: move client base url to environment variable?\n  oidcRedirectUri: `${\n    sails.config.environment === 'production' ? baseUrl : 'http://localhost:3000'\n  }/oidc-callback`,\n\n  smtpHost: process.env.SMTP_HOST,\n  smtpPort: process.env.SMTP_PORT || 587,\n  smtpName: process.env.SMTP_NAME,\n  smtpSecure: process.env.SMTP_SECURE === 'true',\n  smtpTlsRejectUnauthorized: process.env.SMTP_TLS_REJECT_UNAUTHORIZED !== 'false',\n  smtpUser: process.env.SMTP_USER,\n  smtpPassword: process.env.SMTP_PASSWORD,\n  smtpFrom: process.env.SMTP_FROM,\n\n  gravatarBaseUrl: process.env.GRAVATAR_BASE_URL,\n\n  /* Internal */\n\n  internalAccessToken: process.env.INTERNAL_ACCESS_TOKEN,\n  termsType: process.env.TERMS_TYPE || 'custom',\n  customerPanelUrl: process.env.CUSTOMER_PANEL_URL,\n  demoMode: process.env.DEMO_MODE === 'true',\n};\n"
  },
  {
    "path": "server/config/datastores.js",
    "content": "/**\n * Datastores\n * (sails.config.datastores)\n *\n * A set of datastore configurations which tell Sails where to fetch or save\n * data when you execute built-in model methods like `.find()` and `.create()`.\n *\n *  > This file is mainly useful for configuring your development database,\n *  > as well as any additional one-off databases used by individual models.\n *  > Ready to go live?  Head towards `config/env/production.js`.\n *\n * For more information on configuring datastores, check out:\n * https://sailsjs.com/config/datastores\n */\n\nconst pg = require('pg');\n\npg.types.setTypeParser(pg.types.builtins.TIMESTAMP, (value) => new Date(`${value}Z`));\n\nmodule.exports.datastores = {\n  /**\n   *\n   * Your app's default datastore.\n   *\n   * Sails apps read and write to local disk by default, using a built-in\n   * database adapter called `sails-disk`.  This feature is purely for\n   * convenience during development; since `sails-disk` is not designed for\n   * use in a production environment.\n   *\n   * To use a different db _in development_, follow the directions below.\n   * Otherwise, just leave the default datastore as-is, with no `adapter`.\n   *\n   * (For production configuration, see `config/env/production.js`.)\n   *\n   */\n\n  default: {\n    /**\n     *\n     * Want to use a different database during development?\n     *\n     * 1. Choose an adapter:\n     *    https://sailsjs.com/plugins/databases\n     *\n     * 2. Install it as a dependency of your Sails app.\n     *    (For example:  npm install sails-mysql --save)\n     *\n     * 3. Then pass it in, along with a connection URL.\n     *    (See https://sailsjs.com/config/datastores for help.)\n     *\n     */\n\n    adapter: 'sails-postgresql',\n    url: process.env.DATABASE_URL,\n  },\n};\n"
  },
  {
    "path": "server/config/env/production.js",
    "content": "/**\n * Production environment settings\n * (sails.config.*)\n *\n * What you see below is a quick outline of the built-in settings you need\n * to configure your Sails app for production.  The configuration in this file\n * is only used in your production environment, i.e. when you lift your app using:\n *\n * ```\n * NODE_ENV=production node app\n * ```\n *\n * > If you're using git as a version control solution for your Sails app,\n * > this file WILL BE COMMITTED to your repository by default, unless you add\n * > it to your .gitignore file.  If your repository will be publicly viewable,\n * > don't add private/sensitive data (like API secrets / db passwords) to this file!\n *\n * For more best practices and tips, see:\n * https://sailsjs.com/docs/concepts/deployment\n */\n\nconst { URL } = require('url');\n\nconst { customLogger } = require('../../utils/logger');\n\nconst origins = process.env.BASE_URL.split(',').map((baseUrl) => new URL(baseUrl).origin);\n\nmodule.exports = {\n  /**\n   *\n   * Tell Sails what database(s) it should use in production.\n   *\n   * (https://sailsjs.com/config/datastores)\n   *\n   */\n\n  datastores: {\n    /**\n     *\n     * Configure your default production database.\n     *\n     * 1. Choose an adapter:\n     *    https://sailsjs.com/plugins/databases\n     *\n     * 2. Install it as a dependency of your Sails app.\n     *    (For example:  npm install sails-mysql --save)\n     *\n     * 3. Then set it here (`adapter`), along with a connection URL (`url`)\n     *    and any other, adapter-specific customizations.\n     *    (See https://sailsjs.com/config/datastores for help.)\n     *\n     */\n\n    default: {\n      // adapter: 'sails-mysql',\n      // url: 'mysql://user:password@host:port/database',\n      /**\n       *\n       * More adapter-specific options\n       *\n       * > For example, for some hosted PostgreSQL providers (like Heroku), the\n       * > extra `ssl: true` option is mandatory and must be provided.\n       *\n       * More info:\n       * https://sailsjs.com/config/datastores\n       *\n       */\n      // ssl: true,\n    },\n  },\n\n  models: {\n    /**\n     *\n     * To help avoid accidents, Sails automatically sets the automigration\n     * strategy to \"safe\" when your app lifts in production mode.\n     * (This is just here as a reminder.)\n     *\n     * More info:\n     * https://sailsjs.com/docs/concepts/models-and-orm/model-settings#?migrate\n     *\n     */\n    // migrate: 'safe',\n    /**\n     *\n     * If, in production, this app has access to physical-layer CASCADE\n     * constraints (e.g. PostgreSQL or MySQL), then set those up in the\n     * database and uncomment this to disable Waterline's `cascadeOnDestroy`\n     * polyfill.  (Otherwise, if you are using a databse like Mongo, you might\n     * choose to keep this enabled.)\n     *\n     */\n    // cascadeOnDestroy: false,\n  },\n\n  /**\n   * Always disable \"shortcut\" blueprint routes.\n   *\n   * > You'll also want to disable any other blueprint routes if you are not\n   * > actually using them (e.g. \"actions\" and \"rest\") -- but you can do\n   * > that in `config/blueprints.js`, since you'll want to disable them in\n   * > all environments (not just in production.)\n   *\n   */\n\n  blueprints: {\n    // shortcuts: false,\n  },\n\n  /**\n   *\n   * Configure your security settings for production.\n   *\n   * IMPORTANT:\n   * If web browsers will be communicating with your app, be sure that\n   * you have CSRF protection enabled.  To do that, set `csrf: true` over\n   * in the `config/security.js` file (not here), so that CSRF app can be\n   * tested with CSRF protection turned on in development mode too.\n   *\n   */\n\n  security: {\n    /**\n     *\n     * If this app has CORS enabled (see `config/security.js`) with the\n     * `allowCredentials` setting enabled, then you should uncomment the\n     * `allowOrigins` whitelist below.  This sets which \"origins\" are allowed\n     * to send cross-domain (CORS) requests to your Sails app.\n     *\n     * > Replace \"https://example.com\" with the URL of your production server.\n     * > Be sure to use the right protocol!  (\"http://\" vs. \"https://\")\n     *\n     */\n\n    cors: {\n      allRoutes: true,\n      allowOrigins: origins.slice(1),\n      allowRequestHeaders: ['Authorization'],\n      allowCredentials: true,\n    },\n  },\n\n  /**\n   *\n   * Configure how your app handles sessions in production.\n   *\n   * (https://sailsjs.com/config/session)\n   *\n   * > If you have disabled the \"session\" hook, then you can safely remove\n   * > this section from your `config/env/production.js` file.\n   *\n   */\n\n  session: {\n    /**\n     *\n     * Production session store configuration.\n     *\n     * Uncomment the following lines to finish setting up a package called\n     * \"@sailshq/connect-redis\" that will use Redis to handle session data.\n     * This makes your app more scalable by allowing you to share sessions\n     * across a cluster of multiple Sails/Node.js servers and/or processes.\n     * (See http://bit.ly/redis-session-config for more info.)\n     *\n     * > While @sailshq/connect-redis is a popular choice for Sails apps, many\n     * > other compatible packages (like \"connect-mongo\") are available on NPM.\n     * > (For a full list, see https://sailsjs.com/plugins/sessions)\n     *\n     */\n\n    // adapter: '@sailshq/connect-redis',\n    // url: 'redis://user:password@localhost:6379/databasenumber',\n\n    /**\n     *\n     * Production configuration for the session ID cookie.\n     *\n     * Tell browsers (or other user agents) to ensure that session ID cookies\n     * are always transmitted via HTTPS, and that they expire 24 hours after\n     * they are set.\n     *\n     * Note that with `secure: true` set, session cookies will _not_ be\n     * transmitted over unsecured (HTTP) connections. Also, for apps behind\n     * proxies (like Heroku), the `trustProxy` setting under `http` must be\n     * configured in order for `secure: true` to work.\n     *\n     * > While you might want to increase or decrease the `maxAge` or provide\n     * > other options, you should always set `secure: true` in production\n     * > if the app is being served over HTTPS.\n     *\n     * Read more:\n     * https://sailsjs.com/config/session#?the-session-id-cookie\n     *\n     */\n\n    cookie: {\n      // secure: true,\n      maxAge: 24 * 60 * 60 * 1000, // 24 hours\n    },\n  },\n\n  /**\n   *\n   * Set up Socket.io for your production environment.\n   *\n   * (https://sailsjs.com/config/sockets)\n   *\n   * > If you have disabled the \"sockets\" hook, then you can safely remove\n   * > this section from your `config/env/production.js` file.\n   *\n   */\n\n  sockets: {\n    /**\n     *\n     * Uncomment the `onlyAllowOrigins` whitelist below to configure which\n     * \"origins\" are allowed to open socket connections to your Sails app.\n     *\n     * > Replace \"https://example.com\" etc. with the URL(s) of your app.\n     * > Be sure to use the right protocol!  (\"http://\" vs. \"https://\")\n     *\n     */\n\n    onlyAllowOrigins: origins,\n\n    /**\n     *\n     * If you are deploying a cluster of multiple servers and/or processes,\n     * then uncomment the following lines.  This tells Socket.io about a Redis\n     * server it can use to help it deliver broadcasted socket messages.\n     *\n     * > Be sure a compatible version of @sailshq/socket.io-redis is installed!\n     * > (See https://sailsjs.com/config/sockets for the latest version info)\n     *\n     * (https://sailsjs.com/docs/concepts/deployment/scaling)\n     *\n     */\n\n    // adapter: '@sailshq/socket.io-redis',\n    // url: 'redis://user:password@bigsquid.redistogo.com:9562/databasenumber',\n  },\n\n  /**\n   *\n   * Set the production log level.\n   *\n   * (https://sailsjs.com/config/log)\n   *\n   */\n\n  log: {\n    /**\n     * Passthrough plain log message(s) to\n     * custom Winston console and file logger.\n     *\n     * Note that Winston's log levels override Sails' log levels.\n     * Refer: https://github.com/winstonjs/winston#logging\n     */\n\n    inspect: false,\n    custom: customLogger,\n\n    /**\n     * Removes the Sail.js init success logs (ASCII ship art).\n     */\n\n    noShip: true,\n  },\n\n  http: {\n    /**\n     *\n     * The number of milliseconds to cache static assets in production.\n     * (the \"max-age\" to include in the \"Cache-Control\" response header)\n     *\n     */\n\n    cache: 365.25 * 24 * 60 * 60 * 1000, // One year\n\n    /**\n     *\n     * Proxy settings\n     *\n     * If your app will be deployed behind a proxy/load balancer - for example,\n     * on a PaaS like Heroku - then uncomment the `trustProxy` setting below.\n     * This tells Sails/Express how to interpret X-Forwarded headers.\n     *\n     * This setting is especially important if you are using secure cookies\n     * (see the `cookies: secure` setting under `session` above) or if your app\n     * relies on knowing the original IP address that a request came from.\n     *\n     * (https://sailsjs.com/config/http)\n     *\n     */\n\n    trustProxy: process.env.TRUST_PROXY === 'true',\n  },\n\n  /**\n   *\n   * Lift the server on port 80.\n   * (if deploying behind a proxy, or to a PaaS like Heroku or Deis, you\n   * probably don't need to set a port here, because it is oftentimes\n   * handled for you automatically.  If you are not sure if you need to set\n   * this, just try deploying without setting it and see if it works.)\n   *\n   */\n\n  // port: 80,\n\n  /**\n   *\n   * Configure an SSL certificate\n   *\n   * For the safety of your users' data, you should use SSL in production.\n   * ...But in many cases, you may not actually want to set it up _here_.\n   *\n   * Normally, this setting is only relevant when running a single-process\n   * deployment, with no proxy/load balancer in the mix.  But if, on the\n   * other hand, you are using a PaaS like Heroku, you'll want to set up\n   * SSL in your load balancer settings (usually somewhere in your hosting\n   * provider's dashboard-- not here.)\n   *\n   * > For more information about configuring SSL in Sails, see:\n   * > https://sailsjs.com/config/*#?sailsconfigssl\n   *\n   */\n\n  // ssl: undefined,\n\n  /**\n   *\n   * Production overrides for any custom settings specific to your app.\n   * (for example, production credentials for 3rd party APIs like Stripe)\n   *\n   * > See config/custom.js for more info on how to configure these options.\n   *\n   */\n\n  custom: {\n    // baseUrl: 'https://example.com',\n    // internalEmailAddress: 'support@example.com',\n  },\n};\n"
  },
  {
    "path": "server/config/env/test.js",
    "content": "/**\n * Test environment settings\n * (sails.config.*)\n *\n * What you see below is a quick outline of the built-in settings you need\n * to configure your Sails app for test.  The configuration in this file\n * is only used in your test environment, i.e. when you lift your app using:\n *\n * ```\n * NODE_ENV=test node app\n * ```\n *\n * > If you're using git as a version control solution for your Sails app,\n * > this file WILL BE COMMITTED to your repository by default, unless you add\n * > it to your .gitignore file.  If your repository will be publicly viewable,\n * > don't add private/sensitive data (like API secrets / db passwords) to this file!\n *\n * For more best practices and tips, see:\n * https://sailsjs.com/docs/concepts/deployment\n */\n\nmodule.exports = {\n  /**\n   *\n   * Tell Sails what database(s) it should use in test.\n   *\n   * (https://sailsjs.com/config/datastores)\n   *\n   */\n\n  datastores: {\n    /**\n     *\n     * Configure your default test database.\n     *\n     * 1. Choose an adapter:\n     *    https://sailsjs.com/plugins/databases\n     *\n     * 2. Install it as a dependency of your Sails app.\n     *    (For example:  npm install sails-mysql --save)\n     *\n     * 3. Then set it here (`adapter`), along with a connection URL (`url`)\n     *    and any other, adapter-specific customizations.\n     *    (See https://sailsjs.com/config/datastores for help.)\n     *\n     */\n\n    default: {\n      adapter: 'sails-disk',\n\n      /**\n       *\n       * More adapter-specific options\n       *\n       * > For example, for some hosted PostgreSQL providers (like Heroku), the\n       * > extra `ssl: true` option is mandatory and must be provided.\n       *\n       * More info:\n       * https://sailsjs.com/config/datastores\n       *\n       */\n\n      inMemoryOnly: true,\n    },\n  },\n\n  log: {\n    level: 'warn',\n  },\n};\n"
  },
  {
    "path": "server/config/globals.js",
    "content": "/**\n * Global Variable Configuration\n * (sails.config.globals)\n *\n * Configure which global variables which will be exposed\n * automatically by Sails.\n *\n * For more information on any of these options, check out:\n * https://sailsjs.com/config/globals\n */\n\nmodule.exports.globals = {\n  /**\n   *\n   * Whether to expose the locally-installed Lodash as a global variable\n   * (`_`), making  it accessible throughout your app.\n   *\n   */\n\n  _: require('lodash'), // eslint-disable-line global-require\n\n  /**\n   *\n   * This app was generated without a dependency on the \"async\" NPM package.\n   *\n   * > Don't worry!  This is totally unrelated to JavaScript's \"async/await\".\n   * > Your code can (and probably should) use `await` as much as possible.\n   *\n   */\n\n  async: false,\n\n  /**\n   *\n   * Whether to expose each of your app's models as global variables.\n   * (See the link at the top of this file for more information.)\n   *\n   */\n\n  models: true,\n\n  /**\n   *\n   * Whether to expose the Sails app instance as a global variable (`sails`),\n   * making it accessible throughout your app.\n   *\n   */\n\n  sails: true,\n};\n"
  },
  {
    "path": "server/config/http.js",
    "content": "/**\n * HTTP Server Settings\n * (sails.config.http)\n *\n * Configuration for the underlying HTTP server in Sails.\n * (for additional recommended settings, see `config/env/production.js`)\n *\n * For more information on configuration, check out:\n * https://sailsjs.com/config/http\n */\n\nconst serveStatic = require('serve-static');\nconst sails = require('sails');\n\nmodule.exports.http = {\n  /**\n   *\n   * Sails/Express middleware to run for every HTTP request.\n   * (Only applies to HTTP requests -- not virtual WebSocket requests.)\n   *\n   * https://sailsjs.com/documentation/concepts/middleware\n   *\n   */\n\n  middleware: {\n    /**\n     *\n     * The order in which middleware should be run for HTTP requests.\n     * (This Sails app's routes are handled by the \"router\" middleware below.)\n     *\n     */\n    // order: [\n    //   'cookieParser',\n    //   'session',\n    //   'bodyParser',\n    //   'compress',\n    //   'poweredBy',\n    //   'router',\n    //   'www',\n    //   'favicon',\n    // ],\n    /**\n     *\n     * The body parser that will handle incoming multipart HTTP requests.\n     *\n     * https://sailsjs.com/config/http#?customizing-the-body-parser\n     *\n     */\n    // bodyParser: (function _configureBodyParser(){\n    //   var skipper = require('skipper');\n    //   var middlewareFn = skipper({ strict: true });\n    //   return middlewareFn;\n    // })(),\n\n    poweredBy: false,\n\n    www(req, res, next) {\n      const middleware = serveStatic(sails.config.paths.public, {\n        maxAge: sails.config.http.cache,\n        immutable: req.url.startsWith('/assets/'),\n      });\n\n      return middleware(req, res, next);\n    },\n  },\n};\n"
  },
  {
    "path": "server/config/i18n.js",
    "content": "/**\n * Internationalization / Localization Settings\n * (sails.config.i18n)\n *\n * If your app will touch people from all over the world, i18n (or internationalization)\n * may be an important part of your international strategy.\n *\n * For a complete list of options for Sails' built-in i18n support, see:\n * https://sailsjs.com/config/i-18-n\n *\n * For more info on i18n in Sails in general, check out:\n * https://sailsjs.com/docs/concepts/internationalization\n */\n\nmodule.exports.i18n = {\n  /**\n   *\n   * Which locales are supported?\n   *\n   */\n\n  locales: [\n    'ar-YE',\n    'bg-BG',\n    'ca-ES',\n    'cs-CZ',\n    'da-DK',\n    'de-DE',\n    'el-GR',\n    'en-GB',\n    'en-US',\n    'es-ES',\n    'et-EE',\n    'fa-IR',\n    'fi-FI',\n    'fr-FR',\n    'hu-HU',\n    'id-ID',\n    'it-IT',\n    'ja-JP',\n    'ko-KR',\n    'nl-NL',\n    'pl-PL',\n    'pt-BR',\n    'pt-PT',\n    'ro-RO',\n    'ru-RU',\n    'sk-SK',\n    'sr-Cyrl-RS',\n    'sr-Latn-RS',\n    'sv-SE',\n    'tr-TR',\n    'uk-UA',\n    'uz-UZ',\n    'vi-VN',\n    'zh-CN',\n    'zh-TW',\n  ],\n\n  /**\n   *\n   * What is the default locale for the site? Note that this setting will be\n   * overridden for any request that sends an \"Accept-Language\" header (i.e.\n   * most browsers), but it's still useful if you need to localize the\n   * response for requests made by non-browser clients (e.g. cURL).\n   *\n   */\n\n  defaultLocale: process.env.DEFAULT_LANGUAGE || 'en-US',\n\n  /**\n   *\n   * Path (relative to app root) of directory to store locale (translation)\n   * files in.\n   *\n   */\n\n  // localesDirectory: 'config/locales',\n};\n"
  },
  {
    "path": "server/config/locales/ar-YE.json",
    "content": "{\n  \"Archive\": \"أرشيف\",\n  \"Card Created\": \"تم إنشاء البطاقة\",\n  \"Card Moved\": \"تم نقل البطاقة\",\n  \"copy\": \"نسخة\",\n  \"New Comment\": \"تعليق جديد\",\n  \"Test Title\": \"عنوان تجريبي\",\n  \"This is a test text message!\": \"هذه رسالة نصية تجريبية!\",\n  \"This is a *test* **markdown** `message`!\": \"هذه *رسالة* **markdown** `تجريبية`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"هذه <i>رسالة</i> <b>html</b> <code>تجريبية</code>!\",\n  \"Trash\": \"سلة المهملات\",\n  \"You Were Added to Card\": \"تمت إضافتك إلى البطاقة\",\n  \"You Were Mentioned in Comment\": \"تم ذكرك في تعليق\",\n  \"%s added you to %s on %s\": \"%s أضافك إلى %s في %s\",\n  \"%s created %s in %s on %s\": \"%s أنشأ %s في %s في %s\",\n  \"%s left a new comment to %s on %s\": \"%s ترك تعليقاً جديداً على %s في %s\",\n  \"%s mentioned you in %s on %s\": \"%s ذكرك في %s في %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s نقل %s من %s إلى %s في %s\"\n}\n"
  },
  {
    "path": "server/config/locales/bg-BG.json",
    "content": "{\n  \"Archive\": \"Архив\",\n  \"Card Created\": \"Картата е създадена\",\n  \"Card Moved\": \"Картата е преместена\",\n  \"copy\": \"копие\",\n  \"New Comment\": \"Нов коментар\",\n  \"Test Title\": \"Тестово заглавие\",\n  \"This is a test text message!\": \"Това е тестово текстово съобщение!\",\n  \"This is a *test* **markdown** `message`!\": \"Това е *тестово* **markdown** `съобщение`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Това е <i>тестово</i> <b>html</b> <code>съобщение</code>!\",\n  \"Trash\": \"Кошче\",\n  \"You Were Added to Card\": \"Бяхте добавени към картата\",\n  \"You Were Mentioned in Comment\": \"Бяхте споменати в коментар\",\n  \"%s added you to %s on %s\": \"%s ви добави към %s на %s\",\n  \"%s created %s in %s on %s\": \"%s създаде %s в %s на %s\",\n  \"%s left a new comment to %s on %s\": \"%s остави нов коментар към %s на %s\",\n  \"%s mentioned you in %s on %s\": \"%s ви спомена в %s на %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s премести %s от %s към %s на %s\"\n}\n"
  },
  {
    "path": "server/config/locales/ca-ES.json",
    "content": "{\n  \"Archive\": \"Arxivar\",\n  \"Card Created\": \"Targeta creada\",\n  \"Card Moved\": \"Targeta moguda\",\n  \"copy\": \"còpia\",\n  \"New Comment\": \"Comentari nou\",\n  \"Test Title\": \"Títol de prova\",\n  \"This is a test text message!\": \"Aquest és un missatge de text de prova!\",\n  \"This is a *test* **markdown** `message`!\": \"Aquest és un *missatge* **markdown** `de prova`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Aquest és un <i>missatge</i> <b>html</b> <code>de prova</code>!\",\n  \"Trash\": \"Paperera\",\n  \"You Were Added to Card\": \"Has estat afegit a la targeta\",\n  \"You Were Mentioned in Comment\": \"Has estat mencionat en un comentari\",\n  \"%s added you to %s on %s\": \"%s t'ha afegit a %s el %s\",\n  \"%s created %s in %s on %s\": \"%s s'ha creat %s a %s el %s\",\n  \"%s left a new comment to %s on %s\": \"%s ha deixat un comentari nou a %s el %s\",\n  \"%s mentioned you in %s on %s\": \"%s t'ha mencionat a %s el %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s s'ha mogut %s de %s a %s el %s\"\n}\n"
  },
  {
    "path": "server/config/locales/cs-CZ.json",
    "content": "{\n  \"Archive\": \"Archiv\",\n  \"Card Created\": \"Karta vytvořena\",\n  \"Card Moved\": \"Karta přesunuta\",\n  \"copy\": \"kopie\",\n  \"New Comment\": \"Nový komentář\",\n  \"Test Title\": \"Testovací název\",\n  \"This is a test text message!\": \"Toto je testovací textová zpráva!\",\n  \"This is a *test* **markdown** `message`!\": \"Toto je *testovací* **markdown** `zpráva`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Toto je <i>testovací</i> <b>html</b> <code>zpráva</code>!\",\n  \"Trash\": \"Koš\",\n  \"You Were Added to Card\": \"Byli jste přidáni ke kartě\",\n  \"You Were Mentioned in Comment\": \"Byli jste zmíněni v komentáři\",\n  \"%s added you to %s on %s\": \"%s vás přidal k %s dne %s\",\n  \"%s created %s in %s on %s\": \"%s vytvořil %s v %s dne %s\",\n  \"%s left a new comment to %s on %s\": \"%s zanechal nový komentář k %s dne %s\",\n  \"%s mentioned you in %s on %s\": \"%s vás zmínil v %s dne %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s přesunul %s z %s do %s dne %s\"\n}\n"
  },
  {
    "path": "server/config/locales/da-DK.json",
    "content": "{\n  \"Archive\": \"Arkiv\",\n  \"Card Created\": \"Kort oprettet\",\n  \"Card Moved\": \"Kort flyttet\",\n  \"copy\": \"kopi\",\n  \"New Comment\": \"Ny kommentar\",\n  \"Test Title\": \"Test titel\",\n  \"This is a test text message!\": \"Dette er en test tekstbesked!\",\n  \"This is a *test* **markdown** `message`!\": \"Dette er en *test* **markdown** `besked`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Dette er en <i>test</i> <b>html</b> <code>besked</code>!\",\n  \"Trash\": \"Papirkurv\",\n  \"You Were Added to Card\": \"Du blev tilføjet til kortet\",\n  \"You Were Mentioned in Comment\": \"Du blev nævnt i en kommentar\",\n  \"%s added you to %s on %s\": \"%s tilføjede dig til %s den %s\",\n  \"%s created %s in %s on %s\": \"%s oprettede %s i %s den %s\",\n  \"%s left a new comment to %s on %s\": \"%s efterlod en ny kommentar til %s den %s\",\n  \"%s mentioned you in %s on %s\": \"%s nævnte dig i %s den %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s flyttede %s fra %s til %s den %s\"\n}\n"
  },
  {
    "path": "server/config/locales/de-DE.json",
    "content": "{\n  \"Archive\": \"Archiv\",\n  \"Card Created\": \"Karte erstellt\",\n  \"Card Moved\": \"Karte verschoben\",\n  \"copy\": \"Kopie\",\n  \"New Comment\": \"Neuer Kommentar\",\n  \"Test Title\": \"Testtitel\",\n  \"This is a test text message!\": \"Dies ist eine Test-Textnachricht!\",\n  \"This is a *test* **markdown** `message`!\": \"Dies ist eine *Test*-**Markdown**-`Nachricht`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Dies ist eine <i>Test</i>-<b>HTML</b>-<code>Nachricht</code>!\",\n  \"Trash\": \"Papierkorb\",\n  \"You Were Added to Card\": \"Sie wurden zur Karte hinzugefügt\",\n  \"You Were Mentioned in Comment\": \"Sie wurden in einem Kommentar erwähnt\",\n  \"%s added you to %s on %s\": \"%s hat Sie zu %s am %s hinzugefügt\",\n  \"%s created %s in %s on %s\": \"%s hat %s in %s am %s erstellt\",\n  \"%s left a new comment to %s on %s\": \"%s hat einen neuen Kommentar zu %s am %s hinterlassen\",\n  \"%s mentioned you in %s on %s\": \"%s hat Sie in %s am %s erwähnt\",\n  \"%s moved %s from %s to %s on %s\": \"%s hat %s von %s nach %s am %s verschoben\"\n}\n"
  },
  {
    "path": "server/config/locales/el-GR.json",
    "content": "{\n\t\"Archive\": \"Αρχείο\",\n\t\"Card Created\": \"Η κάρτα δημιουργήθηκε\",\n\t\"Card Moved\": \"Η κάρτα μετακινήθηκε\",\n\t\"copy\": \"αντίγραφο\",\n\t\"New Comment\": \"Νέο σχόλιο\",\n\t\"Test Title\": \"Τίτλος δοκιμής\",\n\t\"This is a test text message!\": \"Αυτό είναι ένα δοκιμαστικό μήνυμα!\",\n\t\"This is a *test* **markdown** `message`!\": \"Αυτό είναι ένα *δοκιμαστικό* **markdown** `μήνυμα`!\",\n\t\"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Αυτό είναι ένα <i>δοκιμαστικό</i> <b>html</b> <code>μήνυμα</code>!\",\n\t\"Trash\": \"Κάδος απορριμμάτων\",\n\t\"You Were Added to Card\": \"Προστέθηκες στην κάρτα\",\n\t\"You Were Mentioned in Comment\": \"Αναφέρθηκες σε σχόλιο\",\n\t\"%s added you to %s on %s\": \"%s σε πρόσθεσε στο %s στο %s\",\n\t\"%s created %s in %s on %s\": \"%s δημιούργησε το %s στο %s στο %s\",\n\t\"%s left a new comment to %s on %s\": \"%s άφησε νέο σχόλιο στο %s στο %s\",\n\t\"%s mentioned you in %s on %s\": \"%s σε ανέφερε στο %s στο %s\",\n\t\"%s moved %s from %s to %s on %s\": \"%s μετακίνησε το %s από το %s στο %s στο %s\"\n}\n"
  },
  {
    "path": "server/config/locales/en-GB.json",
    "content": "{\n  \"Archive\": \"Archive\",\n  \"Card Created\": \"Card Created\",\n  \"Card Moved\": \"Card Moved\",\n  \"copy\": \"copy\",\n  \"New Comment\": \"New Comment\",\n  \"Test Title\": \"Test Title\",\n  \"This is a test text message!\": \"This is a test text message!\",\n  \"This is a *test* **markdown** `message`!\": \"This is a *test* **markdown** `message`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"This is a <i>test</i> <b>html</b> <code>message</code>!\",\n  \"Trash\": \"Trash\",\n  \"You Were Added to Card\": \"You Were Added to Card\",\n  \"You Were Mentioned in Comment\": \"You Were Mentioned in Comment\",\n  \"%s added you to %s on %s\": \"%s added you to %s on %s\",\n  \"%s created %s in %s on %s\": \"%s created %s in %s on %s\",\n  \"%s left a new comment to %s on %s\": \"%s left a new comment to %s on %s\",\n  \"%s mentioned you in %s on %s\": \"%s mentioned you in %s on %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s moved %s from %s to %s on %s\"\n}\n"
  },
  {
    "path": "server/config/locales/en-US.json",
    "content": "{\n\t\"Archive\": \"Archive\",\n\t\"Card Created\": \"Card Created\",\n\t\"Card Moved\": \"Card Moved\",\n\t\"copy\": \"copy\",\n\t\"New Comment\": \"New Comment\",\n\t\"Test Title\": \"Test Title\",\n\t\"This is a test text message!\": \"This is a test text message!\",\n\t\"This is a *test* **markdown** `message`!\": \"This is a *test* **markdown** `message`!\",\n\t\"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"This is a <i>test</i> <b>html</b> <code>message</code>!\",\n\t\"Trash\": \"Trash\",\n\t\"You Were Added to Card\": \"You Were Added to Card\",\n\t\"You Were Mentioned in Comment\": \"You Were Mentioned in Comment\",\n\t\"%s added you to %s on %s\": \"%s added you to %s on %s\",\n\t\"%s created %s in %s on %s\": \"%s created %s in %s on %s\",\n\t\"%s left a new comment to %s on %s\": \"%s left a new comment to %s on %s\",\n\t\"%s mentioned you in %s on %s\": \"%s mentioned you in %s on %s\",\n\t\"%s moved %s from %s to %s on %s\": \"%s moved %s from %s to %s on %s\"\n}\n"
  },
  {
    "path": "server/config/locales/es-ES.json",
    "content": "{\n  \"Archive\": \"Archivo\",\n  \"Card Created\": \"Tarjeta creada\",\n  \"Card Moved\": \"Tarjeta movida\",\n  \"copy\": \"copia\",\n  \"New Comment\": \"Nuevo comentario\",\n  \"Test Title\": \"Título de prueba\",\n  \"This is a test text message!\": \"¡Este es un mensaje de texto de prueba!\",\n  \"This is a *test* **markdown** `message`!\": \"¡Este es un *mensaje* **markdown** `de prueba`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"¡Este es un <i>mensaje</i> <b>html</b> <code>de prueba</code>!\",\n  \"Trash\": \"Papelera\",\n  \"You Were Added to Card\": \"Fuiste añadido a la tarjeta\",\n  \"You Were Mentioned in Comment\": \"Fuiste mencionado en un comentario\",\n  \"%s added you to %s on %s\": \"%s te añadió a %s en %s\",\n  \"%s created %s in %s on %s\": \"%s creó %s en %s el %s\",\n  \"%s left a new comment to %s on %s\": \"%s dejó un nuevo comentario en %s el %s\",\n  \"%s mentioned you in %s on %s\": \"%s te mencionó en %s el %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s movió %s de %s a %s el %s\"\n}\n"
  },
  {
    "path": "server/config/locales/et-EE.json",
    "content": "{\n  \"Archive\": \"Arhiiv\",\n  \"Card Created\": \"Kaart loodud\",\n  \"Card Moved\": \"Kaart liigutatud\",\n  \"copy\": \"koopia\",\n  \"New Comment\": \"Uus kommentaar\",\n  \"Test Title\": \"Testi pealkiri\",\n  \"This is a test text message!\": \"See on testi tekstisõnum!\",\n  \"This is a *test* **markdown** `message`!\": \"See on *testi* **markdown** `sõnum`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"See on <i>testi</i> <b>html</b> <code>sõnum</code>!\",\n  \"Trash\": \"Prügikast\",\n  \"You Were Added to Card\": \"Teid lisati kaardile\",\n  \"You Were Mentioned in Comment\": \"Teid mainiti kommentaaris\",\n  \"%s added you to %s on %s\": \"%s lisas teid %s-le %s\",\n  \"%s created %s in %s on %s\": \"%s lõi %s %s-s %s\",\n  \"%s left a new comment to %s on %s\": \"%s jättis uue kommentaari %s-le %s\",\n  \"%s mentioned you in %s on %s\": \"%s mainis teid %s-s %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s liigutas %s %s-st %s-sse %s\"\n}\n"
  },
  {
    "path": "server/config/locales/fa-IR.json",
    "content": "{\n  \"Archive\": \"بایگانی\",\n  \"Card Created\": \"کارت ایجاد شد\",\n  \"Card Moved\": \"کارت منتقل شد\",\n  \"copy\": \"کپی\",\n  \"New Comment\": \"نظر جدید\",\n  \"Test Title\": \"عنوان آزمایشی\",\n  \"This is a test text message!\": \"این یک پیام متنی آزمایشی است!\",\n  \"This is a *test* **markdown** `message`!\": \"این یک *پیام* **markdown** `آزمایشی` است!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"این یک <i>پیام</i> <b>html</b> <code>آزمایشی</code> است!\",\n  \"Trash\": \"سطل زباله\",\n  \"You Were Added to Card\": \"شما به کارت اضافه شدید\",\n  \"You Were Mentioned in Comment\": \"در نظری از شما نام برده شد\",\n  \"%s added you to %s on %s\": \"%s شما را به %s در %s اضافه کرد\",\n  \"%s created %s in %s on %s\": \"%s ایجاد کرد %s در %s در %s\",\n  \"%s left a new comment to %s on %s\": \"%s نظر جدیدی برای %s در %s گذاشت\",\n  \"%s mentioned you in %s on %s\": \"%s از شما در %s در %s نام برد\",\n  \"%s moved %s from %s to %s on %s\": \"%s منتقل کرد %s از %s به %s در %s\"\n}\n"
  },
  {
    "path": "server/config/locales/fi-FI.json",
    "content": "{\n\t\"Archive\": \"Arkisto\",\n\t\"Card Created\": \"Kortti luotu\",\n\t\"Card Moved\": \"Kortti siirretty\",\n\t\"copy\": \"kopio\",\n\t\"New Comment\": \"Uusi kommentti\",\n\t\"Test Title\": \"Testin otsikko\",\n\t\"This is a test text message!\": \"Tämä on testiviesti!\",\n\t\"This is a *test* **markdown** `message`!\": \"Tämä on *testi* **markdown** `viesti`!\",\n\t\"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Tämä on <i>testi</i> <b>html</b> <code>viesti</code>!\",\n\t\"Trash\": \"Roskakori\",\n\t\"You Were Added to Card\": \"Sinut lisättiin korttiin\",\n\t\"You Were Mentioned in Comment\": \"Sinut mainittiin kommentissa\",\n\t\"%s added you to %s on %s\": \"%s lisäsi sinut kohteeseen %s kohteessa %s\",\n\t\"%s created %s in %s on %s\": \"%s loi %s kohteessa %s kohteessa %s\",\n\t\"%s left a new comment to %s on %s\": \"%s jätti uuden kommentin kohteeseen %s kohteessa %s\",\n\t\"%s mentioned you in %s on %s\": \"%s mainitsi sinut kohteessa %s kohteessa %s\",\n\t\"%s moved %s from %s to %s on %s\": \"%s siirsi %s kohteesta %s kohteeseen %s kohteessa %s\"\n}\n"
  },
  {
    "path": "server/config/locales/fr-FR.json",
    "content": "{\n  \"Archive\": \"Archive\",\n  \"Card Created\": \"Carte créée\",\n  \"Card Moved\": \"Carte déplacée\",\n  \"copy\": \"copie\",\n  \"New Comment\": \"Nouveau commentaire\",\n  \"Test Title\": \"Titre de test\",\n  \"This is a test text message!\": \"Ceci est un message texte de test !\",\n  \"This is a *test* **markdown** `message`!\": \"Ceci est un *message* **markdown** `de test` !\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Ceci est un <i>test</i> <b>html</b> <code>message</code>!\",\n  \"Trash\": \"Corbeille\",\n  \"You Were Added to Card\": \"Vous avez été ajouté à la carte\",\n  \"You Were Mentioned in Comment\": \"Vous avez été mentionné dans un commentaire\",\n  \"%s added you to %s on %s\": \"%s vous a ajouté à %s le %s\",\n  \"%s created %s in %s on %s\": \"%s a créé %s dans %s le %s\",\n  \"%s left a new comment to %s on %s\": \"%s a laissé un nouveau commentaire sur %s le %s\",\n  \"%s mentioned you in %s on %s\": \"%s vous a mentionné dans %s le %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s a déplacé %s de %s vers %s le %s\"\n}\n"
  },
  {
    "path": "server/config/locales/hu-HU.json",
    "content": "{\n  \"Archive\": \"Archívum\",\n  \"Card Created\": \"Kártya létrehozva\",\n  \"Card Moved\": \"Kártya áthelyezve\",\n  \"copy\": \"másolat\",\n  \"New Comment\": \"Új hozzászólás\",\n  \"Test Title\": \"Teszt cím\",\n  \"This is a test text message!\": \"Ez itt egy szöveges teszt üzenet!\",\n  \"This is a *test* **markdown** `message`!\": \"Ez itt egy **markdown** formátumú *teszt* `üzenet`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Ez egy <b>html</b> formátumú <i>teszt</i> <code>üzenet</code>!\",\n  \"Trash\": \"Kuka\",\n  \"You Were Added to Card\": \"Hozzádrendeltek egy kártyát\",\n  \"You Were Mentioned in Comment\": \"Megemlítettek egy kártyán\",\n  \"%s added you to %s on %s\": \"%s hozzárendelt téged a(z) %s kártyához a(z) %s projektben\",\n  \"%s created %s in %s on %s\": \"%s létrehozta a(z) %s kártyát a(z) %s projektben ekkor: %s\",\n  \"%s left a new comment to %s on %s\": \"%s hozzászólt a(z) %s kártyához a(z) %s projektben\",\n  \"%s mentioned you in %s on %s\": \"%s megemlített téged a(z) %s kártyán a(z) %s projektben\",\n  \"%s moved %s from %s to %s on %s\": \"%s átmozgatta a(z) %s kártyát a(z) %s projektből a(z) %s projektbe ekkor: %s\"\n}\n"
  },
  {
    "path": "server/config/locales/id-ID.json",
    "content": "{\n  \"Archive\": \"Arsip\",\n  \"Card Created\": \"Kartu dibuat\",\n  \"Card Moved\": \"Kartu dipindahkan\",\n  \"copy\": \"salinan\",\n  \"New Comment\": \"Komentar baru\",\n  \"Test Title\": \"Judul tes\",\n  \"This is a test text message!\": \"Ini adalah pesan teks tes!\",\n  \"This is a *test* **markdown** `message`!\": \"Ini adalah *pesan* **markdown** `tes`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Ini adalah <i>pesan</i> <b>html</b> <code>tes</code>!\",\n  \"Trash\": \"Sampah\",\n  \"You Were Added to Card\": \"Anda ditambahkan ke kartu\",\n  \"You Were Mentioned in Comment\": \"Anda disebutkan dalam komentar\",\n  \"%s added you to %s on %s\": \"%s menambahkan Anda ke %s pada %s\",\n  \"%s created %s in %s on %s\": \"%s membuat %s di %s pada %s\",\n  \"%s left a new comment to %s on %s\": \"%s meninggalkan komentar baru untuk %s pada %s\",\n  \"%s mentioned you in %s on %s\": \"%s menyebutkan Anda di %s pada %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s memindahkan %s dari %s ke %s pada %s\"\n}\n"
  },
  {
    "path": "server/config/locales/it-IT.json",
    "content": "{\r\n  \"Archive\": \"Archivio\",\r\n  \"Card Created\": \"Nuova task creata\",\r\n  \"Card Moved\": \"Task spostata\",\r\n  \"copy\": \"copia\",\r\n  \"New Comment\": \"Nuovo commento\",\r\n  \"Test Title\": \"Titolo di test\",\r\n  \"This is a test text message!\": \"Questo è un messaggio di testo di test!\",\r\n  \"This is a *test* **markdown** `message`!\": \"Questo è un *test* **markdown** `messaggio`!\",\r\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Questo è un <i>test</i> <b>html</b> <code>messaggio</code>!\",\r\n  \"Trash\": \"Cestino\",\r\n  \"You Were Added to Card\": \"Sei stato aggiunto alla task\",\r\n  \"You Were Mentioned in Comment\": \"Sei stato menzionato nel commento\",\r\n  \"%s created %s in %s on %s\": \"%s ha creato %s in %s in %s\",\r\n  \"%s left a new comment to %s on %s\": \"%s ha commentato %s in %s\",\r\n  \"%s mentioned you in %s on %s\": \"%s ti ha menzionato in %s in %s\",\r\n  \"%s moved %s from %s to %s on %s\": \"%s ha spostato %s da %s a %s in %s\"\r\n}\r\n"
  },
  {
    "path": "server/config/locales/ja-JP.json",
    "content": "{\n  \"Archive\": \"アーカイブ\",\n  \"Card Created\": \"カードが作成されました\",\n  \"Card Moved\": \"カードが移動されました\",\n  \"copy\": \"コピー\",\n  \"New Comment\": \"新しいコメント\",\n  \"Test Title\": \"テストタイトル\",\n  \"This is a test text message!\": \"これはテストテキストメッセージです！\",\n  \"This is a *test* **markdown** `message`!\": \"これは*テスト***markdown**`メッセージ`です！\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"これは<i>テスト</i><b>html</b><code>メッセージ</code>です！\",\n  \"Trash\": \"ゴミ箱\",\n  \"You Were Added to Card\": \"カードに追加されました\",\n  \"You Were Mentioned in Comment\": \"コメントでメンションされました\",\n  \"%s added you to %s on %s\": \"%sが%sの%sにあなたを追加しました\",\n  \"%s created %s in %s on %s\": \"%sが%sの%sに%sを作成しました\",\n  \"%s left a new comment to %s on %s\": \"%sが%sの%sに新しいコメントを残しました\",\n  \"%s mentioned you in %s on %s\": \"%sが%sの%sであなたをメンションしました\",\n  \"%s moved %s from %s to %s on %s\": \"%sが%sの%sを%sから%sに移動しました\"\n}\n"
  },
  {
    "path": "server/config/locales/ko-KR.json",
    "content": "{\n  \"Archive\": \"보관함\",\n  \"Card Created\": \"카드가 생성됨\",\n  \"Card Moved\": \"카드가 이동됨\",\n  \"copy\": \"복사본\",\n  \"New Comment\": \"새 댓글\",\n  \"Test Title\": \"테스트 제목\",\n  \"This is a test text message!\": \"이것은 테스트 텍스트 메시지입니다!\",\n  \"This is a *test* **markdown** `message`!\": \"이것은 *테스트* **markdown** `메시지`입니다!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"이것은 <i>테스트</i> <b>html</b> <code>메시지</code>입니다!\",\n  \"Trash\": \"휴지통\",\n  \"You Were Added to Card\": \"카드에 추가되었습니다\",\n  \"You Were Mentioned in Comment\": \"댓글에서 언급되었습니다\",\n  \"%s added you to %s on %s\": \"%s님이 %s의 %s에 당신을 추가했습니다\",\n  \"%s created %s in %s on %s\": \"%s님이 %s의 %s에 %s을(를) 생성했습니다\",\n  \"%s left a new comment to %s on %s\": \"%s님이 %s의 %s에 새 댓글을 남겼습니다\",\n  \"%s mentioned you in %s on %s\": \"%s님이 %s의 %s에서 당신을 언급했습니다\",\n  \"%s moved %s from %s to %s on %s\": \"%s님이 %s의 %s을(를) %s에서 %s(으)로 이동했습니다\"\n}\n"
  },
  {
    "path": "server/config/locales/nl-NL.json",
    "content": "{\n  \"Archive\": \"Archief\",\n  \"Card Created\": \"Kaart aangemaakt\",\n  \"Card Moved\": \"Kaart verplaatst\",\n  \"copy\": \"kopie\",\n  \"New Comment\": \"Nieuwe reactie\",\n  \"Test Title\": \"Test titel\",\n  \"This is a test text message!\": \"Dit is een test tekstbericht!\",\n  \"This is a *test* **markdown** `message`!\": \"Dit is een *test* **markdown** `bericht`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Dit is een <i>test</i> <b>html</b> <code>bericht</code>!\",\n  \"Trash\": \"Prullenbak\",\n  \"You Were Added to Card\": \"Je bent toegevoegd aan kaart\",\n  \"You Were Mentioned in Comment\": \"Je bent genoemd in reactie\",\n  \"%s added you to %s on %s\": \"%s heeft je toegevoegd aan %s op %s\",\n  \"%s created %s in %s on %s\": \"%s heeft %s aangemaakt in %s op %s\",\n  \"%s left a new comment to %s on %s\": \"%s heeft een nieuwe reactie achtergelaten op %s op %s\",\n  \"%s mentioned you in %s on %s\": \"%s heeft je genoemd in %s op %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s heeft %s verplaatst van %s naar %s op %s\"\n}\n"
  },
  {
    "path": "server/config/locales/pl-PL.json",
    "content": "{\n  \"Archive\": \"Archiwum\",\n  \"Card Created\": \"Karta utworzona\",\n  \"Card Moved\": \"Karta przeniesiona\",\n  \"copy\": \"kopia\",\n  \"New Comment\": \"Nowy komentarz\",\n  \"Test Title\": \"Tytuł testowy\",\n  \"This is a test text message!\": \"To jest testowa wiadomość tekstowa!\",\n  \"This is a *test* **markdown** `message`!\": \"To jest *testowa* **markdown** `wiadomość`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"To jest <i>testowa</i> <b>html</b> <code>wiadomość</code>!\",\n  \"Trash\": \"Kosz\",\n  \"You Were Added to Card\": \"Zostałeś dodany do karty\",\n  \"You Were Mentioned in Comment\": \"Zostałeś wspomniany w komentarzu\",\n  \"%s added you to %s on %s\": \"%s dodał cię do %s w dniu %s\",\n  \"%s created %s in %s on %s\": \"%s utworzył %s w %s w dniu %s\",\n  \"%s left a new comment to %s on %s\": \"%s zostawił nowy komentarz do %s w dniu %s\",\n  \"%s mentioned you in %s on %s\": \"%s wspomniał o tobie w %s w dniu %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s przeniósł %s z %s do %s w dniu %s\"\n}\n"
  },
  {
    "path": "server/config/locales/pt-BR.json",
    "content": "{\n  \"Archive\": \"Arquivo\",\n  \"Card Created\": \"Cartão criado\",\n  \"Card Moved\": \"Cartão movido\",\n  \"copy\": \"cópia\",\n  \"New Comment\": \"Novo comentário\",\n  \"Test Title\": \"Título de teste\",\n  \"This is a test text message!\": \"Esta é uma mensagem de texto de teste!\",\n  \"This is a *test* **markdown** `message`!\": \"Esta é uma *mensagem* **markdown** `de teste`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Esta é uma <i>mensagem</i> <b>html</b> <code>de teste</code>!\",\n  \"Trash\": \"Lixeira\",\n  \"You Were Added to Card\": \"Você foi adicionado ao cartão\",\n  \"You Were Mentioned in Comment\": \"Você foi mencionado em comentário\",\n  \"%s added you to %s on %s\": \"%s adicionou você a %s em %s\",\n  \"%s created %s in %s on %s\": \"%s criou %s em %s em %s\",\n  \"%s left a new comment to %s on %s\": \"%s deixou um novo comentário em %s em %s\",\n  \"%s mentioned you in %s on %s\": \"%s mencionou você em %s em %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s moveu %s de %s para %s em %s\"\n}\n"
  },
  {
    "path": "server/config/locales/pt-PT.json",
    "content": "{\n  \"Archive\": \"Arquivo\",\n  \"Card Created\": \"Cartão criado\",\n  \"Card Moved\": \"Cartão movido\",\n  \"copy\": \"cópia\",\n  \"New Comment\": \"Novo comentário\",\n  \"Test Title\": \"Título de teste\",\n  \"This is a test text message!\": \"Esta é uma mensagem de texto de teste!\",\n  \"This is a *test* **markdown** `message`!\": \"Esta é uma *mensagem* **markdown** `de teste`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Esta é uma <i>mensagem</i> <b>html</b> <code>de teste</code>!\",\n  \"Trash\": \"Lixo\",\n  \"You Were Added to Card\": \"Foi adicionado ao cartão\",\n  \"You Were Mentioned in Comment\": \"Foi mencionado num comentário\",\n  \"%s added you to %s on %s\": \"%s adicionou-o a %s em %s\",\n  \"%s created %s in %s on %s\": \"%s criou %s em %s em %s\",\n  \"%s left a new comment to %s on %s\": \"%s deixou um novo comentário em %s em %s\",\n  \"%s mentioned you in %s on %s\": \"%s mencionou-o em %s em %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s moveu %s de %s para %s em %s\"\n}\n"
  },
  {
    "path": "server/config/locales/ro-RO.json",
    "content": "{\n  \"Archive\": \"Arhivă\",\n  \"Card Created\": \"Card creat\",\n  \"Card Moved\": \"Card mutat\",\n  \"copy\": \"copie\",\n  \"New Comment\": \"Comentariu nou\",\n  \"Test Title\": \"Titlu de test\",\n  \"This is a test text message!\": \"Acesta este un mesaj text de test!\",\n  \"This is a *test* **markdown** `message`!\": \"Acesta este un *mesaj* **markdown** `de test`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Acesta este un <i>mesaj</i> <b>html</b> <code>de test</code>!\",\n  \"Trash\": \"Coș de gunoi\",\n  \"You Were Added to Card\": \"Ați fost adăugat la card\",\n  \"You Were Mentioned in Comment\": \"Ați fost menționat într-un comentariu\",\n  \"%s added you to %s on %s\": \"%s v-a adăugat la %s pe %s\",\n  \"%s created %s in %s on %s\": \"%s a creat %s în %s pe %s\",\n  \"%s left a new comment to %s on %s\": \"%s a lăsat un comentariu nou la %s pe %s\",\n  \"%s mentioned you in %s on %s\": \"%s v-a menționat în %s pe %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s a mutat %s din %s în %s pe %s\"\n}\n"
  },
  {
    "path": "server/config/locales/ru-RU.json",
    "content": "{\n\t\"Archive\": \"Архив\",\n\t\"Card Created\": \"Карточка создана\",\n\t\"Card Moved\": \"Карточка перемещена\",\n\t\"copy\": \"копия\",\n\t\"New Comment\": \"Новый комментарий\",\n\t\"Test Title\": \"Тестовый заголовок\",\n\t\"This is a test text message!\": \"Это тестовое сообщение!\",\n\t\"This is a *test* **markdown** `message`!\": \"Это *тестовое* **markdown** `сообщение`!\",\n\t\"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Это <i>тестовое</i> <b>html</b> <code>сообщение</code>!\",\n\t\"Trash\": \"Корзина\",\n\t\"You Were Added to Card\": \"Вы были добавлены к карточке\",\n\t\"You Were Mentioned in Comment\": \"Вы были упомянуты в комментарии\",\n\t\"%s added you to %s on %s\": \"%s добавил(а) вас к %s на %s\",\n\t\"%s created %s in %s on %s\": \"%s создал(а) %s в %s на %s\",\n\t\"%s left a new comment to %s on %s\": \"%s оставил(а) новый комментарий к %s на %s\",\n\t\"%s mentioned you in %s on %s\": \"%s упомянул(а) вас в %s на %s\",\n\t\"%s moved %s from %s to %s on %s\": \"%s переместил(а) %s из %s в %s на %s\"\n}\n"
  },
  {
    "path": "server/config/locales/sk-SK.json",
    "content": "{\n  \"Archive\": \"Archív\",\n  \"Card Created\": \"Karta vytvorená\",\n  \"Card Moved\": \"Karta presunutá\",\n  \"copy\": \"kópia\",\n  \"New Comment\": \"Nový komentár\",\n  \"Test Title\": \"Testovací názov\",\n  \"This is a test text message!\": \"Toto je testovacia textová správa!\",\n  \"This is a *test* **markdown** `message`!\": \"Toto je *testovacia* **markdown** `správa`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Toto je <i>testovacia</i> <b>html</b> <code>správa</code>!\",\n  \"Trash\": \"Kôš\",\n  \"You Were Added to Card\": \"Boli ste pridaní ku karte\",\n  \"You Were Mentioned in Comment\": \"Boli ste spomenutí v komentári\",\n  \"%s added you to %s on %s\": \"%s vás pridal k %s dňa %s\",\n  \"%s created %s in %s on %s\": \"%s vytvoril %s v %s dňa %s\",\n  \"%s left a new comment to %s on %s\": \"%s zanechal nový komentár k %s dňa %s\",\n  \"%s mentioned you in %s on %s\": \"%s vás spomenul v %s dňa %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s presunul %s z %s do %s dňa %s\"\n}\n"
  },
  {
    "path": "server/config/locales/sr-Cyrl-RS.json",
    "content": "{\n  \"Archive\": \"Архива\",\n  \"Card Created\": \"Картица креирана\",\n  \"Card Moved\": \"Картица премештена\",\n  \"copy\": \"копија\",\n  \"New Comment\": \"Нови коментар\",\n  \"Test Title\": \"Тест наслов\",\n  \"This is a test text message!\": \"Ово је тест текстуална порука!\",\n  \"This is a *test* **markdown** `message`!\": \"Ово је *тест* **markdown** `порука`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Ово је <i>тест</i> <b>html</b> <code>порука</code>!\",\n  \"Trash\": \"Корпа за отпатке\",\n  \"You Were Added to Card\": \"Додати сте на картицу\",\n  \"You Were Mentioned in Comment\": \"Поменути сте у коментару\",\n  \"%s added you to %s on %s\": \"%s вас је додао на %s дана %s\",\n  \"%s created %s in %s on %s\": \"%s је креирао %s у %s дана %s\",\n  \"%s left a new comment to %s on %s\": \"%s је оставио нови коментар на %s дана %s\",\n  \"%s mentioned you in %s on %s\": \"%s вас је поменуо у %s дана %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s је преместио %s из %s у %s дана %s\"\n}\n"
  },
  {
    "path": "server/config/locales/sr-Latn-RS.json",
    "content": "{\n  \"Archive\": \"Arhiva\",\n  \"Card Created\": \"Kartica kreirana\",\n  \"Card Moved\": \"Kartica premeštena\",\n  \"copy\": \"kopija\",\n  \"New Comment\": \"Novi komentar\",\n  \"Test Title\": \"Test naslov\",\n  \"This is a test text message!\": \"Ovo je test tekstualna poruka!\",\n  \"This is a *test* **markdown** `message`!\": \"Ovo je *test* **markdown** `poruka`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Ovo je <i>test</i> <b>html</b> <code>poruka</code>!\",\n  \"Trash\": \"Korpa za otpatke\",\n  \"You Were Added to Card\": \"Dodati ste na karticu\",\n  \"You Were Mentioned in Comment\": \"Pomenuti ste u komentaru\",\n  \"%s added you to %s on %s\": \"%s vas je dodao na %s dana %s\",\n  \"%s created %s in %s on %s\": \"%s je kreirao %s u %s dana %s\",\n  \"%s left a new comment to %s on %s\": \"%s je ostavio novi komentar na %s dana %s\",\n  \"%s mentioned you in %s on %s\": \"%s vas je pomenuo u %s dana %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s je premestio %s iz %s u %s dana %s\"\n}\n"
  },
  {
    "path": "server/config/locales/sv-SE.json",
    "content": "{\n  \"Archive\": \"Arkiv\",\n  \"Card Created\": \"Kort skapat\",\n  \"Card Moved\": \"Kort flyttat\",\n  \"copy\": \"kopia\",\n  \"New Comment\": \"Ny kommentar\",\n  \"Test Title\": \"Test titel\",\n  \"This is a test text message!\": \"Detta är ett test textmeddelande!\",\n  \"This is a *test* **markdown** `message`!\": \"Detta är ett *test* **markdown** `meddelande`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Detta är ett <i>test</i> <b>html</b> <code>meddelande</code>!\",\n  \"Trash\": \"Papperskorg\",\n  \"You Were Added to Card\": \"Du lades till på kort\",\n  \"You Were Mentioned in Comment\": \"Du nämndes i kommentar\",\n  \"%s added you to %s on %s\": \"%s lade till dig på %s den %s\",\n  \"%s created %s in %s on %s\": \"%s skapade %s i %s den %s\",\n  \"%s left a new comment to %s on %s\": \"%s lämnade en ny kommentar till %s den %s\",\n  \"%s mentioned you in %s on %s\": \"%s nämnde dig i %s den %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s flyttade %s från %s till %s den %s\"\n}\n"
  },
  {
    "path": "server/config/locales/tr-TR.json",
    "content": "{\n  \"Archive\": \"Arşiv\",\n  \"Card Created\": \"Kart oluşturuldu\",\n  \"Card Moved\": \"Kart taşındı\",\n  \"copy\": \"kopya\",\n  \"New Comment\": \"Yeni yorum\",\n  \"Test Title\": \"Test başlığı\",\n  \"This is a test text message!\": \"Bu bir test metin mesajıdır!\",\n  \"This is a *test* **markdown** `message`!\": \"Bu bir *test* **markdown** `mesajı`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Bu bir <i>test</i> <b>html</b> <code>mesajı</code>!\",\n  \"Trash\": \"Çöp kutusu\",\n  \"You Were Added to Card\": \"Karta eklendiniz\",\n  \"You Were Mentioned in Comment\": \"Bir yorumda bahsedildiniz\",\n  \"%s added you to %s on %s\": \"%s sizi %s'ye %s tarihinde ekledi\",\n  \"%s created %s in %s on %s\": \"%s, %s'i %s içinde %s tarihinde oluşturdu\",\n  \"%s left a new comment to %s on %s\": \"%s, %s'ye %s tarihinde yeni bir yorum bıraktı\",\n  \"%s mentioned you in %s on %s\": \"%s, %s içinde %s tarihinde sizden bahsetti\",\n  \"%s moved %s from %s to %s on %s\": \"%s, %s'yi %s'den %s'ye %s tarihinde taşıdı\"\n}\n"
  },
  {
    "path": "server/config/locales/uk-UA.json",
    "content": "{\n\t\"Archive\": \"Архів\",\n\t\"Card Created\": \"Картку створено\",\n\t\"Card Moved\": \"Картку переміщено\",\n\t\"copy\": \"копія\",\n\t\"New Comment\": \"Новий коментар\",\n\t\"Test Title\": \"Тестовий заголовок\",\n\t\"This is a test text message!\": \"Це нове повідомлення!\",\n\t\"This is a *test* **markdown** `message`!\": \"Це *тестове* **markdown** `повідомлення`!\",\n\t\"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Це <i>тестове</i> <b>html</b> <code>повідомлення</code>!\",\n\t\"Trash\": \"Кошик\",\n\t\"You Were Added to Card\": \"Вас було додано до картки\",\n\t\"You Were Mentioned in Comment\": \"Вас було згадано у коментарі\",\n\t\"%s added you to %s on %s\": \"%s додав(ла) вас до %s на %s\",\n\t\"%s created %s in %s on %s\": \"%s створив(ла) %s в %s на %s\",\n\t\"%s left a new comment to %s on %s\": \"%s залишив(ла) новий коментар до %s на %s\",\n\t\"%s mentioned you in %s on %s\": \"%s згадав(ла) вас в %s на %s\",\n\t\"%s moved %s from %s to %s on %s\": \"%s перемістив(ла) %s з %s в %s на %s\"\n}\n"
  },
  {
    "path": "server/config/locales/uz-UZ.json",
    "content": "{\n  \"Archive\": \"Arxiv\",\n  \"Card Created\": \"Karta yaratildi\",\n  \"Card Moved\": \"Karta ko'chirildi\",\n  \"copy\": \"nusxa\",\n  \"New Comment\": \"Yangi izoh\",\n  \"Test Title\": \"Test sarlavha\",\n  \"This is a test text message!\": \"Bu test matn xabari!\",\n  \"This is a *test* **markdown** `message`!\": \"Bu *test* **markdown** `xabar`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Bu <i>test</i> <b>html</b> <code>xabar</code>!\",\n  \"Trash\": \"Chiqindi quti\",\n  \"You Were Added to Card\": \"Siz kartaga qo'shildingiz\",\n  \"You Were Mentioned in Comment\": \"Siz izohda eslatildingiz\",\n  \"%s added you to %s on %s\": \"%s sizni %s ga %s da qo'shdi\",\n  \"%s created %s in %s on %s\": \"%s yaratdi %s ni %s da %s da\",\n  \"%s left a new comment to %s on %s\": \"%s yangi izoh qoldirdi %s ga %s da\",\n  \"%s mentioned you in %s on %s\": \"%s sizni esladi %s da %s da\",\n  \"%s moved %s from %s to %s on %s\": \"%s ko'chirdi %s ni %s dan %s ga %s da\"\n}\n"
  },
  {
    "path": "server/config/locales/vi-VN.json",
    "content": "{\n  \"Archive\": \"Lưu trữ\",\n  \"Card Created\": \"Thẻ đã được tạo\",\n  \"Card Moved\": \"Thẻ đã được di chuyển\",\n  \"copy\": \"bản sao\",\n  \"New Comment\": \"Bình luận mới\",\n  \"Test Title\": \"Tiêu đề thử nghiệm\",\n  \"This is a test text message!\": \"Đây là tin nhắn văn bản thử nghiệm!\",\n  \"This is a *test* **markdown** `message`!\": \"Đây là *tin nhắn* **markdown** `thử nghiệm`!\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"Đây là <i>tin nhắn</i> <b>html</b> <code>thử nghiệm</code>!\",\n  \"Trash\": \"Thùng rác\",\n  \"You Were Added to Card\": \"Bạn đã được thêm vào thẻ\",\n  \"You Were Mentioned in Comment\": \"Bạn đã được nhắc đến trong bình luận\",\n  \"%s added you to %s on %s\": \"%s đã thêm bạn vào %s vào %s\",\n  \"%s created %s in %s on %s\": \"%s đã tạo %s trong %s vào %s\",\n  \"%s left a new comment to %s on %s\": \"%s đã để lại bình luận mới cho %s vào %s\",\n  \"%s mentioned you in %s on %s\": \"%s đã nhắc đến bạn trong %s vào %s\",\n  \"%s moved %s from %s to %s on %s\": \"%s đã di chuyển %s từ %s đến %s vào %s\"\n}\n"
  },
  {
    "path": "server/config/locales/zh-CN.json",
    "content": "{\n  \"Archive\": \"归档\",\n  \"Card Created\": \"卡片已创建\",\n  \"Card Moved\": \"卡片已移动\",\n  \"copy\": \"副本\",\n  \"New Comment\": \"新评论\",\n  \"Test Title\": \"测试标题\",\n  \"This is a test text message!\": \"这是一条测试文本消息！\",\n  \"This is a *test* **markdown** `message`!\": \"这是一条*测试***markdown**`消息`！\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"这是一条<i>测试</i><b>html</b><code>消息</code>！\",\n  \"Trash\": \"回收站\",\n  \"You Were Added to Card\": \"您已被添加到卡片\",\n  \"You Were Mentioned in Comment\": \"您在评论中被提及\",\n  \"%s added you to %s on %s\": \"%s 在 %s 将您添加到 %s\",\n  \"%s created %s in %s on %s\": \"%s 在 %s 的 %s 中创建了 %s\",\n  \"%s left a new comment to %s on %s\": \"%s 在 %s 对 %s 留下了新评论\",\n  \"%s mentioned you in %s on %s\": \"%s 在 %s 的 %s 中提及了您\",\n  \"%s moved %s from %s to %s on %s\": \"%s 在 %s 将 %s 从 %s 移动到 %s\"\n}\n"
  },
  {
    "path": "server/config/locales/zh-TW.json",
    "content": "{\n  \"Archive\": \"封存\",\n  \"Card Created\": \"卡片已建立\",\n  \"Card Moved\": \"卡片已移動\",\n  \"copy\": \"副本\",\n  \"New Comment\": \"新留言\",\n  \"Test Title\": \"測試標題\",\n  \"This is a test text message!\": \"這是一則測試文字訊息！\",\n  \"This is a *test* **markdown** `message`!\": \"這是一則*測試***markdown**`訊息`！\",\n  \"This is a <i>test</i> <b>html</b> <code>message</code>!\": \"這是一則<i>測試</i><b>html</b><code>訊息</code>！\",\n  \"Trash\": \"垃圾桶\",\n  \"You Were Added to Card\": \"您已被加入卡片\",\n  \"You Were Mentioned in Comment\": \"您在留言中被提及\",\n  \"%s added you to %s on %s\": \"%s 在 %s 將您加入 %s\",\n  \"%s created %s in %s on %s\": \"%s 在 %s 的 %s 中建立了 %s\",\n  \"%s left a new comment to %s on %s\": \"%s 在 %s 對 %s 留下了新留言\",\n  \"%s mentioned you in %s on %s\": \"%s 在 %s 的 %s 中提及了您\",\n  \"%s moved %s from %s to %s on %s\": \"%s 在 %s 將 %s 從 %s 移動到 %s\"\n}\n"
  },
  {
    "path": "server/config/log.js",
    "content": "/**\n * Built-in Log Configuration\n * (sails.config.log)\n *\n * Configure the log level for your app, as well as the transport\n * (Underneath the covers, Sails uses Winston for logging, which\n * allows for some pretty neat custom transports/adapters for log messages)\n *\n * For more information on the Sails logger, check out:\n * https://sailsjs.com/docs/concepts/logging\n */\n\nmodule.exports.log = {\n  /**\n   *\n   * Valid `level` configs: i.e. the minimum log level to capture with\n   * sails.log.*()\n   *\n   * The order of precedence for log levels from lowest to highest is:\n   * silly, verbose, info, debug, warn, error\n   *\n   * You may also set the level to \"silent\" to suppress all logs.\n   *\n   */\n  // level: 'info',\n};\n"
  },
  {
    "path": "server/config/models.js",
    "content": "/**\n * Default model settings\n * (sails.config.models)\n *\n * Your default, project-wide model settings. Can also be overridden on a\n * per-model basis by setting a top-level properties in the model definition.\n *\n * For details about all available model settings, see:\n * https://sailsjs.com/config/models\n *\n * For more general background on Sails model settings, and how to configure\n * them on a project-wide or per-model basis, see:\n * https://sailsjs.com/docs/concepts/models-and-orm/model-settings\n */\n\nmodule.exports.models = {\n  /**\n   *\n   * Whether model methods like `.create()` and `.update()` should ignore\n   * (and refuse to persist) unrecognized data-- i.e. properties other than\n   * those explicitly defined by attributes in the model definition.\n   *\n   * To ease future maintenance of your code base, it is usually a good idea\n   * to set this to `true`.\n   *\n   * > Note that `schema: false` is not supported by every database.\n   * > For example, if you are using a SQL database, then relevant models\n   * > are always effectively `schema: true`.  And if no `schema` setting is\n   * > provided whatsoever, the behavior is left up to the database adapter.\n   * >\n   * > For more info, see:\n   * > https://sailsjs.com/docs/concepts/orm/model-settings#?schema\n   *\n   */\n\n  // schema: true,\n\n  /**\n   *\n   * How and whether Sails will attempt to automatically rebuild the\n   * tables/collections/etc. in your schema.\n   *\n   * > Note that, when running in a production environment, this will be\n   * > automatically set to `migrate: 'safe'`, no matter what you configure\n   * > here.  This is a failsafe to prevent Sails from accidentally running\n   * > auto-migrations on your production database.\n   * >\n   * > For more info, see:\n   * > https://sailsjs.com/docs/concepts/orm/model-settings#?migrate\n   *\n   */\n\n  migrate: 'safe',\n\n  /**\n   *\n   * Base attributes that are included in all of your models by default.\n   * By convention, this is your primary key attribute (`id`), as well as two\n   * other timestamp attributes for tracking when records were last created\n   * or updated.\n   *\n   * > For more info, see:\n   * > https://sailsjs.com/docs/concepts/orm/model-settings#?attributes\n   *\n   */\n\n  attributes: {\n    id: {\n      type: 'string',\n      autoIncrement: true,\n    },\n    createdAt: {\n      type: 'ref',\n      columnName: 'created_at',\n    },\n    updatedAt: {\n      type: 'ref',\n      columnName: 'updated_at',\n    },\n  },\n\n  beforeCreate(valuesToSet, proceed) {\n    valuesToSet.createdAt = new Date().toISOString(); // eslint-disable-line no-param-reassign\n\n    proceed();\n  },\n\n  beforeUpdate(valuesToSet, proceed) {\n    valuesToSet.updatedAt = new Date().toISOString(); // eslint-disable-line no-param-reassign\n\n    proceed();\n  },\n\n  /**\n   *\n   * The set of DEKs (data encryption keys) for at-rest encryption.\n   * i.e. when encrypting/decrypting data for attributes with `encrypt: true`.\n   *\n   * > The `default` DEK is used for all new encryptions, but multiple DEKs\n   * > can be configured to allow for key rotation.  In production, be sure to\n   * > manage these keys like you would any other sensitive credential.\n   *\n   * > For more info, see:\n   * > https://sailsjs.com/docs/concepts/orm/model-settings#?dataEncryptionKeys\n   *\n   */\n\n  dataEncryptionKeys: {\n    default: 'fKSf/hPekelUegjM7IyM/EhHbd7HI9Kiec5Lxy2t+7w=',\n  },\n\n  /**\n   *\n   * Whether or not implicit records for associations should be cleaned up\n   * automatically using the built-in polyfill.  This is especially useful\n   * during development with sails-disk.\n   *\n   * Depending on which databases you're using, you may want to disable this\n   * polyfill in your production environment.\n   *\n   * (For production configuration, see `config/env/production.js`.)\n   *\n   */\n\n  // cascadeOnDestroy: true,\n\n  archiveModelIdentity: false,\n};\n"
  },
  {
    "path": "server/config/policies.js",
    "content": "/**\n * Policy Mappings\n * (sails.config.policies)\n *\n * Policies are simple functions which run **before** your actions.\n *\n * For more information on configuring policies, check out:\n * https://sailsjs.com/docs/concepts/policies\n */\n\nmodule.exports.policies = {\n  /**\n   *\n   * Default policy for all controllers and actions, unless overridden.\n   * (`true` allows public access)\n   *\n   */\n\n  '*': ['is-authenticated', 'is-external'],\n\n  'config/show': ['is-authenticated', 'is-admin'],\n  'config/update': ['is-authenticated', 'is-admin'],\n  'config/test-smtp': ['is-authenticated', 'is-admin'],\n\n  'webhooks/index': ['is-authenticated', 'is-external', 'is-admin'],\n  'webhooks/create': ['is-authenticated', 'is-external', 'is-admin'],\n  'webhooks/update': ['is-authenticated', 'is-external', 'is-admin'],\n  'webhooks/delete': ['is-authenticated', 'is-external', 'is-admin'],\n\n  'access-tokens/delete': ['is-authenticated', 'is-external', 'is-session'],\n\n  'users/index': 'is-authenticated',\n  'users/create': ['is-authenticated', 'is-admin'],\n  'users/show': 'is-authenticated',\n  'users/update': 'is-authenticated',\n  'users/update-email': 'is-authenticated',\n  'users/update-password': 'is-authenticated',\n  'users/update-username': 'is-authenticated',\n  'users/update-avatar': 'is-authenticated',\n  'users/create-api-key': ['is-authenticated', 'is-admin'],\n  'users/delete': ['is-authenticated', 'is-admin'],\n\n  'projects/create': ['is-authenticated', 'is-external', 'is-admin-or-project-owner'],\n\n  '_internal/update-config': ['is-authenticated', 'is-internal'],\n\n  index: true,\n  'swagger/show': true,\n  'bootstrap/show': true,\n  'terms/show': true,\n  'access-tokens/create': true,\n  'access-tokens/exchange-with-oidc': true,\n  'access-tokens/debug-oidc': true,\n  'access-tokens/accept-terms': true,\n  'access-tokens/revoke-pending-token': true,\n};\n"
  },
  {
    "path": "server/config/routes.js",
    "content": "/**\n * Route Mappings\n * (sails.config.routes)\n *\n * Your routes tell Sails what to do each time it receives a request.\n *\n * For more information on configuring custom routes, check out:\n * https://sailsjs.com/anatomy/config/routes-js\n */\n\nconst path = require('path');\nconst sails = require('sails');\n\n// Remove prefix from urlPath, assuming completely matches a subpath of\n// urlPath. The result preserves query params and fragment if present\n//\n// Examples:\n// '/foo', '/foo/bar'     -> '/bar'\n// '/foo', '/foo'         -> '/'\n// '/foo', '/foo?baz=bux' -> '/?baz=bux'\n// '/foo', '/foobar'      -> '/foobar'\nconst removeRoutePrefix = (prefix, urlPath) => {\n  if (urlPath.startsWith(prefix)) {\n    const subpath = urlPath.substring(prefix.length);\n\n    if (subpath.startsWith('/')) {\n      // Prefix matched a complete set of path segments, with a valid path\n      // remaining.\n      return subpath;\n    }\n\n    if (subpath.length === 0 || subpath.startsWith('?') || subpath.startsWith('#')) {\n      // Prefix matched a complete set of path segments, but there is no path\n      // remaining. Add '/'.\n      return `/${subpath}`;\n    }\n  }\n\n  // Either the prefix didn't match at all, or it wasn't a complete path match\n  // (e.g. we don't want to treat '/foo' as a prefix of '/foobar'). Leave the\n  // path as-is.\n  return urlPath;\n};\n\nconst serveStatic = async (prefix, getPathSegment, req, res) => {\n  // Custom config properties are not available when the routes config is\n  // loaded, so resolve the target value just before serving the request.\n  const pathSegment = getPathSegment();\n  // Remove the leading route prefix, since it's already included in path\n  // segment.\n  const normalizedUrlPath = removeRoutePrefix(prefix, req.url);\n\n  const fileManager = sails.hooks['file-manager'].getInstance();\n  const filePathSegment = path.join(pathSegment, normalizedUrlPath);\n\n  let readStream;\n  let headers;\n\n  try {\n    [readStream, headers] = await fileManager.read(filePathSegment, {\n      withHeaders: true,\n    });\n  } catch (err) {\n    return res.sendStatus(404);\n  }\n\n  res.set({\n    ...headers,\n    'Cache-Control': `private, max-age=${sails.config.http.cache}, immutable`,\n  });\n\n  readStream.on('error', () => {\n    if (res.headersSent) {\n      res.destroy();\n    } else {\n      res.sendStatus(404);\n    }\n  });\n\n  return readStream.pipe(res);\n};\n\n/* const publicStaticDirServer = (prefix, getPathSegment) => (req, res, next) => {\n  if (!req.url.startsWith(prefix)) {\n    return next();\n  }\n\n  return serveStatic(prefix, getPathSegment, req, res);\n}; */\n\nconst protectedStaticDirServer = (prefix, getPathSegment) => (req, res, next) => {\n  if (!req.url.startsWith(prefix)) {\n    return next();\n  }\n\n  try {\n    sails.helpers.utils.verifyJwtToken(req.cookies.accessToken);\n  } catch (error) {\n    return res.sendStatus(401);\n  }\n\n  return serveStatic(prefix, getPathSegment, req, res);\n};\n\nmodule.exports.routes = {\n  'GET /api/bootstrap': 'bootstrap/show',\n\n  'GET /api/terms': 'terms/show',\n\n  'GET /api/config': 'config/show',\n  'PATCH /api/config': 'config/update',\n  'POST /api/config/test-smtp': 'config/test-smtp',\n\n  'GET /api/webhooks': 'webhooks/index',\n  'POST /api/webhooks': 'webhooks/create',\n  'PATCH /api/webhooks/:id': 'webhooks/update',\n  'DELETE /api/webhooks/:id': 'webhooks/delete',\n\n  'POST /api/access-tokens': 'access-tokens/create',\n  'POST /api/access-tokens/exchange-with-oidc': 'access-tokens/exchange-with-oidc',\n  'POST /api/access-tokens/debug-oidc': 'access-tokens/debug-oidc',\n  'POST /api/access-tokens/accept-terms': 'access-tokens/accept-terms',\n  'POST /api/access-tokens/revoke-pending-token': 'access-tokens/revoke-pending-token',\n  'DELETE /api/access-tokens/me': 'access-tokens/delete',\n\n  'GET /api/users': 'users/index',\n  'POST /api/users': 'users/create',\n  'GET /api/users/:id': 'users/show',\n  'PATCH /api/users/:id': 'users/update',\n  'PATCH /api/users/:id/email': 'users/update-email',\n  'PATCH /api/users/:id/password': 'users/update-password',\n  'PATCH /api/users/:id/username': 'users/update-username',\n  'POST /api/users/:id/avatar': 'users/update-avatar',\n  'POST /api/users/:id/api-key': 'users/create-api-key',\n  'DELETE /api/users/:id': 'users/delete',\n\n  'GET /api/projects': 'projects/index',\n  'POST /api/projects': 'projects/create',\n  'GET /api/projects/:id': 'projects/show',\n  'PATCH /api/projects/:id': 'projects/update',\n  'DELETE /api/projects/:id': 'projects/delete',\n\n  'POST /api/projects/:projectId/project-managers': 'project-managers/create',\n  'DELETE /api/project-managers/:id': 'project-managers/delete',\n\n  'POST /api/projects/:projectId/background-images': 'background-images/create',\n  'DELETE /api/background-images/:id': 'background-images/delete',\n\n  'POST /api/projects/:projectId/base-custom-field-groups': 'base-custom-field-groups/create',\n  'PATCH /api/base-custom-field-groups/:id': 'base-custom-field-groups/update',\n  'DELETE /api/base-custom-field-groups/:id': 'base-custom-field-groups/delete',\n\n  'POST /api/projects/:projectId/boards': 'boards/create',\n  'GET /api/boards/:id': 'boards/show',\n  'PATCH /api/boards/:id': 'boards/update',\n  'DELETE /api/boards/:id': 'boards/delete',\n\n  'POST /api/boards/:boardId/board-memberships': 'board-memberships/create',\n  'PATCH /api/board-memberships/:id': 'board-memberships/update',\n  'DELETE /api/board-memberships/:id': 'board-memberships/delete',\n\n  'POST /api/boards/:boardId/labels': 'labels/create',\n  'PATCH /api/labels/:id': 'labels/update',\n  'DELETE /api/labels/:id': 'labels/delete',\n\n  'POST /api/boards/:boardId/lists': 'lists/create',\n  'GET /api/lists/:id': 'lists/show',\n  'PATCH /api/lists/:id': 'lists/update',\n  'POST /api/lists/:id/sort': 'lists/sort',\n  'POST /api/lists/:id/move-cards': 'lists/move-cards',\n  'POST /api/lists/:id/clear': 'lists/clear',\n  'DELETE /api/lists/:id': 'lists/delete',\n\n  'GET /api/lists/:listId/cards': 'cards/index',\n  'POST /api/lists/:listId/cards': 'cards/create',\n  'GET /api/cards/:id': 'cards/show',\n  'PATCH /api/cards/:id': 'cards/update',\n  'POST /api/cards/:id/duplicate': 'cards/duplicate',\n  'POST /api/cards/:id/read-notifications': 'cards/read-notifications',\n  'DELETE /api/cards/:id': 'cards/delete',\n  'POST /api/cards/:cardId/card-memberships': 'card-memberships/create',\n  'DELETE /api/cards/:cardId/card-memberships/userId::userId': 'card-memberships/delete',\n  'POST /api/cards/:cardId/card-labels': 'card-labels/create',\n  'DELETE /api/cards/:cardId/card-labels/labelId::labelId': 'card-labels/delete',\n\n  'POST /api/cards/:cardId/task-lists': 'task-lists/create',\n  'GET /api/task-lists/:id': 'task-lists/show',\n  'PATCH /api/task-lists/:id': 'task-lists/update',\n  'DELETE /api/task-lists/:id': 'task-lists/delete',\n\n  'POST /api/task-lists/:taskListId/tasks': 'tasks/create',\n  'PATCH /api/tasks/:id': 'tasks/update',\n  'DELETE /api/tasks/:id': 'tasks/delete',\n\n  'POST /api/cards/:cardId/attachments': 'attachments/create',\n  'PATCH /api/attachments/:id': 'attachments/update',\n  'DELETE /api/attachments/:id': 'attachments/delete',\n\n  'POST /api/boards/:boardId/custom-field-groups': 'custom-field-groups/create-in-board',\n  'POST /api/cards/:cardId/custom-field-groups': 'custom-field-groups/create-in-card',\n  'GET /api/custom-field-groups/:id': 'custom-field-groups/show',\n  'PATCH /api/custom-field-groups/:id': 'custom-field-groups/update',\n  'DELETE /api/custom-field-groups/:id': 'custom-field-groups/delete',\n\n  'POST /api/base-custom-field-groups/:baseCustomFieldGroupId/custom-fields':\n    'custom-fields/create-in-base-custom-field-group',\n  'POST /api/custom-field-groups/:customFieldGroupId/custom-fields':\n    'custom-fields/create-in-custom-field-group',\n  'PATCH /api/custom-fields/:id': 'custom-fields/update',\n  'DELETE /api/custom-fields/:id': 'custom-fields/delete',\n\n  'PATCH /api/cards/:cardId/custom-field-values/customFieldGroupId::customFieldGroupId[:]customFieldId::customFieldId':\n    'custom-field-values/create-or-update',\n  'DELETE /api/cards/:cardId/custom-field-values/customFieldGroupId::customFieldGroupId[:]customFieldId::customFieldId':\n    'custom-field-values/delete',\n\n  'GET /api/cards/:cardId/comments': 'comments/index',\n  'POST /api/cards/:cardId/comments': 'comments/create',\n  'PATCH /api/comments/:id': 'comments/update',\n  'DELETE /api/comments/:id': 'comments/delete',\n\n  'GET /api/boards/:boardId/actions': 'actions/index-in-board',\n  'GET /api/cards/:cardId/actions': 'actions/index-in-card',\n\n  'GET /api/notifications': 'notifications/index',\n  'GET /api/notifications/:id': 'notifications/show',\n  'PATCH /api/notifications/:id': 'notifications/update',\n  'POST /api/notifications/read-all': 'notifications/read-all',\n\n  'POST /api/users/:userId/notification-services': 'notification-services/create-in-user',\n  'POST /api/boards/:boardId/notification-services': 'notification-services/create-in-board',\n  'PATCH /api/notification-services/:id': 'notification-services/update',\n  'POST /api/notification-services/:id/test': 'notification-services/test',\n  'DELETE /api/notification-services/:id': 'notification-services/delete',\n\n  'PATCH /api/_internal/config': '_internal/update-config',\n\n  'GET /swagger.json': 'swagger/show',\n\n  'GET /favicons/*': {\n    fn: protectedStaticDirServer('/favicons', () => sails.config.custom.faviconsPathSegment),\n    skipAssets: false,\n  },\n\n  'GET /user-avatars/*': {\n    fn: protectedStaticDirServer('/user-avatars', () => sails.config.custom.userAvatarsPathSegment),\n    skipAssets: false,\n  },\n\n  'GET /background-images/*': {\n    fn: protectedStaticDirServer(\n      '/background-images',\n      () => sails.config.custom.backgroundImagesPathSegment,\n    ),\n    skipAssets: false,\n  },\n\n  'GET /attachments/:id/download/:filename': {\n    action: 'file-attachments/download',\n    skipAssets: false,\n  },\n\n  'GET r|^/attachments/(\\\\w+)/download/thumbnails/([\\\\w-]+).(\\\\w+)$|id,fileName,fileExtension': {\n    action: 'file-attachments/download-thumbnail',\n    skipAssets: false,\n  },\n\n  'GET /*': {\n    action: 'index',\n    skipAssets: true,\n  },\n};\n"
  },
  {
    "path": "server/config/security.js",
    "content": "/**\n * Security Settings\n * (sails.config.security)\n *\n * These settings affect aspects of your app's security, such\n * as how it deals with cross-origin requests (CORS) and which\n * routes require a CSRF token to be included with the request.\n *\n * For an overview of how Sails handles security, see:\n * https://sailsjs.com/documentation/concepts/security\n *\n * For additional options and more information, see:\n * https://sailsjs.com/config/security\n */\n\nmodule.exports.security = {\n  /**\n   *\n   * CORS is like a more modern version of JSONP-- it allows your application\n   * to circumvent browsers' same-origin policy, so that the responses from\n   * your Sails app hosted on one domain (e.g. example.com) can be received\n   * in the client-side JavaScript code from a page you trust hosted on _some\n   * other_ domain (e.g. trustedsite.net).\n   *\n   * For additional options and more information, see:\n   * https://sailsjs.com/docs/concepts/security/cors\n   *\n   */\n\n  cors: {\n    allRoutes: true,\n    allowOrigins: ['http://localhost:3000'],\n    allowRequestHeaders: ['Authorization'],\n    allowCredentials: true,\n  },\n\n  /**\n   *\n   * By default, Sails' built-in CSRF protection is disabled to facilitate\n   * rapid development.  But be warned!  If your Sails app will be accessed by\n   * web browsers, you should _always_ enable CSRF protection before deploying\n   * to production.\n   *\n   * To enable CSRF protection, set this to `true`.\n   *\n   * For more information, see:\n   * https://sailsjs.com/docs/concepts/security/csrf\n   *\n   */\n\n  // csrf: false,\n};\n"
  },
  {
    "path": "server/config/session.js",
    "content": "/**\n * Session Configuration\n * (sails.config.session)\n *\n * Use the settings below to configure session integration in your app.\n * (for additional recommended settings, see `config/env/production.js`)\n *\n * For all available options, see:\n * https://sailsjs.com/config/session\n */\n\nmodule.exports.session = {\n  /**\n   *\n   * Session secret is automatically generated when your new app is created\n   * Replace at your own risk in production-- you will invalidate the cookies\n   * of your users, forcing them to log in again.\n   *\n   */\n\n  secret: process.env.SECRET_KEY,\n\n  /**\n   *\n   * Customize when built-in session support will be skipped.\n   *\n   * (Useful for performance tuning; particularly to avoid wasting cycles on\n   * session management when responding to simple requests for static assets,\n   * like images or stylesheets.)\n   *\n   * https://sailsjs.com/config/session\n   *\n   */\n\n  // isSessionDisabled: function (req){\n  //   return !!req.path.match(req._sails.LOOKS_LIKE_ASSET_RX);\n  // },\n};\n"
  },
  {
    "path": "server/config/sockets.js",
    "content": "/**\n * WebSocket Server Settings\n * (sails.config.sockets)\n *\n * Use the settings below to configure realtime functionality in your app.\n * (for additional recommended settings, see `config/env/production.js`)\n *\n * For all available options, see:\n * https://sailsjs.com/config/sockets\n */\n\nmodule.exports.sockets = {\n  /**\n   *\n   * `transports`\n   *\n   * The protocols or \"transports\" that socket clients are permitted to\n   * use when connecting and communicating with this Sails application.\n   *\n   * > Never change this here without also configuring `io.sails.transports`\n   * > in your client-side code.  If the client and the server are not using\n   * > the same array of transports, sockets will not work properly.\n   * >\n   * > For more info, see:\n   * > https://sailsjs.com/docs/reference/web-sockets/socket-client\n   *\n   */\n  // transports: [ 'websocket' ],\n  /**\n   *\n   * `beforeConnect`\n   *\n   * This custom beforeConnect function will be run each time BEFORE a new\n   * socket is allowed to connect, when the initial socket.io handshake is\n   * performed with the server.\n   *\n   * https://sailsjs.com/config/sockets#?beforeconnect\n   *\n   */\n  // beforeConnect: function(handshake, proceed) {\n  //\n  //   // `true` allows the socket to connect.\n  //   // (`false` would reject the connection)\n  //   return proceed(undefined, true);\n  //\n  // },\n  /**\n   *\n   * `afterDisconnect`\n   *\n   * This custom afterDisconnect function will be run each time a socket\n   * disconnects\n   *\n   */\n  // afterDisconnect: function(session, socket, done) {\n  //\n  //   // By default: do nothing.\n  //   // (but always trigger the callback)\n  //   return done();\n  //\n  // },\n  /**\n   *\n   * Whether to expose a 'GET /__getcookie' route that sets an HTTP-only\n   * session cookie.\n   *\n   */\n  // grant3rdPartyCookie: true,\n};\n"
  },
  {
    "path": "server/config/swagger.js",
    "content": "const version = require('../version');\n\nmodule.exports = {\n  definition: {\n    openapi: '3.0.0',\n    info: {\n      version,\n      title: 'PLANKA API',\n      description:\n        'API documentation for PLANKA - Real-Time Collaborative Kanban Board Application',\n      license: {\n        name: 'Fair Use License',\n        url: 'https://github.com/plankanban/planka/blob/master/LICENSE.md',\n      },\n    },\n    servers: [\n      {\n        url: '/api',\n        description: 'Base path for API endpoints',\n      },\n    ],\n    components: {\n      securitySchemes: {\n        bearerAuth: {\n          type: 'http',\n          scheme: 'bearer',\n          bearerFormat: 'JWT',\n        },\n        apiKeyAuth: {\n          type: 'apiKey',\n          in: 'header',\n          name: 'X-Api-Key',\n        },\n      },\n    },\n    security: [\n      {\n        bearerAuth: [],\n      },\n      {\n        apiKeyAuth: [],\n      },\n    ],\n  },\n  apis: ['./api/controllers/**/*.js', './api/models/*.js', './api/responses/*.js'],\n};\n"
  },
  {
    "path": "server/config/views.js",
    "content": "/**\n * View Engine Configuration\n * (sails.config.views)\n *\n * Server-sent views are a secure and effective way to get your app up\n * and running. Views are normally served from actions.  Below, you can\n * configure your templating language/framework of choice and configure\n * Sails' layout support.\n *\n * For details on available options for configuring server-side views, check out:\n * https://sailsjs.com/config/views\n *\n * For more background information on views and partials in Sails, check out:\n * https://sailsjs.com/docs/concepts/views\n */\n\nmodule.exports.views = {\n  /**\n   *\n   * Extension to use for your views. When calling `res.view()` in an action,\n   * you can leave this extension off. For example, calling\n   * `res.view('homepage')` will (using default settings) look for a\n   * `views/homepage.ejs` file.\n   *\n   */\n\n  // extension: 'ejs',\n\n  /**\n   *\n   * The path (relative to the views directory, and without extension) to\n   * the default layout file to use, or `false` to disable layouts entirely.\n   *\n   * Note that layouts only work with the built-in EJS view engine!\n   *\n   */\n\n  layout: false,\n};\n"
  },
  {
    "path": "server/constants.js",
    "content": "const AccessTokenSteps = {\n  ACCEPT_TERMS: 'accept-terms',\n};\n\nconst POSITION_GAP = 65536;\n\nconst MAX_SIZE_TO_GET_ENCODING = 8 * 1024 * 1024;\nconst MAX_SIZE_TO_PROCESS_AS_IMAGE = 64 * 1024 * 1024;\n\nmodule.exports = {\n  AccessTokenSteps,\n  POSITION_GAP,\n  MAX_SIZE_TO_GET_ENCODING,\n  MAX_SIZE_TO_PROCESS_AS_IMAGE,\n};\n"
  },
  {
    "path": "server/data/.gitkeep",
    "content": ""
  },
  {
    "path": "server/db/create-admin-user.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/* eslint-disable no-await-in-loop */\n/* eslint-disable no-console */\n\nconst { read } = require('read');\nconst initKnex = require('knex');\n\nconst knexfile = require('./knexfile');\n\nconst knex = initKnex(knexfile);\n\nconst input = async (fieldName, options = {}) => {\n  const readOptions = {\n    prompt: `${fieldName}${!options.isRequired ? ' (optional)' : ''}: `,\n  };\n\n  if (options.isPassword) {\n    Object.assign(readOptions, {\n      silent: true,\n      replace: '*',\n    });\n  }\n\n  let value;\n  while (!value) {\n    value = await read(readOptions);\n\n    if (!options.isPassword) {\n      value = value.trim();\n    }\n\n    if (options.isRequired && !value) {\n      console.log(`${fieldName} cannot be blank!`);\n    } else {\n      break;\n    }\n  }\n\n  return value;\n};\n\n(async () => {\n  try {\n    await knex.migrate.latest();\n\n    process.env.DEFAULT_ADMIN_EMAIL = await input('Email', {\n      isRequired: true,\n    });\n\n    process.env.DEFAULT_ADMIN_PASSWORD = await input('Password', {\n      isRequired: true,\n      isPassword: true,\n    });\n\n    process.env.DEFAULT_ADMIN_NAME = await input('Name', {\n      isRequired: true,\n    });\n\n    process.env.DEFAULT_ADMIN_USERNAME = await input('Username');\n\n    await knex.seed.run({\n      specific: 'default.js',\n    });\n  } catch (error) {\n    process.exitCode = 1;\n    throw error;\n  } finally {\n    knex.destroy();\n  }\n})();\n"
  },
  {
    "path": "server/db/init.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst initKnex = require('knex');\n\nconst knexfile = require('./knexfile');\n\nconst knex = initKnex(knexfile);\n\n(async () => {\n  try {\n    await knex.migrate.latest();\n    await knex.seed.run();\n  } catch (error) {\n    process.exitCode = 1;\n    throw error;\n  } finally {\n    knex.destroy();\n  }\n})();\n"
  },
  {
    "path": "server/db/knexfile.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst path = require('path');\nconst dotenv = require('dotenv');\nconst _ = require('lodash');\n\ndotenv.config({\n  path: path.resolve(__dirname, '../.env'),\n  quiet: true,\n});\n\nfunction buildSSLConfig() {\n  if (process.env.KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE === 'false') {\n    return {\n      rejectUnauthorized: false,\n    };\n  }\n\n  return false;\n}\n\nmodule.exports = {\n  client: 'pg',\n  connection: {\n    connectionString: process.env.DATABASE_URL,\n    ssl: buildSSLConfig(),\n  },\n  migrations: {\n    tableName: 'migration',\n    directory: path.join(__dirname, 'migrations'),\n  },\n  seeds: {\n    directory: path.join(__dirname, 'seeds'),\n  },\n  wrapIdentifier: (value, origImpl) => origImpl(_.snakeCase(value)),\n};\n"
  },
  {
    "path": "server/db/migrations/20250228000022_version_2.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports.up = async (knex) => {\n  await knex.raw(`\n    CREATE EXTENSION IF NOT EXISTS pg_trgm;\n\n    CREATE SEQUENCE next_id_seq;\n    CREATE FUNCTION next_id(OUT id BIGINT) AS $$\n      DECLARE\n        shard INT := 1;\n        epoch BIGINT := 1567191600000;\n        sequence BIGINT;\n        milliseconds BIGINT;\n      BEGIN\n        SELECT nextval('next_id_seq') % 1024 INTO sequence;\n        SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO milliseconds;\n        id := (milliseconds - epoch) << 23;\n        id := id | (shard << 10);\n        id := id | (sequence);\n      END;\n    $$ LANGUAGE PLPGSQL;\n  `);\n\n  await knex.schema.createTable('file_reference', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.integer('total');\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('total');\n  });\n\n  await knex.schema\n    .createTable('user_account', (table) => {\n      /* Columns */\n\n      table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n      table.text('email').notNullable();\n      table.text('password');\n      table.text('role').notNullable();\n      table.text('name').notNullable();\n      table.text('username');\n      table.jsonb('avatar');\n      table.text('phone');\n      table.text('organization');\n      table.text('language');\n      table.boolean('subscribe_to_own_cards').notNullable();\n      table.boolean('subscribe_to_card_when_commenting').notNullable();\n      table.boolean('turn_off_recent_card_highlighting').notNullable();\n      table.boolean('enable_favorites_by_default').notNullable();\n      table.text('default_editor_mode').notNullable();\n      table.text('default_home_view').notNullable();\n      table.text('default_projects_order').notNullable();\n      table.boolean('is_sso_user').notNullable();\n      table.boolean('is_deactivated').notNullable();\n\n      table.timestamp('created_at', true);\n      table.timestamp('updated_at', true);\n      table.timestamp('password_changed_at', true);\n\n      /* Indexes */\n\n      table.unique('email');\n      table.index('role');\n      table.index('username');\n      table.index('is_deactivated');\n    })\n    .raw(\n      'ALTER TABLE user_account ADD CONSTRAINT user_account_username_unique EXCLUDE (username WITH =) WHERE (username IS NOT NULL);',\n    );\n\n  await knex.schema.createTable('identity_provider_user', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('user_id').notNullable();\n\n    table.text('issuer').notNullable();\n    table.text('sub').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.unique(['issuer', 'sub']);\n    table.index('user_id');\n  });\n\n  await knex.schema.createTable('session', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('user_id').notNullable();\n\n    table.text('access_token').notNullable();\n    table.text('http_only_token');\n    table.text('remote_address').notNullable();\n    table.text('user_agent');\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n    table.timestamp('deleted_at', true);\n\n    /* Indexes */\n\n    table.index('user_id');\n    table.unique('access_token');\n    table.index('remote_address');\n  });\n\n  await knex.schema.createTable('project', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('owner_project_manager_id');\n    table.bigInteger('background_image_id');\n\n    table.text('name').notNullable();\n    table.text('description');\n    table.text('background_type');\n    table.text('background_gradient');\n    table.boolean('is_hidden').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('owner_project_manager_id');\n  });\n\n  await knex.schema.createTable('project_favorite', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('project_id').notNullable();\n    table.bigInteger('user_id').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.unique(['project_id', 'user_id']);\n    table.index('user_id');\n  });\n\n  await knex.schema.createTable('project_manager', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('project_id').notNullable();\n    table.bigInteger('user_id').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.unique(['project_id', 'user_id']);\n    table.index('user_id');\n  });\n\n  await knex.schema.createTable('background_image', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('project_id').notNullable();\n\n    table.text('dirname').notNullable();\n    table.text('extension').notNullable();\n    table.bigInteger('size_in_bytes').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('project_id');\n  });\n\n  await knex.schema.createTable('base_custom_field_group', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('project_id').notNullable();\n\n    table.text('name').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('project_id');\n  });\n\n  await knex.schema.createTable('board', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('project_id').notNullable();\n\n    table.specificType('position', 'double precision').notNullable();\n    table.text('name').notNullable();\n    table.text('default_view').notNullable();\n    table.text('default_card_type').notNullable();\n    table.boolean('limit_card_types_to_default_one').notNullable();\n    table.boolean('always_display_card_creator').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('project_id');\n    table.index('position');\n  });\n\n  await knex.schema.createTable('board_subscription', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('board_id').notNullable();\n    table.bigInteger('user_id').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.unique(['board_id', 'user_id']);\n    table.index('user_id');\n  });\n\n  await knex.schema.createTable('board_membership', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('project_id').notNullable();\n    table.bigInteger('board_id').notNullable();\n    table.bigInteger('user_id').notNullable();\n\n    table.text('role').notNullable();\n    table.boolean('can_comment');\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('project_id');\n    table.unique(['board_id', 'user_id']);\n    table.index('user_id');\n  });\n\n  await knex.schema.createTable('label', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('board_id').notNullable();\n\n    table.specificType('position', 'double precision').notNullable();\n    table.text('name');\n    table.text('color').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('board_id');\n    table.index('position');\n  });\n\n  await knex.schema.createTable('list', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('board_id').notNullable();\n\n    table.text('type').notNullable();\n    table.specificType('position', 'double precision');\n    table.text('name');\n    table.text('color');\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('board_id');\n    table.index('type');\n    table.index('position');\n  });\n\n  await knex.schema.createTable('card', async (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('board_id').notNullable();\n    table.bigInteger('list_id').notNullable();\n    table.bigInteger('creator_user_id');\n    table.bigInteger('prev_list_id');\n    table.bigInteger('cover_attachment_id');\n\n    table.text('type').notNullable();\n    table.specificType('position', 'double precision');\n    table.text('name').notNullable();\n    table.text('description');\n    table.timestamp('due_date', true);\n    table.jsonb('stopwatch');\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n    table.timestamp('list_changed_at', true);\n\n    /* Indexes */\n\n    table.index('board_id');\n    table.index('list_id');\n    table.index('creator_user_id');\n    table.index('position');\n    table.index('list_changed_at');\n  }).raw(`\n    CREATE INDEX card_name_index ON card USING GIN (name gin_trgm_ops);\n    CREATE INDEX card_description_index ON card USING GIN (description gin_trgm_ops);\n  `);\n\n  await knex.schema.createTable('card_subscription', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('card_id').notNullable();\n    table.bigInteger('user_id').notNullable();\n\n    table.boolean('is_permanent').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.unique(['card_id', 'user_id']);\n    table.index('user_id');\n  });\n\n  await knex.schema.createTable('card_membership', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('card_id').notNullable();\n    table.bigInteger('user_id').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.unique(['card_id', 'user_id']);\n    table.index('user_id');\n  });\n\n  await knex.schema.createTable('card_label', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('card_id').notNullable();\n    table.bigInteger('label_id').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.unique(['card_id', 'label_id']);\n    table.index('label_id');\n  });\n\n  await knex.schema.createTable('task_list', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('card_id').notNullable();\n\n    table.specificType('position', 'double precision').notNullable();\n    table.text('name').notNullable();\n    table.boolean('show_on_front_of_card').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('card_id');\n    table.index('position');\n  });\n\n  await knex.schema.createTable('task', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('task_list_id').notNullable();\n    table.bigInteger('assignee_user_id');\n\n    table.specificType('position', 'double precision').notNullable();\n    table.text('name').notNullable();\n    table.boolean('is_completed').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('task_list_id');\n    table.index('assignee_user_id');\n    table.index('position');\n  });\n\n  await knex.schema.createTable('attachment', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('card_id').notNullable();\n    table.bigInteger('creator_user_id');\n\n    table.text('type').notNullable();\n    table.jsonb('data').notNullable();\n    table.text('name').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('card_id');\n    table.index('creator_user_id');\n  });\n\n  await knex.schema.createTable('custom_field_group', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('board_id');\n    table.bigInteger('card_id');\n    table.bigInteger('base_custom_field_group_id');\n\n    table.specificType('position', 'double precision').notNullable();\n    table.text('name');\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('board_id');\n    table.index('card_id');\n    table.index('base_custom_field_group_id');\n    table.index('position');\n  });\n\n  await knex.schema.createTable('custom_field', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('base_custom_field_group_id');\n    table.bigInteger('custom_field_group_id');\n\n    table.specificType('position', 'double precision').notNullable();\n    table.text('name').notNullable();\n    table.boolean('show_on_front_of_card').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('base_custom_field_group_id');\n    table.index('custom_field_group_id');\n    table.index('position');\n  });\n\n  await knex.schema.createTable('custom_field_value', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('card_id').notNullable();\n    table.bigInteger('custom_field_group_id').notNullable();\n    table.bigInteger('custom_field_id').notNullable();\n\n    table.text('content').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.unique(['card_id', 'custom_field_group_id', 'custom_field_id']);\n    table.index('custom_field_group_id');\n    table.index('custom_field_id');\n  });\n\n  await knex.schema.createTable('comment', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('card_id').notNullable();\n    table.bigInteger('user_id');\n\n    table.text('text').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('card_id');\n    table.index('user_id');\n  });\n\n  await knex.schema.createTable('action', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('card_id').notNullable();\n    table.bigInteger('user_id');\n\n    table.text('type').notNullable();\n    table.jsonb('data').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('card_id');\n    table.index('user_id');\n  });\n\n  await knex.schema.createTable('notification', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('user_id').notNullable();\n    table.bigInteger('creator_user_id');\n    table.bigInteger('board_id').notNullable();\n    table.bigInteger('card_id').notNullable();\n    table.bigInteger('comment_id');\n    table.bigInteger('action_id');\n\n    table.text('type').notNullable();\n    table.jsonb('data').notNullable();\n    table.boolean('is_read').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('user_id');\n    table.index('creator_user_id');\n    table.index('card_id');\n    table.index('comment_id');\n    table.index('action_id');\n    table.index('is_read');\n  });\n\n  return knex.schema.createTable('notification_service', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('user_id');\n    table.bigInteger('board_id');\n\n    table.text('url').notNullable();\n    table.text('format').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('user_id');\n    table.index('board_id');\n  });\n};\n\nmodule.exports.down = async (knex) => {\n  await knex.schema.dropTable('file_reference');\n  await knex.schema.dropTable('user_account');\n  await knex.schema.dropTable('identity_provider_user');\n  await knex.schema.dropTable('session');\n  await knex.schema.dropTable('project');\n  await knex.schema.dropTable('project_favorite');\n  await knex.schema.dropTable('project_manager');\n  await knex.schema.dropTable('background_image');\n  await knex.schema.dropTable('base_custom_field_group');\n  await knex.schema.dropTable('board');\n  await knex.schema.dropTable('board_subscription');\n  await knex.schema.dropTable('board_membership');\n  await knex.schema.dropTable('label');\n  await knex.schema.dropTable('list');\n  await knex.schema.dropTable('card');\n  await knex.schema.dropTable('card_subscription');\n  await knex.schema.dropTable('card_membership');\n  await knex.schema.dropTable('card_label');\n  await knex.schema.dropTable('task_list');\n  await knex.schema.dropTable('task');\n  await knex.schema.dropTable('attachment');\n  await knex.schema.dropTable('custom_field_group');\n  await knex.schema.dropTable('custom_field');\n  await knex.schema.dropTable('custom_field_value');\n  await knex.schema.dropTable('comment');\n  await knex.schema.dropTable('action');\n  await knex.schema.dropTable('notification');\n  await knex.schema.dropTable('notification_service');\n\n  return knex.raw(`\n    DROP EXTENSION pg_trgm;\n\n    DROP SEQUENCE next_id_seq;\n    DROP FUNCTION next_id(OUT id BIGINT);\n  `);\n};\n"
  },
  {
    "path": "server/db/migrations/20250522151122_add_board_activity_log.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = async (knex) => {\n  await knex.schema.alterTable('action', (table) => {\n    /* Columns */\n\n    table.bigInteger('board_id');\n\n    /* Indexes */\n\n    table.index('board_id');\n  });\n\n  return knex.raw(`\n    UPDATE action\n    SET\n      board_id = card.board_id,\n      data = data || jsonb_build_object('card', jsonb_build_object('name', card.name))\n    FROM card\n    WHERE action.card_id = card.id;\n  `);\n};\n\nexports.down = (knex) =>\n  knex.schema.table('action', (table) => {\n    table.dropColumn('board_id');\n  });\n"
  },
  {
    "path": "server/db/migrations/20250523131647_add_comments_counter.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = async (knex) => {\n  await knex.schema.alterTable('card', (table) => {\n    /* Columns */\n\n    table.integer('comments_total').notNullable().defaultTo(0);\n  });\n\n  await knex.raw(`\n    UPDATE card\n    SET comments_total = comments_total_by_card_id.comments_total\n    FROM (\n      SELECT card_id, COUNT(*) as comments_total\n      FROM comment\n      GROUP BY card_id\n    ) AS comments_total_by_card_id\n    WHERE card.id = comments_total_by_card_id.card_id;\n  `);\n\n  return knex.schema.alterTable('card', (table) => {\n    table.integer('comments_total').notNullable().alter();\n  });\n};\n\nexports.down = (knex) =>\n  knex.schema.table('card', (table) => {\n    table.dropColumn('comments_total');\n  });\n"
  },
  {
    "path": "server/db/migrations/20250603102521_canonicalize_locale_codes.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = (knex) =>\n  knex.raw(`\n    UPDATE user_account\n    SET language =\n      CASE\n        WHEN language = 'sr-Cyrl-CS' THEN 'sr-Cyrl-RS'\n        WHEN language = 'sr-Latn-CS' THEN 'sr-Latn-RS'\n      END\n    WHERE language IN ('sr-Cyrl-CS', 'sr-Latn-CS');\n  `);\n\nexports.down = (knex) =>\n  knex.raw(`\n    UPDATE user_account\n    SET language =\n      CASE\n        WHEN language = 'sr-Cyrl-RS' THEN 'sr-Cyrl-CS'\n        WHEN language = 'sr-Latn-RS' THEN 'sr-Latn-CS'\n      END\n    WHERE language IN ('sr-Cyrl-RS', 'sr-Latn-RS');\n  `);\n"
  },
  {
    "path": "server/db/migrations/20250703122452_move_webhooks_configuration_from_environment_variable_to_ui.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = (knex) =>\n  knex.schema.createTable('webhook', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('board_id');\n\n    table.text('name').notNullable();\n    table.text('url').notNullable();\n    table.text('access_token');\n    table.specificType('events', 'text[]');\n    table.specificType('excluded_events', 'text[]');\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n\n    /* Indexes */\n\n    table.index('board_id');\n  });\n\nexports.down = (knex) => knex.schema.dropTable('webhook');\n"
  },
  {
    "path": "server/db/migrations/20250708200908_persist_closed_state_per_card.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = async (knex) => {\n  await knex.schema.alterTable('card', (table) => {\n    /* Columns */\n\n    table.boolean('is_closed').notNullable().default(false);\n  });\n\n  await knex.raw(`\n    UPDATE card\n    SET is_closed = TRUE\n    FROM list\n    WHERE card.list_id = list.id AND list.type = 'closed';\n  `);\n\n  return knex.schema.alterTable('card', (table) => {\n    table.boolean('is_closed').notNullable().alter();\n  });\n};\n\nexports.down = (knex) =>\n  knex.schema.table('card', (table) => {\n    table.dropColumn('is_closed');\n  });\n"
  },
  {
    "path": "server/db/migrations/20250709160208_add_ability_to_link_tasks_to_cards.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = async (knex) =>\n  knex.schema.alterTable('task', (table) => {\n    /* Columns */\n\n    table.bigInteger('linked_card_id');\n\n    /* Indexes */\n\n    table.index('linked_card_id');\n  });\n\nexports.down = (knex) =>\n  knex.schema.table('task', (table) => {\n    table.dropColumn('linked_card_id');\n  });\n"
  },
  {
    "path": "server/db/migrations/20250721132312_add_ability_to_hide_completed_tasks.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = async (knex) => {\n  await knex.schema.alterTable('task_list', (table) => {\n    /* Columns */\n\n    table.boolean('hide_completed_tasks').notNullable().defaultTo(false);\n  });\n\n  return knex.schema.alterTable('task_list', (table) => {\n    table.boolean('hide_completed_tasks').notNullable().alter();\n  });\n};\n\nexports.down = (knex) =>\n  knex.schema.table('task_list', (table) => {\n    table.dropColumn('hide_completed_tasks');\n  });\n"
  },
  {
    "path": "server/db/migrations/20250728105713_add_legal_requirements.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = async (knex) => {\n  await knex.schema.createTable('config', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.boolean('is_initialized').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n  });\n\n  await knex.schema.alterTable('session', (table) => {\n    /* Columns */\n\n    table.text('pending_token');\n\n    /* Modifications */\n\n    table.setNullable('access_token');\n\n    /* Indexes */\n\n    table.unique('pending_token');\n  });\n\n  await knex.schema.alterTable('user_account', (table) => {\n    /* Columns */\n\n    table.text('terms_signature');\n\n    table.timestamp('terms_accepted_at', true);\n  });\n\n  const isInitialized = !!(await knex('user_account').first());\n\n  await knex('config').insert({\n    isInitialized,\n    id: 1,\n    createdAt: new Date().toISOString(),\n  });\n\n  await knex('session')\n    .update({\n      deletedAt: new Date().toISOString(),\n    })\n    .whereNull('deletedAt');\n};\n\nexports.down = async (knex) => {\n  await knex.schema.dropTable('config');\n\n  await knex('session').del().whereNull('access_token');\n\n  await knex.schema.alterTable('session', (table) => {\n    table.dropColumn('pending_token');\n\n    table.dropNullable('access_token');\n  });\n\n  return knex.schema.table('user_account', (table) => {\n    table.dropColumn('terms_signature');\n    table.dropColumn('terms_accepted_at');\n  });\n};\n"
  },
  {
    "path": "server/db/migrations/20250820144730_track_storage_usage.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst mime = require('mime-types');\n\nexports.up = async (knex) => {\n  await knex.schema.createTable('storage_usage', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.bigInteger('total').notNullable();\n    table.bigInteger('user_avatars').notNullable();\n    table.bigInteger('background_images').notNullable();\n    table.bigInteger('attachments').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n  });\n\n  await knex.schema.alterTable('file_reference', (table) => {\n    table.dropPrimary();\n    table.dropIndex('total');\n  });\n\n  await knex.schema.renameTable('file_reference', 'uploaded_file');\n\n  await knex.schema.alterTable('uploaded_file', (table) => {\n    /* Columns */\n\n    table.text('type').notNullable().defaultTo('attachment');\n    table.text('mime_type');\n    table.bigInteger('size').notNullable().defaultTo(0);\n\n    /* Modifications */\n\n    table.text('id').primary().defaultTo(knex.raw('next_id()')).alter();\n    table.renameColumn('total', 'references_total');\n\n    /* Indexes */\n\n    table.index('type');\n    table.index('references_total');\n  });\n\n  await knex.schema.alterTable('uploaded_file', (table) => {\n    table.text('type').notNullable().alter();\n    table.bigInteger('size').notNullable().alter();\n  });\n\n  await knex.raw(`\n    UPDATE user_account\n    SET avatar = avatar - 'dirname' - 'sizeInBytes' || jsonb_build_object('uploadedFileId', avatar->'dirname', 'size', avatar->'sizeInBytes')\n    WHERE avatar IS NOT NULL;\n  `);\n\n  await knex.schema.alterTable('background_image', (table) => {\n    table.renameColumn('dirname', 'uploaded_file_id');\n    table.renameColumn('size_in_bytes', 'size');\n  });\n\n  await knex.raw(`\n    UPDATE attachment\n    SET data = data - 'fileReferenceId' - 'sizeInBytes' || jsonb_build_object('uploadedFileId', data->'fileReferenceId', 'size', data->'sizeInBytes')\n    WHERE type = 'file';\n  `);\n\n  await knex.raw(`\n    UPDATE uploaded_file\n    SET\n      type = 'attachment',\n      mime_type = attachment.data->>'mimeType',\n      size = (attachment.data->>'size')::bigint\n    FROM attachment\n    WHERE (attachment.data->>'uploadedFileId')::text = uploaded_file.id AND attachment.type = 'file';\n  `);\n\n  const users = await knex('user_account').whereNotNull('avatar');\n  const createdAt = new Date().toISOString();\n\n  await knex.batchInsert(\n    'uploaded_file',\n    users.map(({ avatar }) => ({\n      createdAt,\n      id: avatar.uploadedFileId,\n      type: 'userAvatar',\n      referencesTotal: 1,\n      mimeType: mime.lookup(avatar.extension) || null,\n      size: avatar.size,\n    })),\n  );\n\n  const backgroundImages = await knex('background_image');\n\n  await knex.batchInsert(\n    'uploaded_file',\n    backgroundImages.map((backgroundImage) => ({\n      id: backgroundImage.uploaded_file_id,\n      type: 'backgroundImage',\n      referencesTotal: 1,\n      mimeType: mime.lookup(backgroundImage.extension) || null,\n      size: backgroundImage.size,\n      createdAt: backgroundImage.created_at,\n    })),\n  );\n\n  return knex.raw(`\n    INSERT INTO storage_usage (id, total, user_avatars, background_images, attachments, created_at)\n    SELECT\n      1 AS id,\n      COALESCE(SUM(size), 0) AS total,\n      COALESCE(SUM(CASE WHEN type = 'userAvatar' THEN size ELSE 0 END), 0) AS user_avatars,\n      COALESCE(SUM(CASE WHEN type = 'backgroundImage' THEN size ELSE 0 END), 0) AS background_images,\n      COALESCE(SUM(CASE WHEN type = 'attachment' THEN size ELSE 0 END), 0) AS attachments,\n      timezone('UTC', now()) AS created_at\n    FROM uploaded_file;\n  `);\n};\n\nexports.down = async (knex) => {\n  await knex.schema.dropTable('storage_usage');\n\n  await knex('uploaded_file').delete().whereNot('type', 'attachment');\n\n  await knex.schema.alterTable('uploaded_file', (table) => {\n    table.dropPrimary();\n    table.dropIndex('references_total');\n  });\n\n  await knex.schema.renameTable('uploaded_file', 'file_reference');\n\n  await knex.schema.alterTable('file_reference', (table) => {\n    table.dropColumn('type');\n    table.dropColumn('mime_type');\n    table.dropColumn('size');\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()')).alter();\n    table.renameColumn('references_total', 'total');\n\n    table.index('total');\n  });\n\n  await knex.raw(`\n    UPDATE user_account\n    SET avatar = avatar - 'uploadedFileId' - 'size' || jsonb_build_object('dirname', avatar->'uploadedFileId', 'sizeInBytes', avatar->'size')\n    WHERE avatar IS NOT NULL;\n  `);\n\n  await knex.schema.alterTable('background_image', (table) => {\n    table.renameColumn('uploaded_file_id', 'dirname');\n    table.renameColumn('size', 'size_in_bytes');\n  });\n\n  return knex.raw(`\n    UPDATE attachment\n    SET data = data - 'uploadedFileId' - 'size' || jsonb_build_object('fileReferenceId', data->'uploadedFileId', 'sizeInBytes', data->'size')\n    WHERE type = 'file';\n  `);\n};\n"
  },
  {
    "path": "server/db/migrations/20250905101408_restore_toggleable_due_dates.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = async (knex) => {\n  await knex.schema.alterTable('card', (table) => {\n    /* Columns */\n\n    table.boolean('is_due_completed');\n  });\n\n  return knex('card')\n    .update({\n      isDueCompleted: false,\n    })\n    .whereNotNull('due_date');\n};\n\nexports.down = (knex) =>\n  knex.schema.table('card', (table) => {\n    table.dropColumn('is_due_completed');\n  });\n"
  },
  {
    "path": "server/db/migrations/20250905205438_add_board_setting_to_expand_task_lists_by_default.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = async (knex) => {\n  await knex.schema.alterTable('board', (table) => {\n    /* Columns */\n\n    table.boolean('expand_task_lists_by_default').notNullable().defaultTo(false);\n  });\n\n  return knex.schema.alterTable('board', (table) => {\n    table.boolean('expand_task_lists_by_default').notNullable().alter();\n  });\n};\n\nexports.down = (knex) =>\n  knex.schema.table('board', (table) => {\n    table.dropColumn('expand_task_lists_by_default');\n  });\n"
  },
  {
    "path": "server/db/migrations/20250917123048_add_ability_to_configure_smtp_via_ui.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = async (knex) => {\n  await knex.schema.alterTable('config', (table) => {\n    /* Columns */\n\n    table.text('smtp_host');\n    table.integer('smtp_port');\n    table.text('smtp_name');\n    table.boolean('smtp_secure').notNullable().defaultTo(false);\n    table.boolean('smtp_tls_reject_unauthorized').notNullable().defaultTo(true);\n    table.text('smtp_user');\n    table.text('smtp_password');\n    table.text('smtp_from');\n  });\n\n  return knex.schema.alterTable('config', (table) => {\n    table.boolean('smtp_secure').notNullable().alter();\n    table.boolean('smtp_tls_reject_unauthorized').notNullable().alter();\n  });\n};\n\nexports.down = (knex) =>\n  knex.schema.alterTable('config', (table) => {\n    table.dropColumn('smtp_host');\n    table.dropColumn('smtp_port');\n    table.dropColumn('smtp_name');\n    table.dropColumn('smtp_secure');\n    table.dropColumn('smtp_tls_reject_unauthorized');\n    table.dropColumn('smtp_user');\n    table.dropColumn('smtp_password');\n    table.dropColumn('smtp_from');\n  });\n"
  },
  {
    "path": "server/db/migrations/20251105104948_add_api_key_authentication.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = (knex) =>\n  knex.schema.alterTable('user_account', (table) => {\n    table.text('api_key_prefix');\n    table.text('api_key_hash');\n\n    table.timestamp('api_key_created_at', true);\n\n    /* Indexes */\n\n    table.unique('api_key_hash');\n  });\n\nexports.down = (knex) =>\n  knex.schema.alterTable('user_account', (table) => {\n    table.dropColumn('api_key_prefix');\n    table.dropColumn('api_key_hash');\n    table.dropColumn('api_key_created_at');\n  });\n"
  },
  {
    "path": "server/db/migrations/20251121231641_rename_gin_indexes.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports.up = (knex) =>\n  knex.schema.raw(`\n    ALTER INDEX card_name_index RENAME TO card_name_gin_index;\n    ALTER INDEX card_description_index RENAME TO card_description_gin_index;\n  `);\n\nmodule.exports.down = (knex) =>\n  knex.schema.raw(`\n    ALTER INDEX card_name_gin_index RENAME TO card_name_index;\n    ALTER INDEX card_description_gin_index RENAME TO card_description_index;\n  `);\n"
  },
  {
    "path": "server/db/migrations/20260122093047_add_internal_runtime_configuration.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nexports.up = async (knex) => {\n  await knex.schema.createTable('internal_config', (table) => {\n    /* Columns */\n\n    table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));\n\n    table.text('storage_limit');\n    table.integer('active_users_limit');\n    table.boolean('is_initialized').notNullable();\n\n    table.timestamp('created_at', true);\n    table.timestamp('updated_at', true);\n  });\n\n  const { is_initialized: isInitialized } = await knex('config').select('is_initialized').first();\n\n  await knex('internal_config').insert({\n    isInitialized,\n    id: 1,\n    createdAt: new Date().toISOString(),\n  });\n\n  return knex.schema.alterTable('config', (table) => {\n    table.dropColumn('is_initialized');\n  });\n};\n\nexports.down = async (knex) => {\n  const { is_initialized: isInitialized } = await knex('internal_config')\n    .select('is_initialized')\n    .first();\n\n  await knex.schema.alterTable('config', (table) => {\n    table.boolean('is_initialized').notNullable().default(isInitialized);\n  });\n\n  await knex.schema.alterTable('config', (table) => {\n    table.boolean('is_initialized').notNullable().alter();\n  });\n\n  return knex.schema.dropTable('internal_config');\n};\n"
  },
  {
    "path": "server/db/migrations/20260312000000_add_ability_to_display_card_ages.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nmodule.exports.up = async (knex) => {\n  await knex.schema.alterTable('board', (table) => {\n    table.boolean('display_card_ages').notNullable().defaultTo(false);\n  });\n\n  return knex.schema.alterTable('board', (table) => {\n    table.boolean('display_card_ages').notNullable().alter();\n  });\n};\n\nmodule.exports.down = (knex) =>\n  knex.schema.alterTable('board', (table) => {\n    table.dropColumn('display_card_ages');\n  });\n"
  },
  {
    "path": "server/db/seeds/.gitkeep",
    "content": ""
  },
  {
    "path": "server/db/seeds/default.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst bcrypt = require('bcrypt');\n\nconst buildUserData = () => {\n  const data = {\n    role: 'admin',\n    isSsoUser: false,\n    isDeactivated: false,\n  };\n\n  if (process.env.DEFAULT_ADMIN_PASSWORD) {\n    data.password = bcrypt.hashSync(process.env.DEFAULT_ADMIN_PASSWORD, 10);\n  }\n  if (process.env.DEFAULT_ADMIN_NAME) {\n    data.name = process.env.DEFAULT_ADMIN_NAME;\n  }\n  if (process.env.DEFAULT_ADMIN_USERNAME) {\n    data.username = process.env.DEFAULT_ADMIN_USERNAME.toLowerCase();\n  }\n\n  return data;\n};\n\nconst buildInternalConfigData = () => {\n  const data = {};\n  if (process.env.STORAGE_LIMIT) {\n    data.storageLimit = process.env.STORAGE_LIMIT;\n  }\n  if (process.env.ACTIVE_USERS_LIMIT) {\n    const activeUsersLimit = parseInt(process.env.ACTIVE_USERS_LIMIT, 10);\n\n    if (Number.isInteger(activeUsersLimit)) {\n      data.activeUsersLimit = activeUsersLimit;\n    }\n  }\n\n  return data;\n};\n\nexports.seed = async (knex) => {\n  const defaultAdminEmail =\n    process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase();\n\n  if (defaultAdminEmail) {\n    const userData = buildUserData();\n\n    let userId;\n    try {\n      [{ id: userId }] = await knex('user_account').insert(\n        {\n          ...userData,\n          email: defaultAdminEmail,\n          subscribeToOwnCards: false,\n          subscribeToCardWhenCommenting: true,\n          turnOffRecentCardHighlighting: false,\n          enableFavoritesByDefault: true,\n          defaultEditorMode: 'wysiwyg',\n          defaultHomeView: 'groupedProjects',\n          defaultProjectsOrder: 'byDefault',\n          createdAt: new Date().toISOString(),\n        },\n        'id',\n      );\n    } catch (error) {\n      /* empty */\n    }\n\n    if (!userId) {\n      await knex('user_account').update(userData).where('email', defaultAdminEmail);\n    }\n  }\n\n  const internalConfigData = buildInternalConfigData();\n\n  let activeUsersLimit;\n  if (Object.keys(internalConfigData).length > 0) {\n    [{ active_users_limit: activeUsersLimit }] = await knex('internal_config')\n      .update(internalConfigData)\n      .returning('active_users_limit');\n  } else {\n    ({ active_users_limit: activeUsersLimit } = await knex('internal_config')\n      .select('active_users_limit')\n      .first());\n  }\n\n  if (Number.isInteger(activeUsersLimit)) {\n    let orderByQuery;\n    let orderByQueryValues;\n\n    if (defaultAdminEmail) {\n      orderByQuery = 'CASE WHEN email = ? THEN 0 WHEN role = ? THEN 1 ELSE 2 END';\n      orderByQueryValues = [defaultAdminEmail, 'admin'];\n    } else {\n      orderByQuery = 'CASE WHEN role = ? THEN 0 ELSE 1 END';\n      orderByQueryValues = 'admin';\n    }\n\n    const users = await knex('user_account')\n      .select('id')\n      .where('is_deactivated', false)\n      .orderByRaw(orderByQuery, orderByQueryValues)\n      .orderBy('id')\n      .offset(activeUsersLimit);\n\n    if (users.length > 0) {\n      const userIds = users.map(({ id }) => id);\n\n      await knex('user_account')\n        .update({\n          isDeactivated: true,\n        })\n        .whereIn('id', userIds);\n    }\n  }\n};\n"
  },
  {
    "path": "server/db/upgrade.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/* eslint-disable no-await-in-loop */\n/* eslint-disable no-console */\n/* eslint-disable no-continue */\n/* eslint-disable no-restricted-syntax */\n\nconst { getEncoding } = require('istextorbinary');\nconst mime = require('mime-types');\nconst uuid = require('uuid');\nconst sharp = require('sharp');\nconst initKnex = require('knex');\nconst sails = require('sails');\nconst rc = require('sails/accessible/rc');\nconst _ = require('lodash');\n\nconst knexfile = require('./knexfile');\nconst { MAX_SIZE_TO_GET_ENCODING, POSITION_GAP } = require('../constants');\n\nconst PrevActionTypes = {\n  COMMENT_CARD: 'commentCard',\n};\n\nconst PrevPathSegments = {\n  PROJECT_BACKGROUND_IMAGES: 'public/project-background-images',\n\n  FAVICONS: 'public/favicons',\n  USER_AVATARS: 'public/user-avatars',\n  BACKGROUND_IMAGES: 'public/background-images',\n  ATTACHMENTS: 'private/attachments',\n};\n\nconst readStreamToBuffer = (readStream) =>\n  new Promise((resolve, reject) => {\n    const chunks = [];\n\n    readStream.on('data', (chunk) => {\n      chunks.push(chunk);\n    });\n\n    readStream.on('end', () => {\n      resolve(Buffer.concat(chunks));\n    });\n\n    readStream.on('error', (error) => {\n      reject(error);\n    });\n  });\n\nconst loadSails = () =>\n  new Promise((resolve) => {\n    sails.load(\n      {\n        ...rc('sails'),\n        log: {\n          level: 'error',\n        },\n      },\n      resolve,\n    );\n  });\n\nconst runStep = async (label, func) => {\n  process.stdout.write(`${label}... `);\n\n  try {\n    await func();\n    console.log('[OK]');\n  } catch (error) {\n    console.log('[FAIL]');\n    throw error;\n  }\n};\n\nconst knex = initKnex(knexfile);\n\nconst upgradeDatabase = async () => {\n  await knex.transaction(async (trx) => {\n    await trx.raw(`\n      CREATE SCHEMA v1;\n\n      ALTER SEQUENCE next_id_seq SET SCHEMA v1;\n      ALTER FUNCTION next_id(OUT id BIGINT) SET SCHEMA v1;\n\n      ALTER TABLE migration SET SCHEMA v1;\n      ALTER TABLE migration_lock SET SCHEMA v1;\n      ALTER TABLE archive SET SCHEMA v1;\n      ALTER TABLE user_account SET SCHEMA v1;\n      ALTER TABLE identity_provider_user SET SCHEMA v1;\n      ALTER TABLE session SET SCHEMA v1;\n      ALTER TABLE project SET SCHEMA v1;\n      ALTER TABLE project_manager SET SCHEMA v1;\n      ALTER TABLE board SET SCHEMA v1;\n      ALTER TABLE board_membership SET SCHEMA v1;\n      ALTER TABLE label SET SCHEMA v1;\n      ALTER TABLE list SET SCHEMA v1;\n      ALTER TABLE card SET SCHEMA v1;\n      ALTER TABLE card_subscription SET SCHEMA v1;\n      ALTER TABLE card_membership SET SCHEMA v1;\n      ALTER TABLE card_label SET SCHEMA v1;\n      ALTER TABLE task SET SCHEMA v1;\n      ALTER TABLE attachment SET SCHEMA v1;\n      ALTER TABLE action SET SCHEMA v1;\n      ALTER TABLE notification SET SCHEMA v1;\n    `);\n\n    await trx.migrate.up({\n      name: '20250228000022_version_2.js',\n    });\n\n    const users = await trx('user_account').withSchema('v1').whereNull('deleted_at');\n\n    const userIds = users.map(({ id }) => id);\n    const userIdsSet = new Set(userIds);\n    const whereInUserIds = ['0', ...userIds];\n\n    if (users.length > 0) {\n      await knex\n        .batchInsert(\n          'user_account',\n          users.map((user) => ({\n            ..._.pick(user, [\n              'id',\n              'email',\n              'password',\n              'name',\n              'username',\n              'phone',\n              'organization',\n              'language',\n              'subscribe_to_own_cards',\n              'created_at',\n              'updated_at',\n              'password_changed_at',\n            ]),\n            role: user.is_admin ? User.Roles.ADMIN : User.Roles.BOARD_USER,\n            avatar: user.avatar && {\n              ...user.avatar,\n              sizeInBytes: 0,\n            },\n            subscribe_to_card_when_commenting: true,\n            turn_off_recent_card_highlighting: false,\n            enable_favorites_by_default: true,\n            default_editor_mode: User.EditorModes.WYSIWYG,\n            default_home_view: User.HomeViews.GROUPED_PROJECTS,\n            default_projects_order: User.ProjectOrders.BY_DEFAULT,\n            is_sso_user: user.is_sso,\n            is_deactivated: false,\n          })),\n        )\n        .transacting(trx);\n\n      const identityProviderUsers = await trx('identity_provider_user')\n        .withSchema('v1')\n        .whereRaw('user_id = ANY (?)', [whereInUserIds]);\n\n      if (identityProviderUsers.length > 0) {\n        await knex.batchInsert('identity_provider_user', identityProviderUsers).transacting(trx);\n      }\n\n      const sessions = await trx('session')\n        .withSchema('v1')\n        .whereRaw('user_id = ANY (?)', [whereInUserIds]);\n\n      if (sessions.length > 0) {\n        await knex.batchInsert('session', sessions).transacting(trx);\n      }\n    }\n\n    const projects = await trx('project').withSchema('v1');\n    const whereInProjectIds = ['0', ...projects.map(({ id }) => id)];\n\n    if (projects.length > 0) {\n      const projectsWithBackgroundImage = projects.filter((project) => project.background_image);\n\n      const backgroundImageIdByProjectId = {};\n      if (projectsWithBackgroundImage.length > 0) {\n        const createdAt = new Date().toISOString();\n\n        const backgroundImages = await knex\n          .batchInsert(\n            'background_image',\n            projectsWithBackgroundImage.map((project) => ({\n              ...project.background_image,\n              project_id: project.id,\n              size_in_bytes: 0,\n              created_at: createdAt,\n            })),\n          )\n          .returning(['id', 'project_id'])\n          .transacting(trx);\n\n        backgroundImages.forEach((backgroundImage) => {\n          backgroundImageIdByProjectId[backgroundImage.project_id] = backgroundImage.id;\n        });\n      }\n\n      await knex\n        .batchInsert(\n          'project',\n          projects.map((project) => {\n            const data = {\n              ..._.pick(project, ['id', 'name', 'created_at', 'updated_at']),\n              is_hidden: false,\n            };\n\n            if (project.background) {\n              data.background_type = project.background.type;\n\n              switch (project.background.type) {\n                case Project.BackgroundTypes.GRADIENT:\n                  data.background_gradient = project.background.name;\n\n                  break;\n                case Project.BackgroundTypes.IMAGE:\n                  data.background_image_id = backgroundImageIdByProjectId[project.id];\n\n                  break;\n                default:\n              }\n            }\n\n            return data;\n          }),\n        )\n        .transacting(trx);\n    }\n\n    const projectManagers = await trx('project_manager')\n      .withSchema('v1')\n      .whereRaw('project_id = ANY (?)', [whereInProjectIds]);\n\n    if (projectManagers.length > 0) {\n      await knex.batchInsert('project_manager', projectManagers).transacting(trx);\n    }\n\n    const boards = await trx('board')\n      .withSchema('v1')\n      .whereRaw('project_id = ANY (?)', [whereInProjectIds]);\n\n    const projectIdByBoardId = boards.reduce(\n      (result, board) => ({\n        ...result,\n        [board.id]: board.project_id,\n      }),\n      {},\n    );\n\n    const whereInBoardIds = ['0', ...Object.keys(projectIdByBoardId)];\n\n    if (boards.length > 0) {\n      await knex\n        .batchInsert(\n          'board',\n          boards.map((board) => ({\n            ..._.pick(board, ['id', 'project_id', 'position', 'name', 'created_at', 'updated_at']),\n            default_view: Board.Views.KANBAN,\n            default_card_type: Card.Types.PROJECT,\n            limit_card_types_to_default_one: false,\n            always_display_card_creator: false,\n          })),\n        )\n        .transacting(trx);\n\n      const createdAt = new Date().toISOString();\n\n      await knex\n        .batchInsert(\n          'list',\n          boards.flatMap((board) =>\n            [List.Types.ARCHIVE, List.Types.TRASH].map((type) => ({\n              type,\n              board_id: board.id,\n              created_at: createdAt,\n            })),\n          ),\n        )\n        .transacting(trx);\n    }\n\n    const boardMemberships = await trx('board_membership')\n      .withSchema('v1')\n      .whereRaw('board_id = ANY (?)', [whereInBoardIds]);\n\n    if (boardMemberships.length > 0) {\n      await knex\n        .batchInsert(\n          'board_membership',\n          boardMemberships.map((boardMembership) => ({\n            ..._.pick(boardMembership, [\n              'id',\n              'board_id',\n              'user_id',\n              'role',\n              'can_comment',\n              'created_at',\n              'updated_at',\n            ]),\n            project_id: projectIdByBoardId[boardMembership.board_id],\n          })),\n        )\n        .transacting(trx);\n    }\n\n    const labels = await trx('label')\n      .withSchema('v1')\n      .whereRaw('board_id = ANY (?)', [whereInBoardIds]);\n\n    if (labels.length > 0) {\n      await knex.batchInsert('label', labels).transacting(trx);\n    }\n\n    const lists = await trx('list')\n      .withSchema('v1')\n      .whereRaw('board_id = ANY (?)', [whereInBoardIds]);\n\n    const whereInListIds = ['0', ...lists.map(({ id }) => id)];\n\n    if (lists.length > 0) {\n      await knex\n        .batchInsert(\n          'list',\n          lists.map((list) => ({\n            ..._.pick(list, [\n              'id',\n              'board_id',\n              'position',\n              'name',\n              'color',\n              'created_at',\n              'updated_at',\n            ]),\n            type: List.Types.ACTIVE,\n          })),\n        )\n        .transacting(trx);\n    }\n\n    const cards = await trx('card')\n      .withSchema('v1')\n      .whereRaw('board_id = ANY (?)', [whereInBoardIds])\n      .whereRaw('list_id = ANY (?)', [whereInListIds]);\n\n    const cardById = _.keyBy(cards, 'id');\n    const whereInCardIds = ['0', ...Object.keys(cardById)];\n\n    if (cards.length > 0) {\n      await knex\n        .batchInsert(\n          'card',\n          cards.map((card) => ({\n            ..._.pick(card, [\n              'id',\n              'board_id',\n              'list_id',\n              'cover_attachment_id',\n              'position',\n              'name',\n              'description',\n              'due_date',\n              'stopwatch',\n              'created_at',\n              'updated_at',\n            ]),\n            creator_user_id: userIdsSet.has(card.creator_user_id) ? card.creator_user_id : null,\n            type: Card.Types.PROJECT,\n            list_changed_at: card.created_at,\n          })),\n        )\n        .transacting(trx);\n    }\n\n    const cardSubscriptions = await trx('card_subscription')\n      .withSchema('v1')\n      .whereRaw('card_id = ANY (?)', [whereInCardIds]);\n\n    if (cardSubscriptions.length > 0) {\n      await knex.batchInsert('card_subscription', cardSubscriptions).transacting(trx);\n    }\n\n    const cardMemberships = await trx('card_membership')\n      .withSchema('v1')\n      .whereRaw('card_id = ANY (?)', [whereInCardIds]);\n\n    if (cardMemberships.length > 0) {\n      await knex.batchInsert('card_membership', cardMemberships).transacting(trx);\n    }\n\n    const cardLabels = await trx('card_label')\n      .withSchema('v1')\n      .whereRaw('card_id = ANY (?)', [whereInCardIds]);\n\n    if (cardLabels.length > 0) {\n      await knex.batchInsert('card_label', cardLabels).transacting(trx);\n    }\n\n    const tasks = await trx('task')\n      .withSchema('v1')\n      .whereRaw('card_id = ANY (?)', [whereInCardIds]);\n\n    const tasksByCardId = _.groupBy(tasks, 'card_id');\n    const taskCardIds = Object.keys(tasksByCardId);\n\n    if (taskCardIds.length > 0) {\n      const createdAt = new Date().toISOString();\n\n      const taskLists = await knex\n        .batchInsert(\n          'task_list',\n          taskCardIds.map((cardId) => ({\n            card_id: cardId,\n            position: POSITION_GAP,\n            name: 'Task List',\n            show_on_front_of_card: true,\n            created_at: createdAt,\n          })),\n        )\n        .returning(['id', 'card_id'])\n        .transacting(trx);\n\n      await knex\n        .batchInsert(\n          'task',\n          taskLists.flatMap((taskList) =>\n            tasksByCardId[taskList.card_id].map((task) => ({\n              ..._.pick(task, [\n                'id',\n                'position',\n                'name',\n                'is_completed',\n                'created_at',\n                'updated_at',\n              ]),\n              task_list_id: taskList.id,\n            })),\n          ),\n        )\n        .transacting(trx);\n    }\n\n    const attachments = await trx('attachment')\n      .withSchema('v1')\n      .whereRaw('card_id = ANY (?)', [whereInCardIds]);\n\n    if (attachments.length > 0) {\n      await knex\n        .batchInsert(\n          'attachment',\n          attachments.map((attachment) => ({\n            ..._.pick(attachment, ['id', 'card_id', 'name', 'created_at', 'updated_at']),\n            creator_user_id: userIdsSet.has(attachment.creator_user_id)\n              ? attachment.creator_user_id\n              : null,\n            type: Attachment.Types.FILE,\n            data: {\n              fileReferenceId: attachment.dirname,\n              filename: attachment.filename,\n              mimeType: mime.lookup(attachment.filename) || null,\n              sizeInBytes: 0,\n              encoding: null,\n              image: attachment.image,\n            },\n          })),\n        )\n        .transacting(trx);\n    }\n\n    const actions = await trx('action')\n      .withSchema('v1')\n      .whereRaw('card_id = ANY (?)', [whereInCardIds]);\n\n    const actionById = _.keyBy(actions, 'id');\n    const whereInActionIds = ['0', ...Object.keys(actionById)];\n\n    const commentActions = [];\n    const otherActions = [];\n\n    actions.forEach((action) => {\n      if (action.type === PrevActionTypes.COMMENT_CARD) {\n        commentActions.push(action);\n      } else {\n        otherActions.push(action);\n      }\n    });\n\n    if (commentActions.length > 0) {\n      await knex\n        .batchInsert(\n          'comment',\n          commentActions.map((action) => ({\n            ..._.pick(action, ['id', 'card_id', 'created_at', 'updated_at']),\n            user_id: userIdsSet.has(action.user_id) ? action.user_id : null,\n            text: action.data.text,\n          })),\n        )\n        .transacting(trx);\n    }\n\n    if (otherActions.length > 0) {\n      await knex\n        .batchInsert(\n          'action',\n          otherActions.map((action) => {\n            const data = {\n              ..._.pick(action, ['id', 'card_id', 'type', 'created_at', 'updated_at']),\n              user_id: userIdsSet.has(action.user_id) ? action.user_id : null,\n            };\n\n            switch (action.type) {\n              case Action.Types.CREATE_CARD:\n                data.data = {\n                  list: {\n                    ...action.data.list,\n                    type: List.Types.ACTIVE,\n                  },\n                };\n\n                break;\n              case Action.Types.MOVE_CARD:\n                data.data = {\n                  fromList: {\n                    ...action.data.fromList,\n                    type: List.Types.ACTIVE,\n                  },\n                  toList: {\n                    ...action.data.toList,\n                    type: List.Types.ACTIVE,\n                  },\n                };\n\n                break;\n              default:\n            }\n\n            return data;\n          }),\n        )\n        .transacting(trx);\n    }\n\n    const notifications = await trx('notification')\n      .withSchema('v1')\n      .whereRaw('user_id = ANY (?)', [whereInUserIds])\n      .whereRaw('action_id = ANY (?)', [whereInActionIds])\n      .whereRaw('card_id = ANY (?)', [whereInCardIds]);\n\n    if (notifications.length > 0) {\n      await knex\n        .batchInsert(\n          'notification',\n          notifications.map((notification) => {\n            const card = cardById[notification.card_id];\n            const action = actionById[notification.action_id];\n\n            const data = {\n              ..._.pick(notification, [\n                'id',\n                'user_id',\n                'card_id',\n                'is_read',\n                'created_at',\n                'updated_at',\n              ]),\n              creator_user_id: userIdsSet.has(action.user_id) ? action.user_id : null,\n              board_id: card.board_id,\n              type: action.type,\n            };\n\n            if (action.type === PrevActionTypes.COMMENT_CARD) {\n              Object.assign(data, {\n                comment_id: action.id,\n                data: {\n                  card: _.pick(card, ['name']),\n                  text: action.data.text,\n                },\n              });\n            } else {\n              Object.assign(data, {\n                action_id: action.id,\n                data: {\n                  fromList: {\n                    ...action.data.fromList,\n                    type: List.Types.ACTIVE,\n                  },\n                  toList: {\n                    ...action.data.toList,\n                    type: List.Types.ACTIVE,\n                  },\n                  card: _.pick(card, ['name']),\n                },\n              });\n            }\n\n            return data;\n          }),\n        )\n        .transacting(trx);\n    }\n\n    await trx.schema.dropSchema('v1', true);\n  });\n};\n\nconst upgradeUserAvatars = async () => {\n  const fileManager = sails.hooks['file-manager'].getInstance();\n\n  const dirnames = await fileManager.listDir(PrevPathSegments.USER_AVATARS);\n  const users = await knex('user_account').whereNotNull('avatar');\n\n  if (dirnames) {\n    const userByDirname = _.keyBy(users, 'avatar.dirname');\n\n    for (const dirname of dirnames) {\n      const user = userByDirname[dirname];\n      const dirPathSegment = `${PrevPathSegments.USER_AVATARS}/${dirname}`;\n\n      if (user) {\n        const size = await fileManager.getSize(\n          `${dirPathSegment}/original.${user.avatar.extension}`,\n        );\n\n        if (size) {\n          await knex('user_account')\n            .update({\n              avatar: knex.raw(\"?? || jsonb_build_object('sizeInBytes', ?::bigint)\", [\n                'avatar',\n                size,\n              ]),\n            })\n            .where('id', user.id);\n        }\n      } else {\n        await fileManager.deleteDir(dirPathSegment);\n      }\n    }\n  }\n\n  for (const { avatar } of users) {\n    const dirPathSegment = `${PrevPathSegments.USER_AVATARS}/${avatar.dirname}`;\n\n    const isExists = await fileManager.isExists(`${dirPathSegment}/cover-180.${avatar.extension}`);\n\n    if (isExists) {\n      continue;\n    }\n\n    await fileManager.delete(`${dirPathSegment}/square-100.${avatar.extension}`);\n\n    let readStream;\n    try {\n      readStream = await fileManager.read(`${dirPathSegment}/original.${avatar.extension}`);\n    } catch (error) {\n      continue;\n    }\n\n    const originalBuffer = await readStreamToBuffer(readStream);\n\n    const image = sharp(originalBuffer, {\n      animated: true,\n    });\n\n    const cover180 = image\n      .clone()\n      .resize(180, 180, {\n        withoutEnlargement: true,\n      })\n      .png({\n        quality: 75,\n        force: false,\n      });\n\n    await fileManager.save(\n      `${dirPathSegment}/cover-180.${avatar.extension}`,\n      cover180,\n      mime.lookup(avatar.extension) || null,\n    );\n  }\n};\n\nconst upgradeBackgroundImages = async () => {\n  const fileManager = sails.hooks['file-manager'].getInstance();\n\n  await fileManager.renameDir(\n    PrevPathSegments.PROJECT_BACKGROUND_IMAGES,\n    PrevPathSegments.BACKGROUND_IMAGES,\n  );\n\n  const dirnames = await fileManager.listDir(PrevPathSegments.BACKGROUND_IMAGES);\n  const backgroundImages = await knex('background_image');\n\n  if (dirnames) {\n    const backgroundImageByDirname = _.keyBy(backgroundImages, 'dirname');\n\n    for (const dirname of dirnames) {\n      const backgroundImage = backgroundImageByDirname[dirname];\n      const dirPathSegment = `${PrevPathSegments.BACKGROUND_IMAGES}/${dirname}`;\n\n      if (backgroundImage) {\n        const size = await fileManager.getSize(\n          `${dirPathSegment}/original.${backgroundImage.extension}`,\n        );\n\n        if (size) {\n          await knex('background_image')\n            .update({\n              size_in_bytes: size,\n            })\n            .where('id', backgroundImage.id);\n        }\n      } else {\n        await fileManager.deleteDir(dirPathSegment);\n      }\n    }\n  }\n\n  for (const backgroundImage of backgroundImages) {\n    const dirPathSegment = `${PrevPathSegments.BACKGROUND_IMAGES}/${backgroundImage.dirname}`;\n\n    const isExists = await fileManager.isExists(\n      `${dirPathSegment}/outside-360.${backgroundImage.extension}`,\n    );\n\n    if (isExists) {\n      continue;\n    }\n\n    await fileManager.delete(`${dirPathSegment}/cover-336.${backgroundImage.extension}`);\n\n    let readStream;\n    try {\n      readStream = await fileManager.read(\n        `${dirPathSegment}/original.${backgroundImage.extension}`,\n      );\n    } catch (error) {\n      continue;\n    }\n\n    const originalBuffer = await readStreamToBuffer(readStream);\n\n    const image = sharp(originalBuffer, {\n      animated: true,\n    });\n\n    const outside360 = image\n      .clone()\n      .resize(360, 360, {\n        fit: 'outside',\n        withoutEnlargement: true,\n      })\n      .png({\n        quality: 75,\n        force: false,\n      });\n\n    await fileManager.save(\n      `${dirPathSegment}/outside-360.${backgroundImage.extension}`,\n      outside360,\n      mime.lookup(backgroundImage.extension) || null,\n    );\n  }\n};\n\nconst upgradeFileAttachments = async () => {\n  const fileManager = sails.hooks['file-manager'].getInstance();\n\n  const dirnames = await fileManager.listDir(PrevPathSegments.ATTACHMENTS);\n  const attachments = await knex('attachment').where('type', Attachment.Types.FILE);\n\n  const fileReferenceIds = [];\n  if (dirnames) {\n    const attachmentByDirname = _.keyBy(attachments, 'data.fileReferenceId');\n\n    for (const dirname of dirnames) {\n      const attachment = attachmentByDirname[dirname];\n      const dirPathSegment = `${PrevPathSegments.ATTACHMENTS}/${dirname}`;\n\n      if (attachment) {\n        if (uuid.validate(dirname)) {\n          const fileReferenceId = await knex.transaction(async (trx) => {\n            const [{ id }] = await trx('file_reference').insert(\n              {\n                total: 1,\n                created_at: new Date().toISOString(),\n              },\n              'id',\n            );\n\n            const size = await fileManager.getSize(`${dirPathSegment}/${attachment.data.filename}`);\n\n            let encoding = null;\n            if (size && size <= MAX_SIZE_TO_GET_ENCODING) {\n              const readStream = await fileManager.read(\n                `${dirPathSegment}/${attachment.data.filename}`,\n              );\n\n              const buffer = await readStreamToBuffer(readStream);\n              encoding = getEncoding(buffer);\n            }\n\n            await trx('attachment')\n              .update({\n                data: trx.raw(\n                  \"?? || jsonb_build_object('fileReferenceId', ?::text, 'sizeInBytes', ?::bigint, 'encoding', ?::text)\",\n                  ['data', id, size, encoding],\n                ),\n              })\n              .where('id', attachment.id);\n\n            await fileManager.renameDir(\n              `${dirPathSegment}`,\n              `${PrevPathSegments.ATTACHMENTS}/${id}`,\n            );\n\n            return id;\n          });\n\n          fileReferenceIds.push(fileReferenceId);\n        } else {\n          fileReferenceIds.push(dirname);\n        }\n      } else {\n        await fileManager.deleteDir(dirPathSegment);\n      }\n    }\n  }\n\n  if (fileReferenceIds.length > 0) {\n    await knex('attachment')\n      .update({\n        data: knex.raw('?? || \\'{\"fileReferenceId\":null}\\'', 'data'),\n      })\n      .where('type', Attachment.Types.FILE)\n      .whereRaw(`??->>'fileReferenceId' NOT IN (${fileReferenceIds.map(() => '?').join(', ')})`, [\n        'data',\n        ...fileReferenceIds,\n      ]);\n  }\n\n  const imageAttachments = await knex('attachment')\n    .where('type', Attachment.Types.FILE)\n    .whereRaw(\"??->>'image' IS NOT NULL\", 'data');\n\n  for (const { data } of imageAttachments) {\n    const dirPathSegment = `${PrevPathSegments.ATTACHMENTS}/${data.fileReferenceId}`;\n    const thumbnailsPathSegment = `${dirPathSegment}/thumbnails`;\n\n    const isExists = await fileManager.isExists(\n      `${thumbnailsPathSegment}/outside-720.${data.image.thumbnailsExtension}`,\n    );\n\n    if (isExists) {\n      continue;\n    }\n\n    await fileManager.deleteDir(thumbnailsPathSegment);\n\n    let readStream;\n    try {\n      readStream = await fileManager.read(`${dirPathSegment}/${data.filename}`);\n    } catch (error) {\n      continue;\n    }\n\n    const buffer = await readStreamToBuffer(readStream);\n\n    const image = sharp(buffer, {\n      animated: true,\n    });\n\n    const outside360 = image\n      .clone()\n      .resize(360, 360, {\n        fit: 'outside',\n        withoutEnlargement: true,\n      })\n      .png({\n        quality: 75,\n        force: false,\n      });\n\n    await fileManager.save(\n      `${thumbnailsPathSegment}/outside-360.${data.image.thumbnailsExtension}`,\n      outside360,\n      data.mimeType,\n    );\n\n    const outside720 = image\n      .clone()\n      .resize(720, 720, {\n        fit: 'outside',\n        withoutEnlargement: true,\n      })\n      .png({\n        quality: 75,\n        force: false,\n      });\n\n    await fileManager.save(\n      `${thumbnailsPathSegment}/outside-720.${data.image.thumbnailsExtension}`,\n      outside720,\n      data.mimeType,\n    );\n  }\n};\n\nconst upgradeDataStructure = async () => {\n  if (!sails.hooks.s3.isEnabled()) {\n    return;\n  }\n\n  const fileManager = sails.hooks['file-manager'].getInstance();\n\n  await fileManager.renameDir(PrevPathSegments.FAVICONS, sails.config.custom.faviconsPathSegment);\n\n  await fileManager.renameDir(\n    PrevPathSegments.USER_AVATARS,\n    sails.config.custom.userAvatarsPathSegment,\n  );\n\n  await fileManager.renameDir(\n    PrevPathSegments.BACKGROUND_IMAGES,\n    sails.config.custom.backgroundImagesPathSegment,\n  );\n};\n\n(async () => {\n  try {\n    let migrations;\n    try {\n      migrations = await knex('migration').orderBy('id');\n    } catch (error) {\n      throw new Error('Nothing to upgrade');\n    }\n\n    const migrationNames = migrations.map(({ name }) => name);\n\n    const isV1 = migrationNames[0] === '20180721020022_create_next_id_function.js';\n    const isLatestV1 = migrationNames.at(-1) === '20250131202710_add_list_color.js';\n    const isInitialV2 = migrationNames.at(-1) === '20250228000022_version_2.js';\n\n    if (isV1 && !isLatestV1) {\n      throw new Error('Update to latest v1 first');\n    }\n\n    await loadSails();\n    sails.config.custom.uploadsBasePath = sails.config.appPath;\n\n    if (isV1) {\n      await runStep('Upgrading database', upgradeDatabase);\n    }\n\n    if (isV1 || isInitialV2) {\n      await runStep('Upgrading user avatars', upgradeUserAvatars);\n      await runStep('Upgrading background images', upgradeBackgroundImages);\n      await runStep('Upgrading file attachments', upgradeFileAttachments);\n    }\n\n    await runStep('Upgrading data structure', upgradeDataStructure);\n  } catch (error) {\n    console.error(error);\n    process.exitCode = 1;\n  } finally {\n    knex.destroy();\n    process.exit();\n  }\n})();\n"
  },
  {
    "path": "server/generate-swagger.js",
    "content": "const fs = require('fs');\n// eslint-disable-next-line import/no-extraneous-dependencies\nconst swaggerJsdoc = require('swagger-jsdoc');\n\nconst config = require('./config/swagger');\n\nconst specification = swaggerJsdoc(config);\nfs.writeFileSync('swagger.json', JSON.stringify(specification, null, 2));\n"
  },
  {
    "path": "server/healthcheck.js",
    "content": "/* eslint-disable no-console */\n\nconst http = require('http');\n\nconst options = {\n  host: 'localhost',\n  port: 1337,\n  timeout: 2000,\n};\n\nconst healthcheck = http.request(options, ({ statusCode }) => {\n  console.log(`HEALTHCHECK STATUS: ${statusCode}`);\n  process.exit(statusCode === 200 ? 0 : 1);\n});\n\nhealthcheck.on('error', () => {\n  console.error('HEALTHCHECK ERROR');\n  process.exit(1);\n});\n\nhealthcheck.end();\n"
  },
  {
    "path": "server/nodemon.json",
    "content": "{\n  \"watch\": [\n    \"api/**/*.js\",\n    \"config/**/*.js\",\n    \"db/**/*.js\",\n    \"utils/**/*.js\",\n    \"app.js\",\n    \"constants.js\"\n  ],\n  \"ignore\": [\"node_modules\", \".tmp\", \".venv\"],\n  \"ext\": \"js,json\",\n  \"verbose\": true,\n  \"delay\": 1000,\n  \"legacyWatch\": false\n}\n"
  },
  {
    "path": "server/package.json",
    "content": "{\n  \"name\": \"planka-server\",\n  \"private\": true,\n  \"main\": \"app.js\",\n  \"scripts\": {\n    \"build\": \"node build.js\",\n    \"console\": \"dotenv sails console\",\n    \"db:create-admin-user\": \"node db/create-admin-user.js\",\n    \"db:init\": \"node db/init.js\",\n    \"db:migrate\": \"knex migrate:latest --cwd db\",\n    \"db:seed\": \"knex seed:run --cwd db\",\n    \"db:upgrade\": \"node db/upgrade.js\",\n    \"postinstall\": \"patch-package && npm run setup-python\",\n    \"lint\": \"eslint . --max-warnings=0 --report-unused-disable-directives && echo '✔  Your .js files look good.'\",\n    \"setup-python\": \"node setup-python.js\",\n    \"start\": \"nodemon\",\n    \"start:prod\": \"cross-env NODE_ENV=production node app.js --prod\",\n    \"swagger:generate\": \"node generate-swagger.js\",\n    \"test\": \"mocha test/lifecycle.test.js test/integration/**/*.test.js test/utils/**/*.test.js\"\n  },\n  \"eslintConfig\": {\n    \"plugins\": [\n      \"prettier\"\n    ],\n    \"extends\": [\n      \"airbnb-base\",\n      \"plugin:prettier/recommended\"\n    ],\n    \"rules\": {\n      \"no-throw-literal\": \"off\",\n      \"no-undef\": \"off\",\n      \"prettier/prettier\": [\n        \"error\",\n        {\n          \"printWidth\": 100,\n          \"singleQuote\": true,\n          \"trailingComma\": \"all\"\n        }\n      ]\n    },\n    \"globals\": {\n      \"_\": true,\n      \"sails\": true\n    }\n  },\n  \"overrides\": {\n    \"waterline-utils\": {\n      \"qs\": \"^6.15.0\"\n    }\n  },\n  \"dependencies\": {\n    \"@aws-sdk/client-s3\": \"^3.1009.0\",\n    \"@aws-sdk/lib-storage\": \"^3.1009.0\",\n    \"bcrypt\": \"^6.0.0\",\n    \"bytes\": \"^3.1.2\",\n    \"cross-env\": \"^10.1.0\",\n    \"dotenv\": \"^17.3.1\",\n    \"dotenv-cli\": \"^11.0.0\",\n    \"escape-html\": \"^1.0.3\",\n    \"escape-markdown\": \"^1.0.4\",\n    \"file-type\": \"^21.3.2\",\n    \"fs-extra\": \"^11.3.4\",\n    \"i18n-2\": \"^0.7.3\",\n    \"ico-to-png\": \"^0.2.2\",\n    \"istextorbinary\": \"^9.5.0\",\n    \"jsonwebtoken\": \"^9.0.3\",\n    \"knex\": \"^3.1.0\",\n    \"lodash\": \"^4.17.23\",\n    \"mime-types\": \"^3.0.2\",\n    \"moment\": \"^2.30.1\",\n    \"nodemailer\": \"^7.0.13\",\n    \"openid-client\": \"^5.7.1\",\n    \"patch-package\": \"^8.0.1\",\n    \"pg\": \"^8.20.0\",\n    \"read\": \"^5.0.1\",\n    \"rimraf\": \"^6.1.3\",\n    \"sails\": \"^1.5.17\",\n    \"sails-hook-orm\": \"^4.0.3\",\n    \"sails-hook-sockets\": \"^3.0.2\",\n    \"sails-postgresql\": \"^5.0.1\",\n    \"serve-static\": \"^2.2.1\",\n    \"sharp\": \"^0.34.5\",\n    \"undici\": \"^7.24.0\",\n    \"uuid\": \"^11.1.0\",\n    \"validator\": \"^13.15.26\",\n    \"winston\": \"^3.19.0\",\n    \"zxcvbn\": \"^4.4.2\"\n  },\n  \"devDependencies\": {\n    \"chai\": \"^6.2.2\",\n    \"eslint\": \"^8.57.1\",\n    \"eslint-config-airbnb-base\": \"^15.0.0\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-import\": \"^2.32.0\",\n    \"eslint-plugin-prettier\": \"^5.5.5\",\n    \"ignore\": \"^7.0.5\",\n    \"mocha\": \"^11.7.5\",\n    \"nodemon\": \"^3.1.14\",\n    \"prettier\": \"3.8.1\",\n    \"supertest\": \"^7.2.2\",\n    \"swagger-jsdoc\": \"^6.2.8\"\n  },\n  \"engines\": {\n    \"node\": \">=20\"\n  }\n}\n"
  },
  {
    "path": "server/patches/sails+1.5.17.patch",
    "content": "diff --git a/node_modules/sails/lib/hooks/i18n/index.js b/node_modules/sails/lib/hooks/i18n/index.js\nindex a0c74ed..582ff66 100644\n--- a/node_modules/sails/lib/hooks/i18n/index.js\n+++ b/node_modules/sails/lib/hooks/i18n/index.js\n@@ -108,7 +108,8 @@ module.exports = function(sails) {\n           locales: sails.config.i18n.locales,\n           defaultLocale: sails.config.i18n.defaultLocale,\n           directory: resolvedLocalesDirectory,\n-          extension: I18N_LOCALES_FILE_EXTENSION\n+          extension: I18N_LOCALES_FILE_EXTENSION,\n+          devMode: false\n         });\n \n         // Add all of the i18n prototype methods into this hook.\n@@ -176,7 +177,8 @@ module.exports = function(sails) {\n           defaultLocale: sails.config.i18n.defaultLocale,\n           directory: resolvedLocalesDirectory,\n           extension: I18N_LOCALES_FILE_EXTENSION,\n-          request: req\n+          request: req,\n+          devMode: false\n         });\n \n         // Mix translation capabilities into res.locals.\n"
  },
  {
    "path": "server/patches/skipper-disk+0.5.12.patch",
    "content": "diff --git a/node_modules/skipper-disk/standalone/build-progress-stream.js b/node_modules/skipper-disk/standalone/build-progress-stream.js\nindex ed048dc..3cca22e 100644\n--- a/node_modules/skipper-disk/standalone/build-progress-stream.js\n+++ b/node_modules/skipper-disk/standalone/build-progress-stream.js\n@@ -110,7 +110,7 @@ module.exports = function buildProgressStream (options, __newFile, receiver__, o\n     receiver__.emit('progress', currentFileProgress);\n \n     // and then enforce its `maxBytes`.\n-    if (options.maxBytes && totalBytesWritten >= options.maxBytes) {\n+    if (!_.isNull(options.maxBytes) && totalBytesWritten >= options.maxBytes) {\n \n       var err = new Error();\n       err.code = 'E_EXCEEDS_UPLOAD_LIMIT';\n"
  },
  {
    "path": "server/patches/waterline+0.15.2.patch",
    "content": "diff --git a/node_modules/waterline/lib/waterline/utils/query/forge-adapter-error.js b/node_modules/waterline/lib/waterline/utils/query/forge-adapter-error.js\nindex 3d4a77f..07d09ad 100644\n--- a/node_modules/waterline/lib/waterline/utils/query/forge-adapter-error.js\n+++ b/node_modules/waterline/lib/waterline/utils/query/forge-adapter-error.js\n@@ -223,7 +223,7 @@ module.exports = function forgeAdapterError(err, omen, adapterMethodName, modelI\n \n \n           // If there were any unmatched keys, log a warning and silently ignore them.\n-          if (unmatchedKeys.length > 0) {\n+          /* if (unmatchedKeys.length > 0) {\n             console.warn('\\n'+\n               'Warning: Adapter sent back a uniqueness error, but that error references key(s) ('+unmatchedKeys+') which cannot\\n'+\n               'be matched up with the column names of any attributes in this model (`'+modelIdentity+'`).  This probably\\n'+\n@@ -232,7 +232,7 @@ module.exports = function forgeAdapterError(err, omen, adapterMethodName, modelI\n               'in uniqueness errors, then just use an empty array for the `keys` property of this error.)\\n'+\n               '(Proceeding anyway as if these keys weren\\'t included...)\\n'\n             );\n-          }//>-\n+          } */ //>-\n \n \n           // Build the customizations for our uniqueness error.\n"
  },
  {
    "path": "server/requirements.txt",
    "content": "apprise==1.9.7\n"
  },
  {
    "path": "server/setup-python.js",
    "content": "const { spawnSync } = require('child_process');\nconst path = require('path');\n\nconst VENV_PATH = path.join(__dirname, '.venv');\nconst REQUIREMENTS_PATH = path.join(__dirname, 'requirements.txt');\n\nconst PYTHON_PATH =\n  process.platform === 'win32'\n    ? path.join(VENV_PATH, 'Scripts', 'python.exe')\n    : path.join(VENV_PATH, 'bin', 'python');\n\nconst getSystemPythonCommand = () => {\n  let result = spawnSync('python3', ['--version'], { stdio: 'ignore' });\n  if (result.status === 0) return 'python3';\n\n  result = spawnSync('python', ['--version'], { stdio: 'ignore' });\n  if (result.status === 0) return 'python';\n\n  throw new Error('Python is not installed or not in PATH');\n};\n\nconst run = (command, args) => {\n  const result = spawnSync(command, args, { stdio: 'inherit' });\n  if (result.status !== 0) process.exit(result.status);\n};\n\nrun(getSystemPythonCommand(), ['-m', 'venv', VENV_PATH]);\nrun(PYTHON_PATH, ['-m', 'pip', 'install', '-r', REQUIREMENTS_PATH]);\n"
  },
  {
    "path": "server/start.sh",
    "content": "#!/bin/bash\n\nset -eu\n\n# Load secrets from files if corresponding *__FILE environment variables are set.\n# Only the first line of each file is read (stripping carriage returns and newlines).\n\nread_secret() {\n  local file=\"$1\"\n  head -n 1 \"$file\" | tr -d '\\r\\n'\n}\n\nload_secret() {\n  local envar=\"$1\"\n  local file=\"${envar}__FILE\"\n  if [[ -z \"${!envar:-}\" && -e \"${!file:-}\" ]]; then\n    export \"$envar\"=\"$(read_secret \"${!file}\")\"\n  fi\n}\n\nstart_outgoing_proxy_if_needed() {\n  # If a custom outgoing proxy is set, do not start internal proxy\n  if [[ -n \"${OUTGOING_PROXY:-}\" ]]; then\n    return\n  fi\n\n  # Minimum safe defaults\n  if [[ -z \"${OUTGOING_BLOCKED_HOSTS+x}\" ]]; then\n    OUTGOING_BLOCKED_HOSTS=\"localhost,postgres\"\n  fi\n\n  # If no blocked/allowed rules exist, do not start internal proxy\n  if [[ -z \"${OUTGOING_BLOCKED_IPS:-}\" && -z \"${OUTGOING_BLOCKED_HOSTS:-}\" && -z \"${OUTGOING_ALLOWED_IPS:-}\" && -z \"${OUTGOING_ALLOWED_HOSTS:-}\" ]]; then\n    return\n  fi\n\n  SQUID_CONF=\"/tmp/squid.conf\"\n  SQUID_PID=\"/tmp/squid.pid\"\n\n  : > \"$SQUID_CONF\"\n\n  # Basic settings\n  echo \"pid_filename $SQUID_PID\" >> \"$SQUID_CONF\"\n  echo \"http_port 127.0.0.1:3128\" >> \"$SQUID_CONF\"\n  echo \"max_filedescriptors 4096\" >> \"$SQUID_CONF\"\n  echo \"acl all src all\" >> \"$SQUID_CONF\"\n\n  # Disable caching\n  echo \"cache deny all\" >> \"$SQUID_CONF\"\n  echo \"cache_mem 0\" >> \"$SQUID_CONF\"\n  echo \"memory_pools off\" >> \"$SQUID_CONF\"\n  echo \"cache_swap_low 0\" >> \"$SQUID_CONF\"\n  echo \"cache_swap_high 0\" >> \"$SQUID_CONF\"\n\n  # Disable logs\n  echo \"access_log none\" >> \"$SQUID_CONF\"\n  echo \"cache_store_log none\" >> \"$SQUID_CONF\"\n  echo \"cache_log /dev/null\" >> \"$SQUID_CONF\"\n  echo \"logfile_rotate 0\" >> \"$SQUID_CONF\"\n  echo \"debug_options ALL,0\" >> \"$SQUID_CONF\"\n\n  # Make it pass-through like\n  echo \"forwarded_for delete\" >> \"$SQUID_CONF\"\n  echo \"via off\" >> \"$SQUID_CONF\"\n  echo \"request_header_access X-Forwarded-For deny all\" >> \"$SQUID_CONF\"\n  echo \"request_header_access Via deny all\" >> \"$SQUID_CONF\"\n  echo \"request_header_access Cache-Control deny all\" >> \"$SQUID_CONF\"\n\n  # Allow only local sources\n  echo \"acl localhost_src src 127.0.0.1\" >> \"$SQUID_CONF\"\n  echo \"http_access deny !localhost_src\" >> \"$SQUID_CONF\"\n\n  # Blocked IPs\n  if [[ -n \"${OUTGOING_BLOCKED_IPS:-}\" ]]; then\n    IFS=',' read -ra BLOCKED_IPS <<< \"$OUTGOING_BLOCKED_IPS\"\n    for ip in \"${BLOCKED_IPS[@]}\"; do\n      echo \"acl blocked_ip dst $ip\" >> \"$SQUID_CONF\"\n    done\n    echo \"http_access deny blocked_ip\" >> \"$SQUID_CONF\"\n  fi\n\n  # Blocked hostnames\n  if [[ -n \"${OUTGOING_BLOCKED_HOSTS:-}\" ]]; then\n    IFS=',' read -ra BLOCKED_HOSTS <<< \"$OUTGOING_BLOCKED_HOSTS\"\n    for host in \"${BLOCKED_HOSTS[@]}\"; do\n      echo \"acl blocked_host dstdomain $host\" >> \"$SQUID_CONF\"\n    done\n    echo \"http_access deny blocked_host\" >> \"$SQUID_CONF\"\n  fi\n\n  # Allowed IPs\n  if [[ -n \"${OUTGOING_ALLOWED_IPS:-}\" ]]; then\n    IFS=',' read -ra ALLOWED_IPS <<< \"$OUTGOING_ALLOWED_IPS\"\n    for ip in \"${ALLOWED_IPS[@]}\"; do\n      echo \"acl allowed_ip dst $ip\" >> \"$SQUID_CONF\"\n    done\n    echo \"http_access allow allowed_ip\" >> \"$SQUID_CONF\"\n  fi\n\n  # Allowed hostnames\n  if [[ -n \"${OUTGOING_ALLOWED_HOSTS:-}\" ]]; then\n    IFS=',' read -ra ALLOWED_HOSTS <<< \"$OUTGOING_ALLOWED_HOSTS\"\n    for host in \"${ALLOWED_HOSTS[@]}\"; do\n      echo \"acl allowed_host dstdomain $host\" >> \"$SQUID_CONF\"\n    done\n    echo \"http_access allow allowed_host\" >> \"$SQUID_CONF\"\n  fi\n\n  # If any allowed rules exist, everything else is denied\n  if [[ -n \"${OUTGOING_ALLOWED_IPS+x}${OUTGOING_ALLOWED_HOSTS+x}\" ]]; then\n    echo \"http_access deny all\" >> \"$SQUID_CONF\"\n  else\n    # If no allowed rules exist, everything else is allowed\n    echo \"http_access allow all\" >> \"$SQUID_CONF\"\n  fi\n\n  # Start Squid\n  rm -f \"$SQUID_PID\"\n  squid -N -f \"$SQUID_CONF\" &\n\n  # Set environment variable\n  export OUTGOING_PROXY=\"http://127.0.0.1:3128\"\n}\n\nif [[ -n \"${DATABASE_URL:-}\" ]]; then\n  if [[ -z \"${DATABASE_PASSWORD:-}\" && -e \"${DATABASE_PASSWORD__FILE:-}\" ]]; then\n    DATABASE_PASSWORD=\"$(read_secret \"${DATABASE_PASSWORD__FILE}\")\"\n    export DATABASE_URL=\"${DATABASE_URL/\\$\\{DATABASE_PASSWORD\\}/${DATABASE_PASSWORD}}\"\n  fi\nfi\n\nload_secret SECRET_KEY\nload_secret DEFAULT_ADMIN_PASSWORD\nload_secret S3_SECRET_ACCESS_KEY\nload_secret OIDC_CLIENT_SECRET\nload_secret SMTP_PASSWORD\n\nstart_outgoing_proxy_if_needed\n\nexport NODE_ENV=production\n\nnode db/init.js\nexec node app.js --prod\n"
  },
  {
    "path": "server/terms/_template/de-DE.md",
    "content": "# ⚠️ DIES IST NUR EINE BEISPIEL-VORLAGE\n\nWenn Sie Administrator dieser Instanz sind, können Sie diese Bedingungen an Ihre eigenen Bedürfnisse und rechtlichen Anforderungen anpassen.\n\nEine Anleitung zum Anpassen dieser Vorlage finden Sie in diesem [Guide](https://docs.planka.cloud/docs/configuration/customizing-end-user-terms/).\n\n---\n\n# Nutzungsbedingungen für Endbenutzer – On-Premise-Version\n\n**Stand: 11. Februar 2026 – v1.0**\n\n## 1. Geltungsbereich und Ihr Verhältnis zu PLANKA\n\n### 1.1 Was dieses Dokument regelt\nDiese Nutzungsbedingungen gelten für Sie als Endbenutzer der Software PLANKA in der On-Premise-Version. Die Software wird auf den Systemen Ihres Arbeitgebers oder Auftraggebers betrieben. Sie regeln die Bedingungen, unter denen Sie die Software nutzen dürfen – unabhängig davon, ob Sie Mitarbeiter, externer Dienstleister oder eingeladener Nutzer der Organisation sind, die PLANKA für Sie bereitgestellt hat.\n\n### 1.2 Wer ist wer?\n- **„Anbieter\"** oder **„wir\"**: PLANKA Software GmbH, Lindauer Str. 4, 87439 Kempten, Deutschland\n- **„Kunde\"** oder **„Instanz-Betreiber\"**: Die Organisation, die die Lizenz für PLANKA erworben hat und die Software auf eigenen Systemen betreibt\n- **„Sie\"** oder **„Endbenutzer\"**: Die natürliche Person, die PLANKA über die Installation des Kunden nutzt\n\n### 1.3 Vertragliche Einordnung\nIhr Zugang zu PLANKA basiert auf dem Vertrag zwischen dem Kunden und uns (Allgemeine Geschäftsbedingungen). Dieses Dokument ergänzt diese AGB und regelt Ihre persönlichen Pflichten als Endbenutzer. Es begründet kein eigenständiges Vertragsverhältnis zwischen Ihnen und dem Anbieter.\n\n## 2. Ihr Konto\n\n### 2.1 Kontoerstellung\nJeder Endbenutzer benötigt ein eigenes, persönliches Konto. Gemeinsam genutzte Konten sind nicht gestattet.\n\n### 2.2 Ihre Verantwortung\nSie sind verantwortlich für die Richtigkeit Ihrer Kontodaten, die Geheimhaltung Ihrer Zugangsdaten und alle Aktivitäten, die unter Ihrem Konto stattfinden. Wenn Sie den Verdacht haben, dass Ihr Konto unbefugt genutzt wird, informieren Sie unverzüglich Ihren Instanz-Betreiber.\n\n### 2.3 Zugang und Entzug\nIhr Zugang wird durch den Kunden verwaltet. Der Kunde kann Ihren Zugang jederzeit einschränken oder entziehen. Mit Beendigung Ihres Nutzungsverhältnisses zum Kunden erlischt auch Ihr Zugangsrecht.\n\n## 3. Zulässige Nutzung\n\n### 3.1 Bestimmungsgemäße Nutzung\nSie dürfen PLANKA ausschließlich für die Zwecke nutzen, für die der Kunde Ihnen den Zugang eingerichtet hat, und im Rahmen der Ihnen zugewiesenen Rolle und Berechtigungen.\n\n### 3.2 Verbotene Nutzungen\nFolgendes ist ausdrücklich untersagt:\n- Weitergabe Ihrer Zugangsdaten an andere Personen\n- Nutzung für rechtswidrige, diskriminierende oder missbräuchliche Zwecke\n- Upload von schadhaften Dateien (Viren, Malware) oder rechtswidrigen Inhalten\n- Versuche, auf Daten anderer Nutzer unbefugt zuzugreifen\n- Reverse Engineering, Dekompilierung oder Disassemblierung der Software\n- Nutzung der Software über den vom Kunden lizenzierten Umfang hinaus\n- Kopieren, Vervielfältigen oder Weitergabe der Software oder von Teilen davon\n\n### 3.3 Inhaltsverantwortung\nSie sind für alle Inhalte verantwortlich, die Sie in PLANKA erstellen, hochladen oder teilen. Stellen Sie sicher, dass diese Inhalte keine Rechte Dritter verletzen und im Einklang mit geltendem Recht stehen.\n\n## 4. Geistiges Eigentum und Ihre Inhalte\n\nDie Software PLANKA ist Eigentum der PLANKA Software GmbH. Ihre Nutzungsberechtigung stellt keine Übertragung von Eigentumsrechten dar. Sie behalten das Eigentum an allen Inhalten, die Sie innerhalb von PLANKA erstellen. Die Verwaltung und eventuelle Herausgabe Ihrer Inhalte obliegt dem Kunden (Instanz-Betreiber), nicht dem Anbieter.\n\n## 5. Datenschutz\n\n### 5.1 Hinweis zur Datenverarbeitung\nDie On-Premise-Version von PLANKA wird auf den Systemen des Kunden betrieben. Der Anbieter hat keinen Zugang zu Ihren Daten und verarbeitet keine personenbezogenen Daten der Endbenutzer. Allein verantwortlich für den Datenschutz innerhalb der PLANKA-Installation ist der Kunde (Instanz-Betreiber). Für Fragen zum Datenschutz wenden Sie sich bitte an Ihren Instanz-Betreiber.\n\n### 5.2 Telemetriedaten\nDie Software kann anonymisierte technische Daten an den Anbieter übermitteln, soweit dies zur Lizenzprüfung oder zur Verbesserung der Software erforderlich ist. Personenbezogene Daten der Endbenutzer werden hierbei nicht übermittelt. Einzelheiten ergeben sich aus der technischen Dokumentation.\n\n## 6. Verfügbarkeit und Haftung\n\nDie Verfügbarkeit der Software liegt in der alleinigen Verantwortung des Kunden (Instanz-Betreibers), der die Software auf eigenen Systemen betreibt. Der Anbieter übernimmt keine Verantwortung für Ausfälle, Einschränkungen oder Datenverluste der On-Premise-Installation.\n\nDie Haftung des Anbieters gegenüber Endbenutzern richtet sich nach den gesetzlichen Bestimmungen und den Regelungen in den AGB zwischen dem Anbieter und dem Kunden. Der Anbieter haftet nicht für Schäden, die durch missbräuchliche oder vertragswidrige Nutzung entstehen.\n\n## 7. Vertraulichkeit\n\nZugangsdaten, Lizenzinformationen und interne Daten, auf die Sie über PLANKA Zugriff erhalten, sind vertraulich zu behandeln. Die Vertraulichkeitspflicht gilt über die Dauer Ihrer Zugangsberechtigung hinaus fort.\n\n## 8. Verstöße und Konsequenzen\n\nBei Verstößen gegen diese Nutzungsbedingungen ist der Anbieter berechtigt, den Kunden (Instanz-Betreiber) über den Verstoß zu informieren. Der Kunde kann Ihren Zugang vorübergehend sperren oder dauerhaft entziehen. Das Recht des Anbieters, bei schwerwiegenden Verstößen weitergehende Maßnahmen zu ergreifen, bleibt unberührt.\n\n## 9. Änderungen und anwendbares Recht\n\nDer Anbieter kann diese Nutzungsbedingungen mit Wirkung für die Zukunft ändern. Änderungen werden Ihnen beim nächsten Login angezeigt und erfordern Ihre erneute Bestätigung. Ohne Bestätigung ist eine weitere Nutzung von PLANKA nicht möglich. Es gilt deutsches Recht.\n\n*PLANKA Software GmbH · Lindauer Str. 4 · 87439 Kempten · Deutschland*\n\n[confirmations]::\n---\n\n✔️ **Ich habe diese Nutzungsbedingungen gelesen und akzeptiere sie**\n"
  },
  {
    "path": "server/terms/_template/en-US.md",
    "content": "# ⚠️ THIS IS ONLY A TEMPLATE\n\nIf you are the admin of this instance, you can customize these Terms to suit your own needs and legal requirements.\n\nFor guidance on updating this template, see this [guide](https://docs.planka.cloud/docs/configuration/customizing-end-user-terms/).\n\n---\n\n# End User Terms of Service – On-Premise Version\n\n**Effective: February 11, 2026 – v1.0**\n\n## 1. Scope and Your Relationship with PLANKA\n\n### 1.1 What This Document Covers\nThese End User Terms of Service apply to you as an end user of the PLANKA software in its on-premise version. The software is operated on the systems of your employer or client. They govern the conditions under which you may use the software – regardless of whether you are an employee, external contractor, or invited user of the organization that has made PLANKA available to you.\n\n### 1.2 Who Is Who?\n- **\"Provider\"** or **\"we\"**: PLANKA Software GmbH, Lindauer Str. 4, 87439 Kempten, Germany\n- **\"Customer\"** or **\"Instance Administrator\"**: The organization that has acquired the license for PLANKA and operates the software on its own systems\n- **\"You\"** or **\"End User\"**: The natural person using PLANKA through the Customer's installation\n\n### 1.3 Contractual Framework\nYour access to PLANKA is based on the agreement between the Customer and us (Terms and Conditions). This document supplements those Terms and Conditions and governs your personal obligations as an end user. It does not establish an independent contractual relationship between you and the Provider.\n\n## 2. Your Account\n\n### 2.1 Account Creation\nEach end user requires their own personal account. Shared accounts are not permitted.\n\n### 2.2 Your Responsibility\nYou are responsible for the accuracy of your account information, maintaining the confidentiality of your access credentials, and all activities that occur under your account. If you suspect unauthorized use of your account, notify your instance administrator immediately.\n\n### 2.3 Access and Revocation\nYour access is managed by the Customer. The Customer may restrict or revoke your access at any time. When your relationship with the Customer ends (e.g., end of employment), your right of access also expires.\n\n## 3. Acceptable Use\n\n### 3.1 Intended Use\nYou may use PLANKA exclusively for the purposes for which the Customer has granted you access, and within the scope of the role and permissions assigned to you.\n\n### 3.2 Prohibited Uses\nThe following are expressly prohibited:\n- Sharing your access credentials with other persons\n- Use for unlawful, discriminatory, or abusive purposes\n- Uploading harmful files (viruses, malware) or unlawful content\n- Attempting to access data of other users without authorization\n- Reverse engineering, decompilation, or disassembly of the software\n- Using the software beyond the scope licensed by the Customer\n- Copying, duplicating, or distributing the software or parts thereof\n\n### 3.3 Content Responsibility\nYou are responsible for all content you create, upload, or share in PLANKA. Ensure that such content does not infringe the rights of third parties and complies with applicable law.\n\n## 4. Intellectual Property and Your Content\n\nThe PLANKA software is owned by PLANKA Software GmbH. Your authorization to use the software does not constitute a transfer of ownership rights. You retain ownership of all content you create within PLANKA. The management and any release of your content is the responsibility of the Customer (instance administrator), not the Provider.\n\n## 5. Data Protection\n\n### 5.1 Notice Regarding Data Processing\nThe on-premise version of PLANKA is operated on the Customer's systems. The Provider has no access to your data and does not process any personal data of end users. The Customer (instance administrator) is solely responsible for data protection within the PLANKA installation. For data protection inquiries, please contact your instance administrator.\n\n### 5.2 Telemetry Data\nThe software may transmit anonymized technical data to the Provider to the extent necessary for license verification or software improvement. Personal data of end users is not transmitted. Details are set out in the technical documentation.\n\n## 6. Availability and Liability\n\nThe availability of the software is the sole responsibility of the Customer (instance administrator), who operates the software on its own systems. The Provider assumes no responsibility for outages, limitations, or data loss of the on-premise installation.\n\nThe Provider's liability towards end users is governed by the applicable statutory provisions and the terms set out in the Terms and Conditions between the Provider and the Customer. The Provider is not liable for damages resulting from misuse or use in breach of these terms.\n\n## 7. Confidentiality\n\nAccess credentials, license information, and internal data to which you gain access through PLANKA must be treated as confidential. The confidentiality obligation continues beyond the duration of your access authorization.\n\n## 8. Violations and Consequences\n\nIn the event of violations of these End User Terms of Service, the Provider is entitled to inform the Customer (instance administrator) of the violation. The Customer may temporarily suspend or permanently revoke your access. The Provider's right to take further action in the event of serious violations remains unaffected.\n\n## 9. Changes and Applicable Law\n\nThe Provider may amend these End User Terms of Service with effect for the future. Changes will be displayed to you at your next login and require your renewed confirmation. Without confirmation, further use of PLANKA is not possible. German law applies.\n\n*PLANKA Software GmbH · Lindauer Str. 4 · 87439 Kempten · Germany*\n\n[confirmations]::\n---\n\n✔️ **I have read and accept these End User Terms of Service**\n"
  },
  {
    "path": "server/terms/cloud/de-DE.md",
    "content": "# Nutzungsbedingungen für Endbenutzer – SaaS-Version\n\n**Stand: 11. Februar 2026 – v1.0**\n\n## Teil I – Nutzungsbedingungen\n\n### 1. Geltungsbereich und Ihr Verhältnis zu PLANKA\n\n#### 1.1 Was dieses Dokument regelt\nDiese Nutzungsbedingungen gelten für Sie als Endbenutzer der cloudbasierten Software PLANKA (SaaS). Sie regeln die Bedingungen, unter denen Sie die Software nutzen dürfen – unabhängig davon, ob Sie Mitarbeiter, externer Dienstleister oder eingeladener Nutzer der Organisation sind, die PLANKA für Sie bereitgestellt hat.\n\n#### 1.2 Wer ist wer?\n- **„Anbieter\"** oder **„wir\"**: PLANKA Software GmbH, Lindauer Str. 4, 87439 Kempten, Deutschland\n- **„Kunde\"** oder **„Instanz-Betreiber\"**: Die Organisation, die den Vertrag mit uns über die Bereitstellung von PLANKA geschlossen hat\n- **„Sie\"** oder **„Endbenutzer\"**: Die natürliche Person, die PLANKA über das Konto des Kunden nutzt\n\n#### 1.3 Vertragliche Einordnung\nIhr Zugang zu PLANKA basiert auf dem Vertrag zwischen dem Kunden und uns (Allgemeine Geschäftsbedingungen). Dieses Dokument ergänzt diese AGB und regelt Ihre persönlichen Pflichten als Endbenutzer. Es begründet kein eigenständiges Vertragsverhältnis zwischen Ihnen und dem Anbieter.\n\n### 2. Ihr Konto\n\n#### 2.1 Kontoerstellung\nJeder Endbenutzer benötigt ein eigenes, persönliches Konto. Gemeinsam genutzte Konten sind nicht gestattet.\n\n#### 2.2 Ihre Verantwortung\nSie sind verantwortlich für die Richtigkeit Ihrer Kontodaten, die Geheimhaltung Ihrer Zugangsdaten und alle Aktivitäten, die unter Ihrem Konto stattfinden. Wenn Sie den Verdacht haben, dass Ihr Konto unbefugt genutzt wird, informieren Sie unverzüglich Ihren Instanz-Betreiber.\n\n#### 2.3 Zugang und Entzug\nIhr Zugang wird durch den Kunden verwaltet. Der Kunde kann Ihren Zugang jederzeit einschränken oder entziehen. Mit Beendigung Ihres Nutzungsverhältnisses zum Kunden erlischt auch Ihr Zugangsrecht.\n\n### 3. Zulässige Nutzung\n\n#### 3.1 Bestimmungsgemäße Nutzung\nSie dürfen PLANKA ausschließlich für die Zwecke nutzen, für die der Kunde Ihnen den Zugang eingerichtet hat, und im Rahmen der Ihnen zugewiesenen Rolle und Berechtigungen.\n\n#### 3.2 Verbotene Nutzungen\nFolgendes ist ausdrücklich untersagt:\n- Weitergabe Ihrer Zugangsdaten an andere Personen\n- Nutzung für rechtswidrige, diskriminierende oder missbräuchliche Zwecke\n- Upload von schadhaften Dateien (Viren, Malware) oder rechtswidrigen Inhalten\n- Versuche, auf Daten anderer Nutzer oder Instanzen unbefugt zuzugreifen\n- Automatisierte Massenabfragen oder Aktionen, die die Infrastruktur unverhältnismäßig belasten\n- Reverse Engineering, Dekompilierung oder Disassemblierung der Software\n\n#### 3.3 Inhaltsverantwortung\nSie sind für alle Inhalte verantwortlich, die Sie in PLANKA erstellen, hochladen oder teilen. Stellen Sie sicher, dass diese Inhalte keine Rechte Dritter verletzen und im Einklang mit geltendem Recht stehen.\n\n### 4. Geistiges Eigentum und Ihre Inhalte\n\nDie Software PLANKA ist Eigentum der PLANKA Software GmbH. Ihre Nutzungsberechtigung stellt keine Übertragung von Eigentumsrechten dar. Sie behalten das Eigentum an allen Inhalten, die Sie innerhalb von PLANKA erstellen. Die Verwaltung und eventuelle Herausgabe Ihrer Inhalte obliegt dem Kunden (Instanz-Betreiber), nicht dem Anbieter.\n\n### 5. Verfügbarkeit, Haftung und Datensicherung\n\nDer Anbieter ist bestrebt, PLANKA mit hoher Verfügbarkeit bereitzustellen. Wartungsarbeiten, technische Störungen oder höhere Gewalt können zu vorübergehenden Einschränkungen führen. Ein Anspruch auf ununterbrochene Verfügbarkeit besteht nicht.\n\nDie Haftung des Anbieters gegenüber Endbenutzern richtet sich nach den gesetzlichen Bestimmungen und den Regelungen in den AGB zwischen dem Anbieter und dem Kunden. Der Anbieter haftet nicht für Schäden, die durch missbräuchliche oder vertragswidrige Nutzung entstehen.\n\nErstellen Sie regelmäßig eigene Sicherungskopien Ihrer wichtigen Inhalte, sofern dies in Ihrem Arbeitsumfeld vorgesehen ist. Der Anbieter übernimmt keine Haftung für Datenverluste, die durch eine fehlende Datensicherung seitens des Endbenutzers entstehen.\n\n### 6. Vertraulichkeit\n\nZugangsdaten, Lizenzinformationen und interne Daten, auf die Sie über PLANKA Zugriff erhalten, sind vertraulich zu behandeln. Die Vertraulichkeitspflicht gilt über die Dauer Ihrer Zugangsberechtigung hinaus fort.\n\n### 7. Verstöße und Konsequenzen\n\nBei Verstößen gegen diese Nutzungsbedingungen ist der Anbieter berechtigt, Ihren Zugang vorübergehend zu sperren oder dauerhaft zu entziehen und den Kunden (Instanz-Betreiber) über den Verstoß zu informieren. Das Recht des Anbieters, bei schwerwiegenden Verstößen weitergehende Maßnahmen zu ergreifen, bleibt unberührt.\n\n### 8. Änderungen und anwendbares Recht\n\nDer Anbieter kann diese Nutzungsbedingungen mit Wirkung für die Zukunft ändern. Änderungen werden Ihnen beim nächsten Login angezeigt und erfordern Ihre erneute Bestätigung. Ohne Bestätigung ist eine weitere Nutzung von PLANKA nicht möglich. Es gilt deutsches Recht.\n\n---\n\n## Teil II – Datenschutzhinweise\n\n### 1. Wer ist verantwortlich?\n\n**Verantwortlicher** für die Datenverarbeitung innerhalb Ihrer PLANKA-Instanz ist Ihr Arbeitgeber bzw. Auftraggeber (der „Instanz-Betreiber\"). Dieser entscheidet, welche Daten in PLANKA verarbeitet werden.\n\n**PLANKA Software GmbH** (Lindauer Str. 4, 87439 Kempten, Deutschland) verarbeitet Ihre Daten als Auftragsverarbeiter im Auftrag des Instanz-Betreibers.\n\nBei Datenschutzfragen wenden Sie sich bitte an Ihren Instanz-Betreiber oder an uns unter: datenschutz@planka.group\n\n### 2. Welche Daten werden verarbeitet?\n\nBei der Nutzung von PLANKA verarbeiten wir im Auftrag Ihres Instanz-Betreibers:\n\n- **Kontodaten:** Benutzername, E-Mail-Adresse, Name (sofern angegeben), Profilbild (sofern hochgeladen), Rolle und Berechtigungen\n- **Technische Daten:** IP-Adresse, Browser-Typ, Betriebssystem, Zeitpunkt des Zugriffs\n- **Ihre Inhalte:** Alles, was Sie in PLANKA erstellen, hochladen oder teilen (Boards, Karten, Kommentare, Anhänge)\n\n### 3. Warum werden diese Daten verarbeitet?\n\n| Zweck | Rechtsgrundlage |\n|---|---|\n| Bereitstellung und Betrieb der Software | Art. 6 Abs. 1 lit. b DSGVO (Vertragserfüllung) |\n| Systemsicherheit und Fehleranalyse | Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse) |\n| Datensicherung (Backups) | Art. 6 Abs. 1 lit. b, f DSGVO |\n\n### 4. Wie lange werden Daten gespeichert?\n\nIhre Daten werden für die Dauer Ihres aktiven Kontos gespeichert. Nach Beendigung des Vertrags zwischen Ihrem Instanz-Betreiber und uns hat dieser 30 Tage Zeit, die Daten zu exportieren. Danach werden sie unwiderruflich gelöscht. Technische Protokolldaten (Server-Logs) werden nach 90 Tagen automatisch gelöscht.\n\n### 5. Wer erhält Zugang zu Ihren Daten?\n\nIhre Daten werden ausschließlich verarbeitet von:\n- **Ihrem Instanz-Betreiber** (als Verantwortlicher)\n- **PLANKA Software GmbH** (als Auftragsverarbeiter)\n- **Hosting-Provider** (als Sub-Auftragsverarbeiter, Rechenzentren innerhalb der EU für EU-Kunden)\n\nIhre Inhalte und personenbezogenen Daten werden nicht an Dritte weitergegeben, es sei denn, ein rechtskräftiger richterlicher Beschluss ordnet dies an.\n\n### 6. Cookies\n\nPLANKA verwendet ausschließlich technisch notwendige Cookies (Session- und Login-Cookies). Tracking- oder Marketing-Cookies werden nicht eingesetzt.\n\n### 7. Ihre Rechte\n\nSie haben das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung, Datenübertragbarkeit und Widerspruch. Diese Rechte richten sich primär an Ihren Instanz-Betreiber als Verantwortlichen.\n\nSie haben jederzeit das Recht, eine erteilte Einwilligung zu widerrufen sowie sich bei einer Datenschutzaufsichtsbehörde zu beschweren (zuständig: Bayerisches Landesamt für Datenschutzaufsicht, www.lda.bayern.de).\n\nDie vollständige Datenschutzerklärung mit allen Details zu Sub-Auftragsverarbeitern, technischen Maßnahmen und Drittlandsübermittlungen ist auf unserer Website unter [Datenschutzerklärung](https://planka.app/privacy-app) einsehbar.\n\n*PLANKA Software GmbH · Lindauer Str. 4 · 87439 Kempten · Deutschland*\n\n[confirmations]::\n---\n\n✔️ **Ich habe die Nutzungsbedingungen (Teil I) gelesen und akzeptiere sie**\n✔️ **Ich habe die Datenschutzhinweise (Teil II) gelesen und stimme der Verarbeitung meiner personenbezogenen Daten zu**\n"
  },
  {
    "path": "server/terms/cloud/en-US.md",
    "content": "# End User Terms of Service – SaaS Version\n\n**Effective: February 11, 2026 – v1.0**\n\n## Part I – Terms of Service\n\n### 1. Scope and Your Relationship with PLANKA\n\n#### 1.1 What This Document Covers\nThese End User Terms of Service apply to you as an end user of the cloud-based PLANKA software (SaaS). They govern the conditions under which you may use the software – regardless of whether you are an employee, external contractor, or invited user of the organization that has made PLANKA available to you.\n\n#### 1.2 Who Is Who?\n- **\"Provider\"** or **\"we\"**: PLANKA Software GmbH, Lindauer Str. 4, 87439 Kempten, Germany\n- **\"Customer\"** or **\"Instance Administrator\"**: The organization that has entered into an agreement with us for the provision of PLANKA\n- **\"You\"** or **\"End User\"**: The natural person using PLANKA through the Customer's account\n\n#### 1.3 Contractual Framework\nYour access to PLANKA is based on the agreement between the Customer and us (Terms and Conditions). This document supplements those Terms and Conditions and governs your personal obligations as an end user. It does not establish an independent contractual relationship between you and the Provider.\n\n### 2. Your Account\n\n#### 2.1 Account Creation\nEach end user requires their own personal account. Shared accounts are not permitted.\n\n#### 2.2 Your Responsibility\nYou are responsible for the accuracy of your account information, maintaining the confidentiality of your access credentials, and all activities that occur under your account. If you suspect unauthorized use of your account, notify your instance administrator immediately.\n\n#### 2.3 Access and Revocation\nYour access is managed by the Customer. The Customer may restrict or revoke your access at any time. When your relationship with the Customer ends (e.g., end of employment), your right of access also expires.\n\n### 3. Acceptable Use\n\n#### 3.1 Intended Use\nYou may use PLANKA exclusively for the purposes for which the Customer has granted you access, and within the scope of the role and permissions assigned to you.\n\n#### 3.2 Prohibited Uses\nThe following are expressly prohibited:\n- Sharing your access credentials with other persons\n- Use for unlawful, discriminatory, or abusive purposes\n- Uploading harmful files (viruses, malware) or unlawful content\n- Attempting to access data of other users or instances without authorization\n- Automated mass queries or actions that disproportionately burden the infrastructure\n- Reverse engineering, decompilation, or disassembly of the software\n\n#### 3.3 Content Responsibility\nYou are responsible for all content you create, upload, or share in PLANKA. Ensure that such content does not infringe the rights of third parties and complies with applicable law.\n\n### 4. Intellectual Property and Your Content\n\nThe PLANKA software is owned by PLANKA Software GmbH. Your authorization to use the software does not constitute a transfer of ownership rights. You retain ownership of all content you create within PLANKA. The management and any release of your content is the responsibility of the Customer (instance administrator), not the Provider.\n\n### 5. Availability, Liability, and Data Backup\n\nThe Provider endeavors to provide PLANKA with high availability. However, maintenance, technical disruptions, or force majeure events may result in temporary limitations. There is no entitlement to uninterrupted availability.\n\nThe Provider's liability towards end users is governed by the applicable statutory provisions and the terms set out in the Terms and Conditions between the Provider and the Customer. The Provider is not liable for damages resulting from misuse or use in breach of these terms.\n\nCreate regular backups of your important content where your working environment provides for this. The Provider assumes no liability for data loss resulting from a failure to back up data on the part of the end user.\n\n### 6. Confidentiality\n\nAccess credentials, license information, and internal data to which you gain access through PLANKA must be treated as confidential. The confidentiality obligation continues beyond the duration of your access authorization.\n\n### 7. Violations and Consequences\n\nIn the event of violations of these End User Terms of Service, the Provider is entitled to temporarily suspend or permanently revoke your access and to inform the Customer (instance administrator) of the violation. The Provider's right to take further action in the event of serious violations remains unaffected.\n\n### 8. Changes and Applicable Law\n\nThe Provider may amend these End User Terms of Service with effect for the future. Changes will be displayed to you at your next login and require your renewed confirmation. Without confirmation, further use of PLANKA is not possible. German law applies.\n\n---\n\n## Part II – Privacy Notice\n\n### 1. Who Is Responsible?\n\n**Data Controller** for data processing within your PLANKA instance is your employer or client (the \"Instance Administrator\"). They determine which data is processed in PLANKA.\n\n**PLANKA Software GmbH** (Lindauer Str. 4, 87439 Kempten, Germany) processes your data as a data processor on behalf of the Instance Administrator.\n\nFor data protection inquiries, please contact your instance administrator or reach us at: privacy@planka.group\n\n### 2. What Data Is Processed?\n\nWhen using PLANKA, we process the following data on behalf of your instance administrator:\n\n- **Account Data:** Username, email address, name (if provided), profile picture (if uploaded), role and permissions\n- **Technical Data:** IP address, browser type, operating system, time of access\n- **Your Content:** Everything you create, upload, or share in PLANKA (boards, cards, comments, attachments)\n\n### 3. Why Is This Data Processed?\n\n| Purpose | Legal Basis |\n|---|---|\n| Provision and operation of the software | Art. 6(1)(b) GDPR (contract performance) |\n| System security and error analysis | Art. 6(1)(f) GDPR (legitimate interest) |\n| Data backup | Art. 6(1)(b), (f) GDPR |\n\n### 4. How Long Is Data Stored?\n\nYour data is stored for the duration of your active account. After termination of the contract between your instance administrator and us, they have 30 days to export the data. After that, it will be irreversibly deleted. Technical log data (server logs) is automatically deleted after 90 days.\n\n### 5. Who Has Access to Your Data?\n\nYour data is processed exclusively by:\n- **Your Instance Administrator** (as data controller)\n- **PLANKA Software GmbH** (as data processor)\n- **Hosting providers** (as sub-processors, data centers within the EU for EU customers)\n\nYour content and personal data is not shared with third parties unless required by a legally binding court order.\n\n### 6. Cookies\n\nPLANKA uses only technically necessary cookies (session and login cookies). Tracking or marketing cookies are not used.\n\n### 7. Your Rights\n\nYou have the right to access, rectification, erasure, restriction of processing, data portability, and objection. These rights are primarily directed at your instance administrator as the data controller.\n\nYou have the right to withdraw any consent given at any time and to lodge a complaint with a data protection supervisory authority (responsible: Bavarian State Office for Data Protection Supervision, www.lda.bayern.de).\n\nThe full Privacy Policy with complete details on sub-processors, technical measures, and third-country transfers is available on our website at [Privacy Policy](https://planka.app/privacy-app).\n\n*PLANKA Software GmbH · Lindauer Str. 4 · 87439 Kempten · Germany*\n\n[confirmations]::\n---\n\n✔️ **I have read and accept the Terms of Service (Part I)**\n✔️ **I have read the Privacy Notice (Part II) and consent to the processing of my personal data**\n"
  },
  {
    "path": "server/test/fixtures/.gitkeep",
    "content": ""
  },
  {
    "path": "server/test/integration/controllers/.gitkeep",
    "content": ""
  },
  {
    "path": "server/test/integration/helpers/.gitkeep",
    "content": ""
  },
  {
    "path": "server/test/integration/models/User.test.js",
    "content": "/* const { expect } = require('chai');\n\ndescribe('User (model)', () => {\n  before(async () => {\n    await User.qm.createOne({\n      email: 'test@test.test',\n      password: 'test',\n      role: User.Roles.ADMIN,\n      name: 'test',\n    });\n  });\n\n  describe('#find()', () => {\n    it('should return 1 user', async () => {\n      const users = await User.find();\n\n      expect(users).to.have.lengthOf(1);\n    });\n  });\n}); */\n"
  },
  {
    "path": "server/test/lifecycle.test.js",
    "content": "const dotenv = require('dotenv');\nconst sails = require('sails');\nconst rc = require('sails/accessible/rc');\n\nprocess.env.NODE_ENV = 'test';\n\nbefore(function beforeCallback(done) {\n  this.timeout(5000);\n\n  dotenv.config({ quiet: true });\n\n  sails.lift(rc('sails'), (error) => {\n    if (error) {\n      return done(error);\n    }\n\n    return done();\n  });\n});\n\nafter(function afterCallback(done) {\n  sails.lower(done);\n});\n"
  },
  {
    "path": "server/test/mocha.opts",
    "content": ""
  },
  {
    "path": "server/test/utils/remote-address.test.js",
    "content": "const { expect } = require('chai');\nconst { getRemoteAddress } = require('../../utils/remote-address');\n\n/**\n * Fake HTTP request object\n * given to all api controllers.\n */\nconst MOCK_REQUEST = {\n  ip: '',\n  ips: [],\n};\n\n/**\n * Mocks the `MOCK_REQUEST` value.\n * Should be called before asserting `getRemoteAddress`.\n * @param {string} ip Mock remote IP address\n * @param {any[]} ips Mock array of proxy IP addresses\n */\nconst mockRequest = (ip, ips) => {\n  MOCK_REQUEST.ip = ip;\n  MOCK_REQUEST.ips = ips;\n};\n\n/**\n * Mocks the `TRUST_PROXY` environment variable passed through `docker-compose` file.\n * @param {boolean} trustProxy Whether the TRUST_PROXY environment variable was enabled.\n */\nconst mockProxyFlag = (trustProxy) => {\n  process.env.TRUST_PROXY = trustProxy.toString();\n};\n\ndescribe('remote-address', () => {\n  describe('#getRemoteAddress(Request)', () => {\n    it('should get IPv4 remote address while not behind proxy and TRUST_PROXY=false', async () => {\n      const expectedAddress = '172.2.109.132';\n\n      mockRequest(`::ffff:${expectedAddress}`, null);\n      mockProxyFlag(false);\n\n      expect(getRemoteAddress(MOCK_REQUEST)).to.be.equal(expectedAddress);\n    });\n\n    it('should get IPv6 remote address while not behind proxy and TRUST_PROXY=false', async () => {\n      const expectedAddress = 'f53f:5832:9f1c:fe38:ce3d:1be8:81a2:115e';\n\n      mockRequest(expectedAddress, null);\n      mockProxyFlag(false);\n\n      expect(getRemoteAddress(MOCK_REQUEST)).to.be.equal(expectedAddress);\n    });\n\n    it('should get IPv4 remote address while behind proxy and TRUST_PROXY=true', async () => {\n      const expectedAddress = '172.2.109.132';\n\n      mockRequest(`::ffff:${expectedAddress}`, [\n        `::ffff:${expectedAddress}`,\n        '::ffff:192.182.23.111',\n        '::ffff:120.210.132.14',\n      ]);\n      mockProxyFlag(true);\n\n      expect(getRemoteAddress(MOCK_REQUEST)).to.be.equal(expectedAddress);\n    });\n\n    it('should get IPv6 remote address while behind proxy and TRUST_PROXY=true', async () => {\n      const expectedAddress = 'f53f:5832:9f1c:fe38:ce3d:1be8:81a2:115e';\n\n      mockRequest(expectedAddress, [\n        expectedAddress,\n        '9d74:fb18:3b95:801f:8751:8d18:8207:b322',\n        '598e:4291:e1b3:2991:5d17:00af:1b6b:802c',\n      ]);\n      mockProxyFlag(true);\n\n      expect(getRemoteAddress(MOCK_REQUEST)).to.be.equal(expectedAddress);\n    });\n  });\n});\n"
  },
  {
    "path": "server/utils/build-query-parts.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst QUERY_PARTS_REGEX = /[ ,;]+/;\n\nconst buildQueryParts = (query) =>\n  query.split(QUERY_PARTS_REGEX).flatMap((queryPart) => {\n    if (!queryPart) {\n      return [];\n    }\n\n    return queryPart.toLowerCase();\n  });\n\nmodule.exports = buildQueryParts;\n"
  },
  {
    "path": "server/utils/filenamify.js",
    "content": "/*\n * This file derives from https://github.com/sindresorhus/filename-reserved-regex\n * Licensed under the MIT License:\n *\n * Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n *\n * 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:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * 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.\n */\n\nfunction filenameReservedRegex() {\n  return /[<>:\"/\\\\|?*\\u0000-\\u001F]/g; // eslint-disable-line no-control-regex\n}\n\nfunction windowsReservedNameRegex() {\n  return /^(con|prn|aux|nul|com\\d|lpt\\d)$/i;\n}\n\n/*\n * This file derives from https://github.com/sindresorhus/filenamify\n * Licensed under the MIT License:\n *\n * Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n *\n * 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:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * 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.\n */\n\n// Doesn't make sense to have longer filenames\nconst MAX_FILENAME_LENGTH = 100;\n\nconst reRelativePath = /^\\.+(\\\\|\\/)|^\\.+$/;\nconst reTrailingPeriods = /\\.+$/;\n\nfunction filenamify(string, options = {}) {\n  const reControlChars = /[\\u0000-\\u001F\\u0080-\\u009F]/g; // eslint-disable-line no-control-regex\n  const reRepeatedReservedCharacters = /([<>:\"/\\\\|?*\\u0000-\\u001F]){2,}/g; // eslint-disable-line no-control-regex\n\n  if (typeof string !== 'string') {\n    throw new TypeError('Expected a string');\n  }\n\n  const replacement = options.replacement === undefined ? '!' : options.replacement;\n\n  if (filenameReservedRegex().test(replacement) && reControlChars.test(replacement)) {\n    throw new Error('Replacement string cannot contain reserved filename characters');\n  }\n\n  /* eslint-disable no-param-reassign */\n  if (replacement.length > 0) {\n    string = string.replace(reRepeatedReservedCharacters, '$1');\n  }\n\n  string = string.normalize('NFD');\n  string = string.replace(reRelativePath, replacement);\n  string = string.replace(filenameReservedRegex(), replacement);\n  string = string.replace(reControlChars, replacement);\n  string = string.replace(reTrailingPeriods, '');\n\n  if (replacement.length > 0) {\n    const startedWithDot = string[0] === '.';\n\n    // We removed the whole filename\n    if (!startedWithDot && string[0] === '.') {\n      string = replacement + string;\n    }\n\n    // We removed the whole extension\n    if (string[string.length - 1] === '.') {\n      string += replacement;\n    }\n  }\n\n  string = windowsReservedNameRegex().test(string) ? string + replacement : string;\n  const allowedLength =\n    typeof options.maxLength === 'number' ? options.maxLength : MAX_FILENAME_LENGTH;\n  if (string.length > allowedLength) {\n    const extensionIndex = string.lastIndexOf('.');\n    if (extensionIndex === -1) {\n      string = string.slice(0, allowedLength);\n    } else {\n      const filename = string.slice(0, extensionIndex);\n      const extension = string.slice(extensionIndex);\n      string = filename.slice(0, Math.max(1, allowedLength - extension.length)) + extension;\n    }\n  }\n  /* eslint-enable no-param-reassign */\n\n  return string;\n}\n\nmodule.exports = filenamify;\n"
  },
  {
    "path": "server/utils/inputs.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst {\n  ID_REGEX,\n  IDS_WITH_COMMA_REGEX,\n  MAX_STRING_ID,\n  isIdInRange,\n  isIdsWithCommaInRange,\n} = require('./validators');\n\nconst idInput = {\n  type: 'string',\n  maxLength: MAX_STRING_ID.length,\n  regex: ID_REGEX,\n  custom: isIdInRange,\n};\n\nconst idsInput = {\n  type: 'string',\n  maxLength: MAX_STRING_ID.length * 100 + 99,\n  regex: IDS_WITH_COMMA_REGEX,\n  custom: isIdsWithCommaInRange,\n};\n\nmodule.exports = {\n  idInput,\n  idsInput,\n};\n"
  },
  {
    "path": "server/utils/logger.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst winston = require('winston');\n\n/**\n * The default timestamp used by the logger.\n * Format example: \"2022-08-18 6:30:02\"\n */\nconst defaultLogTimestampFormat = 'YYYY-MM-DD HH:mm:ss';\n\n/**\n * Log level for both console and file log sinks.\n *\n * Refer {@link https://github.com/winstonjs/winston#logging here}\n * for more information on Winston log levels.\n */\nconst logLevel = process.env.LOG_LEVEL || 'warn';\n\nconst logFormat = winston.format.combine(\n  winston.format.uncolorize(),\n  winston.format.timestamp({ format: defaultLogTimestampFormat }),\n  winston.format.printf((log) => `${log.timestamp} [${log.level[0].toUpperCase()}] ${log.message}`),\n);\n\nconst logFile = process.env.LOG_FILE || `${process.cwd()}/logs/planka.log`;\n\n// eslint-disable-next-line new-cap\nconst customLogger = new winston.createLogger({\n  transports: [\n    new winston.transports.File({\n      level: logLevel,\n      format: logFormat,\n      filename: logFile,\n    }),\n    new winston.transports.Console({\n      level: logLevel,\n      format: logFormat,\n    }),\n  ],\n});\n\nmodule.exports = {\n  customLogger,\n};\n"
  },
  {
    "path": "server/utils/mentions.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst MENTION_ID_REGEX = /@\\[.*?\\]\\((.*?)\\)/g;\nconst MENTION_USERNAME_REGEX = /@\\[(.*?)\\]\\(.*?\\)/g;\n\nconst extractMentionIds = (text) => {\n  const matches = [...text.matchAll(MENTION_ID_REGEX)];\n  return matches.map((match) => match[1]);\n};\n\nconst mentionMarkupToText = (markup) =>\n  markup.replace(MENTION_USERNAME_REGEX, (_, username) => `@${username}`);\n\nmodule.exports = {\n  extractMentionIds,\n  mentionMarkupToText,\n};\n"
  },
  {
    "path": "server/utils/migrations.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst { POSITION_GAP } = require('../constants');\n\nconst addPosition = async (knex, tableName, parentFieldName) => {\n  await knex.schema.table(tableName, (table) => {\n    /* Columns */\n\n    table.specificType('position', 'double precision');\n\n    /* Indexes */\n\n    table.index('position');\n  });\n\n  const records = await knex(tableName).orderBy([parentFieldName, 'id']);\n\n  let prevParentId;\n  let position;\n\n  // eslint-disable-next-line no-restricted-syntax\n  for (record of records) {\n    if (record[parentFieldName] === prevParentId) {\n      position += POSITION_GAP;\n    } else {\n      prevParentId = record[parentFieldName];\n      position = POSITION_GAP;\n    }\n\n    // eslint-disable-next-line no-await-in-loop\n    await knex(tableName)\n      .update({\n        position,\n      })\n      .where('id', record.id);\n  }\n\n  return knex.schema.table(tableName, (table) => {\n    table.dropNullable('position');\n  });\n};\n\nconst removePosition = (knex, tableName) =>\n  knex.schema.table(tableName, (table) => {\n    table.dropColumn('position');\n  });\n\nmodule.exports = {\n  addPosition,\n  removePosition,\n};\n"
  },
  {
    "path": "server/utils/normalize-values.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst normalizeValues = (rules, values, record) => {\n  if (record) {\n    // eslint-disable-next-line no-param-reassign\n    values = {\n      ...record,\n      ...values,\n    };\n  }\n\n  return Object.entries(rules).reduce((result, [fieldName, { setTo, defaultTo }]) => {\n    let value = values[fieldName];\n\n    if (!_.isUndefined(setTo)) {\n      const setToValue = _.isFunction(setTo) ? setTo(values) : setTo;\n\n      if (!_.isUndefined(setToValue)) {\n        value = setToValue;\n      }\n    }\n\n    if (!_.isUndefined(defaultTo) && _.isNil(value)) {\n      const defaultToValue = _.isFunction(defaultTo) ? defaultTo(values) : defaultTo;\n\n      if (!_.isUndefined(defaultToValue)) {\n        value = defaultToValue;\n      }\n    }\n\n    if (!_.isUndefined(value)) {\n      if (!record || value !== record[fieldName]) {\n        result[fieldName] = value; // eslint-disable-line no-param-reassign\n      }\n    }\n\n    return result;\n  }, {});\n};\n\nmodule.exports = normalizeValues;\n"
  },
  {
    "path": "server/utils/remote-address.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\n/**\n * The IP address of the client that just made a request to this application, whether\n * or not the TRUST_PROXY env variable is true and if endpoint accessed through a proxy.\n * @param {Request} request The endpoint Request object\n * @returns The IP address of the client that just made a request\n */\nconst getRemoteAddress = (request) => {\n  let remoteAddress = request.ip;\n\n  // Assert if \"X-Forwarded-For\" header contains any addresses\n  if (process.env.TRUST_PROXY === 'true' && !_.isEmpty(request.ips)) {\n    // eslint-disable-next-line prefer-destructuring\n    remoteAddress = request.ips[0];\n  }\n\n  // Convert address from IPV6 to IPV4 if client device is IPV4.\n  const defaultIPV6Regex = /^::ffff:((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$/g;\n  if (remoteAddress.match(defaultIPV6Regex)) {\n    remoteAddress = remoteAddress.replace('::ffff:', '');\n  }\n\n  return remoteAddress;\n};\n\nmodule.exports = {\n  getRemoteAddress,\n};\n"
  },
  {
    "path": "server/utils/send_notifications.py",
    "content": "# Copyright (c) 2024 PLANKA Software GmbH\n# Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n\nimport sys\nimport json\nimport logging\nimport apprise\n\n\nBLOCKED_SCHEMAS_SET = {\n    'syslog',\n    'dbus',\n    'kde',\n    'qt',\n    'glib',\n    'gnome',\n    'macosx',\n    'windows',\n}\n\n\nlast_apprise_message = None\nclass CaptureWarningHandler(logging.Handler):\n    def emit(self, record):\n        global last_apprise_message\n        last_apprise_message = record.getMessage()\n\n\ncapture_warning_handler = CaptureWarningHandler()\ncapture_warning_handler.setLevel(logging.WARNING)\n\napprise_logger = logging.getLogger('apprise')\napprise_logger.setLevel(logging.WARNING)\napprise_logger.propagate = False\napprise_logger.addHandler(capture_warning_handler)\n\n\ndef send_notification(url, title, body, body_format):\n    app = apprise.Apprise()\n    app.add(url)\n    return app.notify(title=title, body=body, body_format=body_format)\n\n\nif __name__ == '__main__':\n    services = json.loads(sys.argv[1])\n    title = sys.argv[2]\n    body_by_format = json.loads(sys.argv[3])\n\n    errors = []\n    for service in services:\n        url = service['url']\n        schema = url.split(':')[0]\n\n        if schema in BLOCKED_SCHEMAS_SET:\n            errors.append(f'[{schema}] Blocked service schema')\n            continue\n\n        body_format = service['format']\n        body = body_by_format[body_format]\n\n        last_apprise_message = None\n        if not send_notification(url, title, body, body_format):\n            if last_apprise_message:\n                if last_apprise_message == 'There are no service(s) to notify':\n                    errors.append(f'[{schema}] Unknown service URL')\n                else:\n                    errors.append(f'[{schema}] {last_apprise_message}')\n            else:\n                errors.append(f'[{schema}] Unknown error')\n\n    if errors:\n        for error in errors:\n            print(error, file=sys.stderr)\n\n        sys.exit(1)\n"
  },
  {
    "path": "server/utils/validators.js",
    "content": "/*!\n * Copyright (c) 2024 PLANKA Software GmbH\n * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md\n */\n\nconst validator = require('validator');\nconst zxcvbn = require('zxcvbn');\nconst moment = require('moment');\n\nconst MAX_STRING_ID = '9223372036854775807';\n\nconst ID_REGEX = /^[1-9][0-9]*$/;\nconst IDS_WITH_COMMA_REGEX = /^[1-9][0-9]*(,[1-9][0-9]*)*$/;\nconst USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\\.)?[a-zA-Z0-9])*$/;\n\nconst is = (defaultValue) => (value) => value === defaultValue;\n\nconst isUrl = (value) =>\n  validator.isURL(value, {\n    protocols: ['http', 'https'],\n    require_tld: false,\n    require_protocol: true,\n  });\n\nconst isIdInRange = (value) => value.length < MAX_STRING_ID.length || value <= MAX_STRING_ID;\n\nconst isIdsWithCommaInRange = (value) => _.every(value.split(','), isIdInRange);\n\nconst isId = (value) =>\n  value.length <= MAX_STRING_ID.length && ID_REGEX.test(value) && isIdInRange(value);\n\nconst isIds = (values) => _.every(values, isId);\n\nconst isPassword = (value) => zxcvbn(value).score >= 2; // TODO: move to config\n\nconst isEmailOrUsername = (value) =>\n  value.includes('@')\n    ? validator.isEmail(value)\n    : value.length >= 3 && value.length <= 32 && USERNAME_REGEX.test(value);\n\nconst isDueDate = (value) => moment(value, moment.ISO_8601, true).isValid();\n\nconst isStopwatch = (value) => {\n  if (!_.isPlainObject(value) || _.size(value) !== 2) {\n    return false;\n  }\n\n  if (!_.isNull(value.startedAt) && !moment(value.startedAt, moment.ISO_8601, true).isValid()) {\n    return false;\n  }\n\n  if (!_.isFinite(value.total) || value.total < 0) {\n    return false;\n  }\n\n  return true;\n};\n\nmodule.exports = {\n  MAX_STRING_ID,\n\n  ID_REGEX,\n  IDS_WITH_COMMA_REGEX,\n  USERNAME_REGEX,\n\n  is,\n  isUrl,\n  isIdInRange,\n  isIdsWithCommaInRange,\n  isId,\n  isIds,\n  isPassword,\n  isEmailOrUsername,\n  isDueDate,\n  isStopwatch,\n};\n"
  },
  {
    "path": "server/version-template.ejs",
    "content": "module.exports = '<%= pkg.version %>';\n"
  },
  {
    "path": "server/version.js",
    "content": "module.exports = '2.1.0';\n"
  },
  {
    "path": "server/views/.gitkeep",
    "content": ""
  }
]